From 0f54aa11b59942797d5788ef30df58145c5e94d8 Mon Sep 17 00:00:00 2001 From: Ben Gotow Date: Tue, 26 Sep 2017 11:33:08 -0700 Subject: [PATCH] =?UTF-8?q?Adopt=20=E2=9C=A8=20prettier=20=E2=9C=A8,=20upg?= =?UTF-8?q?rade=20ESLint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc | 73 +- .prettierrc | 5 + app/build/Gruntfile.js | 49 +- app/build/create-signed-windows-installer.js | 30 +- app/build/tasks/coffeelint-task.js | 24 +- app/build/tasks/create-mac-dmg.js | 45 +- app/build/tasks/create-mac-zip.js | 33 +- app/build/tasks/csslint-task.js | 14 +- app/build/tasks/docs-build-task.js | 121 +- app/build/tasks/docs-render-task.js | 69 +- app/build/tasks/eslint-task.js | 2 +- app/build/tasks/installer-linux-task.js | 48 +- app/build/tasks/lesslint-task.js | 10 +- app/build/tasks/nylaslint-task.js | 273 +- app/build/tasks/package-task.js | 153 +- app/build/tasks/setup-mac-keychain-task.js | 130 +- app/build/tasks/task-helpers.js | 30 +- .../lib/components/account-switcher.cjsx | 7 +- .../specs/sidebar-item-spec.es6 | 52 +- .../lib/activity-data-source.es6 | 7 +- .../lib/activity-list-actions.es6 | 4 +- .../lib/activity-list-button.jsx | 35 +- .../lib/activity-list-empty-state.jsx | 14 +- .../lib/activity-list-item-container.jsx | 61 +- .../activity-list/lib/activity-list-store.jsx | 71 +- .../activity-list/lib/activity-list.jsx | 37 +- .../activity-list/lib/main.es6 | 7 +- .../activity-list/lib/plugin-helpers.es6 | 23 +- .../activity-list/lib/test-data-source.es6 | 6 +- .../specs/activity-list-spec.jsx | 194 +- .../analytics/analytics-electron/index.es6 | 133 +- .../analytics/lib/analytics-store.es6 | 59 +- app/internal_packages/analytics/lib/main.es6 | 4 +- .../attachments/lib/main.es6 | 6 +- .../attachments/lib/message-attachments.jsx | 83 +- .../lib/label-picker-popover.jsx | 179 +- .../category-picker/lib/label-picker.cjsx | 7 +- .../lib/move-picker-popover.jsx | 157 +- .../category-picker/lib/move-picker.cjsx | 7 +- .../composer-emoji/lib/categorized-emoji.es6 | 16 +- .../composer-emoji/lib/emoji-actions.es6 | 5 +- .../lib/emoji-button-popover.jsx | 171 +- .../composer-emoji/lib/emoji-button.jsx | 19 +- .../lib/emoji-composer-extension.jsx | 203 +- .../lib/emoji-message-extension.jsx | 8 +- .../composer-emoji/lib/emoji-picker.jsx | 23 +- .../composer-emoji/lib/emoji-store.jsx | 21 +- .../composer-emoji/lib/main.es6 | 4 +- .../specs/emoji-button-popover-spec.jsx | 18 +- .../specs/emoji-composer-extension-spec.jsx | 103 +- .../lib/markdown-editor.cjsx | 8 +- .../composer-signature/lib/main.es6 | 6 +- .../lib/preferences-signatures.jsx | 160 +- .../lib/signature-composer-dropdown.jsx | 133 +- .../lib/signature-composer-extension.es6 | 9 +- .../lib/signature-utils.es6 | 8 +- .../specs/preferences-signatures-spec.jsx | 117 +- .../signature-composer-dropdown-spec.jsx | 79 +- .../signature-composer-extension-spec.es6 | 46 +- .../specs/signature-store-spec.jsx | 59 +- .../composer-spellcheck/lib/main.es6 | 6 +- .../lib/spellcheck-composer-extension.es6 | 81 +- .../spellcheck-composer-extension-spec.es6 | 42 +- .../composer-templates/lib/main.es6 | 6 +- .../lib/preferences-templates.jsx | 231 +- .../lib/template-composer-extension.es6 | 46 +- .../lib/template-editor.es6 | 35 +- .../lib/template-picker.jsx | 68 +- .../lib/template-status-bar.jsx | 12 +- .../composer-templates/lib/template-store.es6 | 96 +- .../specs/template-store-spec.es6 | 64 +- .../composer-translate/lib/main.jsx | 56 +- .../composer/lib/account-contact-field.jsx | 58 +- .../composer/lib/action-bar-plugins.jsx | 43 +- .../composer/lib/collapsed-participants.jsx | 59 +- .../composer/lib/compose-button.jsx | 8 +- .../composer/lib/composer-editor.jsx | 63 +- .../composer/lib/composer-header-actions.jsx | 46 +- .../composer/lib/composer-header.jsx | 185 +- .../composer/lib/composer-view.jsx | 289 +- app/internal_packages/composer/lib/fields.es6 | 14 +- .../lib/inline-image-composer-extension.es6 | 37 +- .../lib/inline-image-upload-container.jsx | 71 +- app/internal_packages/composer/lib/main.jsx | 36 +- .../composer/lib/send-action-button.jsx | 75 +- .../composer/lib/subject-text-field.jsx | 24 +- .../composer/specs/composer-header-spec.jsx | 121 +- .../specs/send-action-button-spec.jsx | 72 +- .../custom-fonts/lib/main.js | 1 - .../custom-sounds/lib/main.es6 | 12 +- .../draft-list/lib/draft-list-send-status.jsx | 20 +- .../draft-list/lib/draft-list-toolbar.jsx | 31 +- .../draft-list/lib/draft-toolbar-buttons.cjsx | 5 +- app/internal_packages/draft-list/lib/main.es6 | 42 +- .../draft-list/lib/sending-cancel-button.cjsx | 5 +- .../draft-list/lib/sending-progress-bar.cjsx | 5 +- .../events/lib/event-header.cjsx | 4 +- .../lib/github-contact-card-section.jsx | 44 +- .../lib/github-user-store.es6 | 18 +- .../github-contact-card/lib/main.jsx | 8 +- .../lib/link-tracking-button.jsx | 27 +- .../lib/link-tracking-composer-extension.es6 | 18 +- .../lib/link-tracking-constants.es6 | 6 +- .../lib/link-tracking-message-extension.jsx | 40 +- .../lib/link-tracking-message-popover.jsx | 34 +- .../link-tracking/lib/main.es6 | 12 +- .../link-tracking-composer-extension-spec.es6 | 34 +- .../main-calendar/lib/calendar-wrapper.jsx | 58 +- .../lib/event-description-frame.jsx | 40 +- .../main-calendar/lib/quick-event-button.jsx | 19 +- .../main-calendar/lib/quick-event-popover.jsx | 38 +- .../lib/autoload-images-actions.es6 | 7 +- .../lib/autoload-images-extension.es6 | 6 +- .../lib/autoload-images-header.jsx | 19 +- .../lib/autoload-images-store.es6 | 42 +- .../message-autoload-images/lib/main.es6 | 5 +- .../specs/autoload-images-extension-spec.es6 | 14 +- .../message-list/lib/autolinker.es6 | 36 +- .../message-list/lib/autoscale-images.es6 | 5 +- .../lib/email-frame-styles-store.es6 | 11 +- .../message-list/lib/email-frame.jsx | 62 +- .../message-list/lib/find-in-thread.jsx | 113 +- .../lib/inline-image-listeners.es6 | 10 +- .../message-list/lib/main.jsx | 12 +- .../message-list/lib/message-controls.jsx | 76 +- .../message-list/lib/message-item-body.jsx | 74 +- .../lib/message-item-container.jsx | 57 +- .../message-list/lib/message-item.jsx | 177 +- .../message-list-hidden-messages-toggle.jsx | 21 +- .../message-list/lib/message-list.jsx | 177 +- .../message-list/lib/message-participants.jsx | 126 +- .../message-list/lib/message-timestamp.jsx | 30 +- .../lib/sidebar-participant-picker.jsx | 34 +- .../lib/sidebar-plugin-container.jsx | 27 +- .../lib/thread-archive-button.jsx | 23 +- .../message-list/lib/thread-star-button.jsx | 27 +- .../lib/thread-toggle-unread-button.jsx | 32 +- .../message-list/lib/thread-trash-button.jsx | 24 +- .../message-list/specs/autolinker-spec.es6 | 29 +- .../lib/github-store.es6 | 8 +- .../message-view-on-github/lib/main.jsx | 4 +- .../lib/view-on-github-button.jsx | 50 +- .../mode-switch/lib/main.es6 | 9 +- .../mode-switch/lib/mode-toggle.jsx | 32 +- .../lib/items/account-error-notif.jsx | 107 +- .../lib/items/default-client-notif.jsx | 52 +- .../lib/items/dev-mode-notif.jsx | 15 +- .../lib/items/disabled-mail-rules-notif.jsx | 28 +- .../lib/items/offline-notification.jsx | 41 +- .../lib/items/unstable-channel-notif.jsx | 24 +- .../lib/items/update-notification.jsx | 34 +- .../notifications/lib/main.es6 | 30 +- .../notifications/lib/notif-wrapper.jsx | 27 +- .../lib/sidebar/initial-sync-activity.jsx | 40 +- .../lib/sidebar/sync-activity.jsx | 62 +- .../lib/sidebar/syncback-activity.jsx | 50 +- .../specs/account-error-notif-spec.jsx | 69 +- .../specs/default-client-notif-spec.jsx | 66 +- .../specs/dev-mode-notif-spec.jsx | 28 +- .../specs/disabled-mail-rules-notif-spec.jsx | 73 +- .../notifications/specs/priority-spec.jsx | 68 +- .../specs/update-notification-spec.jsx | 125 +- .../onboarding/lib/account-providers.es6 | 2 +- .../lib/decorators/create-page-for-form.jsx | 200 +- .../onboarding/lib/form-error-message.jsx | 21 +- .../onboarding/lib/form-field.jsx | 29 +- app/internal_packages/onboarding/lib/main.es6 | 16 +- .../onboarding/lib/onboarding-actions.es6 | 12 +- .../onboarding/lib/onboarding-helpers.es6 | 83 +- .../onboarding/lib/onboarding-root.jsx | 27 +- .../onboarding/lib/onboarding-store.es6 | 83 +- .../onboarding/lib/page-account-choose.jsx | 27 +- .../lib/page-account-onboarding-success.jsx | 26 +- .../lib/page-account-settings-exchange.jsx | 71 +- .../lib/page-account-settings-gmail.jsx | 20 +- .../lib/page-account-settings-imap.jsx | 114 +- .../onboarding/lib/page-account-settings.jsx | 58 +- .../onboarding/lib/page-authenticate.jsx | 21 +- .../lib/page-initial-preferences.cjsx | 9 +- .../onboarding/lib/page-top-bar.jsx | 27 +- .../onboarding/lib/page-tutorial.jsx | 49 +- .../onboarding/lib/page-welcome.jsx | 22 +- .../open-tracking/lib/main.es6 | 21 +- .../lib/open-tracking-button.jsx | 25 +- .../lib/open-tracking-composer-extension.es6 | 17 +- .../lib/open-tracking-constants.es6 | 6 +- .../open-tracking/lib/open-tracking-icon.jsx | 27 +- .../lib/open-tracking-message-popover.jsx | 34 +- .../lib/open-tracking-message-status.jsx | 37 +- .../open-tracking-composer-extension-spec.es6 | 26 +- .../specs/open-tracking-icon-spec.jsx | 45 +- .../open-tracking-message-status-spec.jsx | 40 +- .../lib/clearbit-data-source.es6 | 29 +- .../participant-profile/lib/main.es6 | 25 +- .../lib/participant-profile-store.es6 | 53 +- .../lib/sidebar-participant-profile.jsx | 118 +- .../lib/sidebar-related-threads.jsx | 76 +- .../personal-level-indicators/lib/main.es6 | 4 +- .../lib/personal-level-icon.jsx | 14 +- .../phishing-detection/docs/main.coffee | 3 +- .../phishing-detection/docs/main.html | 662 +- .../phishing-detection/lib/main.jsx | 19 +- .../phishing-detection/specs/main-spec.jsx | 4 +- .../preferences/lib/main.jsx | 105 +- .../preferences/lib/preferences-root.jsx | 46 +- .../preferences/lib/preferences-tabs-bar.jsx | 50 +- .../lib/tabs/config-schema-item.jsx | 38 +- .../lib/tabs/keymaps/command-item.jsx | 66 +- .../lib/tabs/keymaps/displayed-keybindings.js | 7 +- .../keymaps/mousetrap-keybinding-helpers.js | 196 +- .../lib/tabs/preferences-account-details.jsx | 98 +- .../lib/tabs/preferences-account-list.jsx | 39 +- .../lib/tabs/preferences-accounts.jsx | 24 +- .../lib/tabs/preferences-appearance.jsx | 58 +- .../lib/tabs/preferences-general.jsx | 55 +- .../lib/tabs/preferences-identity.jsx | 89 +- .../lib/tabs/preferences-keymaps.jsx | 67 +- .../lib/tabs/preferences-mail-rules.jsx | 141 +- .../preferences/lib/tabs/sending-section.jsx | 32 +- .../lib/tabs/update-channel-section.jsx | 48 +- .../lib/tabs/workspace-section.jsx | 54 +- .../preferences-account-details-spec.jsx | 167 +- app/internal_packages/print/lib/main.es6 | 4 +- .../print/lib/print-window.es6 | 19 +- app/internal_packages/print/lib/printer.es6 | 7 +- .../remove-tracking-pixels/lib/main.es6 | 234 +- .../specs/tracking-pixels-extension-spec.es6 | 19 +- .../send-and-archive/lib/main.es6 | 9 +- .../lib/send-and-archive-extension.es6 | 46 +- app/internal_packages/send-later/lib/main.es6 | 41 +- .../send-later/lib/send-later-button.jsx | 90 +- .../send-later/lib/send-later-constants.es6 | 4 +- .../send-later/lib/send-later-popover.jsx | 19 +- .../send-later/lib/send-later-status.jsx | 51 +- .../specs/send-later-button-spec.jsx | 99 +- .../specs/send-later-popover-spec.jsx | 13 +- .../send-reminders/lib/main.es6 | 41 +- ...nd-reminders-account-sidebar-extension.es6 | 7 +- .../lib/send-reminders-composer-button.jsx | 66 +- .../lib/send-reminders-constants.es6 | 4 +- .../lib/send-reminders-headers.jsx | 59 +- .../send-reminders-mailbox-perspective.es6 | 39 +- .../lib/send-reminders-popover-button.jsx | 62 +- .../lib/send-reminders-popover.jsx | 29 +- .../lib/send-reminders-store.es6 | 45 +- .../send-reminders-thread-list-extension.es6 | 18 +- .../lib/send-reminders-thread-timestamp.jsx | 34 +- .../lib/send-reminders-toolbar-button.jsx | 15 +- .../lib/send-reminders-utils.es6 | 44 +- .../system-tray/lib/main.es6 | 4 +- .../lib/system-tray-icon-store.es6 | 38 +- .../specs/system-tray-icon-store-spec.es6 | 75 +- .../theme-picker/lib/main.jsx | 7 +- .../theme-picker/lib/theme-option.jsx | 42 +- .../theme-picker/lib/theme-picker.jsx | 49 +- .../theme-picker/specs/theme-picker-spec.jsx | 6 +- .../lib/injects-toolbar-buttons.jsx | 52 +- .../thread-list/lib/main.es6 | 4 +- .../thread-list/lib/message-list-toolbar.jsx | 40 +- .../thread-list/lib/selected-items-stack.jsx | 50 +- .../lib/thread-list-context-menu.es6 | 181 +- .../lib/thread-list-data-source.es6 | 110 +- .../thread-list/lib/thread-list-icon.cjsx | 7 +- .../lib/thread-list-participants.cjsx | 5 +- .../lib/thread-list-quick-actions.cjsx | 6 +- .../lib/thread-list-scroll-tooltip.cjsx | 7 +- .../thread-list/lib/thread-list-toolbar.jsx | 19 +- .../lib/thread-toolbar-buttons.jsx | 171 +- .../thread-search/lib/main.es6 | 10 +- .../thread-search/lib/search-actions.es6 | 10 +- .../lib/search-mailbox-perspective.es6 | 37 +- .../lib/search-query-subscription.es6 | 130 +- .../thread-search/lib/search-store.es6 | 75 +- .../thread-search/lib/thread-search-bar.jsx | 54 +- .../thread-snooze/lib/main.es6 | 30 +- .../thread-snooze/lib/snooze-actions.es6 | 8 +- .../thread-snooze/lib/snooze-buttons.jsx | 51 +- .../thread-snooze/lib/snooze-constants.es6 | 4 +- .../thread-snooze/lib/snooze-mail-label.jsx | 41 +- .../thread-snooze/lib/snooze-popover.jsx | 55 +- .../thread-snooze/lib/snooze-store.es6 | 79 +- .../thread-snooze/lib/snooze-utils.es6 | 14 +- .../thread-snooze/specs/snooze-store-spec.es6 | 124 +- .../thread-snooze/specs/snooze-utils-spec.es6 | 185 +- app/internal_packages/undo-redo/lib/main.es6 | 13 +- .../lib/undo-redo-thread-list-toast.jsx | 29 +- .../undo-redo/lib/undo-send-store.es6 | 47 +- .../undo-redo/lib/undo-send-toast.jsx | 30 +- .../unread-notifications/lib/main.es6 | 61 +- .../unread-notifications/specs/main-spec.es6 | 231 +- .../verify-install-location/lib/main.es6 | 67 +- .../plugins/lib/main.jsx | 8 +- .../plugins/lib/package-set.jsx | 30 +- .../plugins/lib/package.jsx | 93 +- .../plugins/lib/packages-store.jsx | 261 +- .../plugins/lib/plugins-tabs-view.jsx | 22 +- .../plugins/lib/preferences-plugins.jsx | 9 +- .../plugins/lib/tab-explore.jsx | 23 +- .../plugins/lib/tab-installed.jsx | 71 +- .../plugins/lib/tabs-store.jsx | 3 - .../plugins/lib/tabs.jsx | 14 +- .../thread-sharing/lib/copy-button.jsx | 51 +- .../thread-sharing/lib/external-threads.es6 | 61 +- .../thread-sharing/lib/main.es6 | 6 +- .../lib/thread-sharing-button.jsx | 27 +- .../lib/thread-sharing-constants.es6 | 6 +- .../lib/thread-sharing-popover.jsx | 120 +- app/spec/async-test-spec.es6 | 24 +- .../components/blockquote-manager-spec.es6 | 30 +- app/spec/database-object-registry-spec.es6 | 27 +- app/spec/fixtures/table-data.es6 | 58 +- app/spec/mailbox-perspective-spec.es6 | 248 +- app/spec/merani-protocol-handler-spec.es6 | 7 +- app/spec/models/model-spec.es6 | 163 +- app/spec/models/model-with-metadata-spec.es6 | 40 +- .../models/mutable-query-result-set-spec.es6 | 141 +- app/spec/models/query-range-spec.es6 | 101 +- app/spec/models/query-spec.es6 | 196 +- .../models/query-subscription-pool-spec.es6 | 16 +- app/spec/models/query-subscription-spec.es6 | 378 +- app/spec/n1-spec-runner/console-reporter.es6 | 6 +- .../n1-spec-runner/jasmine-extensions.es6 | 39 +- app/spec/n1-spec-runner/master-after-each.es6 | 14 +- .../n1-spec-runner/master-before-each.es6 | 39 +- app/spec/n1-spec-runner/n1-gui-reporter.cjsx | 7 +- app/spec/n1-spec-runner/n1-spec-loader.es6 | 23 +- app/spec/n1-spec-runner/n1-spec-runner.es6 | 103 +- .../n1-spec-runner/nylas-test-constants.es6 | 16 +- .../react-test-utils-extensions.es6 | 20 +- app/spec/n1-spec-runner/spec-bootstrap.es6 | 2 +- app/spec/n1-spec-runner/terminal-reporter.es6 | 4 +- .../inline-style-transformer-spec.es6 | 42 +- app/spec/spellchecker-spec.es6 | 56 +- app/spec/stores/database-store-spec.es6 | 110 +- app/spec/stores/draft-factory-spec.es6 | 603 +- app/spec/stores/draft-helpers-spec.es6 | 69 +- app/spec/stores/draft-store-spec.es6 | 284 +- app/spec/stores/feature-usage-store-spec.es6 | 100 +- .../folder-sync-progress-store-spec.es6 | 54 +- app/spec/stores/identity-store-spec.es6 | 116 +- app/spec/stores/send-actions-store-spec.es6 | 183 +- app/spec/tasks/task-factory-spec.es6 | 161 +- app/spec/undo-stack-spec.es6 | 90 +- app/spec/utils/date-utils-spec.es6 | 137 +- app/spec_disabled/nylas-env-spec.es6 | 133 +- .../stores/undo-redo-store-spec.es6 | 176 +- .../tasks/destroy-model-task-spec.es6 | 87 +- .../tasks/send-draft-task-spec.es6 | 574 +- app/src/backoff-schedulers.es6 | 44 +- app/src/browser/application.es6 | 268 +- app/src/browser/auto-update-manager.es6 | 35 +- app/src/browser/config-migrator.es6 | 3 +- .../browser/config-persistence-manager.es6 | 69 +- app/src/browser/file-list-cache.es6 | 4 +- app/src/browser/linux-updater-adapter.es6 | 18 +- .../browser/mailspring-protocol-handler.es6 | 6 +- app/src/browser/main.js | 114 +- app/src/browser/system-tray-manager.es6 | 19 +- app/src/browser/window-launcher.es6 | 44 +- app/src/browser/window-manager.es6 | 110 +- app/src/browser/windows-updater.js | 103 +- app/src/canvas-utils.es6 | 172 +- .../chrome-user-agent-stylesheet-string.es6 | 2 +- app/src/compile-cache.js | 147 +- app/src/compile-support/babel.js | 52 +- app/src/compile-support/cjsx.js | 47 +- app/src/compile-support/coffee-script.js | 48 +- app/src/compile-support/typescript.js | 46 +- app/src/components/attachment-items.jsx | 192 +- app/src/components/billing-modal.jsx | 46 +- app/src/components/bolded-search-result.jsx | 28 +- app/src/components/button-dropdown.cjsx | 16 +- app/src/components/code-snippet.jsx | 15 +- app/src/components/config-prop-container.jsx | 10 +- .../contenteditable/blockquote-manager.es6 | 26 +- .../contenteditable/clipboard-service.es6 | 70 +- .../contenteditable-service.es6 | 14 +- .../contenteditable/contenteditable.cjsx | 17 +- .../emphasis-formatting-extension.es6 | 28 +- .../contenteditable/exported-selection.es6 | 14 +- .../contenteditable/floating-toolbar.cjsx | 18 +- .../contenteditable/link-editor.cjsx | 10 +- .../contenteditable/link-manager.es6 | 55 +- .../contenteditable/list-manager.es6 | 44 +- .../contenteditable/mouse-service.es6 | 58 +- .../paragraph-formatting-extension.es6 | 10 +- .../contenteditable/tab-manager.es6 | 10 +- .../toolbar-button-manager.es6 | 32 +- .../contenteditable/toolbar-buttons.jsx | 27 +- app/src/components/date-input.jsx | 57 +- app/src/components/date-picker-popover.jsx | 65 +- app/src/components/date-picker.jsx | 95 +- .../components/decorators/auto-focuses.jsx | 51 +- app/src/components/decorators/compose.es6 | 10 +- .../decorators/has-tutorial-tip.jsx | 100 +- .../decorators/listens-to-flux-store.jsx | 26 +- .../decorators/listens-to-movement-keys.jsx | 73 +- .../decorators/listens-to-observable.jsx | 38 +- app/src/components/disclosure-triangle.cjsx | 7 +- app/src/components/drop-zone.jsx | 27 +- app/src/components/editable-list.jsx | 86 +- app/src/components/editable-table.jsx | 90 +- app/src/components/empty-list-state.cjsx | 9 +- app/src/components/evented-iframe.cjsx | 11 +- app/src/components/feature-used-up-modal.jsx | 49 +- app/src/components/fixed-popover.jsx | 166 +- app/src/components/flexbox.jsx | 27 +- app/src/components/flux-container.jsx | 15 +- app/src/components/focus-container.jsx | 22 +- app/src/components/generated-form.cjsx | 126 +- .../components/injected-component-label.jsx | 11 +- app/src/components/injected-component-set.jsx | 59 +- app/src/components/injected-component.jsx | 86 +- app/src/components/key-commands-region.jsx | 53 +- app/src/components/lazy-rendered-list.jsx | 80 +- app/src/components/list-data-source.es6 | 68 +- app/src/components/list-selection.es6 | 44 +- app/src/components/list-tabular-item.cjsx | 17 +- app/src/components/list-tabular.jsx | 153 +- app/src/components/mail-important-icon.cjsx | 9 +- app/src/components/mail-label-set.jsx | 34 +- app/src/components/mail-label.jsx | 29 +- app/src/components/menu.cjsx | 36 +- .../metadata-composer-toggle-button.jsx | 88 +- app/src/components/modal.jsx | 64 +- .../components/multiselect-action-bar.cjsx | 7 +- app/src/components/multiselect-dropdown.jsx | 44 +- app/src/components/multiselect-list.cjsx | 19 +- app/src/components/multiselect-toolbar.jsx | 44 +- app/src/components/newsletter-signup.jsx | 93 +- app/src/components/notification.jsx | 95 +- .../nylas-calendar/calendar-constants.es6 | 8 +- .../nylas-calendar/calendar-data-source.es6 | 22 +- .../calendar-event-container.jsx | 95 +- .../nylas-calendar/calendar-event-popover.jsx | 208 +- .../nylas-calendar/calendar-event.jsx | 102 +- .../nylas-calendar/calendar-helpers.jsx | 8 +- .../nylas-calendar/calendar-toggles.jsx | 57 +- .../nylas-calendar/current-time-indicator.jsx | 35 +- .../nylas-calendar/event-grid-background.jsx | 53 +- .../event-participants-input.jsx | 121 +- .../nylas-calendar/event-search-bar.jsx | 75 +- .../nylas-calendar/footer-controls.jsx | 22 +- .../nylas-calendar/header-controls.jsx | 58 +- .../nylas-calendar/mini-month-view.jsx | 120 +- .../components/nylas-calendar/month-view.jsx | 16 +- .../nylas-calendar/nylas-calendar.jsx | 161 +- .../components/nylas-calendar/top-banner.jsx | 17 +- .../week-view-all-day-events.jsx | 30 +- .../nylas-calendar/week-view-event-column.jsx | 61 +- .../components/nylas-calendar/week-view.jsx | 380 +- app/src/components/oauth-signin-page.jsx | 109 +- .../components/open-identity-page-button.jsx | 49 +- app/src/components/outline-view-item.jsx | 88 +- app/src/components/outline-view.jsx | 41 +- .../overlaid-components/anchor-constants.es6 | 6 +- .../custom-contenteditable-components.es6 | 12 +- .../overlaid-components.jsx | 185 +- .../overlaid-composer-extension.es6 | 29 +- .../components/participants-text-field.jsx | 149 +- app/src/components/resizable-region.cjsx | 25 +- app/src/components/retina-img.jsx | 60 +- app/src/components/scenario-editor-models.es6 | 52 +- app/src/components/scenario-editor-row.jsx | 130 +- app/src/components/scenario-editor.jsx | 41 +- app/src/components/scroll-region.cjsx | 24 +- app/src/components/scrollbar-ticks.jsx | 43 +- app/src/components/search-bar.jsx | 64 +- app/src/components/selectable-table.jsx | 189 +- app/src/components/spinner.cjsx | 7 +- app/src/components/swipe-container.jsx | 119 +- app/src/components/switch.jsx | 15 +- app/src/components/syncing-list-state.jsx | 18 +- app/src/components/tab-group-region.cjsx | 3 +- .../components/table/table-data-source.es6 | 93 +- app/src/components/table/table.jsx | 109 +- app/src/components/time-picker.jsx | 180 +- app/src/components/toast.jsx | 78 +- app/src/components/tokenizing-text-field.jsx | 453 +- app/src/components/undo-toast.jsx | 31 +- app/src/components/unsafe-component.cjsx | 5 +- app/src/components/webview.jsx | 92 +- app/src/config-schema.es6 | 154 +- app/src/config-utils.js | 17 +- app/src/date-utils.es6 | 167 +- .../decorators/inflates-draft-client-id.jsx | 37 +- app/src/default-client-helper.es6 | 190 +- app/src/dom-walkers.es6 | 12 +- .../raven-error-reporter.js | 34 +- app/src/error-logger.js | 121 +- .../extensions/account-sidebar-extension.es6 | 5 +- app/src/extensions/extension-utils.es6 | 2 +- app/src/extensions/thread-list-extension.es6 | 5 +- app/src/flux/action-bridge.es6 | 27 +- app/src/flux/actions.es6 | 12 +- app/src/flux/attributes.es6 | 18 +- app/src/flux/attributes/attribute-boolean.es6 | 4 +- .../flux/attributes/attribute-collection.es6 | 23 +- .../flux/attributes/attribute-datetime.es6 | 10 +- .../flux/attributes/attribute-joined-data.es6 | 11 +- app/src/flux/attributes/attribute-object.es6 | 6 +- app/src/flux/attributes/attribute-string.es6 | 2 +- app/src/flux/attributes/attribute.es6 | 23 +- app/src/flux/attributes/matcher.es6 | 108 +- app/src/flux/errors.es6 | 24 +- app/src/flux/mailsync-bridge.es6 | 132 +- app/src/flux/models/account.es6 | 53 +- app/src/flux/models/category.es6 | 62 +- app/src/flux/models/contact.es6 | 297 +- app/src/flux/models/event.es6 | 38 +- app/src/flux/models/file.es6 | 9 +- app/src/flux/models/message-utils.es6 | 6 +- app/src/flux/models/message.es6 | 158 +- app/src/flux/models/model-with-metadata.es6 | 21 +- app/src/flux/models/model.es6 | 10 +- .../flux/models/mutable-query-result-set.es6 | 25 +- .../models/mutable-query-subscription.es6 | 35 +- .../flux/models/provider-syncback-request.es6 | 4 +- app/src/flux/models/query-range.es6 | 33 +- app/src/flux/models/query-result-set.es6 | 25 +- .../flux/models/query-subscription-pool.es6 | 10 +- app/src/flux/models/query-subscription.es6 | 101 +- app/src/flux/models/query.es6 | 96 +- app/src/flux/models/thread.es6 | 95 +- .../flux/models/unread-query-subscription.es6 | 20 +- app/src/flux/nylas-api-request.es6 | 49 +- app/src/flux/stores/account-store.es6 | 265 +- app/src/flux/stores/attachment-store.es6 | 316 +- app/src/flux/stores/badge-store.es6 | 27 +- app/src/flux/stores/category-store.es6 | 71 +- app/src/flux/stores/contact-store.es6 | 44 +- .../flux/stores/database-change-record.es6 | 3 +- app/src/flux/stores/database-store.es6 | 181 +- app/src/flux/stores/draft-editing-session.es6 | 138 +- app/src/flux/stores/draft-helpers.es6 | 78 +- app/src/flux/stores/draft-store.es6 | 290 +- app/src/flux/stores/feature-usage-store.jsx | 68 +- .../flux/stores/focused-contacts-store.es6 | 63 +- app/src/flux/stores/focused-content-store.es6 | 48 +- .../flux/stores/focused-perspective-store.es6 | 114 +- .../stores/folder-sync-progress-store.es6 | 71 +- app/src/flux/stores/identity-store.es6 | 65 +- app/src/flux/stores/mail-rules-store.es6 | 42 +- .../flux/stores/message-body-processor.es6 | 55 +- app/src/flux/stores/modal-store.jsx | 20 +- .../stores/observable-list-data-source.es6 | 33 +- app/src/flux/stores/online-status-store.es6 | 24 +- app/src/flux/stores/outbox-store.es6 | 12 +- app/src/flux/stores/popover-store.jsx | 24 +- app/src/flux/stores/preferences-ui-store.es6 | 44 +- app/src/flux/stores/recently-read-store.es6 | 24 +- .../stores/searchable-component-store.es6 | 162 +- app/src/flux/stores/send-actions-store.es6 | 60 +- app/src/flux/stores/signature-store.es6 | 104 +- app/src/flux/stores/task-queue.es6 | 44 +- app/src/flux/stores/thread-counts-store.es6 | 14 +- app/src/flux/stores/undo-redo-store.es6 | 41 +- app/src/flux/stores/update-channel-store.es6 | 31 +- app/src/flux/tasks/change-folder-task.es6 | 21 +- app/src/flux/tasks/change-labels-task.es6 | 23 +- app/src/flux/tasks/change-mail-task.es6 | 7 +- app/src/flux/tasks/change-starred-task.es6 | 21 +- app/src/flux/tasks/change-unread-task.es6 | 7 +- app/src/flux/tasks/destroy-category-task.es6 | 3 +- app/src/flux/tasks/destroy-draft-task.es6 | 2 +- app/src/flux/tasks/destroy-model-task.es6 | 32 +- app/src/flux/tasks/event-rsvp-task.es6 | 4 +- .../flux/tasks/reprocess-mail-rules-task.es6 | 64 +- app/src/flux/tasks/send-draft-task.es6 | 73 +- .../tasks/send-feature-usage-event-task.es6 | 1 - app/src/flux/tasks/syncback-category-task.es6 | 3 +- app/src/flux/tasks/syncback-draft-task.es6 | 3 +- app/src/flux/tasks/syncback-event-task.es6 | 8 +- app/src/flux/tasks/syncback-metadata-task.es6 | 3 +- app/src/flux/tasks/task-factory.es6 | 59 +- app/src/flux/tasks/task.es6 | 28 +- app/src/fs-utils.es6 | 12 +- app/src/global/nylas-component-kit.es6 | 164 +- app/src/global/nylas-exports.es6 | 20 +- app/src/global/nylas-observables.es6 | 109 +- app/src/key-manager.es6 | 33 +- app/src/keymap-manager.es6 | 66 +- app/src/less-compile-cache.es6 | 2 +- app/src/mail-rules-processor.es6 | 78 +- app/src/mail-rules-templates.es6 | 106 +- app/src/mailbox-perspective.es6 | 215 +- app/src/mailsync-process.es6 | 97 +- app/src/menu-manager.es6 | 14 +- app/src/native-notifications.es6 | 16 +- app/src/nylas-env.es6 | 286 +- app/src/package-manager.es6 | 24 +- app/src/package.es6 | 23 +- app/src/promise-extensions.es6 | 17 +- app/src/registries/command-registry.es6 | 16 +- app/src/registries/component-registry.es6 | 76 +- .../registries/database-object-registry.es6 | 8 +- app/src/registries/extension-registry.es6 | 24 +- app/src/registries/serializable-registry.es6 | 28 +- app/src/registries/service-registry.es6 | 2 +- app/src/registries/sound-registry.es6 | 6 +- .../searchable-components/iframe-searcher.es6 | 12 +- .../searchable-components/real-dom-parser.es6 | 62 +- .../searchable-components/search-match.jsx | 15 +- .../searchable-component-maker.jsx | 61 +- .../unified-dom-parser.es6 | 100 +- .../virtual-dom-parser.es6 | 99 +- app/src/secondary-window-bootstrap.es6 | 2 +- app/src/services/battery-status-manager.es6 | 13 +- app/src/services/inline-style-transformer.es6 | 22 +- app/src/services/quote-string-detector.es6 | 6 +- app/src/services/quoted-html-transformer.es6 | 129 +- app/src/services/sanitize-transformer.es6 | 405 +- app/src/services/search/search-query-ast.es6 | 52 +- .../search/search-query-backend-imap.es6 | 17 +- .../search/search-query-backend-local.es6 | 7 +- .../services/search/search-query-parser.es6 | 50 +- .../services/unwrapped-signature-detector.es6 | 20 +- app/src/sheet-container.jsx | 55 +- app/src/sheet-toolbar.jsx | 135 +- app/src/sheet.jsx | 59 +- app/src/spellchecker.es6 | 83 +- app/src/style-manager.es6 | 14 +- app/src/system-start-service.es6 | 141 +- app/src/theme-manager.es6 | 23 +- app/src/undo-stack.es6 | 30 +- app/src/virtual-dom-utils.es6 | 22 +- app/src/window-bootstrap.es6 | 7 +- app/src/window-event-handler.es6 | 163 +- .../lib/my-composer-button.jsx | 13 +- package.json | 16 +- yarn.lock | 6648 +++++++++++++++++ 631 files changed, 26762 insertions(+), 17613 deletions(-) create mode 100644 .prettierrc create mode 100644 yarn.lock diff --git a/.eslintrc b/.eslintrc index f3d506040..6f468fb95 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,14 @@ { "parser": "babel-eslint", - "extends": "airbnb", + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module", + "ecmaFeatures": { + "modules": true, + "jsx": true + } + }, + "extends": ["react-app", "prettier", "prettier/react"], "globals": { "NylasEnv": false, "$n": false, @@ -16,59 +24,20 @@ "node": true, "jasmine": true }, + "plugins": ["prettier"], "rules": { - "arrow-body-style": "off", - "arrow-parens": "off", - "class-methods-use-this": "off", - "prefer-arrow-callback": ["error", {"allowNamedFunctions": true}], - "eqeqeq": ["error", "smart"], - "id-length": "off", - "object-curly-spacing": "off", - "max-len": "off", - "new-cap": ["error", {"capIsNew": false}], - "newline-per-chained-call": "off", - "no-bitwise": "off", - "no-lonely-if": "off", - "no-console": "off", - "no-continue": "off", - "no-constant-condition": "off", - "no-loop-func": "off", - "no-plusplus": "off", - "no-shadow": "error", - "no-underscore-dangle": "off", - "object-shorthand": "off", - "quotes": "off", - "quote-props": ["error", "consistent-as-needed", { "keywords": true }], - "no-param-reassign": ["error", { "props": false }], - "semi": "off", - "no-mixed-operators": "off", - "import/extensions": ["error", "never", { "json": "always" }], - "import/no-unresolved": ["error", {"ignore": ["nylas-exports", "nylas-component-kit", "electron", "nylas-store", "react-dom/server", "nylas-observables", "windows-shortcuts", "moment-round", "better-sqlite3", "chrono-node", "event-kit", "enzyme"]}], - "import/no-extraneous-dependencies": "off", - "import/newline-after-import": "off", - "import/prefer-default-export": "off", - "react/no-multi-comp": "off", - "react/no-find-dom-node": "off", - "react/no-string-refs": "off", - "react/no-unused-prop-types": "off", - "react/forbid-prop-types": "off", - "jsx-a11y/no-static-element-interactions": "off", - "react/prop-types": ["error", {"ignore": ["children"]}], - "react/sort-comp": "error", - "no-restricted-syntax": [ - "error", "ForInStatement", "LabeledStatement", "WithStatement" - ], - "comma-dangle": ["error", { - "arrays": "always-multiline", - "objects": "always-multiline", - "imports": "always-multiline", - "exports": "always-multiline", - "functions": "ignore" - }], - "no-useless-return": "off" + "prettier/prettier": "error" }, "settings": { - "import/core-modules": [ "nylas-exports", "nylas-component-kit", "electron", "nylas-store", "nylas-observables" ], - "import/resolver": {"node": {"extensions": [".es6", ".jsx", ".coffee", ".json", ".cjsx", ".js"]}} + "import/core-modules": [ + "nylas-exports", + "nylas-component-kit", + "electron", + "nylas-store", + "nylas-observables" + ], + "import/resolver": { + "node": { "extensions": [".es6", ".jsx", ".coffee", ".json", ".cjsx", ".js"] } + } } } diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..8c81fbaed --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "printWidth": 100, + "singleQuote": true, + "trailingComma": "es5" +} \ No newline at end of file diff --git a/app/build/Gruntfile.js b/app/build/Gruntfile.js index 378e005ae..ca5a50c3d 100644 --- a/app/build/Gruntfile.js +++ b/app/build/Gruntfile.js @@ -2,7 +2,7 @@ /* eslint import/no-dynamic-require: 0 */ const path = require('path'); -module.exports = (grunt) => { +module.exports = grunt => { if (!grunt.option('platform')) { grunt.option('platform', process.platform); } @@ -15,17 +15,17 @@ module.exports = (grunt) => { const appDir = path.resolve(path.join('app')); const buildDir = path.join(appDir, 'build'); const tasksDir = path.join(buildDir, 'tasks'); - const taskHelpers = require(path.join(tasksDir, 'task-helpers'))(grunt) + const taskHelpers = require(path.join(tasksDir, 'task-helpers'))(grunt); // This allows all subsequent paths to the relative to the root of the repo grunt.config.init({ - 'taskHelpers': taskHelpers, - 'rootDir': path.resolve('./'), - 'buildDir': buildDir, - 'appDir': appDir, - 'classDocsOutputDir': path.join(buildDir, 'docs_src', 'classes'), - 'outputDir': path.join(appDir, 'dist'), - 'appJSON': grunt.file.readJSON(path.join(appDir, 'package.json')), + taskHelpers: taskHelpers, + rootDir: path.resolve('./'), + buildDir: buildDir, + appDir: appDir, + classDocsOutputDir: path.join(buildDir, 'docs_src', 'classes'), + outputDir: path.join(appDir, 'dist'), + appJSON: grunt.file.readJSON(path.join(appDir, 'package.json')), 'source:coffeescript': [ 'internal_packages/**/*.cjsx', 'internal_packages/**/*.coffee', @@ -59,35 +59,18 @@ module.exports = (grunt) => { grunt.loadTasks(tasksDir); grunt.file.setBase(appDir); - grunt.registerTask('docs', [ - 'docs-build', - 'docs-render', - ]); + grunt.registerTask('docs', ['docs-build', 'docs-render']); - grunt.registerTask('lint', [ - 'eslint', - 'lesslint', - 'nylaslint', - 'coffeelint', - 'csslint', - ]); + grunt.registerTask('lint', ['eslint', 'lesslint', 'nylaslint', 'coffeelint', 'csslint']); if (grunt.option('platform') === 'win32') { - grunt.registerTask("build-client", [ - "package", + grunt.registerTask('build-client', [ + 'package', // The Windows electron-winstaller task must be run outside of grunt ]); } else if (grunt.option('platform') === 'darwin') { - grunt.registerTask("build-client", [ - "package", - "create-mac-zip", - "create-mac-dmg", - ]); + grunt.registerTask('build-client', ['package', 'create-mac-zip', 'create-mac-dmg']); } else if (grunt.option('platform') === 'linux') { - grunt.registerTask("build-client", [ - "package", - "create-deb-installer", - "create-rpm-installer", - ]); + grunt.registerTask('build-client', ['package', 'create-deb-installer', 'create-rpm-installer']); } -} +}; diff --git a/app/build/create-signed-windows-installer.js b/app/build/create-signed-windows-installer.js index 3e9fecc19..cbea252fb 100644 --- a/app/build/create-signed-windows-installer.js +++ b/app/build/create-signed-windows-installer.js @@ -4,10 +4,10 @@ * directly from a powershell command. */ const path = require('path'); -const {createWindowsInstaller} = require('electron-winstaller'); +const { createWindowsInstaller } = require('electron-winstaller'); -const appDir = path.join(__dirname, ".."); -const {version} = require(path.join(appDir, 'package.json')); +const appDir = path.join(__dirname, '..'); +const { version } = require(path.join(appDir, 'package.json')); const config = { usePackageJson: false, @@ -17,23 +17,25 @@ const config = { iconUrl: 'http://edgehill.s3.amazonaws.com/static/mailspring.ico', certificateFile: process.env.CERTIFICATE_FILE, certificatePassword: process.env.WINDOWS_CODESIGN_KEY_PASSWORD, - description: "Mailspring", + description: 'Mailspring', version: version, - title: "mailspring", + title: 'mailspring', authors: 'Foundry 376, LLC', setupIcon: path.join(appDir, 'build', 'resources', 'win', 'mailspring.ico'), setupExe: 'MailspringSetup.exe', exe: 'mailspring.exe', name: 'Mailspring', -} +}; console.log(config); -console.log("---> Starting") +console.log('---> Starting'); -createWindowsInstaller(config).then(() => { - console.log("createWindowsInstaller succeeded.") - process.exit(0); -}).catch((e) => { - console.error(`createWindowsInstaller failed: ${e.message}`); - process.exit(1); -}); +createWindowsInstaller(config) + .then(() => { + console.log('createWindowsInstaller succeeded.'); + process.exit(0); + }) + .catch(e => { + console.error(`createWindowsInstaller failed: ${e.message}`); + process.exit(1); + }); diff --git a/app/build/tasks/coffeelint-task.js b/app/build/tasks/coffeelint-task.js index 4579d4725..bfafa36d5 100644 --- a/app/build/tasks/coffeelint-task.js +++ b/app/build/tasks/coffeelint-task.js @@ -1,25 +1,17 @@ -module.exports = (grunt) => { +module.exports = grunt => { grunt.config.merge({ coffeelint: { - 'options': { + options: { configFile: 'build/config/coffeelint.json', }, - 'src': grunt.config('source:coffeescript'), - 'build': [ - 'build/tasks/**/*.coffee', - ], - 'test': [ - 'spec/**/*.cjsx', - 'spec/**/*.coffee', - ], - 'static': [ - 'static/**/*.coffee', - 'static/**/*.cjsx', - ], - 'target': (grunt.option("target") ? grunt.option("target").split(" ") : []), + src: grunt.config('source:coffeescript'), + build: ['build/tasks/**/*.coffee'], + test: ['spec/**/*.cjsx', 'spec/**/*.coffee'], + static: ['static/**/*.coffee', 'static/**/*.cjsx'], + target: grunt.option('target') ? grunt.option('target').split(' ') : [], }, }); grunt.loadNpmTasks('grunt-contrib-coffee'); grunt.loadNpmTasks('grunt-coffeelint-cjsx'); -} +}; diff --git a/app/build/tasks/create-mac-dmg.js b/app/build/tasks/create-mac-dmg.js index 3ad24404a..e4d644332 100644 --- a/app/build/tasks/create-mac-dmg.js +++ b/app/build/tasks/create-mac-dmg.js @@ -1,25 +1,34 @@ const path = require('path'); -const createDMG = require('electron-installer-dmg') +const createDMG = require('electron-installer-dmg'); -module.exports = (grunt) => { +module.exports = grunt => { grunt.registerTask('create-mac-dmg', 'Create DMG for Mailspring', function pack() { const done = this.async(); - const dmgPath = path.join(grunt.config('outputDir'), "Mailspring.dmg"); - createDMG({ - appPath: path.join(grunt.config('outputDir'), "Mailspring-darwin-x64", "Mailspring.app"), - name: "Mailspring", - background: path.resolve(grunt.config('appDir'), 'build', 'resources', 'mac', 'DMG-background.png'), - icon: path.resolve(grunt.config('appDir'), 'build', 'resources', 'mac', 'mailspring.icns'), - overwrite: true, - out: grunt.config('outputDir'), - }, (err) => { - if (err) { - done(err); - return - } + const dmgPath = path.join(grunt.config('outputDir'), 'Mailspring.dmg'); + createDMG( + { + appPath: path.join(grunt.config('outputDir'), 'Mailspring-darwin-x64', 'Mailspring.app'), + name: 'Mailspring', + background: path.resolve( + grunt.config('appDir'), + 'build', + 'resources', + 'mac', + 'DMG-background.png' + ), + icon: path.resolve(grunt.config('appDir'), 'build', 'resources', 'mac', 'mailspring.icns'), + overwrite: true, + out: grunt.config('outputDir'), + }, + err => { + if (err) { + done(err); + return; + } - grunt.log.writeln(`>> Created ${dmgPath}`); - done(null); - }) + grunt.log.writeln(`>> Created ${dmgPath}`); + done(null); + } + ); }); }; diff --git a/app/build/tasks/create-mac-zip.js b/app/build/tasks/create-mac-zip.js index deeb98c7b..6dfae8ec9 100644 --- a/app/build/tasks/create-mac-zip.js +++ b/app/build/tasks/create-mac-zip.js @@ -3,33 +3,36 @@ /* eslint quote-props: 0 */ const path = require('path'); -module.exports = (grunt) => { - const {spawn} = grunt.config('taskHelpers') +module.exports = grunt => { + const { spawn } = grunt.config('taskHelpers'); grunt.registerTask('create-mac-zip', 'Zip up Mailspring', function pack() { const done = this.async(); const zipPath = path.join(grunt.config('outputDir'), 'Mailspring.zip'); if (grunt.file.exists(zipPath)) { - grunt.file.delete(zipPath, {force: true}); + grunt.file.delete(zipPath, { force: true }); } const orig = process.cwd(); process.chdir(path.join(grunt.config('outputDir'), 'Mailspring-darwin-x64')); - spawn({ - cmd: "zip", - args: ["-9", "-y", "-r", "-9", "-X", zipPath, 'Mailspring.app'], - }, (error) => { - process.chdir(orig); + spawn( + { + cmd: 'zip', + args: ['-9', '-y', '-r', '-9', '-X', zipPath, 'Mailspring.app'], + }, + error => { + process.chdir(orig); - if (error) { - done(error); - return; + if (error) { + done(error); + return; + } + + grunt.log.writeln(`>> Created ${zipPath}`); + done(null); } - - grunt.log.writeln(`>> Created ${zipPath}`); - done(null); - }); + ); }); }; diff --git a/app/build/tasks/csslint-task.js b/app/build/tasks/csslint-task.js index f00c92744..6867c3b43 100644 --- a/app/build/tasks/csslint-task.js +++ b/app/build/tasks/csslint-task.js @@ -1,4 +1,4 @@ -module.exports = (grunt) => { +module.exports = grunt => { grunt.config.merge({ csslint: { options: { @@ -11,9 +11,9 @@ module.exports = (grunt) => { 'display-property-grouping': false, 'fallback-colors': false, 'font-sizes': false, - 'gradients': false, - 'ids': false, - 'important': false, + gradients: false, + ids: false, + important: false, 'known-properties': false, 'outline-none': false, 'overqualified-elements': false, @@ -23,11 +23,9 @@ module.exports = (grunt) => { 'vendor-prefix': false, 'duplicate-properties': false, // doesn't place nice with mixins }, - src: [ - 'static/**/*.css', - ], + src: ['static/**/*.css'], }, }); grunt.loadNpmTasks('grunt-contrib-csslint'); -} +}; diff --git a/app/build/tasks/docs-build-task.js b/app/build/tasks/docs-build-task.js index f31601c6e..301bee329 100644 --- a/app/build/tasks/docs-build-task.js +++ b/app/build/tasks/docs-build-task.js @@ -10,19 +10,24 @@ const joanna = require('joanna'); const tello = require('tello'); module.exports = function(grunt) { - - let {cp, mkdir, rm} = grunt.config('taskHelpers'); + let { cp, mkdir, rm } = grunt.config('taskHelpers'); let getClassesToInclude = function() { let modulesPath = path.resolve(__dirname, '..', '..', 'internal_packages'); let classes = {}; fs.traverseTreeSync(modulesPath, function(modulePath) { // Don't traverse inside dependencies - if (modulePath.match(/node_modules/g)) { return false; } + if (modulePath.match(/node_modules/g)) { + return false; + } // Don't traverse blacklisted packages (that have docs, but we don't want to include) - if (path.basename(modulePath) !== 'package.json') { return true; } - if (!fs.isFileSync(modulePath)) { return true; } + if (path.basename(modulePath) !== 'package.json') { + return true; + } + if (!fs.isFileSync(modulePath)) { + return true; + } let apiPath = path.join(path.dirname(modulePath), 'api.json'); if (fs.isFileSync(apiPath)) { @@ -42,12 +47,10 @@ module.exports = function(grunt) { }; return grunt.registerTask('docs-build', 'Builds the API docs in src', function() { - - grunt.log.writeln("Time to build the docs!") + grunt.log.writeln('Time to build the docs!'); let done = this.async(); - let classDocsOutputDir = grunt.config.get('classDocsOutputDir'); let cjsxOutputDir = path.join(classDocsOutputDir, 'temp-cjsx'); @@ -58,9 +61,7 @@ module.exports = function(grunt) { let srcPath = path.resolve(__dirname, '..', '..', 'src'); - const blacklist = ['/K2/', - 'legacy-edgehill-api', - 'edgehill-api']; + const blacklist = ['/K2/', 'legacy-edgehill-api', 'edgehill-api']; let in_blacklist = function(file) { for (var i = 0; i < blacklist.length; i++) { @@ -72,90 +73,90 @@ module.exports = function(grunt) { }; fs.traverseTreeSync(srcPath, function(file) { - if (in_blacklist(file)) { - console.log("Skipping " + file); + console.log('Skipping ' + file); // Skip K2 - } - - // Convert CJSX into coffeescript that can be read by Donna - else if (path.extname(file) === '.cjsx') { + } else if (path.extname(file) === '.cjsx') { + // Convert CJSX into coffeescript that can be read by Donna let transformed = cjsxtransform(grunt.file.read(file)); // Only attempt to parse this file as documentation if it contains // real Coffeescript classes. if (transformed.indexOf('\nclass ') > 0) { + grunt.log.writeln('Found class in file: ' + file); - grunt.log.writeln("Found class in file: " + file) - - grunt.file.write(path.join(cjsxOutputDir, path.basename(file).slice(0, -5 + 1 || undefined)+'coffee'), transformed); + grunt.file.write( + path.join( + cjsxOutputDir, + path.basename(file).slice(0, -5 + 1 || undefined) + 'coffee' + ), + transformed + ); } - } - else if (path.extname(file) === '.jsx') { - console.log('Transforming ' + file) + } else if (path.extname(file) === '.jsx') { + console.log('Transforming ' + file); let fileStr = grunt.file.read(file); - let transformed = require("babel-core").transform(fileStr, { - plugins: ["transform-react-jsx", - "transform-class-properties"], - presets: ['react', 'electron'] + let transformed = require('babel-core').transform(fileStr, { + plugins: ['transform-react-jsx', 'transform-class-properties'], + presets: ['react', 'electron'], }); - grunt.file.write(path.join(cjsxOutputDir, path.basename(file).slice(0, -3 || undefined)+'js'), transformed.code); - } - else if (path.extname(file) == '.es6') { - console.log(file); + grunt.file.write( + path.join(cjsxOutputDir, path.basename(file).slice(0, -3 || undefined) + 'js'), + transformed.code + ); + } else if (path.extname(file) == '.es6') { + console.log(file); let fileStr = grunt.file.read(file); - let transformed = require("babel-core").transform(fileStr, { - plugins: ["transform-class-properties", - "transform-function-bind"], - presets: ['react', 'electron'] - + let transformed = require('babel-core').transform(fileStr, { + plugins: ['transform-class-properties', 'transform-function-bind'], + presets: ['react', 'electron'], }); if (transformed.code.indexOf('class ') > 0) { - grunt.log.writeln("Found class in file: " + file) + grunt.log.writeln('Found class in file: ' + file); - grunt.file.write(path.join(cjsxOutputDir, path.basename(file).slice(0, -3 || undefined)+'js'), transformed.code); + grunt.file.write( + path.join(cjsxOutputDir, path.basename(file).slice(0, -3 || undefined) + 'js'), + transformed.code + ); } - } - else if (path.extname(file) == '.coffee' || - path.extname(file) == '.js') { + } else if (path.extname(file) == '.coffee' || path.extname(file) == '.js') { let dest_path = path.join(cjsxOutputDir, path.basename(file)); - console.log("Copying " + file + " to " + dest_path); + console.log('Copying ' + file + ' to ' + dest_path); fs_extra.copySync(file, dest_path); } return true; }); - grunt.log.ok('Done transforming, starting donna extraction') - grunt.log.writeln('cjsxOutputDir: ' + cjsxOutputDir) + grunt.log.ok('Done transforming, starting donna extraction'); + grunt.log.writeln('cjsxOutputDir: ' + cjsxOutputDir); // Process coffeescript source let metadata = donna.generateMetadata([cjsxOutputDir]); grunt.log.ok('---- Done with Donna (cjsx metadata)----'); - // DEBUG // Use to check individual files - var js_files = [] + var js_files = []; fs.traverseTreeSync(cjsxOutputDir, function(file) { if (path.extname(file) === '.js') { - console.log('testing joanna on ' + file) - let meta = joanna([file]) - console.log('testing tello on ' + file) - tello.digest(meta) - console.log('passed') + console.log('testing joanna on ' + file); + let meta = joanna([file]); + console.log('testing tello on ' + file); + tello.digest(meta); + console.log('passed'); } }); - var js_files = [] + var js_files = []; fs.traverseTreeSync(cjsxOutputDir, function(file) { if (path.extname(file) === '.js') { - js_files.push(file.toString()) + js_files.push(file.toString()); } }); @@ -164,32 +165,28 @@ module.exports = function(grunt) { let jsx_metadata = joanna(js_files); grunt.log.ok('---- Done with Joanna (jsx metadata)----'); - Object.assign(metadata[0].files, jsx_metadata.files); console.log(metadata[0]); grunt.file.write('/tmp/metadata.json', JSON.stringify(metadata, null, 2)); - - try { api = tello.digest(metadata); } catch (e) { - console.log(e) + console.log(e); console.log(e.stack); - console.log(metadata) + console.log(metadata); return; } console.log('---- Done with Tello ----'); Object.assign(api.classes, getClassesToInclude()); - console.log(api.classes) - + console.log(api.classes); api.classes = sortClasses(api.classes); - console.log(api.classes) + console.log(api.classes); let apiJson = JSON.stringify(api, null, 2); let apiJsonPath = path.join(classDocsOutputDir, 'api.json'); @@ -197,6 +194,4 @@ module.exports = function(grunt) { return done(); }); }); - - }; diff --git a/app/build/tasks/docs-render-task.js b/app/build/tasks/docs-render-task.js index 96701d0cc..e0409cde4 100644 --- a/app/build/tasks/docs-render-task.js +++ b/app/build/tasks/docs-render-task.js @@ -7,10 +7,11 @@ const _ = require('underscore'); marked.setOptions({ highlight(code) { return require('highlight.js').highlightAuto(code).value; - } + }, }); -let standardClassURLRoot = 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/'; +let standardClassURLRoot = + 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/'; let standardClasses = [ 'string', @@ -29,22 +30,21 @@ let standardClasses = [ 'typeerror', 'syntaxerror', 'referenceerror', - 'rangeerror' + 'rangeerror', ]; let thirdPartyClasses = { 'react.component': 'https://facebook.github.io/react/docs/component-api.html', - 'promise': 'https://github.com/petkaantonov/bluebird/blob/master/API.md', - 'range': 'https://developer.mozilla.org/en-US/docs/Web/API/Range', - 'selection': 'https://developer.mozilla.org/en-US/docs/Web/API/Selection', - 'node': 'https://developer.mozilla.org/en-US/docs/Web/API/Node', + promise: 'https://github.com/petkaantonov/bluebird/blob/master/API.md', + range: 'https://developer.mozilla.org/en-US/docs/Web/API/Range', + selection: 'https://developer.mozilla.org/en-US/docs/Web/API/Selection', + node: 'https://developer.mozilla.org/en-US/docs/Web/API/Node', }; module.exports = function(grunt) { + let { cp, mkdir, rm } = grunt.config('taskHelpers'); - let {cp, mkdir, rm} = grunt.config('taskHelpers'); - - let relativePathForClass = classname => classname+'.html'; + let relativePathForClass = classname => classname + '.html'; let outputPathFor = function(relativePath) { let classDocsOutputDir = grunt.config.get('classDocsOutputDir'); @@ -53,8 +53,12 @@ module.exports = function(grunt) { var processFields = function(json, fields, tasks) { let val; - if (fields == null) { fields = []; } - if (tasks == null) { tasks = []; } + if (fields == null) { + fields = []; + } + if (tasks == null) { + tasks = []; + } if (json instanceof Array) { return (() => { let result = []; @@ -86,7 +90,6 @@ module.exports = function(grunt) { }; return grunt.registerTask('docs-render', 'Builds html from the API docs', function() { - let documentation, filename, html, match, meta, name, result, section, val; let classDocsOutputDir = grunt.config.get('classDocsOutputDir'); @@ -96,7 +99,6 @@ module.exports = function(grunt) { let apiJsonPath = path.join(classDocsOutputDir, 'api.json'); let apiJSON = JSON.parse(grunt.file.read(apiJsonPath)); - for (var classname in apiJSON.classes) { // Parse a "@Section" out of the description if one is present let contents = apiJSON.classes[classname]; @@ -111,36 +113,34 @@ module.exports = function(grunt) { // Replace superClass "React" with "React.Component". The Coffeescript Lexer // is so bad. - if (contents.superClass === "React") { - contents.superClass = "React.Component"; + if (contents.superClass === 'React') { + contents.superClass = 'React.Component'; } classes.push({ name: classname, documentation: contents, - section + section, }); } - // Build Sidebar metadata we can hand off to each of the templates to // generate the sidebar let sidebar = {}; for (var i = 0; i < classes.length; i++) { - var current_class = classes[i]; - console.log(current_class.name + ' ' + current_class.section) + var current_class = classes[i]; + console.log(current_class.name + ' ' + current_class.section); - if (!(current_class.section in sidebar)) { - sidebar[current_class.section] = [] - } - sidebar[current_class.section].push(current_class.name) + if (!(current_class.section in sidebar)) { + sidebar[current_class.section] = []; + } + sidebar[current_class.section].push(current_class.name); } - // Prepare to render by loading handlebars partials let templatesPath = path.resolve(grunt.config('buildDir'), 'docs_templates'); grunt.file.recurse(templatesPath, function(abspath, root, subdir, filename) { - if ((filename[0] === '_') && (path.extname(filename) === '.html')) { + if (filename[0] === '_' && path.extname(filename) === '.html') { return Handlebars.registerPartial(filename, grunt.file.read(abspath)); } }); @@ -153,7 +153,6 @@ module.exports = function(grunt) { knownClassnames[classname.toLowerCase()] = val; } - let expandTypeReferences = function(val) { let refRegex = /{([\w.]*)}/g; while ((match = refRegex.exec(val)) !== null) { @@ -161,12 +160,12 @@ module.exports = function(grunt) { let label = match[1]; let url = false; if (Array.from(standardClasses).includes(term)) { - url = standardClassURLRoot+term; + url = standardClassURLRoot + term; } else if (thirdPartyClasses[term]) { url = thirdPartyClasses[term]; } else if (knownClassnames[term]) { url = relativePathForClass(knownClassnames[term].name); - grunt.log.ok("Found: " + term) + grunt.log.ok('Found: ' + term); } else { console.warn(`Cannot find class named ${term}`); } @@ -205,28 +204,26 @@ module.exports = function(grunt) { let classTemplatePath = path.join(templatesPath, 'class.md'); let classTemplate = Handlebars.compile(grunt.file.read(classTemplatePath)); - for ({name, documentation, section} of Array.from(classes)) { + for ({ name, documentation, section } of Array.from(classes)) { // Recursively process `description` and `type` fields to process markdown, // expand references to types, functions and other files. processFields(documentation, ['description'], [expandFuncReferences]); processFields(documentation, ['type'], [expandTypeReferences]); - result = classTemplate({name, documentation, section}); + result = classTemplate({ name, documentation, section }); grunt.file.write(outputPathFor(name + '.md'), result); } let sidebarTemplatePath = path.join(templatesPath, 'sidebar.md'); let sidebarTemplate = Handlebars.compile(grunt.file.read(sidebarTemplatePath)); - grunt.file.write(outputPathFor('Sidebar.md'), - sidebarTemplate({sidebar})); - + grunt.file.write(outputPathFor('Sidebar.md'), sidebarTemplate({ sidebar })); // Remove temp cjsx output - return fs.removeSync(outputPathFor("temp-cjsx")); + return fs.removeSync(outputPathFor('temp-cjsx')); }); }; function __guard__(value, transform) { - return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; + return typeof value !== 'undefined' && value !== null ? transform(value) : undefined; } diff --git a/app/build/tasks/eslint-task.js b/app/build/tasks/eslint-task.js index d0dfba848..d5304abbd 100644 --- a/app/build/tasks/eslint-task.js +++ b/app/build/tasks/eslint-task.js @@ -1,7 +1,7 @@ const chalk = require('chalk'); const eslint = require('eslint'); -module.exports = (grunt) => { +module.exports = grunt => { grunt.config.merge({ eslint: { options: { diff --git a/app/build/tasks/installer-linux-task.js b/app/build/tasks/installer-linux-task.js index 86b807bce..0f77882e1 100644 --- a/app/build/tasks/installer-linux-task.js +++ b/app/build/tasks/installer-linux-task.js @@ -3,8 +3,8 @@ const fs = require('fs'); const path = require('path'); const _ = require('underscore'); -module.exports = (grunt) => { - const {spawn} = grunt.config('taskHelpers'); +module.exports = grunt => { + const { spawn } = grunt.config('taskHelpers'); const outputDir = grunt.config.get('outputDir'); const contentsDir = path.join(grunt.config('outputDir'), `mailspring-linux-${process.arch}`); @@ -17,23 +17,23 @@ module.exports = (grunt) => { // a few helpers const writeFromTemplate = (filePath, data) => { - const template = _.template(String(fs.readFileSync(filePath))) + const template = _.template(String(fs.readFileSync(filePath))); const finishedPath = path.join(outputDir, path.basename(filePath).replace('.in', '')); grunt.file.write(finishedPath, template(data)); return finishedPath; - } + }; const getInstalledSize = (dir, callback) => { const cmd = 'du'; const args = ['-sk', dir]; - spawn({cmd, args}, (error, {stdout}) => { + spawn({ cmd, args }, (error, { stdout }) => { const installedSize = stdout.split(/\s+/).shift() || '200000'; // default to 200MB callback(null, installedSize); }); - } + }; grunt.registerTask('create-rpm-installer', 'Create rpm package', function mkrpmf() { - const done = this.async() + const done = this.async(); if (!arch) { done(new Error(`Unsupported arch ${process.arch}`)); return; @@ -41,7 +41,7 @@ module.exports = (grunt) => { const rpmDir = path.join(grunt.config('outputDir'), 'rpm'); if (grunt.file.exists(rpmDir)) { - grunt.file.delete(rpmDir, {force: true}); + grunt.file.delete(rpmDir, { force: true }); } const templateData = { @@ -52,19 +52,19 @@ module.exports = (grunt) => { linuxShareDir: '/usr/local/share/mailspring', linuxAssetsDir: linuxAssetsDir, contentsDir: contentsDir, - } + }; // This populates mailspring.spec - const specInFilePath = path.join(linuxAssetsDir, 'redhat', 'mailspring.spec.in') - writeFromTemplate(specInFilePath, templateData) + const specInFilePath = path.join(linuxAssetsDir, 'redhat', 'mailspring.spec.in'); + writeFromTemplate(specInFilePath, templateData); // This populates mailspring.desktop - const desktopInFilePath = path.join(linuxAssetsDir, 'mailspring.desktop.in') - writeFromTemplate(desktopInFilePath, templateData) + const desktopInFilePath = path.join(linuxAssetsDir, 'mailspring.desktop.in'); + writeFromTemplate(desktopInFilePath, templateData); - const cmd = path.join(grunt.config('appDir'), 'script', 'mkrpm') - const args = [outputDir, contentsDir, linuxAssetsDir] - spawn({cmd, args}, (error) => { + const cmd = path.join(grunt.config('appDir'), 'script', 'mkrpm'); + const args = [outputDir, contentsDir, linuxAssetsDir]; + spawn({ cmd, args }, error => { if (error) { return done(error); } @@ -74,7 +74,7 @@ module.exports = (grunt) => { }); grunt.registerTask('create-deb-installer', 'Create debian package', function mkdebf() { - const done = this.async() + const done = this.async(); if (!arch) { done(`Unsupported arch ${process.arch}`); return; @@ -97,20 +97,20 @@ module.exports = (grunt) => { section: 'devel', maintainer: 'Mailspring Team ', installedSize: installedSize, - } - writeFromTemplate(path.join(linuxAssetsDir, 'debian', 'control.in'), data) - writeFromTemplate(path.join(linuxAssetsDir, 'mailspring.desktop.in'), data) + }; + writeFromTemplate(path.join(linuxAssetsDir, 'debian', 'control.in'), data); + writeFromTemplate(path.join(linuxAssetsDir, 'mailspring.desktop.in'), data); - const icon = path.join(grunt.config('appDir'), 'build', 'resources', 'mailspring.png') + const icon = path.join(grunt.config('appDir'), 'build', 'resources', 'mailspring.png'); const cmd = path.join(grunt.config('appDir'), 'script', 'mkdeb'); const args = [version, arch, icon, linuxAssetsDir, contentsDir, outputDir]; - spawn({cmd, args}, (spawnError) => { + spawn({ cmd, args }, spawnError => { if (spawnError) { return done(spawnError); } grunt.log.ok(`Created ${outputDir}/mailspring-${version}-${arch}.deb`); - return done() + return done(); }); }); }); -} +}; diff --git a/app/build/tasks/lesslint-task.js b/app/build/tasks/lesslint-task.js index 26aae5ae7..72024b4c1 100644 --- a/app/build/tasks/lesslint-task.js +++ b/app/build/tasks/lesslint-task.js @@ -1,11 +1,7 @@ -module.exports = (grunt) => { +module.exports = grunt => { grunt.config.merge({ lesslint: { - src: [ - 'internal_packages/**/*.less', - 'dot-nylas/**/*.less', - 'static/**/*.less', - ], + src: ['internal_packages/**/*.less', 'dot-nylas/**/*.less', 'static/**/*.less'], options: { less: { paths: ['static', 'static/variables/'], @@ -17,4 +13,4 @@ module.exports = (grunt) => { grunt.loadNpmTasks('grunt-contrib-less'); grunt.loadNpmTasks('grunt-lesslint'); -} +}; diff --git a/app/build/tasks/nylaslint-task.js b/app/build/tasks/nylaslint-task.js index ad6808890..9b715f495 100644 --- a/app/build/tasks/nylaslint-task.js +++ b/app/build/tasks/nylaslint-task.js @@ -3,159 +3,175 @@ const path = require('path'); const fs = require('fs-plus'); function normalizeRequirePath(requirePath, fPath) { - if (requirePath[0] === ".") { + if (requirePath[0] === '.') { return path.normalize(path.join(path.dirname(fPath), requirePath)); } return requirePath; } - -module.exports = (grunt) => { +module.exports = grunt => { grunt.config.merge({ nylaslint: { src: grunt.config('source:coffeescript').concat(grunt.config('source:es6')), }, }); - grunt.registerMultiTask('nylaslint', 'Check requires for file extensions compiled away', function nylaslint() { - const done = this.async(); + grunt.registerMultiTask( + 'nylaslint', + 'Check requires for file extensions compiled away', + function nylaslint() { + const done = this.async(); - // Enable once path errors are fixed. - if (process.platform === 'win32') { - done(); - return; - } - - const extensionRegex = /require ['"].*\.(coffee|cjsx|jsx|es6|es)['"]/i; - - for (const fileset of this.files) { - grunt.log.writeln(`Nylinting ${fileset.src.length} files.`); - - const esExtensions = { - ".es6": true, - ".es": true, - ".jsx": true, - }; - - const errors = []; - - const esExport = {}; - const esNoExport = {}; - const esExportDefault = {}; - - // Temp TODO. Fix spec files - for (const f of fileset.src) { - if (!esExtensions[path.extname(f)]) { continue; } - if (!/-spec/.test(f)) { continue; } - - const content = fs.readFileSync(f, {encoding: 'utf8'}); - - // https://regex101.com/r/rQ3eD0/1 - // Matches only the first describe block - const describeRe = /[\n]describe\(['"](.*?)['"], ?\(\) ?=> ?/m; - if (describeRe.test(content)) { - errors.push(`${f}: Spec has to start with function`); - } + // Enable once path errors are fixed. + if (process.platform === 'win32') { + done(); + return; } - // NOTE: Comment me in if you want to fix these files. - // _str = require('underscore.string') - // replacer = (match, describeName) -> - // fnName = _str.camelize(describeName, true) - // return "\ndescribe('#{describeName}', function #{fnName}() " - // newContent = content.replace(describeRe, replacer) - // fs.writeFileSync(f, newContent, encoding:'utf8') + const extensionRegex = /require ['"].*\.(coffee|cjsx|jsx|es6|es)['"]/i; - // Build the list of ES6 files that export things and categorize - for (const f of fileset.src) { - if (!esExtensions[path.extname(f)]) { continue; } - const lookupPath = `${path.dirname(f)}/${path.basename(f, path.extname(f))}`; - const content = fs.readFileSync(f, {encoding: 'utf8'}); + for (const fileset of this.files) { + grunt.log.writeln(`Nylinting ${fileset.src.length} files.`); - if (/module.exports\s?=\s?.+/gmi.test(content)) { - if (!f.endsWith('nylas-exports.es6')) { - errors.push(`${f}: Don't use module.exports in ES6`); + const esExtensions = { + '.es6': true, + '.es': true, + '.jsx': true, + }; + + const errors = []; + + const esExport = {}; + const esNoExport = {}; + const esExportDefault = {}; + + // Temp TODO. Fix spec files + for (const f of fileset.src) { + if (!esExtensions[path.extname(f)]) { + continue; + } + if (!/-spec/.test(f)) { + continue; + } + + const content = fs.readFileSync(f, { encoding: 'utf8' }); + + // https://regex101.com/r/rQ3eD0/1 + // Matches only the first describe block + const describeRe = /[\n]describe\(['"](.*?)['"], ?\(\) ?=> ?/m; + if (describeRe.test(content)) { + errors.push(`${f}: Spec has to start with function`); } } - if (/^export/gmi.test(content)) { - if (/^export default/gmi.test(content)) { - esExportDefault[lookupPath] = true; - } else { - esExport[lookupPath] = true; + // NOTE: Comment me in if you want to fix these files. + // _str = require('underscore.string') + // replacer = (match, describeName) -> + // fnName = _str.camelize(describeName, true) + // return "\ndescribe('#{describeName}', function #{fnName}() " + // newContent = content.replace(describeRe, replacer) + // fs.writeFileSync(f, newContent, encoding:'utf8') + + // Build the list of ES6 files that export things and categorize + for (const f of fileset.src) { + if (!esExtensions[path.extname(f)]) { + continue; } - } else { - esNoExport[lookupPath] = true; - } - } + const lookupPath = `${path.dirname(f)}/${path.basename(f, path.extname(f))}`; + const content = fs.readFileSync(f, { encoding: 'utf8' }); - // Now look again through all ES6 files, this time to check imports - // instead of exports. - for (const f of fileset.src) { - let result = null; - if (!esExtensions[path.extname(f)]) { - continue; - } - const content = fs.readFileSync(f, {encoding: 'utf8'}); - const importRe = /import \{.*\} from ['"](.*?)['"]/gmi; - - while (result = importRe.exec(content)) { - for (const requirePath of result.slice(1)) { - const lookupPath = normalizeRequirePath(requirePath, f); - if (esExportDefault[lookupPath] || esNoExport[lookupPath]) { - errors.push(`${f}: Don't destructure default export ${requirePath}`); + if (/module.exports\s?=\s?.+/gim.test(content)) { + if (!f.endsWith('nylas-exports.es6')) { + errors.push(`${f}: Don't use module.exports in ES6`); } } - } - } - // Now look through all coffeescript files - // If they require things from ES6 files, ensure they're using the - // proper syntax. - for (const f of fileset.src) { - let result = null; - if (esExtensions[path.extname(f)]) { - continue; - } - const content = fs.readFileSync(f, {encoding: 'utf8'}); - if (extensionRegex.test(content)) { - errors.push(`${f}: Remove extensions when requiring files`); - } - - const requireRe = /require[ (]['"]([\w_./-]*?)['"]/gmi; - - while (result = requireRe.exec(content)) { - for (const requirePath of result.slice(1)) { - const lookupPath = normalizeRequirePath(requirePath, f); - - const baseRequirePath = path.basename(requirePath); - - const plainRequireRe = new RegExp(`require[ (]['"].*${baseRequirePath}['"]\\)?$`, "gm"); - const defaultRequireRe = new RegExp(`require\\(['"].*${baseRequirePath}['"]\\)\\.default`, "gm"); - - if (esExport[lookupPath]) { - if (!plainRequireRe.test(content)) { - errors.push(`${f}: No \`default\` exported ${requirePath}`); - } - } else if (esNoExport[lookupPath]) { - errors.push(`${f}: Nothing exported from ${requirePath}`); - } else if (esExportDefault[lookupPath]) { - if (!defaultRequireRe.test(content)) { - errors.push(`${f}: Add \`default\` to require ${requirePath}`); - } + if (/^export/gim.test(content)) { + if (/^export default/gim.test(content)) { + esExportDefault[lookupPath] = true; } else { - // must be a coffeescript or core file - if (defaultRequireRe.test(content)) { - errors.push(`${f}: Don't ask for \`default\` from ${requirePath}`); + esExport[lookupPath] = true; + } + } else { + esNoExport[lookupPath] = true; + } + } + + // Now look again through all ES6 files, this time to check imports + // instead of exports. + for (const f of fileset.src) { + let result = null; + if (!esExtensions[path.extname(f)]) { + continue; + } + const content = fs.readFileSync(f, { encoding: 'utf8' }); + const importRe = /import \{.*\} from ['"](.*?)['"]/gim; + + while ((result = importRe.exec(content))) { + for (const requirePath of result.slice(1)) { + const lookupPath = normalizeRequirePath(requirePath, f); + if (esExportDefault[lookupPath] || esNoExport[lookupPath]) { + errors.push(`${f}: Don't destructure default export ${requirePath}`); } } } } - } - if (errors.length > 0) { - for (const err of errors) { grunt.log.error(err); } - const error = ` + // Now look through all coffeescript files + // If they require things from ES6 files, ensure they're using the + // proper syntax. + for (const f of fileset.src) { + let result = null; + if (esExtensions[path.extname(f)]) { + continue; + } + const content = fs.readFileSync(f, { encoding: 'utf8' }); + if (extensionRegex.test(content)) { + errors.push(`${f}: Remove extensions when requiring files`); + } + + const requireRe = /require[ (]['"]([\w_./-]*?)['"]/gim; + + while ((result = requireRe.exec(content))) { + for (const requirePath of result.slice(1)) { + const lookupPath = normalizeRequirePath(requirePath, f); + + const baseRequirePath = path.basename(requirePath); + + const plainRequireRe = new RegExp( + `require[ (]['"].*${baseRequirePath}['"]\\)?$`, + 'gm' + ); + const defaultRequireRe = new RegExp( + `require\\(['"].*${baseRequirePath}['"]\\)\\.default`, + 'gm' + ); + + if (esExport[lookupPath]) { + if (!plainRequireRe.test(content)) { + errors.push(`${f}: No \`default\` exported ${requirePath}`); + } + } else if (esNoExport[lookupPath]) { + errors.push(`${f}: Nothing exported from ${requirePath}`); + } else if (esExportDefault[lookupPath]) { + if (!defaultRequireRe.test(content)) { + errors.push(`${f}: Add \`default\` to require ${requirePath}`); + } + } else { + // must be a coffeescript or core file + if (defaultRequireRe.test(content)) { + errors.push(`${f}: Don't ask for \`default\` from ${requirePath}`); + } + } + } + } + } + + if (errors.length > 0) { + for (const err of errors) { + grunt.log.error(err); + } + const error = ` Please fix the #{errors.length} linter errors above. These are the issues we're looking for: ISSUES WITH COFFEESCRIPT FILES: @@ -180,10 +196,11 @@ module.exports = (grunt) => { 6. Spec has to start with function Top-level "describe" blocks can no longer use the "() => {}" function syntax. This will incorrectly bind "this" to the "window" object instead of the jasmine object. The top-level "describe" block must use the "function describeName() {}" syntax `; - done(new Error(error)); + done(new Error(error)); + } } - } - done(null); - }); -} + done(null); + } + ); +}; diff --git a/app/build/tasks/package-task.js b/app/build/tasks/package-task.js index 0b6954fd3..8ca87d70c 100644 --- a/app/build/tasks/package-task.js +++ b/app/build/tasks/package-task.js @@ -1,4 +1,4 @@ -/* eslint global-require: 0 *//* eslint prefer-template: 0 */ +/* eslint global-require: 0 */ /* eslint prefer-template: 0 */ /* eslint quote-props: 0 */ const packager = require('electron-packager'); const path = require('path'); @@ -8,13 +8,13 @@ const fs = require('fs-plus'); const coffeereact = require('coffee-react'); const glob = require('glob'); const babel = require('babel-core'); -const {execSync} = require('child_process'); -const symlinkedPackages = [] +const { execSync } = require('child_process'); +const symlinkedPackages = []; -module.exports = (grunt) => { +module.exports = grunt => { const packageJSON = grunt.config('appJSON'); - const babelPath = path.join(grunt.config('rootDir'), '.babelrc') - const babelOptions = JSON.parse(fs.readFileSync(babelPath)) + const babelPath = path.join(grunt.config('rootDir'), '.babelrc'); + const babelOptions = JSON.parse(fs.readFileSync(babelPath)); function runCopyPlatformSpecificResources(buildPath, electronVersion, platform, arch, callback) { // these files (like nylas-mailto-default.reg) go alongside the ASAR, @@ -41,33 +41,28 @@ module.exports = (grunt) => { * for the symlink copy function to use after the packaging is complete. */ function resolveRealSymlinkPaths(appDir) { - console.log("---> Resolving symlinks"); - const dirs = [ - 'internal_packages', - 'src', - 'spec', - 'node_modules', - ]; + console.log('---> Resolving symlinks'); + const dirs = ['internal_packages', 'src', 'spec', 'node_modules']; - dirs.forEach((dir) => { + dirs.forEach(dir => { const absoluteDir = path.join(appDir, dir); - fs.readdirSync(absoluteDir).forEach((packageName) => { - const relativePackageDir = path.join(dir, packageName) - const absolutePackageDir = path.join(absoluteDir, packageName) - const realPackagePath = fs.realpathSync(absolutePackageDir).replace('/private/', '/') + fs.readdirSync(absoluteDir).forEach(packageName => { + const relativePackageDir = path.join(dir, packageName); + const absolutePackageDir = path.join(absoluteDir, packageName); + const realPackagePath = fs.realpathSync(absolutePackageDir).replace('/private/', '/'); if (realPackagePath !== absolutePackageDir) { - console.log(` ---> Resolving '${relativePackageDir}' to '${realPackagePath}'`) - symlinkedPackages.push({realPackagePath, relativePackageDir}) + console.log(` ---> Resolving '${relativePackageDir}' to '${realPackagePath}'`); + symlinkedPackages.push({ realPackagePath, relativePackageDir }); } }); }); } function runCopySymlinkedPackages(buildPath, electronVersion, platform, arch, callback) { - console.log("---> Moving symlinked node modules / internal packages into build folder.") + console.log('---> Moving symlinked node modules / internal packages into build folder.'); - symlinkedPackages.forEach(({realPackagePath, relativePackageDir}) => { - const packagePath = path.join(buildPath, relativePackageDir) + symlinkedPackages.forEach(({ realPackagePath, relativePackageDir }) => { + const packagePath = path.join(buildPath, relativePackageDir); console.log(` ---> Copying ${realPackagePath} to ${packagePath}`); fs.removeSync(packagePath); fs.copySync(realPackagePath, packagePath); @@ -77,13 +72,13 @@ module.exports = (grunt) => { } function runTranspilers(buildPath, electronVersion, platform, arch, callback) { - console.log("---> Running babel and coffeescript transpilers") + console.log('---> Running babel and coffeescript transpilers'); grunt.config('source:coffeescript').forEach(pattern => { - glob.sync(pattern, {cwd: buildPath}).forEach((relPath) => { - const coffeepath = path.join(buildPath, relPath) - if (/(node_modules|\.js$)/.test(coffeepath)) return - console.log(` ---> Compiling ${coffeepath.slice(coffeepath.indexOf("/app") + 4)}`) + glob.sync(pattern, { cwd: buildPath }).forEach(relPath => { + const coffeepath = path.join(buildPath, relPath); + if (/(node_modules|\.js$)/.test(coffeepath)) return; + console.log(` ---> Compiling ${coffeepath.slice(coffeepath.indexOf('/app') + 4)}`); const outPath = coffeepath.replace(path.extname(coffeepath), '.js'); const res = coffeereact.compile(grunt.file.read(coffeepath), { bare: false, @@ -95,25 +90,34 @@ module.exports = (grunt) => { generatedFile: path.basename(outPath), sourceFiles: [path.relative(buildPath, coffeepath)], }); - grunt.file.write(outPath, `${res.js}\n//# sourceMappingURL=${path.basename(outPath)}.map\n`); + grunt.file.write( + outPath, + `${res.js}\n//# sourceMappingURL=${path.basename(outPath)}.map\n` + ); grunt.file.write(`${outPath}.map`, res.v3SourceMap); fs.unlinkSync(coffeepath); }); }); grunt.config('source:es6').forEach(pattern => { - glob.sync(pattern, {cwd: buildPath}).forEach((relPath) => { - const es6Path = path.join(buildPath, relPath) - if (/(node_modules|\.js$)/.test(es6Path)) return + glob.sync(pattern, { cwd: buildPath }).forEach(relPath => { + const es6Path = path.join(buildPath, relPath); + if (/(node_modules|\.js$)/.test(es6Path)) return; const outPath = es6Path.replace(path.extname(es6Path), '.js'); - console.log(` ---> Compiling ${es6Path.slice(es6Path.indexOf("/app") + 4)}`) - const res = babel.transformFileSync(es6Path, Object.assign(babelOptions, { - sourceMaps: true, - sourceRoot: '/', - sourceMapTarget: path.relative(buildPath, outPath), - sourceFileName: path.relative(buildPath, es6Path), - })); - grunt.file.write(outPath, `${res.code}\n//# sourceMappingURL=${path.basename(outPath)}.map\n`); + console.log(` ---> Compiling ${es6Path.slice(es6Path.indexOf('/app') + 4)}`); + const res = babel.transformFileSync( + es6Path, + Object.assign(babelOptions, { + sourceMaps: true, + sourceRoot: '/', + sourceMapTarget: path.relative(buildPath, outPath), + sourceFileName: path.relative(buildPath, es6Path), + }) + ); + grunt.file.write( + outPath, + `${res.code}\n//# sourceMappingURL=${path.basename(outPath)}.map\n` + ); grunt.file.write(`${outPath}.map`, JSON.stringify(res.map)); fs.unlinkSync(es6Path); }); @@ -129,21 +133,30 @@ module.exports = (grunt) => { packager: { appVersion: packageJSON.version, platform: platform, - protocols: [{ - name: "Mailspring Protocol", - schemes: ["mailspring"], - }, { - name: "Mailto Protocol", - schemes: ["mailto"], - }], + protocols: [ + { + name: 'Mailspring Protocol', + schemes: ['mailspring'], + }, + { + name: 'Mailto Protocol', + schemes: ['mailto'], + }, + ], dir: grunt.config('appDir'), - appCategoryType: "public.app-category.business", + appCategoryType: 'public.app-category.business', tmpdir: tmpdir, arch: { - 'win32': 'ia32', + win32: 'ia32', }[platform], icon: { - darwin: path.resolve(grunt.config('appDir'), 'build', 'resources', 'mac', 'mailspring.icns'), + darwin: path.resolve( + grunt.config('appDir'), + 'build', + 'resources', + 'mac', + 'mailspring.icns' + ), win32: path.resolve(grunt.config('appDir'), 'build', 'resources', 'win', 'mailspring.ico'), linux: undefined, }[platform], @@ -155,19 +168,23 @@ module.exports = (grunt) => { appCopyright: `Copyright (C) 2014-${new Date().getFullYear()} Foundry 376, LLC. All rights reserved.`, derefSymlinks: false, asar: { - 'unpack': "{" + [ - 'mailsync', - 'mailsync.exe', - '*.dll', - '*.node', - '**/vendor/**', - 'examples/**', - '**/src/tasks/**', - '**/node_modules/spellchecker/**', - '**/node_modules/windows-shortcuts/**', - ].join(',') + "}", + unpack: + '{' + + [ + 'mailsync', + 'mailsync.exe', + '*.dll', + '*.node', + '**/vendor/**', + 'examples/**', + '**/src/tasks/**', + '**/node_modules/spellchecker/**', + '**/node_modules/windows-shortcuts/**', + ].join(',') + + '}', }, - ignore: [ // These are all relative to client-app + ignore: [ + // These are all relative to client-app // top level dirs we never want /^\/build.*/, /^\/dist.*/, @@ -235,7 +252,7 @@ module.exports = (grunt) => { // Electron.app/Contents/Info.plist. A majority of the defaults are // left in the Electron Info.plist file extendInfo: path.resolve(grunt.config('appDir'), 'build', 'resources', 'mac', 'extra.plist'), - appBundleId: "com.mailspring.mailspring", + appBundleId: 'com.mailspring.mailspring', afterCopy: [ runCopyPlatformSpecificResources, runWriteCommitHashIntoPackage, @@ -243,7 +260,7 @@ module.exports = (grunt) => { runTranspilers, ], }, - }) + }); grunt.registerTask('package', 'Package Mailspring', function pack() { const done = this.async(); @@ -253,14 +270,14 @@ module.exports = (grunt) => { console.log(util.inspect(grunt.config.get('packager'), true, 7, true)); const ongoing = setInterval(() => { - const elapsed = Math.round((Date.now() - start) / 1000.0) + const elapsed = Math.round((Date.now() - start) / 1000.0); console.log(`---> Packaging for ${elapsed}s`); - }, 1000) + }, 1000); - resolveRealSymlinkPaths(grunt.config('appDir')) + resolveRealSymlinkPaths(grunt.config('appDir')); packager(grunt.config.get('packager'), (err, appPaths) => { - clearInterval(ongoing) + clearInterval(ongoing); if (err) { grunt.fail.fatal(err); return done(err); diff --git a/app/build/tasks/setup-mac-keychain-task.js b/app/build/tasks/setup-mac-keychain-task.js index 90e466c14..5ebb14983 100644 --- a/app/build/tasks/setup-mac-keychain-task.js +++ b/app/build/tasks/setup-mac-keychain-task.js @@ -23,58 +23,86 @@ const fs = require('fs-plus'); // codesign -dvvv /path/to/N1.app // // Which should return "accepted" -module.exports = (grunt) => { +module.exports = grunt => { let getCertData; - const {spawnP} = grunt.config('taskHelpers') - const tmpKeychain = "n1-build.keychain"; + const { spawnP } = grunt.config('taskHelpers'); + const tmpKeychain = 'n1-build.keychain'; const unlockKeychain = (keychain, keychainPass) => { const args = ['unlock-keychain', '-p', keychainPass, keychain]; - return spawnP({cmd: "security", args}); + return spawnP({ cmd: 'security', args }); }; const cleanupKeychain = () => { - if (fs.existsSync(path.join(process.env.HOME, "Library", "Keychains", tmpKeychain))) { - return spawnP({cmd: "security", args: ["delete-keychain", tmpKeychain]}); + if (fs.existsSync(path.join(process.env.HOME, 'Library', 'Keychains', tmpKeychain))) { + return spawnP({ cmd: 'security', args: ['delete-keychain', tmpKeychain] }); } - return Promise.resolve() + return Promise.resolve(); }; const buildMacKeychain = () => { const crypto = require('crypto'); const tmpPass = crypto.randomBytes(32).toString('hex'); - const {appleCert, nylasCert, nylasPrivateKey, keyPass} = getCertData(); - const codesignBin = path.join("/", "usr", "bin", "codesign"); + const { appleCert, nylasCert, nylasPrivateKey, keyPass } = getCertData(); + const codesignBin = path.join('/', 'usr', 'bin', 'codesign'); // Create a custom, temporary keychain - return cleanupKeychain() - .then(() => spawnP({cmd: "security", args: ["create-keychain", '-p', tmpPass, tmpKeychain]})) - - // Due to a bug in OSX, you must list-keychain with -s in order for it - // to actually add it to the list of keychains. See http://stackoverflow.com/questions/20391911/os-x-keychain-not-visible-to-keychain-access-app-in-mavericks - .then(() => spawnP({cmd: "security", args: ["list-keychains", "-s", tmpKeychain]})) - - // Make the custom keychain default, so xcodebuild will use it for signing - .then(() => spawnP({cmd: "security", args: ["default-keychain", "-s", tmpKeychain]})) - - // Unlock the keychain - .then(() => unlockKeychain(tmpKeychain, tmpPass)) - - // Set keychain timeout to 1 hour for long builds - .then(() => spawnP({cmd: "security", args: ["set-keychain-settings", "-t", "3600", "-l", tmpKeychain]})) - - // Add certificates to keychain and allow codesign to access them - .then(() => spawnP({cmd: "security", args: ["import", appleCert, "-k", tmpKeychain, "-T", codesignBin]})) - - .then(() => spawnP({cmd: "security", args: ["import", nylasCert, "-k", tmpKeychain, "-T", codesignBin]})) - - // Load the password for the private key from environment variables - .then(() => spawnP({cmd: "security", args: ["import", nylasPrivateKey, "-k", tmpKeychain, "-P", keyPass, "-T", codesignBin]})) - - // mark that the codesign utility should be allowed to access the keychain without - // prompting for access. (Needed for Mac OS Sierra and above) - // https://stackoverflow.com/questions/39868578/security-codesign-in-sierra-keychain-ignores-access-control-settings-and-ui-p - .then(() => spawnP({cmd: "security", args: ["set-key-partition-list", "-S", "apple-tool:,apple:,codesign:", "-k", tmpPass, tmpKeychain]})) + return ( + cleanupKeychain() + .then(() => + spawnP({ cmd: 'security', args: ['create-keychain', '-p', tmpPass, tmpKeychain] }) + ) + // Due to a bug in OSX, you must list-keychain with -s in order for it + // to actually add it to the list of keychains. See http://stackoverflow.com/questions/20391911/os-x-keychain-not-visible-to-keychain-access-app-in-mavericks + .then(() => spawnP({ cmd: 'security', args: ['list-keychains', '-s', tmpKeychain] })) + // Make the custom keychain default, so xcodebuild will use it for signing + .then(() => spawnP({ cmd: 'security', args: ['default-keychain', '-s', tmpKeychain] })) + // Unlock the keychain + .then(() => unlockKeychain(tmpKeychain, tmpPass)) + // Set keychain timeout to 1 hour for long builds + .then(() => + spawnP({ + cmd: 'security', + args: ['set-keychain-settings', '-t', '3600', '-l', tmpKeychain], + }) + ) + // Add certificates to keychain and allow codesign to access them + .then(() => + spawnP({ + cmd: 'security', + args: ['import', appleCert, '-k', tmpKeychain, '-T', codesignBin], + }) + ) + .then(() => + spawnP({ + cmd: 'security', + args: ['import', nylasCert, '-k', tmpKeychain, '-T', codesignBin], + }) + ) + // Load the password for the private key from environment variables + .then(() => + spawnP({ + cmd: 'security', + args: ['import', nylasPrivateKey, '-k', tmpKeychain, '-P', keyPass, '-T', codesignBin], + }) + ) + // mark that the codesign utility should be allowed to access the keychain without + // prompting for access. (Needed for Mac OS Sierra and above) + // https://stackoverflow.com/questions/39868578/security-codesign-in-sierra-keychain-ignores-access-control-settings-and-ui-p + .then(() => + spawnP({ + cmd: 'security', + args: [ + 'set-key-partition-list', + '-S', + 'apple-tool:,apple:,codesign:', + '-k', + tmpPass, + tmpKeychain, + ], + }) + ) + ); }; getCertData = () => { @@ -86,7 +114,7 @@ module.exports = (grunt) => { const keyPass = process.env.APPLE_CODESIGN_KEY_PASSWORD; if (!keyPass) { - throw new Error("APPLE_CODESIGN_KEY_PASSWORD must be set"); + throw new Error('APPLE_CODESIGN_KEY_PASSWORD must be set'); } if (!fs.existsSync(appleCert)) { throw new Error(`${appleCert} doesn't exist`); @@ -98,21 +126,27 @@ module.exports = (grunt) => { throw new Error(`${nylasPrivateKey} doesn't exist`); } - return {appleCert, nylasCert, nylasPrivateKey, keyPass}; + return { appleCert, nylasCert, nylasPrivateKey, keyPass }; }; const shouldRun = () => { if (process.platform !== 'darwin') { grunt.log.writeln(`Skipping keychain setup since ${process.platform} is not darwin`); - return false + return false; } - return !!process.env.SIGN_BUILD - } + return !!process.env.SIGN_BUILD; + }; - grunt.registerTask('setup-mac-keychain', 'Setup Mac Keychain to sign the app', function setupMacKeychain() { - const done = this.async(); - if (!shouldRun()) return done(); + grunt.registerTask( + 'setup-mac-keychain', + 'Setup Mac Keychain to sign the app', + function setupMacKeychain() { + const done = this.async(); + if (!shouldRun()) return done(); - return buildMacKeychain().then(done).catch(grunt.fail.fatal); - }); -} + return buildMacKeychain() + .then(done) + .catch(grunt.fail.fatal); + } + ); +}; diff --git a/app/build/tasks/task-helpers.js b/app/build/tasks/task-helpers.js index 12c8de989..0f10a8a6b 100644 --- a/app/build/tasks/task-helpers.js +++ b/app/build/tasks/task-helpers.js @@ -1,6 +1,6 @@ const childProcess = require('child_process'); -module.exports = (grunt) => { +module.exports = grunt => { function spawn(options, callback) { const stdout = []; const stderr = []; @@ -8,25 +8,31 @@ module.exports = (grunt) => { const proc = childProcess.spawn(options.cmd, options.args, options.opts); proc.stdout.on('data', data => stdout.push(data.toString())); proc.stderr.on('data', data => stderr.push(data.toString())); - proc.on('error', (processError) => { - return error != null ? error : (error = processError) + proc.on('error', processError => { + return error != null ? error : (error = processError); }); proc.on('close', (exitCode, signal) => { - if (exitCode !== 0) { if (typeof error === 'undefined' || error === null) { error = new Error(signal); } } - const results = {stderr: stderr.join(''), stdout: stdout.join(''), code: exitCode}; - if (exitCode !== 0) { grunt.log.error(results.stderr); } + if (exitCode !== 0) { + if (typeof error === 'undefined' || error === null) { + error = new Error(signal); + } + } + const results = { stderr: stderr.join(''), stdout: stdout.join(''), code: exitCode }; + if (exitCode !== 0) { + grunt.log.error(results.stderr); + } return callback(error, results, exitCode); }); } function spawnP(options) { return new Promise((resolve, reject) => { - spawn(options, (error) => { + spawn(options, error => { if (error) return reject(error); - return resolve() - }) - }) + return resolve(); + }); + }); } - return {spawn, spawnP}; -} + return { spawn, spawnP }; +}; diff --git a/app/internal_packages/account-sidebar/lib/components/account-switcher.cjsx b/app/internal_packages/account-sidebar/lib/components/account-switcher.cjsx index 7c5dd4cac..81e284524 100644 --- a/app/internal_packages/account-sidebar/lib/components/account-switcher.cjsx +++ b/app/internal_packages/account-sidebar/lib/components/account-switcher.cjsx @@ -1,5 +1,4 @@ -React = require 'react' -{Actions} = require 'nylas-exports' +{Actions, React, PropTypes} = require 'nylas-exports' {RetinaImg} = require 'nylas-component-kit' AccountCommands = require '../account-commands' @@ -8,8 +7,8 @@ class AccountSwitcher extends React.Component @displayName: 'AccountSwitcher' @propTypes: - accounts: React.PropTypes.array.isRequired - sidebarAccountIds: React.PropTypes.array.isRequired + accounts: PropTypes.array.isRequired + sidebarAccountIds: PropTypes.array.isRequired _makeMenuTemplate: => diff --git a/app/internal_packages/account-sidebar/specs/sidebar-item-spec.es6 b/app/internal_packages/account-sidebar/specs/sidebar-item-spec.es6 index cd85247ec..adf043013 100644 --- a/app/internal_packages/account-sidebar/specs/sidebar-item-spec.es6 +++ b/app/internal_packages/account-sidebar/specs/sidebar-item-spec.es6 @@ -1,29 +1,29 @@ -import {Folder, Actions} from "nylas-exports" -import SidebarItem from "../lib/sidebar-item" +import { Folder, Actions } from 'nylas-exports'; +import SidebarItem from '../lib/sidebar-item'; -describe("sidebar-item", function sidebarItemSpec() { - it("preserves nested labels on rename", () => { - spyOn(Actions, "queueTask") - const categories = [new Folder({path: 'a.b/c', accountId: window.TEST_ACCOUNT_ID})] - NylasEnv.savedState.sidebarKeysCollapsed = {} - const item = SidebarItem.forCategories(categories) - item.onEdited(item, 'd') +describe('sidebar-item', function sidebarItemSpec() { + it('preserves nested labels on rename', () => { + spyOn(Actions, 'queueTask'); + const categories = [new Folder({ path: 'a.b/c', accountId: window.TEST_ACCOUNT_ID })]; + NylasEnv.savedState.sidebarKeysCollapsed = {}; + const item = SidebarItem.forCategories(categories); + item.onEdited(item, 'd'); - const task = Actions.queueTask.calls[0].args[0] - const {existingPath, path} = task; - expect(existingPath).toBe("a.b/c") - expect(path).toBe("a.b/d") - }) - it("preserves labels on rename", () => { - spyOn(Actions, "queueTask") - const categories = [new Folder({path: 'a', accountId: window.TEST_ACCOUNT_ID})] - NylasEnv.savedState.sidebarKeysCollapsed = {} - const item = SidebarItem.forCategories(categories) - item.onEdited(item, 'b') + const task = Actions.queueTask.calls[0].args[0]; + const { existingPath, path } = task; + expect(existingPath).toBe('a.b/c'); + expect(path).toBe('a.b/d'); + }); + it('preserves labels on rename', () => { + spyOn(Actions, 'queueTask'); + const categories = [new Folder({ path: 'a', accountId: window.TEST_ACCOUNT_ID })]; + NylasEnv.savedState.sidebarKeysCollapsed = {}; + const item = SidebarItem.forCategories(categories); + item.onEdited(item, 'b'); - const task = Actions.queueTask.calls[0].args[0] - const {existingPath, path} = task; - expect(existingPath).toBe("a") - expect(path).toBe("b") - }) -}) + const task = Actions.queueTask.calls[0].args[0]; + const { existingPath, path } = task; + expect(existingPath).toBe('a'); + expect(path).toBe('b'); + }); +}); diff --git a/app/internal_packages/activity-list/lib/activity-data-source.es6 b/app/internal_packages/activity-list/lib/activity-data-source.es6 index 0f05b5766..42e7a9238 100644 --- a/app/internal_packages/activity-list/lib/activity-data-source.es6 +++ b/app/internal_packages/activity-list/lib/activity-data-source.es6 @@ -1,9 +1,8 @@ -import {Rx, Message, DatabaseStore} from 'nylas-exports'; +import { Rx, Message, DatabaseStore } from 'nylas-exports'; export default class ActivityDataSource { - buildObservable({openTrackingId, linkTrackingId, messageLimit}) { - const query = DatabaseStore - .findAll(Message) + buildObservable({ openTrackingId, linkTrackingId, messageLimit }) { + const query = DatabaseStore.findAll(Message) .order(Message.attributes.date.descending()) .where(Message.attributes.pluginMetadata.contains(openTrackingId, linkTrackingId)) .limit(messageLimit); diff --git a/app/internal_packages/activity-list/lib/activity-list-actions.es6 b/app/internal_packages/activity-list/lib/activity-list-actions.es6 index 04821ea83..7d4a454a7 100644 --- a/app/internal_packages/activity-list/lib/activity-list-actions.es6 +++ b/app/internal_packages/activity-list/lib/activity-list-actions.es6 @@ -1,8 +1,6 @@ import Reflux from 'reflux'; -const ActivityListActions = Reflux.createActions([ - "resetSeen", -]); +const ActivityListActions = Reflux.createActions(['resetSeen']); for (const key of Object.keys(ActivityListActions)) { ActivityListActions[key].sync = true; diff --git a/app/internal_packages/activity-list/lib/activity-list-button.jsx b/app/internal_packages/activity-list/lib/activity-list-button.jsx index f0e16e337..5ac202d6f 100644 --- a/app/internal_packages/activity-list/lib/activity-list-button.jsx +++ b/app/internal_packages/activity-list/lib/activity-list-button.jsx @@ -1,11 +1,10 @@ import React from 'react'; -import {Actions, ReactDOM} from 'nylas-exports'; -import {RetinaImg} from 'nylas-component-kit'; +import { Actions, ReactDOM } from 'nylas-exports'; +import { RetinaImg } from 'nylas-component-kit'; import ActivityList from './activity-list'; import ActivityListStore from './activity-list-store'; - class ActivityListButton extends React.Component { static displayName = 'ActivityListButton'; @@ -24,39 +23,29 @@ class ActivityListButton extends React.Component { onClick = () => { const buttonRect = ReactDOM.findDOMNode(this).getBoundingClientRect(); - Actions.openPopover( - , - {originRect: buttonRect, direction: 'down'} - ); - } + Actions.openPopover(, { originRect: buttonRect, direction: 'down' }); + }; _onDataChanged = () => { this.setState(this._getStateFromStores()); - } + }; _getStateFromStores() { return { unreadCount: ActivityListStore.unreadCount(), - } + }; } render() { - let unreadCountClass = "unread-count"; - let iconClass = "activity-toolbar-icon"; + let unreadCountClass = 'unread-count'; + let iconClass = 'activity-toolbar-icon'; if (this.state.unreadCount) { - unreadCountClass += " active"; - iconClass += " unread"; + unreadCountClass += ' active'; + iconClass += ' unread'; } return ( -
-
- {this.state.unreadCount} -
+
+
{this.state.unreadCount}
- Enable read receipts or - link tracking to - see notifications here. + Enable read receipts{' '} + or link + tracking {' '} + to see notifications here.
); -} +}; export default ActivityListEmptyState; diff --git a/app/internal_packages/activity-list/lib/activity-list-item-container.jsx b/app/internal_packages/activity-list/lib/activity-list-item-container.jsx index ceb0131ed..7e54fe85f 100644 --- a/app/internal_packages/activity-list/lib/activity-list-item-container.jsx +++ b/app/internal_packages/activity-list/lib/activity-list-item-container.jsx @@ -1,19 +1,16 @@ import React from 'react'; +import PropTypes from 'prop-types'; -import {DisclosureTriangle, - Flexbox, - RetinaImg} from 'nylas-component-kit'; -import {DateUtils} from 'nylas-exports'; +import { DisclosureTriangle, Flexbox, RetinaImg } from 'nylas-component-kit'; +import { DateUtils } from 'nylas-exports'; import ActivityListStore from './activity-list-store'; -import {pluginFor} from './plugin-helpers'; - +import { pluginFor } from './plugin-helpers'; class ActivityListItemContainer extends React.Component { - static displayName = 'ActivityListItemContainer'; static propTypes = { - group: React.PropTypes.array, + group: PropTypes.array, }; constructor(props) { @@ -27,15 +24,15 @@ class ActivityListItemContainer extends React.Component { ActivityListStore.focusThread(threadId); } - _onCollapseToggled = (event) => { + _onCollapseToggled = event => { event.stopPropagation(); - this.setState({collapsed: !this.state.collapsed}); - } + this.setState({ collapsed: !this.state.collapsed }); + }; _getText() { const text = { - recipient: "Someone", - title: "(No Subject)", + recipient: 'Someone', + title: '(No Subject)', date: new Date(0), }; const lastAction = this.props.group[0]; @@ -64,18 +61,13 @@ class ActivityListItemContainer extends React.Component { const date = new Date(0); date.setUTCSeconds(action.timestamp); actions.push( -
+
- {action.recipient ? action.recipient.displayName() : "Someone"} + {action.recipient ? action.recipient.displayName() : 'Someone'}
-
- {DateUtils.shortTimeString(date)} -
+
{DateUtils.shortTimeString(date)}
); @@ -83,7 +75,7 @@ class ActivityListItemContainer extends React.Component { return (
{actions}
@@ -92,10 +84,10 @@ class ActivityListItemContainer extends React.Component { render() { const lastAction = this.props.group[0]; - let className = "activity-list-item"; - if (!ActivityListStore.hasBeenViewed(lastAction)) className += " unread"; + let className = 'activity-list-item'; + if (!ActivityListStore.hasBeenViewed(lastAction)) className += ' unread'; const text = this._getText(); - let disclosureTriangle = (
); + let disclosureTriangle =
; if (this.props.group.length > 1) { disclosureTriangle = ( { this._onClick(lastAction.threadId) }}> +
{ + this._onClick(lastAction.threadId); + }} + > - +
-
- {DateUtils.shortTimeString(text.date)} -
+
{DateUtils.shortTimeString(text.date)}
-
- {text.title} -
+
{text.title}
{this.renderActivityContainer()}
); } - } export default ActivityListItemContainer; diff --git a/app/internal_packages/activity-list/lib/activity-list-store.jsx b/app/internal_packages/activity-list/lib/activity-list-store.jsx index c7fa3339a..b35b9829f 100644 --- a/app/internal_packages/activity-list/lib/activity-list-store.jsx +++ b/app/internal_packages/activity-list/lib/activity-list-store.jsx @@ -8,8 +8,7 @@ import { } from 'nylas-exports'; import ActivityListActions from './activity-list-actions'; import ActivityDataSource from './activity-data-source'; -import {pluginFor} from './plugin-helpers'; - +import { pluginFor } from './plugin-helpers'; class ActivityListStore extends NylasStore { activate() { @@ -38,7 +37,7 @@ class ActivityListStore extends NylasStore { } else if (!this._unreadCount) { return null; } - return "999+"; + return '999+'; } hasBeenViewed(action) { @@ -47,16 +46,18 @@ class ActivityListStore extends NylasStore { } focusThread(threadId) { - NylasEnv.displayWindow() - Actions.closePopover() - DatabaseStore.find(Thread, threadId).then((thread) => { + NylasEnv.displayWindow(); + Actions.closePopover(); + DatabaseStore.find(Thread, threadId).then(thread => { if (!thread) { - NylasEnv.reportError(new Error(`ActivityListStore::focusThread: Can't find thread`, {threadId})) - NylasEnv.showErrorDialog(`Can't find the selected thread in your mailbox`) + NylasEnv.reportError( + new Error(`ActivityListStore::focusThread: Can't find thread`, { threadId }) + ); + NylasEnv.showErrorDialog(`Can't find the selected thread in your mailbox`); return; } Actions.ensureCategoryIsFocused('sent', thread.accountId); - Actions.setFocus({collection: 'thread', item: thread}); + Actions.setFocus({ collection: 'thread', item: thread }); }); } @@ -85,14 +86,16 @@ class ActivityListStore extends NylasStore { _getActivity() { const dataSource = this._dataSource(); - this._subscription = dataSource.buildObservable({ - openTrackingId: NylasEnv.packages.pluginIdFor('open-tracking'), - linkTrackingId: NylasEnv.packages.pluginIdFor('link-tracking'), - messageLimit: 500, - }).subscribe((messages) => { - this._messages = messages; - this._updateActivity(); - }); + this._subscription = dataSource + .buildObservable({ + openTrackingId: NylasEnv.packages.pluginIdFor('open-tracking'), + linkTrackingId: NylasEnv.packages.pluginIdFor('link-tracking'), + messageLimit: 500, + }) + .subscribe(messages => { + this._messages = messages; + this._updateActivity(); + }); } _updateActivity() { @@ -107,10 +110,12 @@ class ActivityListStore extends NylasStore { const sidebarAccountIds = FocusedPerspectiveStore.sidebarAccountIds(); for (const message of messages) { if (sidebarAccountIds.length > 1 || message.accountId === sidebarAccountIds[0]) { - const openTrackingId = NylasEnv.packages.pluginIdFor('open-tracking') - const linkTrackingId = NylasEnv.packages.pluginIdFor('link-tracking') - if (message.metadataForPluginId(openTrackingId) || - message.metadataForPluginId(linkTrackingId)) { + const openTrackingId = NylasEnv.packages.pluginIdFor('open-tracking'); + const linkTrackingId = NylasEnv.packages.pluginIdFor('link-tracking'); + if ( + message.metadataForPluginId(openTrackingId) || + message.metadataForPluginId(linkTrackingId) + ) { actions = actions.concat(this._openActionsForMessage(message)); actions = actions.concat(this._linkActionsForMessage(message)); } @@ -119,7 +124,7 @@ class ActivityListStore extends NylasStore { if (!this._lastNotified) this._lastNotified = {}; for (const notification of this._notifications) { const lastNotified = this._lastNotified[notification.threadId]; - const {notificationInterval} = pluginFor(notification.pluginId); + const { notificationInterval } = pluginFor(notification.pluginId); if (!lastNotified || lastNotified < Date.now() - notificationInterval) { NativeNotifications.displayNotification(notification.data); this._lastNotified[notification.threadId] = Date.now(); @@ -137,7 +142,7 @@ class ActivityListStore extends NylasStore { } _openActionsForMessage(message) { - const openTrackingId = NylasEnv.packages.pluginIdFor('open-tracking') + const openTrackingId = NylasEnv.packages.pluginIdFor('open-tracking'); const openMetadata = message.metadataForPluginId(openTrackingId); const recipients = message.to.concat(message.cc, message.bcc); const actions = []; @@ -150,10 +155,12 @@ class ActivityListStore extends NylasStore { pluginId: openTrackingId, threadId: message.threadId, data: { - title: "New open", - subtitle: `${recipient ? recipient.displayName() : "Someone"} just opened ${message.subject}`, + title: 'New open', + subtitle: `${recipient + ? recipient.displayName() + : 'Someone'} just opened ${message.subject}`, canReply: false, - tag: "message-open", + tag: 'message-open', onActivate: () => { this.focusThread(message.threadId); }, @@ -176,8 +183,8 @@ class ActivityListStore extends NylasStore { } _linkActionsForMessage(message) { - const linkTrackingId = NylasEnv.packages.pluginIdFor('link-tracking') - const linkMetadata = message.metadataForPluginId(linkTrackingId) + const linkTrackingId = NylasEnv.packages.pluginIdFor('link-tracking'); + const linkMetadata = message.metadataForPluginId(linkTrackingId); const recipients = message.to.concat(message.cc, message.bcc); const actions = []; if (linkMetadata && linkMetadata.links) { @@ -189,10 +196,12 @@ class ActivityListStore extends NylasStore { pluginId: linkTrackingId, threadId: message.threadId, data: { - title: "New click", - subtitle: `${recipient ? recipient.displayName() : "Someone"} just clicked ${link.url}.`, + title: 'New click', + subtitle: `${recipient + ? recipient.displayName() + : 'Someone'} just clicked ${link.url}.`, canReply: false, - tag: "link-open", + tag: 'link-open', onActivate: () => { this.focusThread(message.threadId); }, diff --git a/app/internal_packages/activity-list/lib/activity-list.jsx b/app/internal_packages/activity-list/lib/activity-list.jsx index e60473e3c..0a5aee77f 100644 --- a/app/internal_packages/activity-list/lib/activity-list.jsx +++ b/app/internal_packages/activity-list/lib/activity-list.jsx @@ -1,15 +1,13 @@ import React from 'react'; import classnames from 'classnames'; -import {Flexbox, - ScrollRegion} from 'nylas-component-kit'; +import { Flexbox, ScrollRegion } from 'nylas-component-kit'; import ActivityListStore from './activity-list-store'; import ActivityListActions from './activity-list-actions'; import ActivityListItemContainer from './activity-list-item-container'; import ActivityListEmptyState from './activity-list-empty-state'; class ActivityList extends React.Component { - static displayName = 'ActivityList'; constructor() { @@ -28,7 +26,7 @@ class ActivityList extends React.Component { _onDataChanged = () => { this.setState(this._getStateFromStores()); - } + }; _getStateFromStores() { const actions = ActivityListStore.actions(); @@ -36,7 +34,7 @@ class ActivityList extends React.Component { actions: actions, empty: actions instanceof Array && actions.length === 0, collapsedToggles: this.state ? this.state.collapsedToggles : {}, - } + }; } _groupActions(actions) { @@ -44,8 +42,10 @@ class ActivityList extends React.Component { for (const action of actions) { if (groupedActions.length > 0) { const currentGroup = groupedActions[groupedActions.length - 1]; - if (action.messageId === currentGroup[0].messageId && - action.pluginId === currentGroup[0].pluginId) { + if ( + action.messageId === currentGroup[0].messageId && + action.pluginId === currentGroup[0].pluginId + ) { groupedActions[groupedActions.length - 1].push(action); } else { groupedActions.push([action]); @@ -59,13 +59,11 @@ class ActivityList extends React.Component { renderActions() { if (this.state.empty) { - return ( - - ) + return ; } const groupedActions = this._groupActions(this.state.actions); - return groupedActions.map((group) => { + return groupedActions.map(group => { return ( - - {this.renderActions()} - + + {this.renderActions()} ); } diff --git a/app/internal_packages/activity-list/lib/main.es6 b/app/internal_packages/activity-list/lib/main.es6 index 6ac9bd2b6..73a4f7fe6 100644 --- a/app/internal_packages/activity-list/lib/main.es6 +++ b/app/internal_packages/activity-list/lib/main.es6 @@ -1,10 +1,10 @@ -import {ComponentRegistry, WorkspaceStore} from 'nylas-exports'; -import {HasTutorialTip} from 'nylas-component-kit'; +import { ComponentRegistry, WorkspaceStore } from 'nylas-exports'; +import { HasTutorialTip } from 'nylas-component-kit'; import ActivityListButton from './activity-list-button'; import ActivityListStore from './activity-list-store'; const ActivityListButtonWithTutorialTip = HasTutorialTip(ActivityListButton, { - title: "Open and link tracking", + title: 'Open and link tracking', instructions: "If you've enabled link tracking or read receipts, those events will appear here!", }); @@ -15,7 +15,6 @@ export function activate() { ActivityListStore.activate(); } - export function deactivate() { ComponentRegistry.unregister(ActivityListButtonWithTutorialTip); } diff --git a/app/internal_packages/activity-list/lib/plugin-helpers.es6 b/app/internal_packages/activity-list/lib/plugin-helpers.es6 index 8e9d5a6a4..770d0a2b9 100644 --- a/app/internal_packages/activity-list/lib/plugin-helpers.es6 +++ b/app/internal_packages/activity-list/lib/plugin-helpers.es6 @@ -1,22 +1,21 @@ - export function pluginFor(id) { - const openTrackingId = NylasEnv.packages.pluginIdFor('open-tracking') - const linkTrackingId = NylasEnv.packages.pluginIdFor('link-tracking') + const openTrackingId = NylasEnv.packages.pluginIdFor('open-tracking'); + const linkTrackingId = NylasEnv.packages.pluginIdFor('link-tracking'); if (id === openTrackingId) { return { - name: "open", - predicate: "opened", - iconName: "icon-activity-mailopen.png", + name: 'open', + predicate: 'opened', + iconName: 'icon-activity-mailopen.png', notificationInterval: 600000, // 10 minutes in ms - } + }; } if (id === linkTrackingId) { return { - name: "link", - predicate: "clicked", - iconName: "icon-activity-linkopen.png", + name: 'link', + predicate: 'clicked', + iconName: 'icon-activity-linkopen.png', notificationInterval: 10000, // 10 seconds in ms - } + }; } - return undefined + return undefined; } diff --git a/app/internal_packages/activity-list/lib/test-data-source.es6 b/app/internal_packages/activity-list/lib/test-data-source.es6 index 40022ee02..893d11939 100644 --- a/app/internal_packages/activity-list/lib/test-data-source.es6 +++ b/app/internal_packages/activity-list/lib/test-data-source.es6 @@ -5,14 +5,14 @@ export default class TestDataSource { manuallyTrigger = (messages = []) => { this.onNext(messages); - } + }; subscribe(onNext) { this.onNext = onNext; this.manuallyTrigger(); const dispose = () => { this._unsub(); - } - return {dispose}; + }; + return { dispose }; } } diff --git a/app/internal_packages/activity-list/specs/activity-list-spec.jsx b/app/internal_packages/activity-list/specs/activity-list-spec.jsx index 3a4b7ab8e..4bbc903a2 100644 --- a/app/internal_packages/activity-list/specs/activity-list-spec.jsx +++ b/app/internal_packages/activity-list/specs/activity-list-spec.jsx @@ -12,79 +12,97 @@ import ActivityList from '../lib/activity-list'; import ActivityListStore from '../lib/activity-list-store'; import TestDataSource from '../lib/test-data-source'; -const OPEN_TRACKING_ID = 'open-tracking-id' -const LINK_TRACKING_ID = 'link-tracking-id' +const OPEN_TRACKING_ID = 'open-tracking-id'; +const LINK_TRACKING_ID = 'link-tracking-id'; const messages = [ new Message({ id: 'a', - accountId: "0000000000000000000000000", + accountId: '0000000000000000000000000', bcc: [], cc: [], - snippet: "Testing.", - subject: "Open me!", - threadId: "0000000000000000000000000", - to: [new Contact({ - name: "Jackie Luo", - email: "jackie@nylas.com", - })], + snippet: 'Testing.', + subject: 'Open me!', + threadId: '0000000000000000000000000', + to: [ + new Contact({ + name: 'Jackie Luo', + email: 'jackie@nylas.com', + }), + ], }), new Message({ id: 'b', - accountId: "0000000000000000000000000", - bcc: [new Contact({ - name: "Ben Gotow", - email: "ben@nylas.com", - })], + accountId: '0000000000000000000000000', + bcc: [ + new Contact({ + name: 'Ben Gotow', + email: 'ben@nylas.com', + }), + ], cc: [], - snippet: "Hey! I am in town for the week...", - subject: "Coffee?", - threadId: "0000000000000000000000000", - to: [new Contact({ - name: "Jackie Luo", - email: "jackie@nylas.com", - })], + snippet: 'Hey! I am in town for the week...', + subject: 'Coffee?', + threadId: '0000000000000000000000000', + to: [ + new Contact({ + name: 'Jackie Luo', + email: 'jackie@nylas.com', + }), + ], }), new Message({ id: 'c', - accountId: "0000000000000000000000000", + accountId: '0000000000000000000000000', bcc: [], - cc: [new Contact({ - name: "Evan Morikawa", - email: "evan@nylas.com", - })], + cc: [ + new Contact({ + name: 'Evan Morikawa', + email: 'evan@nylas.com', + }), + ], snippet: "Here's the latest deals!", - subject: "Newsletter", - threadId: "0000000000000000000000000", - to: [new Contact({ - name: "Juan Tejada", - email: "juan@nylas.com", - })], + subject: 'Newsletter', + threadId: '0000000000000000000000000', + to: [ + new Contact({ + name: 'Juan Tejada', + email: 'juan@nylas.com', + }), + ], }), ]; let pluginValue = { open_count: 1, - open_data: [{ - timestamp: 1461361759.351055, - }], + open_data: [ + { + timestamp: 1461361759.351055, + }, + ], }; messages[0].directlyAttachMetadata(OPEN_TRACKING_ID, pluginValue); pluginValue = { - links: [{ - click_count: 1, - click_data: [{ - timestamp: 1461349232.495837, - }], - }], + links: [ + { + click_count: 1, + click_data: [ + { + timestamp: 1461349232.495837, + }, + ], + }, + ], tracked: true, }; messages[0].directlyAttachMetadata(LINK_TRACKING_ID, pluginValue); pluginValue = { open_count: 1, - open_data: [{ - timestamp: 1461361763.283720, - }], + open_data: [ + { + timestamp: 1461361763.28372, + }, + ], }; messages[1].directlyAttachMetadata(OPEN_TRACKING_ID, pluginValue); pluginValue = { @@ -98,51 +116,55 @@ pluginValue = { }; messages[2].directlyAttachMetadata(OPEN_TRACKING_ID, pluginValue); pluginValue = { - links: [{ - click_count: 0, - click_data: [], - }], + links: [ + { + click_count: 0, + click_data: [], + }, + ], tracked: true, }; messages[2].directlyAttachMetadata(LINK_TRACKING_ID, pluginValue); - describe('ActivityList', function activityList() { beforeEach(() => { this.testSource = new TestDataSource(); - spyOn(NylasEnv.packages, 'pluginIdFor').andCallFake((pluginName) => { + spyOn(NylasEnv.packages, 'pluginIdFor').andCallFake(pluginName => { if (pluginName === 'open-tracking') { - return OPEN_TRACKING_ID + return OPEN_TRACKING_ID; } if (pluginName === 'link-tracking') { - return LINK_TRACKING_ID + return LINK_TRACKING_ID; } - return null - }) - spyOn(ActivityListStore, "_dataSource").andReturn(this.testSource); - spyOn(FocusedPerspectiveStore, "sidebarAccountIds").andReturn(["0000000000000000000000000"]); - spyOn(DatabaseStore, "run").andCallFake((query) => { + return null; + }); + spyOn(ActivityListStore, '_dataSource').andReturn(this.testSource); + spyOn(FocusedPerspectiveStore, 'sidebarAccountIds').andReturn(['0000000000000000000000000']); + spyOn(DatabaseStore, 'run').andCallFake(query => { if (query._klass === Thread) { const thread = new Thread({ - id: "0000000000000000000000000", + id: '0000000000000000000000000', accountId: TEST_ACCOUNT_ID, }); return Promise.resolve(thread); } return null; }); - spyOn(ActivityListStore, "focusThread").andCallThrough(); - spyOn(NylasEnv, "displayWindow"); - spyOn(Actions, "closePopover"); - spyOn(Actions, "setFocus"); - spyOn(Actions, "ensureCategoryIsFocused"); + spyOn(ActivityListStore, 'focusThread').andCallThrough(); + spyOn(NylasEnv, 'displayWindow'); + spyOn(Actions, 'closePopover'); + spyOn(Actions, 'setFocus'); + spyOn(Actions, 'ensureCategoryIsFocused'); ActivityListStore.activate(); this.component = ReactTestUtils.renderIntoDocument(); }); describe('when no actions are found', () => { it('should show empty state', () => { - const items = ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, "activity-list-item"); + const items = ReactTestUtils.scryRenderedDOMComponentsWithClass( + this.component, + 'activity-list-item' + ); expect(items.length).toBe(0); }); }); @@ -151,37 +173,61 @@ describe('ActivityList', function activityList() { it('should show activity list items', () => { this.testSource.manuallyTrigger(messages); waitsFor(() => { - const items = ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, "activity-list-item"); + const items = ReactTestUtils.scryRenderedDOMComponentsWithClass( + this.component, + 'activity-list-item' + ); return items.length > 0; }); runs(() => { - expect(ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, "activity-list-item").length).toBe(3); + expect( + ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, 'activity-list-item') + .length + ).toBe(3); }); }); it('should show the correct items', () => { this.testSource.manuallyTrigger(messages); waitsFor(() => { - const items = ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, "activity-list-item"); + const items = ReactTestUtils.scryRenderedDOMComponentsWithClass( + this.component, + 'activity-list-item' + ); return items.length > 0; }); runs(() => { - expect(ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, "activity-list-item")[0].textContent).toBe("Someone opened:Apr 22 2016Coffee?"); - expect(ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, "activity-list-item")[1].textContent).toBe("Jackie Luo opened:Apr 22 2016Open me!"); - expect(ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, "activity-list-item")[2].textContent).toBe("Jackie Luo clicked:Apr 22 2016(No Subject)"); + expect( + ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, 'activity-list-item')[0] + .textContent + ).toBe('Someone opened:Apr 22 2016Coffee?'); + expect( + ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, 'activity-list-item')[1] + .textContent + ).toBe('Jackie Luo opened:Apr 22 2016Open me!'); + expect( + ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, 'activity-list-item')[2] + .textContent + ).toBe('Jackie Luo clicked:Apr 22 2016(No Subject)'); }); }); xit('should focus the thread', () => { runs(() => { return this.testSource.manuallyTrigger(messages); - }) + }); waitsFor(() => { - const items = ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, "activity-list-item"); + const items = ReactTestUtils.scryRenderedDOMComponentsWithClass( + this.component, + 'activity-list-item' + ); return items.length > 0; }); runs(() => { - const item = ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, "activity-list-item")[0]; + const item = ReactTestUtils.scryRenderedDOMComponentsWithClass( + this.component, + 'activity-list-item' + )[0]; ReactTestUtils.Simulate.click(item); }); waitsFor(() => { diff --git a/app/internal_packages/analytics/analytics-electron/index.es6 b/app/internal_packages/analytics/analytics-electron/index.es6 index 135013fe7..1c210dd3e 100644 --- a/app/internal_packages/analytics/analytics-electron/index.es6 +++ b/app/internal_packages/analytics/analytics-electron/index.es6 @@ -3,7 +3,7 @@ const assert = require('assert'); const crypto = require('crypto'); const validate = require('@segment/loosely-validate-event'); const debug = require('debug')('analytics-node'); -const version = `3.0.0` +const version = `3.0.0`; // BG: Dependencies of analytics-node I lifted in @@ -11,13 +11,13 @@ const version = `3.0.0` const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; function uid(length, fn) { - const str = (bytes) => { + const str = bytes => { const res = []; for (let i = 0; i < bytes.length; i++) { res.push(chars[bytes[i] % chars.length]); } return res.join(''); - } + }; if (typeof length === 'function') { fn = length; @@ -45,9 +45,8 @@ function removeSlash(str) { return String(str).replace(/\/+$/, ''); } - -const setImmediate = global.setImmediate || process.nextTick.bind(process) -const noop = () => {} +const setImmediate = global.setImmediate || process.nextTick.bind(process); +const noop = () => {}; export default class Analytics { /** @@ -62,16 +61,16 @@ export default class Analytics { */ constructor(writeKey, options) { - options = options || {} + options = options || {}; - assert(writeKey, 'You must pass your Segment project\'s write key.') + assert(writeKey, "You must pass your Segment project's write key."); - this.queue = [] - this.writeKey = writeKey - this.host = removeSlash(options.host || 'https://api.segment.io') - this.flushAt = Math.max(options.flushAt, 1) || 20 - this.flushInterval = options.flushInterval || 10000 - this.flushed = false + this.queue = []; + this.writeKey = writeKey; + this.host = removeSlash(options.host || 'https://api.segment.io'); + this.flushAt = Math.max(options.flushAt, 1) || 20; + this.flushInterval = options.flushInterval || 10000; + this.flushed = false; } /** @@ -83,9 +82,9 @@ export default class Analytics { */ identify(message, callback) { - validate(message, 'identify') - this.enqueue('identify', message, callback) - return this + validate(message, 'identify'); + this.enqueue('identify', message, callback); + return this; } /** @@ -97,9 +96,9 @@ export default class Analytics { */ group(message, callback) { - validate(message, 'group') - this.enqueue('group', message, callback) - return this + validate(message, 'group'); + this.enqueue('group', message, callback); + return this; } /** @@ -111,9 +110,9 @@ export default class Analytics { */ track(message, callback) { - validate(message, 'track') - this.enqueue('track', message, callback) - return this + validate(message, 'track'); + this.enqueue('track', message, callback); + return this; } /** @@ -125,9 +124,9 @@ export default class Analytics { */ page(message, callback) { - validate(message, 'page') - this.enqueue('page', message, callback) - return this + validate(message, 'page'); + this.enqueue('page', message, callback); + return this; } /** @@ -139,9 +138,9 @@ export default class Analytics { */ screen(message, callback) { - validate(message, 'screen') - this.enqueue('screen', message, callback) - return this + validate(message, 'screen'); + this.enqueue('screen', message, callback); + return this; } /** @@ -153,9 +152,9 @@ export default class Analytics { */ alias(message, callback) { - validate(message, 'alias') - this.enqueue('alias', message, callback) - return this + validate(message, 'alias'); + this.enqueue('alias', message, callback); + return this; } /** @@ -169,45 +168,51 @@ export default class Analytics { */ enqueue(type, message, callback) { - callback = callback || noop + callback = callback || noop; - message = Object.assign({}, message) - message.type = type - message.context = Object.assign({ - library: { - name: 'analytics-node', - version, + message = Object.assign({}, message); + message.type = type; + message.context = Object.assign( + { + library: { + name: 'analytics-node', + version, + }, }, - }, message.context) + message.context + ); - message._metadata = Object.assign({ - nodeVersion: process.versions.node, - }, message._metadata) + message._metadata = Object.assign( + { + nodeVersion: process.versions.node, + }, + message._metadata + ); if (!message.timestamp) { - message.timestamp = new Date() + message.timestamp = new Date(); } if (!message.messageId) { - message.messageId = `node-${uid(32)}` + message.messageId = `node-${uid(32)}`; } - debug('%s: %o', type, message) + debug('%s: %o', type, message); - this.queue.push({ message, callback }) + this.queue.push({ message, callback }); if (!this.flushed) { - this.flushed = true - this.flush() - return + this.flushed = true; + this.flush(); + return; } if (this.queue.length >= this.flushAt) { - this.flush() + this.flush(); } if (this.flushInterval && !this.timer) { - this.timer = setTimeout(this.flush.bind(this), this.flushInterval) + this.timer = setTimeout(this.flush.bind(this), this.flushInterval); } } @@ -219,29 +224,29 @@ export default class Analytics { */ async flush(callback) { - callback = callback || noop + callback = callback || noop; if (this.timer) { - clearTimeout(this.timer) - this.timer = null + clearTimeout(this.timer); + this.timer = null; } if (!this.queue.length) { - setImmediate(callback) + setImmediate(callback); return; } - const items = this.queue.splice(0, this.flushAt) - const callbacks = items.map(item => item.callback) - const messages = items.map(item => item.message) + const items = this.queue.splice(0, this.flushAt); + const callbacks = items.map(item => item.callback); + const messages = items.map(item => item.message); const data = { batch: messages, timestamp: new Date(), sentAt: new Date(), - } + }; - debug('flush: %o', data) + debug('flush: %o', data); const options = { body: JSON.stringify(data), @@ -250,11 +255,11 @@ export default class Analytics { method: 'POST', }; options.headers.set('Accept', 'application/json'); - options.headers.set('Authorization', `Basic ${btoa(`${this.writeKey}:`)}`) + options.headers.set('Authorization', `Basic ${btoa(`${this.writeKey}:`)}`); options.headers.set('Content-Type', 'application/json'); - const runCallbacks = (err) => { - callbacks.forEach((cb) => cb(err)) + const runCallbacks = err => { + callbacks.forEach(cb => cb(err)); callback(err, data); debug('flushed: %o', data); }; diff --git a/app/internal_packages/analytics/lib/analytics-store.es6 b/app/internal_packages/analytics/lib/analytics-store.es6 index 9dac3c611..445f46bbb 100644 --- a/app/internal_packages/analytics/lib/analytics-store.es6 +++ b/app/internal_packages/analytics/lib/analytics-store.es6 @@ -1,14 +1,14 @@ -import _ from 'underscore' -import NylasStore from 'nylas-store' +import _ from 'underscore'; +import NylasStore from 'nylas-store'; import { IdentityStore, Actions, AccountStore, FocusedPerspectiveStore, NylasAPIRequest, -} from 'nylas-exports' +} from 'nylas-exports'; -import AnalyticsSink from '../analytics-electron' +import AnalyticsSink from '../analytics-electron'; /** * We wait 15 seconds to give the alias time to register before we send @@ -17,16 +17,15 @@ import AnalyticsSink from '../analytics-electron' const DEBOUNCE_TIME = 5 * 1000; class AnalyticsStore extends NylasStore { - activate() { // Allow requests to be grouped together if they're fired back-to-back, // but generally report each event as it happens. This segment library // is intended for a server where the user doesn't quit... - this.analytics = new AnalyticsSink("mailspring", { + this.analytics = new AnalyticsSink('mailspring', { host: `${NylasAPIRequest.rootURLForServer('identity')}/api/s`, flushInterval: 500, flushAt: 5, - }) + }); this.launchTime = Date.now(); const identifySoon = _.debounce(this.identify, DEBOUNCE_TIME); @@ -39,7 +38,7 @@ class AnalyticsStore extends NylasStore { this.listenTo(IdentityStore, identifySoon); this.listenTo(Actions.recordUserEvent, (eventName, eventArgs) => { this.track(eventName, eventArgs); - }) + }); } // Properties applied to all events (only). @@ -53,7 +52,9 @@ class AnalyticsStore extends NylasStore { currentProvider = 'Unified'; } else { // Warning: when you auth a new account there's a single moment where the account cannot be found - const account = perspective ? AccountStore.accountForId(perspective.accountIds[0]) : AccountStore.accounts()[0]; + const account = perspective + ? AccountStore.accountForId(perspective.accountIds[0]) + : AccountStore.accounts()[0]; currentProvider = account && account.displayProvider(); } @@ -67,10 +68,10 @@ class AnalyticsStore extends NylasStore { const theme = NylasEnv.themes ? NylasEnv.themes.getActiveTheme() : null; return { - version: NylasEnv.getVersion().split("-")[0], + version: NylasEnv.getVersion().split('-')[0], platform: process.platform, activeTheme: theme ? theme.name : null, - workspaceMode: NylasEnv.config.get("core.workspace.mode"), + workspaceMode: NylasEnv.config.get('core.workspace.mode'), }; } @@ -79,13 +80,15 @@ class AnalyticsStore extends NylasStore { firstDaySeen: this.firstDaySeen(), timeSinceLaunch: (Date.now() - this.launchTime) / 1000, accountCount: AccountStore.accounts().length, - providers: AccountStore.accounts().map((a) => a.displayProvider()), + providers: AccountStore.accounts().map(a => a.displayProvider()), }); } personalTraits() { const identity = IdentityStore.identity(); - if (!(identity && identity.id)) { return {}; } + if (!(identity && identity.id)) { + return {}; + } return { email: identity.emailAddress, @@ -97,26 +100,24 @@ class AnalyticsStore extends NylasStore { track(eventName, eventArgs = {}) { // if (NylasEnv.inDevMode()) { return } - const identity = IdentityStore.identity() - if (!(identity && identity.id)) { return; } + const identity = IdentityStore.identity(); + if (!(identity && identity.id)) { + return; + } this.analytics.track({ event: eventName, userId: identity.id, - properties: Object.assign({}, - eventArgs, - this.eventState(), - this.superTraits(), - ), - }) + properties: Object.assign({}, eventArgs, this.eventState(), this.superTraits()), + }); } firstDaySeen() { - let firstDaySeen = NylasEnv.config.get("firstDaySeen"); + let firstDaySeen = NylasEnv.config.get('firstDaySeen'); if (!firstDaySeen) { - const [y, m, d] = (new Date()).toISOString().split(/[-|T]/); + const [y, m, d] = new Date().toISOString().split(/[-|T]/); firstDaySeen = `${m}/${d}/${y}`; - NylasEnv.config.set("firstDaySeen", firstDaySeen); + NylasEnv.config.set('firstDaySeen', firstDaySeen); } return firstDaySeen; } @@ -127,12 +128,14 @@ class AnalyticsStore extends NylasStore { } const identity = IdentityStore.identity(); - if (!(identity && identity.id)) { return; } + if (!(identity && identity.id)) { + return; + } this.analytics.identify({ userId: identity.id, traits: this.baseTraits(), - integrations: {All: true}, + integrations: { All: true }, }); // Ensure we never send PI anywhere but Mixpanel @@ -145,7 +148,7 @@ class AnalyticsStore extends NylasStore { Mixpanel: true, }, }); - } + }; } -export default new AnalyticsStore() +export default new AnalyticsStore(); diff --git a/app/internal_packages/analytics/lib/main.es6 b/app/internal_packages/analytics/lib/main.es6 index 32dec3335..fa31d329e 100644 --- a/app/internal_packages/analytics/lib/main.es6 +++ b/app/internal_packages/analytics/lib/main.es6 @@ -1,5 +1,5 @@ -import AnalyticsStore from './analytics-store' +import AnalyticsStore from './analytics-store'; export function activate() { - AnalyticsStore.activate() + AnalyticsStore.activate(); } diff --git a/app/internal_packages/attachments/lib/main.es6 b/app/internal_packages/attachments/lib/main.es6 index d1b983ed3..b4df69814 100644 --- a/app/internal_packages/attachments/lib/main.es6 +++ b/app/internal_packages/attachments/lib/main.es6 @@ -1,8 +1,8 @@ -import {ComponentRegistry} from 'nylas-exports'; -import MessageAttachments from './message-attachments' +import { ComponentRegistry } from 'nylas-exports'; +import MessageAttachments from './message-attachments'; export function activate() { - ComponentRegistry.register(MessageAttachments, {role: 'MessageAttachments'}) + ComponentRegistry.register(MessageAttachments, { role: 'MessageAttachments' }); } export function deactivate() { diff --git a/app/internal_packages/attachments/lib/message-attachments.jsx b/app/internal_packages/attachments/lib/message-attachments.jsx index bd24a5d72..a3deae447 100644 --- a/app/internal_packages/attachments/lib/message-attachments.jsx +++ b/app/internal_packages/attachments/lib/message-attachments.jsx @@ -1,13 +1,12 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import {Actions, Utils, AttachmentStore} from 'nylas-exports' -import {AttachmentItem, ImageAttachmentItem} from 'nylas-component-kit' - +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { Actions, Utils, AttachmentStore } from 'nylas-exports'; +import { AttachmentItem, ImageAttachmentItem } from 'nylas-component-kit'; class MessageAttachments extends Component { - static displayName = 'MessageAttachments' + static displayName = 'MessageAttachments'; - static containerRequired = false + static containerRequired = false; static propTypes = { files: PropTypes.array, @@ -15,42 +14,42 @@ class MessageAttachments extends Component { headerMessageId: PropTypes.string, filePreviewPaths: PropTypes.object, canRemoveAttachments: PropTypes.bool, - } + }; static defaultProps = { downloads: {}, filePreviewPaths: {}, - } + }; - onOpenAttachment = (file) => { - Actions.fetchAndOpenFile(file) - } + onOpenAttachment = file => { + Actions.fetchAndOpenFile(file); + }; - onRemoveAttachment = (file) => { - const {headerMessageId} = this.props + onRemoveAttachment = file => { + const { headerMessageId } = this.props; Actions.removeAttachment({ headerMessageId: headerMessageId, file: file, - }) - } + }); + }; - onDownloadAttachment = (file) => { - Actions.fetchAndSaveFile(file) - } + onDownloadAttachment = file => { + Actions.fetchAndSaveFile(file); + }; - onAbortDownload = (file) => { - Actions.abortFetchFile(file) - } + onAbortDownload = file => { + Actions.abortFetchFile(file); + }; renderAttachment(AttachmentRenderer, file) { - const {canRemoveAttachments, downloads, filePreviewPaths, headerMessageId} = this.props - const download = downloads[file.id] - const filePath = AttachmentStore.pathForFile(file) - const fileIconName = `file-${file.displayExtension()}.png` - const displayName = file.displayName() - const displaySize = file.displayFileSize() - const contentType = file.contentType - const displayFilePreview = NylasEnv.config.get('core.attachments.displayFilePreview') + const { canRemoveAttachments, downloads, filePreviewPaths, headerMessageId } = this.props; + const download = downloads[file.id]; + const filePath = AttachmentStore.pathForFile(file); + const fileIconName = `file-${file.displayExtension()}.png`; + const displayName = file.displayName(); + const displaySize = file.displayFileSize(); + const contentType = file.contentType; + const displayFilePreview = NylasEnv.config.get('core.attachments.displayFilePreview'); const filePreviewPath = displayFilePreview ? filePreviewPaths[file.id] : null; return ( @@ -68,26 +67,24 @@ class MessageAttachments extends Component { onOpenAttachment={() => this.onOpenAttachment(file)} onDownloadAttachment={() => this.onDownloadAttachment(file)} onAbortDownload={() => this.onAbortDownload(file)} - onRemoveAttachment={canRemoveAttachments ? () => this.onRemoveAttachment(headerMessageId, file) : null} + onRemoveAttachment={ + canRemoveAttachments ? () => this.onRemoveAttachment(headerMessageId, file) : null + } /> - ) + ); } render() { - const {files} = this.props; - const nonImageFiles = files.filter((f) => !Utils.shouldDisplayAsImage(f)); - const imageFiles = files.filter((f) => Utils.shouldDisplayAsImage(f)); + const { files } = this.props; + const nonImageFiles = files.filter(f => !Utils.shouldDisplayAsImage(f)); + const imageFiles = files.filter(f => Utils.shouldDisplayAsImage(f)); return (
- {nonImageFiles.map((file) => - this.renderAttachment(AttachmentItem, file) - )} - {imageFiles.map((file) => - this.renderAttachment(ImageAttachmentItem, file) - )} + {nonImageFiles.map(file => this.renderAttachment(AttachmentItem, file))} + {imageFiles.map(file => this.renderAttachment(ImageAttachmentItem, file))}
- ) + ); } } -export default MessageAttachments +export default MessageAttachments; diff --git a/app/internal_packages/category-picker/lib/label-picker-popover.jsx b/app/internal_packages/category-picker/lib/label-picker-popover.jsx index 2894218e3..b2a9b6256 100644 --- a/app/internal_packages/category-picker/lib/label-picker-popover.jsx +++ b/app/internal_packages/category-picker/lib/label-picker-popover.jsx @@ -1,12 +1,7 @@ /* eslint jsx-a11y/tabindex-no-positive: 0 */ -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import { - Menu, - RetinaImg, - LabelColorizer, - BoldedSearchResult, -} from 'nylas-component-kit' +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { Menu, RetinaImg, LabelColorizer, BoldedSearchResult } from 'nylas-component-kit'; import { Utils, Actions, @@ -14,92 +9,92 @@ import { Label, SyncbackCategoryTask, ChangeLabelsTask, -} from 'nylas-exports' -import {Categories} from 'nylas-observables' - +} from 'nylas-exports'; +import { Categories } from 'nylas-observables'; export default class LabelPickerPopover extends Component { - static propTypes = { threads: PropTypes.array.isRequired, account: PropTypes.object.isRequired, }; constructor(props) { - super(props) - this._labels = [] - this.state = this._recalculateState(this.props, {searchValue: ''}) + super(props); + this._labels = []; + this.state = this._recalculateState(this.props, { searchValue: '' }); } componentDidMount() { - this._registerObservables() + this._registerObservables(); } componentWillReceiveProps(nextProps) { - this._registerObservables(nextProps) - this.setState(this._recalculateState(nextProps)) + this._registerObservables(nextProps); + this.setState(this._recalculateState(nextProps)); } componentWillUnmount() { - this._unregisterObservables() + this._unregisterObservables(); } _registerObservables = (props = this.props) => { - this._unregisterObservables() + this._unregisterObservables(); this.disposables = [ - Categories.forAccount(props.account).sort().subscribe(this._onLabelsChanged), - ] + Categories.forAccount(props.account) + .sort() + .subscribe(this._onLabelsChanged), + ]; }; _unregisterObservables = () => { if (this.disposables) { - this.disposables.forEach(disp => disp.dispose()) + this.disposables.forEach(disp => disp.dispose()); } }; - _recalculateState = (props = this.props, {searchValue = (this.state.searchValue || "")} = {}) => { - const {threads} = props + _recalculateState = (props = this.props, { searchValue = this.state.searchValue || '' } = {}) => { + const { threads } = props; if (threads.length === 0) { - return {categoryData: [], searchValue} + return { categoryData: [], searchValue }; } - const categoryData = this._labels.filter(label => - Utils.wordSearchRegExp(searchValue).test(label.displayName) - ).map((label) => { - return { - id: label.id, - category: label, - displayName: label.displayName, - backgroundColor: LabelColorizer.backgroundColorDark(label), - usage: threads.filter(t => t.categories.find(c => c.id === label.id)).length, - numThreads: threads.length, - }; - }); + const categoryData = this._labels + .filter(label => Utils.wordSearchRegExp(searchValue).test(label.displayName)) + .map(label => { + return { + id: label.id, + category: label, + displayName: label.displayName, + backgroundColor: LabelColorizer.backgroundColorDark(label), + usage: threads.filter(t => t.categories.find(c => c.id === label.id)).length, + numThreads: threads.length, + }; + }); if (searchValue.length > 0) { categoryData.push({ searchValue: searchValue, newCategoryItem: true, - id: "category-create-new", + id: 'category-create-new', }); } - return {categoryData, searchValue} + return { categoryData, searchValue }; }; - _onLabelsChanged = (categories) => { + _onLabelsChanged = categories => { this._labels = categories.filter(c => { - return (c instanceof Label) && (!c.role); + return c instanceof Label && !c.role; }); - this.setState(this._recalculateState()) + this.setState(this._recalculateState()); }; _onEscape = () => { - Actions.closePopover() + Actions.closePopover(); }; - _onSelectLabel = (item) => { - const {account, threads} = this.props + _onSelectLabel = item => { + const { account, threads } = this.props; if (threads.length === 0) return; @@ -107,52 +102,56 @@ export default class LabelPickerPopover extends Component { const syncbackTask = new SyncbackCategoryTask({ path: this.state.searchValue, accountId: account.id, - }) + }); - TaskQueue.waitForPerformRemote(syncbackTask).then((finishedTask) => { + TaskQueue.waitForPerformRemote(syncbackTask).then(finishedTask => { if (!finishedTask.created) { - NylasEnv.showErrorDialog({title: "Error", message: `Could not create label.`}) + NylasEnv.showErrorDialog({ title: 'Error', message: `Could not create label.` }); return; } - Actions.queueTask(new ChangeLabelsTask({ - source: "Category Picker: New Category", - threads: threads, - labelsToRemove: [], - labelsToAdd: [finishedTask.created], - })); - }) + Actions.queueTask( + new ChangeLabelsTask({ + source: 'Category Picker: New Category', + threads: threads, + labelsToRemove: [], + labelsToAdd: [finishedTask.created], + }) + ); + }); Actions.queueTask(syncbackTask); } else if (item.usage === threads.length) { - Actions.queueTask(new ChangeLabelsTask({ - source: "Category Picker: Existing Category", - threads: threads, - labelsToRemove: [item.category], - labelsToAdd: [], - })); + Actions.queueTask( + new ChangeLabelsTask({ + source: 'Category Picker: Existing Category', + threads: threads, + labelsToRemove: [item.category], + labelsToAdd: [], + }) + ); } else { - Actions.queueTask(new ChangeLabelsTask({ - source: "Category Picker: Existing Category", - threads: threads, - labelsToRemove: [], - labelsToAdd: [item.category], - })); + Actions.queueTask( + new ChangeLabelsTask({ + source: 'Category Picker: Existing Category', + threads: threads, + labelsToRemove: [], + labelsToAdd: [item.category], + }) + ); } - Actions.closePopover() + Actions.closePopover(); }; - _onSearchValueChange = (event) => { - this.setState( - this._recalculateState(this.props, {searchValue: event.target.value}) - ) + _onSearchValueChange = event => { + this.setState(this._recalculateState(this.props, { searchValue: event.target.value })); }; - _renderCheckbox = (item) => { - const styles = {} + _renderCheckbox = item => { + const styles = {}; let checkStatus; - styles.backgroundColor = item.backgroundColor + styles.backgroundColor = item.backgroundColor; if (item.usage === 0) { - checkStatus = + checkStatus = ; } else if (item.usage < item.numThreads) { checkStatus = ( this._onSelectLabel(item)} /> - ) + ); } else { checkStatus = ( this._onSelectLabel(item)} /> - ) + ); } return ( @@ -183,10 +182,10 @@ export default class LabelPickerPopover extends Component { /> {checkStatus}
- ) + ); }; - _renderCreateNewItem = ({searchValue}) => { + _renderCreateNewItem = ({ searchValue }) => { return (
“{searchValue}” (create new)
- ) + ); }; - _renderItem = (item) => { + _renderItem = item => { if (item.divider) { - return + return ; } else if (item.newCategoryItem) { - return this._renderCreateNewItem(item) + return this._renderCreateNewItem(item); } return (
{this._renderCheckbox(item)}
- +
- ) + ); }; render() { @@ -229,7 +228,7 @@ export default class LabelPickerPopover extends Component { value={this.state.searchValue} onChange={this._onSearchValueChange} />, - ] + ]; return (
@@ -241,9 +240,9 @@ export default class LabelPickerPopover extends Component { itemContent={this._renderItem} onSelect={this._onSelectLabel} onEscape={this._onEscape} - defaultSelectedIndex={this.state.searchValue === "" ? -1 : 0} + defaultSelectedIndex={this.state.searchValue === '' ? -1 : 0} />
- ) + ); } } diff --git a/app/internal_packages/category-picker/lib/label-picker.cjsx b/app/internal_packages/category-picker/lib/label-picker.cjsx index 22c0da927..ae035343c 100644 --- a/app/internal_packages/category-picker/lib/label-picker.cjsx +++ b/app/internal_packages/category-picker/lib/label-picker.cjsx @@ -1,9 +1,8 @@ _ = require 'underscore' -React = require 'react' -ReactDOM = require 'react-dom' {Actions, AccountStore, + React, ReactDOM, PropTypes, WorkspaceStore} = require 'nylas-exports' {RetinaImg, @@ -19,10 +18,10 @@ class LabelPicker extends React.Component @containerRequired: false @propTypes: - items: React.PropTypes.array + items: PropTypes.array @contextTypes: - sheetDepth: React.PropTypes.number + sheetDepth: PropTypes.number constructor: (@props) -> @_account = AccountStore.accountForItems(@props.items) diff --git a/app/internal_packages/category-picker/lib/move-picker-popover.jsx b/app/internal_packages/category-picker/lib/move-picker-popover.jsx index 0a226391c..462b01aee 100644 --- a/app/internal_packages/category-picker/lib/move-picker-popover.jsx +++ b/app/internal_packages/category-picker/lib/move-picker-popover.jsx @@ -1,12 +1,7 @@ /* eslint jsx-a11y/tabindex-no-positive: 0 */ -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import { - Menu, - RetinaImg, - LabelColorizer, - BoldedSearchResult, -} from 'nylas-component-kit' +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { Menu, RetinaImg, LabelColorizer, BoldedSearchResult } from 'nylas-component-kit'; import { Utils, Actions, @@ -17,12 +12,10 @@ import { ChangeFolderTask, ChangeLabelsTask, FocusedPerspectiveStore, -} from 'nylas-exports' -import {Categories} from 'nylas-observables' - +} from 'nylas-exports'; +import { Categories } from 'nylas-observables'; export default class MovePickerPopover extends Component { - static propTypes = { threads: PropTypes.array.isRequired, account: PropTypes.object.isRequired, @@ -32,7 +25,7 @@ export default class MovePickerPopover extends Component { super(props); this._standardFolders = []; this._userCategories = []; - this.state = this._recalculateState(this.props, {searchValue: ''}); + this.state = this._recalculateState(this.props, { searchValue: '' }); } componentDidMount() { @@ -51,7 +44,9 @@ export default class MovePickerPopover extends Component { _registerObservables = (props = this.props) => { this._unregisterObservables(); this.disposables = [ - Categories.forAccount(props.account).sort().subscribe(this._onCategoriesChanged), + Categories.forAccount(props.account) + .sort() + .subscribe(this._onCategoriesChanged), ]; }; @@ -61,16 +56,16 @@ export default class MovePickerPopover extends Component { } }; - _recalculateState = (props = this.props, {searchValue = (this.state.searchValue || "")} = {}) => { - const {threads, account} = props + _recalculateState = (props = this.props, { searchValue = this.state.searchValue || '' } = {}) => { + const { threads, account } = props; if (threads.length === 0) { - return {categoryData: [], searchValue} + return { categoryData: [], searchValue }; } const currentCategories = FocusedPerspectiveStore.current().categories() || []; const currentCategoryIds = currentCategories.map(c => c.id); const viewingAllMail = !currentCategories.find(c => c.role === 'spam' || c.role === 'trash'); - const hidden = account ? ["drafts", "sent", "snoozed"] : []; + const hidden = account ? ['drafts', 'sent', 'snoozed'] : []; if (viewingAllMail) { hidden.push('all'); @@ -78,18 +73,17 @@ export default class MovePickerPopover extends Component { const categoryData = [] .concat(this._standardFolders) - .concat([{divider: true, id: "category-divider"}]) + .concat([{ divider: true, id: 'category-divider' }]) .concat(this._userCategories) - .filter((cat) => - // remove categories that are part of the current perspective or locked - !hidden.includes(cat.role) && !currentCategoryIds.includes(cat.id) + .filter( + cat => + // remove categories that are part of the current perspective or locked + !hidden.includes(cat.role) && !currentCategoryIds.includes(cat.id) ) - .filter((cat) => - Utils.wordSearchRegExp(searchValue).test(cat.displayName) - ) - .map((cat) => { + .filter(cat => Utils.wordSearchRegExp(searchValue).test(cat.displayName)) + .map(cat => { if (cat.divider) { - return cat + return cat; } return { id: cat.id, @@ -103,24 +97,24 @@ export default class MovePickerPopover extends Component { const newItemData = { searchValue: searchValue, newCategoryItem: true, - id: "category-create-new", - } - categoryData.push(newItemData) + id: 'category-create-new', + }; + categoryData.push(newItemData); } - return {categoryData, searchValue} + return { categoryData, searchValue }; }; - _onCategoriesChanged = (categories) => { + _onCategoriesChanged = categories => { this._standardFolders = categories.filter(c => c.role && c instanceof Folder); this._userCategories = categories.filter(c => !c.role || !(c instanceof Folder)); - this.setState(this._recalculateState()) + this.setState(this._recalculateState()); }; _onEscape = () => { - Actions.closePopover() + Actions.closePopover(); }; - _onSelectCategory = (item) => { + _onSelectCategory = item => { if (this.props.threads.length === 0) { return; } @@ -132,7 +126,7 @@ export default class MovePickerPopover extends Component { } Actions.popSheet(); Actions.closePopover(); - } + }; _onCreateCategory = () => { const syncbackTask = new SyncbackCategoryTask({ @@ -140,46 +134,49 @@ export default class MovePickerPopover extends Component { accountId: this.props.account.id, }); - TaskQueue.waitForPerformRemote(syncbackTask).then((finishedTask) => { + TaskQueue.waitForPerformRemote(syncbackTask).then(finishedTask => { if (!finishedTask.created) { - NylasEnv.showErrorDialog({title: "Error", message: `Could not create folder.`}) + NylasEnv.showErrorDialog({ title: 'Error', message: `Could not create folder.` }); return; } - this._onMoveToCategory({category: finishedTask.created}); + this._onMoveToCategory({ category: finishedTask.created }); }); Actions.queueTask(syncbackTask); - } + }; - _onMoveToCategory = ({category}) => { - const {threads} = this.props + _onMoveToCategory = ({ category }) => { + const { threads } = this.props; if (category instanceof Folder) { - Actions.queueTask(new ChangeFolderTask({ - source: "Category Picker: New Category", - threads: threads, - folder: category, - })); + Actions.queueTask( + new ChangeFolderTask({ + source: 'Category Picker: New Category', + threads: threads, + folder: category, + }) + ); } else { const all = []; - threads.forEach(({labels}) => all.push(...labels)); + threads.forEach(({ labels }) => all.push(...labels)); - Actions.queueTask(new ChangeLabelsTask({ - source: "Category Picker: New Category", - labelsToRemove: all, - labelsToAdd: [category], - threads: threads, - })); + Actions.queueTask( + new ChangeLabelsTask({ + source: 'Category Picker: New Category', + labelsToRemove: all, + labelsToAdd: [category], + threads: threads, + }) + ); } }; - _onSearchValueChange = (event) => { - this.setState( - this._recalculateState(this.props, {searchValue: event.target.value}) - ) + _onSearchValueChange = event => { + this.setState(this._recalculateState(this.props, { searchValue: event.target.value })); }; - _renderCreateNewItem = ({searchValue}) => { - const icon = CategoryStore.getInboxCategory(this.props.account) instanceof Folder ? 'folder' : 'tag'; + _renderCreateNewItem = ({ searchValue }) => { + const icon = + CategoryStore.getInboxCategory(this.props.account) instanceof Folder ? 'folder' : 'tag'; return (
@@ -192,37 +189,35 @@ export default class MovePickerPopover extends Component { “{searchValue}” (create new)
- ) + ); }; - _renderItem = (item) => { + _renderItem = item => { if (item.divider) { - return + return ; } else if (item.newCategoryItem) { - return this._renderCreateNewItem(item) + return this._renderCreateNewItem(item); } - const icon = (item.category instanceof Folder) ? ( - - ) : ( - - ); + const icon = + item.category instanceof Folder ? ( + + ) : ( + + ); return (
{icon}
- +
- ) + ); }; render() { @@ -236,7 +231,7 @@ export default class MovePickerPopover extends Component { value={this.state.searchValue} onChange={this._onSearchValueChange} />, - ] + ]; return (
@@ -248,9 +243,9 @@ export default class MovePickerPopover extends Component { itemContent={this._renderItem} onSelect={this._onSelectCategory} onEscape={this._onEscape} - defaultSelectedIndex={this.state.searchValue === "" ? -1 : 0} + defaultSelectedIndex={this.state.searchValue === '' ? -1 : 0} />
- ) + ); } } diff --git a/app/internal_packages/category-picker/lib/move-picker.cjsx b/app/internal_packages/category-picker/lib/move-picker.cjsx index 3a02cdbeb..d339d9c2a 100644 --- a/app/internal_packages/category-picker/lib/move-picker.cjsx +++ b/app/internal_packages/category-picker/lib/move-picker.cjsx @@ -1,8 +1,7 @@ _ = require 'underscore' -React = require 'react' -ReactDOM = require 'react-dom' {Actions, + React, ReactDOM, PropTypes, AccountStore, WorkspaceStore} = require 'nylas-exports' @@ -19,10 +18,10 @@ class MovePicker extends React.Component @containerRequired: false @propTypes: - items: React.PropTypes.array + items: PropTypes.array @contextTypes: - sheetDepth: React.PropTypes.number + sheetDepth: PropTypes.number constructor: (@props) -> @_account = AccountStore.accountForItems(@props.items) diff --git a/app/internal_packages/composer-emoji/lib/categorized-emoji.es6 b/app/internal_packages/composer-emoji/lib/categorized-emoji.es6 index 54159aa80..21696748f 100644 --- a/app/internal_packages/composer-emoji/lib/categorized-emoji.es6 +++ b/app/internal_packages/composer-emoji/lib/categorized-emoji.es6 @@ -1,5 +1,5 @@ const categorizedEmojiList = { - 'People': [ + People: [ 'grinning', 'grimacing', 'grin', @@ -205,7 +205,7 @@ const categorizedEmojiList = { 'ring', 'closed_umbrella', ], - 'Nature': [ + Nature: [ 'dog', 'cat', 'mouse', @@ -422,7 +422,7 @@ const categorizedEmojiList = { 'fork_and_knife', 'knife_fork_plate', ], - 'Activity': [ + Activity: [ 'soccer', 'basketball', 'football', @@ -598,7 +598,7 @@ const categorizedEmojiList = { 'kaaba', 'shinto_shrine', ], - 'Objects': [ + Objects: [ 'watch', 'iphone', 'calling', @@ -777,7 +777,7 @@ const categorizedEmojiList = { 'mag', 'mag_right', ], - 'Symbols': [ + Symbols: [ 'heart', 'yellow_heart', 'green_heart', @@ -1048,7 +1048,7 @@ const categorizedEmojiList = { 'clock1130', 'clock1230', ], - 'Flags': [ + Flags: [ 'flag-ac', 'flag-ad', 'flag-ae', @@ -1307,5 +1307,5 @@ const categorizedEmojiList = { 'flag-zm', 'flag-zw', ], -} -export default categorizedEmojiList +}; +export default categorizedEmojiList; diff --git a/app/internal_packages/composer-emoji/lib/emoji-actions.es6 b/app/internal_packages/composer-emoji/lib/emoji-actions.es6 index ea0150df0..4c705a9e3 100644 --- a/app/internal_packages/composer-emoji/lib/emoji-actions.es6 +++ b/app/internal_packages/composer-emoji/lib/emoji-actions.es6 @@ -1,9 +1,6 @@ import Reflux from 'reflux'; -const EmojiActions = Reflux.createActions([ - "selectEmoji", - "useEmoji", -]); +const EmojiActions = Reflux.createActions(['selectEmoji', 'useEmoji']); for (const key of Object.keys(EmojiActions)) { EmojiActions[key].sync = true; diff --git a/app/internal_packages/composer-emoji/lib/emoji-button-popover.jsx b/app/internal_packages/composer-emoji/lib/emoji-button-popover.jsx index 256607a3b..8054fccc2 100644 --- a/app/internal_packages/composer-emoji/lib/emoji-button-popover.jsx +++ b/app/internal_packages/composer-emoji/lib/emoji-button-popover.jsx @@ -1,6 +1,6 @@ import React from 'react'; -import {Actions} from 'nylas-exports'; -import {RetinaImg, ScrollRegion} from 'nylas-component-kit'; +import { Actions } from 'nylas-exports'; +import { RetinaImg, ScrollRegion } from 'nylas-component-kit'; import EmojiStore from './emoji-store'; import EmojiActions from './emoji-actions'; @@ -11,15 +11,13 @@ class EmojiButtonPopover extends React.Component { constructor() { super(); - const {categoryNames, - categorizedEmoji, - categoryPositions} = this.getStateFromStore(); + const { categoryNames, categorizedEmoji, categoryPositions } = this.getStateFromStore(); this.state = { - emojiName: "Emoji Picker", + emojiName: 'Emoji Picker', categoryNames: categoryNames, categorizedEmoji: categorizedEmoji, categoryPositions: categoryPositions, - searchValue: "", + searchValue: '', activeTab: Object.keys(categorizedEmoji)[0], }; } @@ -36,69 +34,77 @@ class EmojiButtonPopover extends React.Component { this._mounted = false; } - onMouseDown = (event) => { + onMouseDown = event => { const emojiName = this.calcEmojiByPosition(this.calcPosition(event)); if (!emojiName) return null; - EmojiActions.selectEmoji({emojiName: emojiName, replaceSelection: false}); + EmojiActions.selectEmoji({ emojiName: emojiName, replaceSelection: false }); Actions.closePopover(); - return null - } + return null; + }; onScroll = () => { - const emojiContainer = document.querySelector(".emoji-finder-container .scroll-region-content"); - const tabContainer = document.querySelector(".emoji-tabs"); - tabContainer.className = emojiContainer.scrollTop ? "emoji-tabs shadow" : "emoji-tabs"; + const emojiContainer = document.querySelector('.emoji-finder-container .scroll-region-content'); + const tabContainer = document.querySelector('.emoji-tabs'); + tabContainer.className = emojiContainer.scrollTop ? 'emoji-tabs shadow' : 'emoji-tabs'; if (emojiContainer.scrollTop === 0) { - this.setState({activeTab: Object.keys(this.state.categorizedEmoji)[0]}); + this.setState({ activeTab: Object.keys(this.state.categorizedEmoji)[0] }); } else { for (const category of Object.keys(this.state.categoryPositions)) { - if (emojiContainer.scrollTop >= this.state.categoryPositions[category].top && - emojiContainer.scrollTop <= this.state.categoryPositions[category].bottom) { - this.setState({activeTab: category}); + if ( + emojiContainer.scrollTop >= this.state.categoryPositions[category].top && + emojiContainer.scrollTop <= this.state.categoryPositions[category].bottom + ) { + this.setState({ activeTab: category }); } } } - } + }; - onHover = (event) => { + onHover = event => { const emojiName = this.calcEmojiByPosition(this.calcPosition(event)); if (emojiName) { - this.setState({emojiName: emojiName}); + this.setState({ emojiName: emojiName }); } else { - this.setState({emojiName: "Emoji Picker"}); + this.setState({ emojiName: 'Emoji Picker' }); } - } + }; onMouseOut = () => { - this.setState({emojiName: "Emoji Picker"}); - } + this.setState({ emojiName: 'Emoji Picker' }); + }; - onChange = (event) => { + onChange = event => { const searchValue = event.target.value; if (searchValue.length > 0) { const searchMatches = this.findSearchMatches(searchValue); - this.setState({ - categorizedEmoji: { - 'Search Results': searchMatches, - }, - categoryPositions: { - 'Search Results': { - top: 25, - bottom: 25 + Math.ceil(searchMatches.length / 8) * 24, + this.setState( + { + categorizedEmoji: { + 'Search Results': searchMatches, }, + categoryPositions: { + 'Search Results': { + top: 25, + bottom: 25 + Math.ceil(searchMatches.length / 8) * 24, + }, + }, + searchValue: searchValue, + activeTab: null, }, - searchValue: searchValue, - activeTab: null, - }, this.renderCanvas); + this.renderCanvas + ); } else { this.setState(this.getStateFromStore, () => { - this.setState({ - searchValue: searchValue, - activeTab: Object.keys(this.state.categorizedEmoji)[0], - }, this.renderCanvas); + this.setState( + { + searchValue: searchValue, + activeTab: Object.keys(this.state.categorizedEmoji)[0], + }, + this.renderCanvas + ); }); } - } + }; getStateFromStore = () => { let categorizedEmoji = categorizedEmojiList; @@ -115,16 +121,16 @@ class EmojiButtonPopover extends React.Component { ]; const frequentlyUsedEmoji = EmojiStore.frequentlyUsedEmoji(); if (frequentlyUsedEmoji.length > 0) { - categorizedEmoji = {'Frequently Used': frequentlyUsedEmoji}; + categorizedEmoji = { 'Frequently Used': frequentlyUsedEmoji }; for (const category of Object.keys(categorizedEmojiList)) { categorizedEmoji[category] = categorizedEmojiList[category]; } - categoryNames = ["Frequently Used"].concat(categoryNames); + categoryNames = ['Frequently Used'].concat(categoryNames); } // Calculates where each category should be (variable because Frequently // Used may or may not be present) for (const name of categoryNames) { - categoryPositions[name] = {top: 0, bottom: 0}; + categoryPositions[name] = { top: 0, bottom: 0 }; } let verticalPos = 25; for (const category of Object.keys(categoryPositions)) { @@ -139,12 +145,12 @@ class EmojiButtonPopover extends React.Component { categorizedEmoji: categorizedEmoji, categoryPositions: categoryPositions, }; - } + }; scrollToCategory(category) { - const container = document.querySelector(".emoji-finder-container .scroll-region-content"); + const container = document.querySelector('.emoji-finder-container .scroll-region-content'); if (this.state.searchValue.length > 0) { - this.setState({searchValue: ""}); + this.setState({ searchValue: '' }); this.setState(this.getStateFromStore, () => { this.renderCanvas(); container.scrollTop = this.state.categoryPositions[category].top + 16; @@ -152,14 +158,14 @@ class EmojiButtonPopover extends React.Component { } else { container.scrollTop = this.state.categoryPositions[category].top + 16; } - this.setState({activeTab: category}) + this.setState({ activeTab: category }); } findSearchMatches(searchValue) { // TODO: Find matches for aliases, too. const searchMatches = []; for (const category of Object.keys(categorizedEmojiList)) { - categorizedEmojiList[category].forEach((emojiName) => { + categorizedEmojiList[category].forEach(emojiName => { if (emojiName.indexOf(searchValue) !== -1) { searchMatches.push(emojiName); } @@ -177,39 +183,43 @@ class EmojiButtonPopover extends React.Component { return position; } - calcEmojiByPosition = (position) => { + calcEmojiByPosition = position => { for (const category of Object.keys(this.state.categoryPositions)) { const LEFT_BOUNDARY = 8; const RIGHT_BOUNDARY = 204; const EMOJI_WIDTH = 24.5; const EMOJI_HEIGHT = 24; const EMOJI_PER_ROW = 8; - if (position.x >= LEFT_BOUNDARY && - position.x <= RIGHT_BOUNDARY && - position.y >= this.state.categoryPositions[category].top && - position.y <= this.state.categoryPositions[category].bottom) { + if ( + position.x >= LEFT_BOUNDARY && + position.x <= RIGHT_BOUNDARY && + position.y >= this.state.categoryPositions[category].top && + position.y <= this.state.categoryPositions[category].bottom + ) { const x = Math.round((position.x + 5) / EMOJI_WIDTH); - const y = Math.round((position.y - this.state.categoryPositions[category].top + 10) / EMOJI_HEIGHT); + const y = Math.round( + (position.y - this.state.categoryPositions[category].top + 10) / EMOJI_HEIGHT + ); const index = x + (y - 1) * EMOJI_PER_ROW - 1; return this.state.categorizedEmoji[category][index]; } } return null; - } + }; renderTabs() { const tabs = []; - this.state.categoryNames.forEach((category) => { - let className = `emoji-tab ${(category.replace(/ /g, '-')).toLowerCase()}` + this.state.categoryNames.forEach(category => { + let className = `emoji-tab ${category.replace(/ /g, '-').toLowerCase()}`; if (category === this.state.activeTab) { - className += " active"; + className += ' active'; } tabs.push( -
+
this.scrollToCategory(category)} /> @@ -222,14 +232,14 @@ class EmojiButtonPopover extends React.Component { renderCanvas() { const keys = Object.keys(this.state.categoryPositions); this._canvasEl.height = this.state.categoryPositions[keys[keys.length - 1]].bottom * 2; - const ctx = this._canvasEl.getContext("2d"); - ctx.font = "24px Nylas-Pro"; + const ctx = this._canvasEl.getContext('2d'); + ctx.font = '24px Nylas-Pro'; ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; ctx.clearRect(0, 0, this._canvasEl.width, this._canvasEl.height); const position = { x: 15, y: 45, - } + }; let idx = 0; const categoryNames = Object.keys(this.state.categorizedEmoji); @@ -238,12 +248,12 @@ class EmojiButtonPopover extends React.Component { if (!this._mounted) return; this.renderCategory(categoryNames[idx], idx, ctx, position, renderNextCategory); idx += 1; - } + }; renderNextCategory(); } renderCategory(category, i, ctx, pos, callback) { - const position = pos + const position = pos; if (i > 0) { position.x = 18; position.y += 48; @@ -267,10 +277,10 @@ class EmojiButtonPopover extends React.Component { position.x += 50; } - return {src, x, y}; + return { src, x, y }; }); - const drawEmojiAt = ({src, x, y} = {}) => { + const drawEmojiAt = ({ src, x, y } = {}) => { if (!src) { return; } @@ -282,9 +292,9 @@ class EmojiButtonPopover extends React.Component { } else { drawEmojiAt(emojiToDraw.shift()); } - } + }; this._emojiPreloadImage.src = src; - } + }; drawEmojiAt(emojiToDraw.shift()); } @@ -292,13 +302,8 @@ class EmojiButtonPopover extends React.Component { render() { return (
-
- {this.renderTabs()} -
- +
{this.renderTabs()}
+
{ this._canvasEl = el; }} + ref={el => { + this._canvasEl = el; + }} width="400" height="2000" onMouseDown={this.onMouseDown} onMouseOut={this.onMouseOut} onMouseMove={this.onHover} - style={{zoom: "0.5"}} + style={{ zoom: '0.5' }} />
-
- {this.state.emojiName} -
+
{this.state.emojiName}
); } diff --git a/app/internal_packages/composer-emoji/lib/emoji-button.jsx b/app/internal_packages/composer-emoji/lib/emoji-button.jsx index 59eb56b65..fb31f6511 100644 --- a/app/internal_packages/composer-emoji/lib/emoji-button.jsx +++ b/app/internal_packages/composer-emoji/lib/emoji-button.jsx @@ -1,23 +1,24 @@ -import {Actions, React, ReactDOM} from 'nylas-exports'; -import {RetinaImg} from 'nylas-component-kit'; +import { Actions, React, ReactDOM } from 'nylas-exports'; +import { RetinaImg } from 'nylas-component-kit'; import EmojiButtonPopover from './emoji-button-popover'; - class EmojiButton extends React.Component { static displayName = 'EmojiButton'; onClick = () => { const buttonRect = ReactDOM.findDOMNode(this).getBoundingClientRect(); - Actions.openPopover( - , - {originRect: buttonRect, direction: 'up'} - ) - } + Actions.openPopover(, { originRect: buttonRect, direction: 'up' }); + }; render() { return ( - ); diff --git a/app/internal_packages/composer-emoji/lib/emoji-composer-extension.jsx b/app/internal_packages/composer-emoji/lib/emoji-composer-extension.jsx index 604547c79..e61bcfeba 100644 --- a/app/internal_packages/composer-emoji/lib/emoji-composer-extension.jsx +++ b/app/internal_packages/composer-emoji/lib/emoji-composer-extension.jsx @@ -1,116 +1,119 @@ -import {DOMUtils, ComposerExtension, RegExpUtils} from 'nylas-exports'; +import { DOMUtils, ComposerExtension, RegExpUtils } from 'nylas-exports'; import emoji from 'node-emoji'; import EmojiStore from './emoji-store'; import EmojiActions from './emoji-actions'; import EmojiPicker from './emoji-picker'; - class EmojiComposerExtension extends ComposerExtension { - static selState = null; - static onContentChanged = ({editor}) => { - const sel = editor.currentSelection() - const {emojiOptions, triggerWord} = EmojiComposerExtension._findEmojiOptions(sel); + static onContentChanged = ({ editor }) => { + const sel = editor.currentSelection(); + const { emojiOptions, triggerWord } = EmojiComposerExtension._findEmojiOptions(sel); if (sel.anchorNode && sel.isCollapsed) { if (emojiOptions.length > 0) { - if (!DOMUtils.closest(sel.anchorNode, "n1-emoji-autocomplete")) { + if (!DOMUtils.closest(sel.anchorNode, 'n1-emoji-autocomplete')) { const anchorOffset = Math.max(sel.anchorOffset - triggerWord.length - 1, 0); - editor.select(sel.anchorNode, - anchorOffset, - sel.focusNode, - sel.focusOffset) - - DOMUtils.wrap(sel.getRangeAt(0), "n1-emoji-autocomplete"); + editor.select(sel.anchorNode, anchorOffset, sel.focusNode, sel.focusOffset); + + DOMUtils.wrap(sel.getRangeAt(0), 'n1-emoji-autocomplete'); editor.currentSelection().collapseToEnd(); } } else { - if (DOMUtils.closest(sel.anchorNode, "n1-emoji-autocomplete")) { - editor.unwrapNodeAndSelectAll(DOMUtils.closest(sel.anchorNode, "n1-emoji-autocomplete")); + if (DOMUtils.closest(sel.anchorNode, 'n1-emoji-autocomplete')) { + editor.unwrapNodeAndSelectAll(DOMUtils.closest(sel.anchorNode, 'n1-emoji-autocomplete')); editor.currentSelection().collapseToEnd(); } } } else { - if (DOMUtils.closest(sel.anchorNode, "n1-emoji-autocomplete")) { - editor.unwrapNodeAndSelectAll(DOMUtils.closest(sel.anchorNode, "n1-emoji-autocomplete")); + if (DOMUtils.closest(sel.anchorNode, 'n1-emoji-autocomplete')) { + editor.unwrapNodeAndSelectAll(DOMUtils.closest(sel.anchorNode, 'n1-emoji-autocomplete')); editor.currentSelection().collapseToEnd(); } } }; - static onBlur = ({editor}) => { + static onBlur = ({ editor }) => { EmojiComposerExtension.selState = editor.currentSelection().exportSelection(); }; - static onFocus = ({editor}) => { + static onFocus = ({ editor }) => { if (EmojiComposerExtension.selState) { editor.select(EmojiComposerExtension.selState); EmojiComposerExtension.selState = null; } }; - static toolbarComponentConfig = ({toolbarState}) => { + static toolbarComponentConfig = ({ toolbarState }) => { const sel = toolbarState.selectionSnapshot; if (sel) { - const {emojiOptions} = EmojiComposerExtension._findEmojiOptions(sel); + const { emojiOptions } = EmojiComposerExtension._findEmojiOptions(sel); if (emojiOptions.length > 0 && !toolbarState.dragging && !toolbarState.doubleDown) { - const locationRefNode = DOMUtils.closest(sel.anchorNode, "n1-emoji-autocomplete"); + const locationRefNode = DOMUtils.closest(sel.anchorNode, 'n1-emoji-autocomplete'); if (!locationRefNode) { return null; } - const selectedEmoji = locationRefNode.getAttribute("selectedEmoji"); + const selectedEmoji = locationRefNode.getAttribute('selectedEmoji'); return { component: EmojiPicker, - props: {emojiOptions, - selectedEmoji}, + props: { + emojiOptions, + selectedEmoji, + }, locationRefNode: locationRefNode, width: EmojiComposerExtension._emojiPickerWidth(emojiOptions), height: EmojiComposerExtension._emojiPickerHeight(emojiOptions), hidePointer: true, - } + }; } } return null; }; static editingActions = () => { - return [{ - action: EmojiActions.selectEmoji, - callback: EmojiComposerExtension._onSelectEmoji, - }] + return [ + { + action: EmojiActions.selectEmoji, + callback: EmojiComposerExtension._onSelectEmoji, + }, + ]; }; - static onKeyDown = ({editor, event}) => { - const sel = editor.currentSelection() - const {emojiOptions} = EmojiComposerExtension._findEmojiOptions(sel); + static onKeyDown = ({ editor, event }) => { + const sel = editor.currentSelection(); + const { emojiOptions } = EmojiComposerExtension._findEmojiOptions(sel); if (emojiOptions.length > 0) { - if (event.key === "ArrowDown" || event.key === "ArrowRight" || - event.key === "ArrowUp" || event.key === "ArrowLeft") { + if ( + event.key === 'ArrowDown' || + event.key === 'ArrowRight' || + event.key === 'ArrowUp' || + event.key === 'ArrowLeft' + ) { event.preventDefault(); - const moveToNext = (event.key === "ArrowDown" || event.key === "ArrowRight"); - const emojiNameNode = DOMUtils.closest(sel.anchorNode, "n1-emoji-autocomplete"); + const moveToNext = event.key === 'ArrowDown' || event.key === 'ArrowRight'; + const emojiNameNode = DOMUtils.closest(sel.anchorNode, 'n1-emoji-autocomplete'); if (!emojiNameNode) return null; - const selectedEmoji = emojiNameNode.getAttribute("selectedEmoji"); + const selectedEmoji = emojiNameNode.getAttribute('selectedEmoji'); if (selectedEmoji) { const emojiIndex = emojiOptions.indexOf(selectedEmoji); if (emojiIndex < emojiOptions.length - 1 && moveToNext) { - emojiNameNode.setAttribute("selectedEmoji", emojiOptions[emojiIndex + 1]); + emojiNameNode.setAttribute('selectedEmoji', emojiOptions[emojiIndex + 1]); } else if (emojiIndex > 0 && !moveToNext) { - emojiNameNode.setAttribute("selectedEmoji", emojiOptions[emojiIndex - 1]); + emojiNameNode.setAttribute('selectedEmoji', emojiOptions[emojiIndex - 1]); } else { const index = moveToNext ? 0 : emojiOptions.length - 1; - emojiNameNode.setAttribute("selectedEmoji", emojiOptions[index]); + emojiNameNode.setAttribute('selectedEmoji', emojiOptions[index]); } } else { const index = moveToNext ? 1 : emojiOptions.length - 1; - emojiNameNode.setAttribute("selectedEmoji", emojiOptions[index]); + emojiNameNode.setAttribute('selectedEmoji', emojiOptions[index]); } - } else if (event.key === "Enter" || event.key === "Tab") { + } else if (event.key === 'Enter' || event.key === 'Tab') { event.preventDefault(); - const emojiNameNode = DOMUtils.closest(sel.anchorNode, "n1-emoji-autocomplete"); + const emojiNameNode = DOMUtils.closest(sel.anchorNode, 'n1-emoji-autocomplete'); if (!emojiNameNode) return null; - let selectedEmoji = emojiNameNode.getAttribute("selectedEmoji"); + let selectedEmoji = emojiNameNode.getAttribute('selectedEmoji'); if (!selectedEmoji) selectedEmoji = emojiOptions[0]; const args = { editor: editor, @@ -125,8 +128,8 @@ class EmojiComposerExtension extends ComposerExtension { return null; }; - static applyTransformsForSending = ({draftBodyRootNode}) => { - const imgs = draftBodyRootNode.querySelectorAll('img') + static applyTransformsForSending = ({ draftBodyRootNode }) => { + const imgs = draftBodyRootNode.querySelectorAll('img'); for (const imgEl of Array.from(imgs)) { const names = imgEl.className.split(' '); if (names[0] === 'emoji') { @@ -136,9 +139,9 @@ class EmojiComposerExtension extends ComposerExtension { } } } - } + }; - static unapplyTransformsForSending = ({draftBodyRootNode}) => { + static unapplyTransformsForSending = ({ draftBodyRootNode }) => { const treeWalker = document.createTreeWalker(draftBodyRootNode, NodeFilter.SHOW_TEXT); while (treeWalker.nextNode()) { const textNode = treeWalker.currentNode; @@ -148,7 +151,7 @@ class EmojiComposerExtension extends ComposerExtension { emojiPlusTrailingEl.splitText(match.length); const emojiEl = emojiPlusTrailingEl; const imgEl = document.createElement('img'); - const emojiName = emoji.which(match[0]) + const emojiName = emoji.which(match[0]); imgEl.className = `emoji ${emojiName}`; imgEl.src = EmojiStore.getImagePath(emojiName); imgEl.width = '14'; @@ -157,61 +160,73 @@ class EmojiComposerExtension extends ComposerExtension { emojiEl.parentNode.replaceChild(imgEl, emojiEl); } } - } + }; static _findEmojiOptions(sel) { - if (sel.anchorNode && - sel.anchorNode.nodeValue && - sel.anchorNode.nodeValue.length > 0 && - sel.isCollapsed) { + if ( + sel.anchorNode && + sel.anchorNode.nodeValue && + sel.anchorNode.nodeValue.length > 0 && + sel.isCollapsed + ) { const words = sel.anchorNode.nodeValue.substring(0, sel.anchorOffset); - let index = words.lastIndexOf(":"); - let lastWord = ""; - if (index !== -1 && words.lastIndexOf(" ") < index) { + let index = words.lastIndexOf(':'); + let lastWord = ''; + if (index !== -1 && words.lastIndexOf(' ') < index) { lastWord = words.substring(index + 1, sel.anchorOffset); } else { - const {text} = EmojiComposerExtension._getTextUntilSpace(sel.anchorNode, sel.anchorOffset); - index = text.lastIndexOf(":"); - if (index !== -1 && text.lastIndexOf(" ") < index) { + const { text } = EmojiComposerExtension._getTextUntilSpace( + sel.anchorNode, + sel.anchorOffset + ); + index = text.lastIndexOf(':'); + if (index !== -1 && text.lastIndexOf(' ') < index) { lastWord = text.substring(index + 1); } else { - return {triggerWord: "", emojiOptions: []}; + return { triggerWord: '', emojiOptions: [] }; } } if (lastWord.length > 0) { - return {triggerWord: lastWord, emojiOptions: EmojiComposerExtension._findMatches(lastWord)}; + return { + triggerWord: lastWord, + emojiOptions: EmojiComposerExtension._findMatches(lastWord), + }; } - return {triggerWord: lastWord, emojiOptions: []}; + return { triggerWord: lastWord, emojiOptions: [] }; } - return {triggerWord: "", emojiOptions: []}; + return { triggerWord: '', emojiOptions: [] }; } - static _onSelectEmoji = ({editor, actionArg}) => { - const {emojiName, replaceSelection} = actionArg; + static _onSelectEmoji = ({ editor, actionArg }) => { + const { emojiName, replaceSelection } = actionArg; if (!emojiName) return null; if (replaceSelection) { const sel = editor.currentSelection(); - if (sel.anchorNode && - sel.anchorNode.nodeValue && - sel.anchorNode.nodeValue.length > 0 && - sel.isCollapsed) { + if ( + sel.anchorNode && + sel.anchorNode.nodeValue && + sel.anchorNode.nodeValue.length > 0 && + sel.isCollapsed + ) { const words = sel.anchorNode.nodeValue.substring(0, sel.anchorOffset); - let index = words.lastIndexOf(":"); + let index = words.lastIndexOf(':'); let lastWord = words.substring(index + 1, sel.anchorOffset); - if (index !== -1 && words.lastIndexOf(" ") < index) { - editor.select(sel.anchorNode, - sel.anchorOffset - lastWord.length - 1, - sel.focusNode, - sel.focusOffset); + if (index !== -1 && words.lastIndexOf(' ') < index) { + editor.select( + sel.anchorNode, + sel.anchorOffset - lastWord.length - 1, + sel.focusNode, + sel.focusOffset + ); } else { - const {text, textNode} = EmojiComposerExtension._getTextUntilSpace(sel.anchorNode, sel.anchorOffset); - index = text.lastIndexOf(":"); + const { text, textNode } = EmojiComposerExtension._getTextUntilSpace( + sel.anchorNode, + sel.anchorOffset + ); + index = text.lastIndexOf(':'); lastWord = text.substring(index + 1); - const offset = textNode.nodeValue.lastIndexOf(":"); - editor.select(textNode, - offset, - sel.focusNode, - sel.focusOffset); + const offset = textNode.nodeValue.lastIndexOf(':'); + editor.select(textNode, offset, sel.focusNode, sel.focusOffset); editor.delete(); } } @@ -223,8 +238,8 @@ class EmojiComposerExtension extends ComposerExtension { width="14" height="14" style="margin-top: -5px;">`; - editor.insertHTML(html, {selectInsertion: false}); - EmojiActions.useEmoji({emojiName: emojiName, emojiChar: emojiChar}); + editor.insertHTML(html, { selectInsertion: false }); + EmojiActions.useEmoji({ emojiName: emojiName, emojiChar: emojiChar }); return null; }; @@ -251,25 +266,26 @@ class EmojiComposerExtension extends ComposerExtension { static _getTextUntilSpace(node, offset) { let text = node.nodeValue.substring(0, offset); let prevTextNode = DOMUtils.previousTextNode(node); - if (!prevTextNode) return {text: text, textNode: node}; + if (!prevTextNode) return { text: text, textNode: node }; while (prevTextNode) { - if (prevTextNode.nodeValue.indexOf(" ") === -1 && - prevTextNode.nodeValue.indexOf(":") === -1) { + if ( + prevTextNode.nodeValue.indexOf(' ') === -1 && + prevTextNode.nodeValue.indexOf(':') === -1 + ) { text = prevTextNode.nodeValue + text; prevTextNode = DOMUtils.previousTextNode(prevTextNode); - } else if (prevTextNode.nextSibling && - prevTextNode.nextSibling.nodeName !== "DIV") { + } else if (prevTextNode.nextSibling && prevTextNode.nextSibling.nodeName !== 'DIV') { text = prevTextNode.nodeValue.trim() + text; break; } else { break; } } - return {text: text, textNode: prevTextNode}; + return { text: text, textNode: prevTextNode }; } static _findMatches(word) { - const emojiOptions = [] + const emojiOptions = []; const emojiNames = Object.keys(emoji.emoji).sort(); for (const emojiName of emojiNames) { if (word === emojiName.substring(0, word.length)) { @@ -278,7 +294,6 @@ class EmojiComposerExtension extends ComposerExtension { } return emojiOptions; } - } export default EmojiComposerExtension; diff --git a/app/internal_packages/composer-emoji/lib/emoji-message-extension.jsx b/app/internal_packages/composer-emoji/lib/emoji-message-extension.jsx index 4e260aeaf..d92437475 100644 --- a/app/internal_packages/composer-emoji/lib/emoji-message-extension.jsx +++ b/app/internal_packages/composer-emoji/lib/emoji-message-extension.jsx @@ -1,5 +1,5 @@ /* eslint no-cond-assign:0 */ -import {MessageViewExtension, RegExpUtils} from 'nylas-exports'; +import { MessageViewExtension, RegExpUtils } from 'nylas-exports'; import emoji from 'node-emoji'; import EmojiStore from './emoji-store'; @@ -15,7 +15,7 @@ function makeIntoEmojiTag(nodeArg, emojiName) { } class EmojiMessageExtension extends MessageViewExtension { - static renderedMessageBodyIntoDocument({document}) { + static renderedMessageBodyIntoDocument({ document }) { const emojiRegex = RegExpUtils.emojiRegex(); // Look for emoji in the content of text nodes @@ -27,10 +27,10 @@ class EmojiMessageExtension extends MessageViewExtension { const node = treeWalker.currentNode; let match = null; - while (match = emojiRegex.exec(node.textContent)) { + while ((match = emojiRegex.exec(node.textContent))) { const matchEmojiName = emoji.which(match[0]); if (matchEmojiName) { - const matchNode = (match.index === 0) ? node : node.splitText(match.index); + const matchNode = match.index === 0 ? node : node.splitText(match.index); matchNode.splitText(match[0].length); const imageNode = document.createElement('img'); makeIntoEmojiTag(imageNode, matchEmojiName); diff --git a/app/internal_packages/composer-emoji/lib/emoji-picker.jsx b/app/internal_packages/composer-emoji/lib/emoji-picker.jsx index b085cd350..82e3a8c54 100644 --- a/app/internal_packages/composer-emoji/lib/emoji-picker.jsx +++ b/app/internal_packages/composer-emoji/lib/emoji-picker.jsx @@ -1,15 +1,14 @@ -import {React, ReactDOM} from 'nylas-exports'; +import { React, ReactDOM, PropTypes } from 'nylas-exports'; import emoji from 'node-emoji'; import EmojiStore from './emoji-store'; import EmojiActions from './emoji-actions'; - class EmojiPicker extends React.Component { - static displayName = "EmojiPicker"; + static displayName = 'EmojiPicker'; static propTypes = { - emojiOptions: React.PropTypes.array, - selectedEmoji: React.PropTypes.string, + emojiOptions: PropTypes.array, + selectedEmoji: PropTypes.string, }; constructor(props) { @@ -18,14 +17,14 @@ class EmojiPicker extends React.Component { } componentDidUpdate() { - const selectedButton = ReactDOM.findDOMNode(this).querySelector(".emoji-option"); + const selectedButton = ReactDOM.findDOMNode(this).querySelector('.emoji-option'); if (selectedButton) { selectedButton.scrollIntoViewIfNeeded(); } } onMouseDown(emojiName) { - EmojiActions.selectEmoji({emojiName, replaceSelection: true}); + EmojiActions.selectEmoji({ emojiName, replaceSelection: true }); } render() { @@ -34,7 +33,7 @@ class EmojiPicker extends React.Component { if (emojiIndex === -1) emojiIndex = 0; if (this.props.emojiOptions) { this.props.emojiOptions.forEach((emojiOption, i) => { - const emojiClass = emojiIndex === i ? "btn btn-icon emoji-option" : "btn btn-icon"; + const emojiClass = emojiIndex === i ? 'btn btn-icon emoji-option' : 'btn btn-icon'; let emojiChar = emoji.get(emojiOption); emojiChar = ( ); emojiButtons.push( @@ -57,11 +56,7 @@ class EmojiPicker extends React.Component { emojiButtons.push(
); }); } - return ( -
- {emojiButtons} -
- ); + return
{emojiButtons}
; } } diff --git a/app/internal_packages/composer-emoji/lib/emoji-store.jsx b/app/internal_packages/composer-emoji/lib/emoji-store.jsx index 342a27224..676fae240 100644 --- a/app/internal_packages/composer-emoji/lib/emoji-store.jsx +++ b/app/internal_packages/composer-emoji/lib/emoji-store.jsx @@ -31,7 +31,7 @@ class EmojiStore extends NylasStore { const sortedEmoji = this._emoji; sortedEmoji.sort((a, b) => { if (a.frequency < b.frequency) return 1; - return (b.frequency < a.frequency) ? -1 : 0; + return b.frequency < a.frequency ? -1 : 0; }); const sortedEmojiNames = []; for (const emoji of sortedEmoji) { @@ -41,23 +41,23 @@ class EmojiStore extends NylasStore { return sortedEmojiNames.slice(0, 32); } return sortedEmojiNames; - } + }; getImagePath(emojiName) { - emojiData = emojiData || require('./emoji-data').emojiData + emojiData = emojiData || require('./emoji-data').emojiData; for (const emoji of emojiData) { if (emoji.short_names.indexOf(emojiName) !== -1) { - if (process.platform === "darwin") { + if (process.platform === 'darwin') { return `images/composer-emoji/apple/${emoji.image}`; } return `images/composer-emoji/twitter/${emoji.image}`; } } - return '' + return ''; } - _onUseEmoji = (emoji) => { - const savedEmoji = _.find(this._emoji, (curEmoji) => { + _onUseEmoji = emoji => { + const savedEmoji = _.find(this._emoji, curEmoji => { return curEmoji.emojiChar === emoji.emojiChar; }); if (savedEmoji) { @@ -66,17 +66,16 @@ class EmojiStore extends NylasStore { } savedEmoji.frequency++; } else { - Object.assign(emoji, {frequency: 1}); + Object.assign(emoji, { frequency: 1 }); this._emoji.push(emoji); } this._saveEmoji(); this.trigger(); - } + }; _saveEmoji = () => { window.localStorage.setItem(EmojiJSONKey, JSON.stringify(this._emoji)); - } - + }; } export default new EmojiStore(); diff --git a/app/internal_packages/composer-emoji/lib/main.es6 b/app/internal_packages/composer-emoji/lib/main.es6 index 657ee3def..95ddb2cac 100644 --- a/app/internal_packages/composer-emoji/lib/main.es6 +++ b/app/internal_packages/composer-emoji/lib/main.es6 @@ -1,4 +1,4 @@ -import {ExtensionRegistry, ComponentRegistry} from 'nylas-exports'; +import { ExtensionRegistry, ComponentRegistry } from 'nylas-exports'; import EmojiStore from './emoji-store'; import EmojiComposerExtension from './emoji-composer-extension'; import EmojiMessageExtension from './emoji-message-extension'; @@ -7,7 +7,7 @@ import EmojiButton from './emoji-button'; export function activate() { ExtensionRegistry.Composer.register(EmojiComposerExtension); ExtensionRegistry.MessageView.register(EmojiMessageExtension); - ComponentRegistry.register(EmojiButton, {role: 'Composer:ActionButton'}); + ComponentRegistry.register(EmojiButton, { role: 'Composer:ActionButton' }); EmojiStore.activate(); } diff --git a/app/internal_packages/composer-emoji/specs/emoji-button-popover-spec.jsx b/app/internal_packages/composer-emoji/specs/emoji-button-popover-spec.jsx index 9e1ba8e40..cdfa1ed51 100644 --- a/app/internal_packages/composer-emoji/specs/emoji-button-popover-spec.jsx +++ b/app/internal_packages/composer-emoji/specs/emoji-button-popover-spec.jsx @@ -1,8 +1,8 @@ import React from 'react'; import ReactTestUtils from 'react-dom/test-utils'; -import {findDOMNode} from 'react-dom'; -import {renderIntoDocument} from '../../../spec/nylas-test-utils'; +import { findDOMNode } from 'react-dom'; +import { renderIntoDocument } from '../../../spec/nylas-test-utils'; import Contenteditable from '../../../src/components/contenteditable/contenteditable'; import EmojiButtonPopover from '../lib/emoji-button-popover'; import EmojiComposerExtension from '../lib/emoji-composer-extension'; @@ -12,12 +12,14 @@ describe('EmojiButtonPopover', function emojiButtonPopover() { this.position = { x: 20, y: 40, - } + }; spyOn(EmojiButtonPopover.prototype, 'calcPosition').andReturn(this.position); spyOn(EmojiComposerExtension, '_onSelectEmoji').andCallThrough(); this.component = renderIntoDocument(); - this.canvas = findDOMNode(ReactTestUtils.findRenderedDOMComponentWithTag(this.component, 'canvas')); + this.canvas = findDOMNode( + ReactTestUtils.findRenderedDOMComponentWithTag(this.component, 'canvas') + ); this.composer = renderIntoDocument( { it('should filter for matches', () => { - this.searchNode = findDOMNode(ReactTestUtils.findRenderedDOMComponentWithClass(this.component, 'search')) + this.searchNode = findDOMNode( + ReactTestUtils.findRenderedDOMComponentWithClass(this.component, 'search') + ); const event = { target: { - value: "heart", + value: 'heart', }, - } + }; ReactTestUtils.Simulate.change(this.searchNode, event); ReactTestUtils.Simulate.mouseDown(this.canvas); expect(EmojiComposerExtension._onSelectEmoji).toHaveBeenCalled(); diff --git a/app/internal_packages/composer-emoji/specs/emoji-composer-extension-spec.jsx b/app/internal_packages/composer-emoji/specs/emoji-composer-extension-spec.jsx index 239d0e852..c8fcbebc3 100644 --- a/app/internal_packages/composer-emoji/specs/emoji-composer-extension-spec.jsx +++ b/app/internal_packages/composer-emoji/specs/emoji-composer-extension-spec.jsx @@ -2,99 +2,132 @@ import React from 'react'; import ReactDOM from 'react-dom'; import ReactTestUtils from 'react-dom/test-utils'; -import {renderIntoDocument} from '../../../spec/nylas-test-utils'; +import { renderIntoDocument } from '../../../spec/nylas-test-utils'; import Contenteditable from '../../../src/components/contenteditable/contenteditable'; import EmojiComposerExtension from '../lib/emoji-composer-extension'; xdescribe('EmojiComposerExtension', function emojiComposerExtension() { beforeEach(() => { - spyOn(EmojiComposerExtension, 'onContentChanged').andCallThrough() - spyOn(EmojiComposerExtension, '_onSelectEmoji').andCallThrough() + spyOn(EmojiComposerExtension, 'onContentChanged').andCallThrough(); + spyOn(EmojiComposerExtension, '_onSelectEmoji').andCallThrough(); this.component = renderIntoDocument( - ) + ); this.editableNode = ReactDOM.findDOMNode(this.component).querySelector('[contenteditable]'); - }) + }); describe('when emoji trigger is typed', () => { beforeEach(() => { - this._performEdit = (newHTML) => { + this._performEdit = newHTML => { this.editableNode.innerHTML = newHTML.substr(0, newHTML.length - 1); const sel = document.getSelection(); const textNode = this.editableNode.childNodes[0]; sel.setBaseAndExtent(textNode, textNode.length, textNode, textNode.length); - } - }) + }; + }); it('should show the emoji picker', () => { this._performEdit('Testing! :h'); waitsFor(() => { - return ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, 'emoji-picker').length > 0 + return ( + ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, 'emoji-picker').length > + 0 + ); }); - }) + }); it('should be focused on the first emoji in the list', () => { this._performEdit('Testing! :h'); waitsFor(() => { - return ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, 'emoji-option').length > 0 + return ( + ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, 'emoji-option').length > + 0 + ); }); runs(() => { - expect(ReactDOM.findDOMNode(ReactTestUtils.findRenderedDOMComponentWithClass(this.component, 'emoji-option')).textContent.indexOf(":haircut:") !== -1).toBe(true); + expect( + ReactDOM.findDOMNode( + ReactTestUtils.findRenderedDOMComponentWithClass(this.component, 'emoji-option') + ).textContent.indexOf(':haircut:') !== -1 + ).toBe(true); }); - }) + }); it('should insert an emoji on enter', () => { this._performEdit('Testing! :h'); waitsFor(() => { - return ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, 'emoji-picker').length > 0 + return ( + ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, 'emoji-picker').length > + 0 + ); }); runs(() => { - ReactTestUtils.Simulate.keyDown(this.editableNode, {key: "Enter", keyCode: 13, which: 13}); + ReactTestUtils.Simulate.keyDown(this.editableNode, { + key: 'Enter', + keyCode: 13, + which: 13, + }); }); waitsFor(() => { - return EmojiComposerExtension._onSelectEmoji.calls.length > 0 - }) - runs(() => { - expect(this.editableNode.innerHTML).toContain("emoji haircut") + return EmojiComposerExtension._onSelectEmoji.calls.length > 0; }); - }) + runs(() => { + expect(this.editableNode.innerHTML).toContain('emoji haircut'); + }); + }); it('should insert an emoji on click', () => { this._performEdit('Testing! :h'); waitsFor(() => { - return ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, 'emoji-picker').length > 0 + return ( + ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, 'emoji-picker').length > + 0 + ); }); runs(() => { - const button = ReactDOM.findDOMNode(ReactTestUtils.findRenderedDOMComponentWithClass(this.component, 'emoji-option')) + const button = ReactDOM.findDOMNode( + ReactTestUtils.findRenderedDOMComponentWithClass(this.component, 'emoji-option') + ); ReactTestUtils.Simulate.mouseDown(button); - expect(EmojiComposerExtension._onSelectEmoji).toHaveBeenCalled() + expect(EmojiComposerExtension._onSelectEmoji).toHaveBeenCalled(); }); waitsFor(() => { - return EmojiComposerExtension._onSelectEmoji.calls.length > 0 - }) - runs(() => { - expect(this.editableNode.innerHTML).toContain("emoji haircut") + return EmojiComposerExtension._onSelectEmoji.calls.length > 0; }); - }) + runs(() => { + expect(this.editableNode.innerHTML).toContain('emoji haircut'); + }); + }); it('should move to the next emoji on arrow down', () => { this._performEdit('Testing! :h'); waitsFor(() => { - return ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, 'emoji-option').length > 0 + return ( + ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, 'emoji-option').length > + 0 + ); }); runs(() => { - ReactTestUtils.Simulate.keyDown(this.editableNode, {key: "ArrowDown", keyCode: 40, which: 40}); + ReactTestUtils.Simulate.keyDown(this.editableNode, { + key: 'ArrowDown', + keyCode: 40, + which: 40, + }); }); waitsFor(() => { - return EmojiComposerExtension.onContentChanged.calls.length > 1 + return EmojiComposerExtension.onContentChanged.calls.length > 1; }); runs(() => { - expect(ReactDOM.findDOMNode(ReactTestUtils.findRenderedDOMComponentWithClass(this.component, 'emoji-option')).textContent.indexOf(":hamburger:") !== -1).toBe(true); + expect( + ReactDOM.findDOMNode( + ReactTestUtils.findRenderedDOMComponentWithClass(this.component, 'emoji-option') + ).textContent.indexOf(':hamburger:') !== -1 + ).toBe(true); }); - }) - }) -}) + }); + }); +}); diff --git a/app/internal_packages/composer-markdown/lib/markdown-editor.cjsx b/app/internal_packages/composer-markdown/lib/markdown-editor.cjsx index daae60aee..d2e490ef8 100644 --- a/app/internal_packages/composer-markdown/lib/markdown-editor.cjsx +++ b/app/internal_packages/composer-markdown/lib/markdown-editor.cjsx @@ -1,6 +1,6 @@ Utils = require './utils' SimpleMDE = require 'simplemde' -{React, ReactDOM, QuotedHTMLTransformer} = require 'nylas-exports' +{React, ReactDOM, PropTypes, QuotedHTMLTransformer} = require 'nylas-exports' # Keep a file-scope variable containing the contents of the markdown stylesheet. # This will be embedded in the markdown preview iFrame, as well as the email body. @@ -19,11 +19,11 @@ class MarkdownEditor extends React.Component @containerRequired: false @contextTypes: - parentTabGroup: React.PropTypes.object, + parentTabGroup: PropTypes.object, @propTypes: - body: React.PropTypes.string.isRequired, - onBodyChanged: React.PropTypes.func.isRequired, + body: PropTypes.string.isRequired, + onBodyChanged: PropTypes.func.isRequired, componentDidMount: => @mde = new SimpleMDE( diff --git a/app/internal_packages/composer-signature/lib/main.es6 b/app/internal_packages/composer-signature/lib/main.es6 index 11ac60cae..01cf8517d 100644 --- a/app/internal_packages/composer-signature/lib/main.es6 +++ b/app/internal_packages/composer-signature/lib/main.es6 @@ -1,12 +1,12 @@ -import {PreferencesUIStore, ExtensionRegistry, ComponentRegistry} from 'nylas-exports'; +import { PreferencesUIStore, ExtensionRegistry, ComponentRegistry } from 'nylas-exports'; import SignatureComposerExtension from './signature-composer-extension'; import SignatureComposerDropdown from './signature-composer-dropdown'; export function activate() { this.preferencesTab = new PreferencesUIStore.TabItem({ - tabId: "Signatures", - displayName: "Signatures", + tabId: 'Signatures', + displayName: 'Signatures', componentClassFn: () => require('./preferences-signatures').default, // eslint-disable-line }); diff --git a/app/internal_packages/composer-signature/lib/preferences-signatures.jsx b/app/internal_packages/composer-signature/lib/preferences-signatures.jsx index 485c9a8b5..5b7350b94 100644 --- a/app/internal_packages/composer-signature/lib/preferences-signatures.jsx +++ b/app/internal_packages/composer-signature/lib/preferences-signatures.jsx @@ -1,104 +1,97 @@ import React from 'react'; import { - Flexbox, - RetinaImg, - EditableList, - Contenteditable, - ScrollRegion, - MultiselectDropdown, + Flexbox, + RetinaImg, + EditableList, + Contenteditable, + ScrollRegion, + MultiselectDropdown, } from 'nylas-component-kit'; -import {AccountStore, SignatureStore, Actions} from 'nylas-exports'; - +import { AccountStore, SignatureStore, Actions } from 'nylas-exports'; export default class PreferencesSignatures extends React.Component { static displayName = 'PreferencesSignatures'; constructor() { - super() - this.state = this._getStateFromStores() + super(); + this.state = this._getStateFromStores(); } componentDidMount() { - this.unsubscribers = [ - SignatureStore.listen(this._onChange), - ] + this.unsubscribers = [SignatureStore.listen(this._onChange)]; } componentWillUnmount() { this.unsubscribers.forEach(unsubscribe => unsubscribe()); } - _onChange = () => { - this.setState(this._getStateFromStores()) - } + this.setState(this._getStateFromStores()); + }; _getStateFromStores() { - const signatures = SignatureStore.getSignatures() - const accountsAndAliases = AccountStore.aliases() - const selected = SignatureStore.selectedSignature() - const defaults = SignatureStore.getDefaults() + const signatures = SignatureStore.getSignatures(); + const accountsAndAliases = AccountStore.aliases(); + const selected = SignatureStore.selectedSignature(); + const defaults = SignatureStore.getDefaults(); return { signatures: signatures, selectedSignature: selected, defaults: defaults, accountsAndAliases: accountsAndAliases, editAsHTML: this.state ? this.state.editAsHTML : false, - } + }; } - _onCreateButtonClick = () => { - this._onAddSignature() - } + this._onAddSignature(); + }; _onAddSignature = () => { - Actions.addSignature() - } + Actions.addSignature(); + }; - _onDeleteSignature = (signature) => { - Actions.removeSignature(signature) - } + _onDeleteSignature = signature => { + Actions.removeSignature(signature); + }; - _onEditSignature = (edit) => { + _onEditSignature = edit => { let editedSig; - if (typeof edit === "object") { + if (typeof edit === 'object') { editedSig = { title: this.state.selectedSignature.title, body: edit.target.value, - } + }; } else { editedSig = { title: edit, body: this.state.selectedSignature.body, - } + }; } - Actions.updateSignature(editedSig, this.state.selectedSignature.id) - } + Actions.updateSignature(editedSig, this.state.selectedSignature.id); + }; - _onSelectSignature = (sig) => { - Actions.selectSignature(sig.id) - } + _onSelectSignature = sig => { + Actions.selectSignature(sig.id); + }; - _onToggleAccount = (account) => { - Actions.toggleAccount(account.email) - } + _onToggleAccount = account => { + Actions.toggleAccount(account.email); + }; _onToggleEditAsHTML = () => { - const toggled = !this.state.editAsHTML - this.setState({editAsHTML: toggled}) - } + const toggled = !this.state.editAsHTML; + this.setState({ editAsHTML: toggled }); + }; - _renderListItemContent = (sig) => { - return sig.title - } + _renderListItemContent = sig => { + return sig.title; + }; _renderSignatureToolbar() { return (
-
- Default for: {this._renderAccountPicker()} -
+
Default for: {this._renderAccountPicker()}
Edit raw HTML
- ) + ); } - _selectItemKey = (accountOrAlias) => { - return accountOrAlias.id - } + _selectItemKey = accountOrAlias => { + return accountOrAlias.id; + }; - _isChecked = (accountOrAlias) => { + _isChecked = accountOrAlias => { if (!this.state.selectedSignature) { return false; } - return (this.state.defaults[accountOrAlias.email] === this.state.selectedSignature.id); - } + return this.state.defaults[accountOrAlias.email] === this.state.selectedSignature.id; + }; _labelForAccountPicker() { - const sel = this.state.accountsAndAliases.filter((accountOrAlias) => { - return this._isChecked(accountOrAlias) - }) + const sel = this.state.accountsAndAliases.filter(accountOrAlias => { + return this._isChecked(accountOrAlias); + }); const numSelected = sel.length; - return numSelected.toString() + (numSelected === 1 ? " Account" : " Accounts") + return numSelected.toString() + (numSelected === 1 ? ' Account' : ' Accounts'); } _renderAccountPicker() { - const buttonText = this._labelForAccountPicker() + const buttonText = this._labelForAccountPicker(); return ( accountOrAlias.email} - /> - ) - } - - _renderEditableSignature() { - const selectedBody = this.state.selectedSignature ? this.state.selectedSignature.body : "" - return ( - - ) - } - - _renderHTMLSignature() { - return ( -