├── .editorconfig ├── .github ├── ISSUE_TEMPLATE.md ├── gdc-create-credentials.png ├── gdc-oauth-client-id-creation.png ├── screenshot.png ├── setup1.png ├── setup2.png ├── setup3.png └── wmail_wavebox.gif ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets ├── fonts │ ├── fontawesome │ │ ├── FontAwesome.otf │ │ ├── font-awesome.min.css │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── materialdesign │ │ ├── MaterialIcons-Regular.ttf │ │ └── material-icons.css │ └── roboto │ │ ├── .gitignore │ │ ├── Roboto-Black.ttf │ │ ├── Roboto-BlackItalic.ttf │ │ ├── Roboto-Bold.ttf │ │ ├── Roboto-BoldItalic.ttf │ │ ├── Roboto-Light.ttf │ │ ├── Roboto-LightItalic.ttf │ │ ├── Roboto-Medium.ttf │ │ ├── Roboto-MediumItalic.ttf │ │ ├── Roboto-Regular.ttf │ │ ├── Roboto-Thin.ttf │ │ ├── Roboto-ThinItalic.ttf │ │ └── roboto-fontface.css ├── icons │ ├── app.icns │ ├── app.ico │ ├── app.png │ ├── app.svg │ ├── app_128.png │ ├── app_16.png │ ├── app_24.png │ ├── app_256.png │ ├── app_32.png │ ├── app_48.png │ ├── app_512.png │ ├── app_64.png │ └── app_96.png ├── images │ ├── ginbox_icon_512.png │ ├── ginbox_mode_full_small.png │ ├── ginbox_mode_inbox.png │ ├── ginbox_mode_unreadunbundled.png │ ├── ginbox_mode_zero_small.png │ ├── gmail_icon_512.png │ ├── gmail_inbox_categories_small.png │ ├── gmail_inbox_priority_small.png │ ├── gmail_inbox_unread_small.png │ ├── google_services │ │ ├── logo_calendar_128px.png │ │ ├── logo_calendar_32px.png │ │ ├── logo_calendar_48px.png │ │ ├── logo_calendar_64px.png │ │ ├── logo_contacts_128px.png │ │ ├── logo_contacts_32px.png │ │ ├── logo_contacts_48px.png │ │ ├── logo_contacts_64px.png │ │ ├── logo_drive_128px.png │ │ ├── logo_drive_32px.png │ │ ├── logo_drive_48px.png │ │ ├── logo_drive_64px.png │ │ ├── logo_hangouts_128px.png │ │ ├── logo_hangouts_16px.png │ │ ├── logo_hangouts_24px.png │ │ ├── logo_hangouts_32px.png │ │ ├── logo_hangouts_48px.png │ │ ├── logo_hangouts_64px.png │ │ ├── logo_keep_128px.png │ │ ├── logo_keep_32px.png │ │ ├── logo_keep_48px.png │ │ └── logo_keep_64px.png │ └── mailbox_right_click_settings.png └── webpack.config.js ├── package.json ├── src ├── app │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── AppAnalytics.js │ │ │ ├── AppPrimaryMenu.js │ │ │ ├── AuthGoogle.js │ │ │ ├── AuthHTTP.html │ │ │ ├── AuthHTTP.js │ │ │ ├── KeyboardShortcuts.js │ │ │ ├── main.js │ │ │ ├── storage │ │ │ │ ├── StorageBucket.js │ │ │ │ ├── StorageBucketAppMutable.js │ │ │ │ ├── appStorage.js │ │ │ │ ├── avatarStorage.js │ │ │ │ ├── index.js │ │ │ │ ├── mailboxStorage.js │ │ │ │ └── settingStorage.js │ │ │ ├── stores │ │ │ │ ├── mailboxStore.js │ │ │ │ └── settingStore.js │ │ │ └── windows │ │ │ │ ├── ContentWindow.js │ │ │ │ ├── MailboxesSessionManager.js │ │ │ │ ├── MailboxesWindow.js │ │ │ │ ├── WMailWindow.js │ │ │ │ └── WindowManager.js │ │ └── index.js │ └── webpack.config.js ├── scenes │ ├── mailboxes │ │ ├── package.json │ │ ├── src │ │ │ ├── Components │ │ │ │ ├── ColorPickerButton.js │ │ │ │ ├── Grid │ │ │ │ │ ├── Col.js │ │ │ │ │ ├── Container.js │ │ │ │ │ ├── Row.js │ │ │ │ │ └── index.js │ │ │ │ ├── TrayIconEditor.js │ │ │ │ ├── TrayPreview.js │ │ │ │ ├── TrayRenderer.js │ │ │ │ ├── WebView.js │ │ │ │ └── index.js │ │ │ ├── Dispatch │ │ │ │ ├── index.js │ │ │ │ ├── mailboxDispatch.js │ │ │ │ └── navigationDispatch.js │ │ │ ├── Notifications │ │ │ │ ├── Notification.js │ │ │ │ └── UnreadNotifications.js │ │ │ ├── ReactComponents.less │ │ │ ├── index.js │ │ │ ├── mailboxes.html │ │ │ ├── notification.html │ │ │ ├── offline.html │ │ │ ├── reporter.js │ │ │ ├── stores │ │ │ │ ├── StorageBucket.js │ │ │ │ ├── alt.js │ │ │ │ ├── appWizard │ │ │ │ │ ├── appWizardActions.js │ │ │ │ │ ├── appWizardStore.js │ │ │ │ │ └── index.js │ │ │ │ ├── compose │ │ │ │ │ ├── composeActions.js │ │ │ │ │ ├── composeStore.js │ │ │ │ │ └── index.js │ │ │ │ ├── dictionaries │ │ │ │ │ ├── dictionariesActions.js │ │ │ │ │ ├── dictionariesStore.js │ │ │ │ │ └── index.js │ │ │ │ ├── google │ │ │ │ │ ├── GoogleHTTPTransporter.js │ │ │ │ │ ├── googleActions.js │ │ │ │ │ ├── googleHTTP.js │ │ │ │ │ ├── googleStore.js │ │ │ │ │ └── index.js │ │ │ │ ├── http │ │ │ │ │ ├── httpActions.js │ │ │ │ │ ├── httpStore.js │ │ │ │ │ └── index.js │ │ │ │ ├── mailbox │ │ │ │ │ ├── avatarPersistence.js │ │ │ │ │ ├── index.js │ │ │ │ │ ├── mailboxActions.js │ │ │ │ │ ├── mailboxPersistence.js │ │ │ │ │ ├── mailboxStore.js │ │ │ │ │ └── migration.js │ │ │ │ ├── mailboxWizard │ │ │ │ │ ├── Configurations.js │ │ │ │ │ ├── index.js │ │ │ │ │ ├── mailboxWizardActions.js │ │ │ │ │ └── mailboxWizardStore.js │ │ │ │ ├── platform │ │ │ │ │ ├── index.js │ │ │ │ │ ├── platformActions.js │ │ │ │ │ └── platformStore.js │ │ │ │ └── settings │ │ │ │ │ ├── index.js │ │ │ │ │ ├── migration.js │ │ │ │ │ ├── settingsActions.js │ │ │ │ │ ├── settingsPersistence.js │ │ │ │ │ └── settingsStore.js │ │ │ └── ui │ │ │ │ ├── App.js │ │ │ │ ├── AppBadge.js │ │ │ │ ├── AppContent.js │ │ │ │ ├── AppWizard │ │ │ │ ├── AppWizard.js │ │ │ │ ├── AppWizardComplete.js │ │ │ │ ├── AppWizardMailto.js │ │ │ │ ├── AppWizardStart.js │ │ │ │ ├── AppWizardTray.js │ │ │ │ └── index.js │ │ │ │ ├── DictionaryInstaller │ │ │ │ ├── DictionaryInstallHandler.js │ │ │ │ └── DictionaryInstallStepper.js │ │ │ │ ├── Mailbox │ │ │ │ ├── Google │ │ │ │ │ ├── GoogleMailboxCalendarTab.js │ │ │ │ │ ├── GoogleMailboxCommunicationTab.js │ │ │ │ │ ├── GoogleMailboxContactsTab.js │ │ │ │ │ ├── GoogleMailboxMailTab.js │ │ │ │ │ ├── GoogleMailboxNotesTab.js │ │ │ │ │ └── GoogleMailboxStorageTab.js │ │ │ │ ├── MailboxComposePicker.js │ │ │ │ ├── MailboxSearch.js │ │ │ │ ├── MailboxTab.js │ │ │ │ ├── MailboxTabSleepable.js │ │ │ │ ├── MailboxTargetUrl.js │ │ │ │ ├── MailboxWindows.js │ │ │ │ └── mailboxWindow.less │ │ │ │ ├── MailboxWizard │ │ │ │ ├── AddMailboxWizardDialog.js │ │ │ │ ├── ConfigureCompleteWizardDialog.js │ │ │ │ ├── ConfigureGinboxMailboxWizard.js │ │ │ │ ├── ConfigureGmailMailboxWizard.js │ │ │ │ ├── ConfigureMailboxServicesDialog.js │ │ │ │ ├── ConfigureMailboxWizardDialog.js │ │ │ │ ├── MailboxWizard.js │ │ │ │ └── index.js │ │ │ │ ├── NewsDialog.js │ │ │ │ ├── NewsDialog.less │ │ │ │ ├── Settings │ │ │ │ ├── AccountSettings.js │ │ │ │ ├── Accounts │ │ │ │ │ ├── AccountAdvancedSettings.js │ │ │ │ │ ├── AccountAvatarSettings.js │ │ │ │ │ ├── AccountCustomCodeSettings.js │ │ │ │ │ ├── AccountManagementSettings.js │ │ │ │ │ ├── AccountServiceSettings.js │ │ │ │ │ ├── AccountUnreadSettings.js │ │ │ │ │ └── CustomCodeEditingModal.js │ │ │ │ ├── AdvancedSettings.js │ │ │ │ ├── General │ │ │ │ │ ├── DownloadSettingsSection.js │ │ │ │ │ ├── InfoSettingsSection.js │ │ │ │ │ ├── LanguageSettingsSection.js │ │ │ │ │ ├── NotificationSettingsSection.js │ │ │ │ │ ├── PlatformSettingsSection.js │ │ │ │ │ ├── TraySettingsSection.js │ │ │ │ │ └── UISettingsSection.js │ │ │ │ ├── GeneralSettings.js │ │ │ │ ├── SettingsDialog.js │ │ │ │ └── settingStyles.js │ │ │ │ ├── Sidelist │ │ │ │ ├── Sidelist.js │ │ │ │ ├── SidelistItemAddMailbox.js │ │ │ │ ├── SidelistItemMailbox │ │ │ │ │ ├── SidelistItemMailbox.js │ │ │ │ │ ├── SidelistItemMailboxAvatar.js │ │ │ │ │ ├── SidelistItemMailboxPopover.js │ │ │ │ │ ├── SidelistItemMailboxService.js │ │ │ │ │ ├── SidelistItemMailboxServices.js │ │ │ │ │ └── index.js │ │ │ │ ├── SidelistItemNews.js │ │ │ │ ├── SidelistItemSettings.js │ │ │ │ ├── SidelistItemWizard.js │ │ │ │ ├── SidelistMailboxes.js │ │ │ │ ├── SidelistStyles.js │ │ │ │ ├── SidelistStyles.less │ │ │ │ └── index.js │ │ │ │ ├── Tray.js │ │ │ │ ├── UpdateCheckDialog.js │ │ │ │ ├── Welcome │ │ │ │ └── Welcome.js │ │ │ │ ├── appContent.less │ │ │ │ ├── appTheme.js │ │ │ │ └── layout.less │ │ └── webpack.config.js │ └── platform │ │ ├── src │ │ ├── nativeRequire.js │ │ └── webviewInjection │ │ │ ├── Browser │ │ │ ├── Browser.js │ │ │ ├── ContextMenu.js │ │ │ ├── DictionaryLoad.js │ │ │ ├── KeyboardNavigator.js │ │ │ └── Spellchecker.js │ │ │ ├── Google │ │ │ ├── GinboxApi.js │ │ │ ├── GinboxChangeEmitter.js │ │ │ ├── GmailApiExtras.js │ │ │ ├── GmailChangeEmitter.js │ │ │ ├── GoogleMail.js │ │ │ ├── GoogleService.js │ │ │ └── GoogleWindowOpen.js │ │ │ ├── WMail │ │ │ ├── CustomCode.js │ │ │ └── WMail.js │ │ │ ├── elconsole.js │ │ │ ├── googleMail.js │ │ │ ├── googleService.js │ │ │ └── injector.js │ │ └── webpack.config.js └── shared │ ├── Models │ ├── Mailbox │ │ ├── Google.js │ │ ├── Mailbox.js │ │ ├── MailboxServices.js │ │ ├── MailboxTypes.js │ │ └── index.js │ ├── Model.js │ ├── Settings │ │ ├── AppSettings.js │ │ ├── LanguageSettings.js │ │ ├── NewsSettings.js │ │ ├── OSSettings.js │ │ ├── ProxySettings.js │ │ ├── SettingsIdent.js │ │ ├── TraySettings.js │ │ ├── UISettings.js │ │ └── index.js │ └── index.js │ ├── b64Assets.js │ ├── constants.js │ ├── dictionaries.js │ ├── dictionaryExcludes.js │ └── index.js └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [{package.json,*.yml}] 12 | indent_style = space 13 | indent_size = 2 14 | trim_trailing_whitespace = true 15 | insert_final_newline = true 16 | 17 | [*.md] 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * Which version of WMail are you using? [e.g. 1.3.1] 2 | 3 | * Which Operating System are you using? [e.g. Windows 10, Mac OSX 10, Ubuntu 14.04] 4 | -------------------------------------------------------------------------------- /.github/gdc-create-credentials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/.github/gdc-create-credentials.png -------------------------------------------------------------------------------- /.github/gdc-oauth-client-id-creation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/.github/gdc-oauth-client-id-creation.png -------------------------------------------------------------------------------- /.github/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/.github/screenshot.png -------------------------------------------------------------------------------- /.github/setup1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/.github/setup1.png -------------------------------------------------------------------------------- /.github/setup2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/.github/setup2.png -------------------------------------------------------------------------------- /.github/setup3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/.github/setup3.png -------------------------------------------------------------------------------- /.github/wmail_wavebox.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/.github/wmail_wavebox.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | credentials.js 2 | node_modules 3 | bin 4 | dist 5 | WMail-darwin-x64 6 | WMail-linux-ia32/ 7 | WMail-linux-x64/ 8 | WMail-win32-ia32/ 9 | WMail-win32-x64/ 10 | WMail-win32-ia32-Installer/ 11 | *.log 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | install: 3 | - sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y 4 | - sudo apt-get update -qq 5 | - if [ "$CXX" = "g++" ]; then sudo apt-get install -qq g++-4.8; fi 6 | - if [ "$CXX" = "g++" ]; then export CXX="g++-4.8" CC="gcc-4.8"; fi 7 | - npm install -g standard 8 | 9 | language: node_js 10 | node_js: 11 | - "6.2.0" 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Pull Requests 2 | 3 | By submitting a pull request, you represent that you have the right to license your contribution to us and the community, and agree by submitting the patch that your contributions are licensed under the MPL-2.0 license. 4 | 5 | # Raising a new issue & requesting Features 6 | 7 | Thanks for being an A* tester and helping to make WMail better by either reporting a bug or raising an issue! Before you create a new issue here are a few things that might be worth checking first... 8 | 9 | 1. Make sure you're using the [latest version and also check the latest pre-releases](https://github.com/Thomas101/wmail/releases). You may find that your bug has been fixed or feature added in a pre-release. You can find the full list of all releases available to download along with the changelog for each release on the [releases page](https://github.com/Thomas101/wmail/releases) 10 | 11 | 2. Take a quick look to see if somebody else has already raised an issue on the [issues page](https://github.com/Thomas101/wmail/issues). It makes it much easier for me to track discussion around a single issue rather than dealing with duplicates 12 | 13 | 3. Check the [closed issues with the "waiting release" tag](https://github.com/Thomas101/wmail/issues?q=is%3Aissue+label%3AWaiting-release+is%3Aclosed). These are bugs that have been fixed or features that have been added that haven't quite made it into a release just yet. Once an issue is given the waiting release tag it will make it out into a release shortly 14 | 15 | 4. Some commonly raised issues have their own section in the [FAQs](https://github.com/Thomas101/wmail/wiki/FAQs). The answer to your question may be there already! 16 | 17 | Once you're happy that your issue / feature hasn't already been raised or fixed feel free to raise it! 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # We've moved! 2 | 3 | Hi! This repository is no longer being used and has been archived for historical purposes. 4 | 5 | Find out more at [https://wavebox.io](https://wavebox.io/) 6 | -------------------------------------------------------------------------------- /assets/fonts/fontawesome/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/fonts/fontawesome/FontAwesome.otf -------------------------------------------------------------------------------- /assets/fonts/fontawesome/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/fonts/fontawesome/fontawesome-webfont.eot -------------------------------------------------------------------------------- /assets/fonts/fontawesome/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/fonts/fontawesome/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /assets/fonts/fontawesome/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/fonts/fontawesome/fontawesome-webfont.woff -------------------------------------------------------------------------------- /assets/fonts/fontawesome/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/fonts/fontawesome/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /assets/fonts/materialdesign/MaterialIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/fonts/materialdesign/MaterialIcons-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/materialdesign/material-icons.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Material Icons'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: url(MaterialIcons-Regular.ttf) format('truetype'); 6 | } 7 | 8 | .material-icons { 9 | font-family: 'Material Icons'; 10 | font-weight: normal; 11 | font-style: normal; 12 | font-size: 24px; /* Preferred icon size */ 13 | display: inline-block; 14 | width: 1em; 15 | height: 1em; 16 | line-height: 1; 17 | text-transform: none; 18 | letter-spacing: normal; 19 | word-wrap: normal; 20 | white-space: nowrap; 21 | direction: ltr; 22 | 23 | /* Support for all WebKit browsers. */ 24 | -webkit-font-smoothing: antialiased; 25 | /* Support for Safari and Chrome. */ 26 | text-rendering: optimizeLegibility; 27 | 28 | /* Support for Firefox. */ 29 | -moz-osx-font-smoothing: grayscale; 30 | 31 | /* Support for IE. */ 32 | font-feature-settings: 'liga'; 33 | } 34 | -------------------------------------------------------------------------------- /assets/fonts/roboto/.gitignore: -------------------------------------------------------------------------------- 1 | # OS specific trash 2 | .DS_Store 3 | ._.DS_Store 4 | Thumbs.db 5 | 6 | # Sass-specific 7 | .sass-cache 8 | -------------------------------------------------------------------------------- /assets/fonts/roboto/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/fonts/roboto/Roboto-Black.ttf -------------------------------------------------------------------------------- /assets/fonts/roboto/Roboto-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/fonts/roboto/Roboto-BlackItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/fonts/roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/roboto/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/fonts/roboto/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/roboto/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/fonts/roboto/Roboto-Light.ttf -------------------------------------------------------------------------------- /assets/fonts/roboto/Roboto-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/fonts/roboto/Roboto-LightItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/fonts/roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/roboto/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/fonts/roboto/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/fonts/roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/roboto/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/fonts/roboto/Roboto-Thin.ttf -------------------------------------------------------------------------------- /assets/fonts/roboto/Roboto-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/fonts/roboto/Roboto-ThinItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/roboto/roboto-fontface.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto'; 3 | src: url('Roboto-Thin.ttf') format('truetype'); 4 | font-weight: 100; 5 | font-style: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: 'Roboto-Thin'; 10 | src: url('Roboto-Thin.ttf') format('truetype'); 11 | } 12 | 13 | @font-face { 14 | font-family: 'Roboto'; 15 | src: url('Roboto-ThinItalic.ttf') format('truetype'); 16 | font-weight: 100; 17 | font-style: italic; 18 | } 19 | 20 | @font-face { 21 | font-family: 'Roboto-ThinItalic'; 22 | src: url('Roboto-ThinItalic.ttf') format('truetype'); 23 | } 24 | 25 | @font-face { 26 | font-family: 'Roboto'; 27 | src: url('Roboto-Light.ttf') format('truetype'); 28 | font-weight: 300; 29 | font-style: normal; 30 | } 31 | 32 | @font-face { 33 | font-family: 'Roboto-Light'; 34 | src: url('Roboto-Light.ttf') format('truetype'); 35 | } 36 | 37 | @font-face { 38 | font-family: 'Roboto'; 39 | src: url('Roboto-LightItalic.ttf') format('truetype'); 40 | font-weight: 300; 41 | font-style: italic; 42 | } 43 | 44 | @font-face { 45 | font-family: 'Roboto-LightItalic'; 46 | src: url('Roboto-LightItalic.ttf') format('truetype'); 47 | } 48 | 49 | @font-face { 50 | font-family: 'Roboto'; 51 | src: url('Roboto-Regular.ttf') format('truetype'); 52 | font-weight: 400; 53 | font-style: normal; 54 | } 55 | 56 | @font-face { 57 | font-family: 'Roboto-Regular'; 58 | src: url('Roboto-Regular.ttf') format('truetype'); 59 | } 60 | 61 | @font-face { 62 | font-family: 'Roboto'; 63 | src: url('Roboto-RegularItalic.ttf') format('truetype'); 64 | font-weight: 400; 65 | font-style: italic; 66 | } 67 | 68 | @font-face { 69 | font-family: 'Roboto-RegularItalic'; 70 | src: url('Roboto-RegularItalic.ttf') format('truetype'); 71 | } 72 | 73 | @font-face { 74 | font-family: 'Roboto'; 75 | src: url('Roboto-Medium.ttf') format('truetype'); 76 | font-weight: 500; 77 | font-style: normal; 78 | } 79 | 80 | @font-face { 81 | font-family: 'Roboto-Medium'; 82 | src: url('Roboto-Medium.ttf') format('truetype'); 83 | } 84 | 85 | @font-face { 86 | font-family: 'Roboto'; 87 | src: url('Roboto-MediumItalic.ttf') format('truetype'); 88 | font-weight: 500; 89 | font-style: italic; 90 | } 91 | 92 | @font-face { 93 | font-family: 'Roboto-MediumItalic'; 94 | src: url('Roboto-MediumItalic.ttf') format('truetype'); 95 | } 96 | 97 | @font-face { 98 | font-family: 'Roboto'; 99 | src: url('Roboto-Bold.ttf') format('truetype'); 100 | font-weight: 700; 101 | font-style: normal; 102 | } 103 | 104 | @font-face { 105 | font-family: 'Roboto-Bold'; 106 | src: url('Roboto-Bold.ttf') format('truetype'); 107 | } 108 | 109 | @font-face { 110 | font-family: 'Roboto'; 111 | src: url('Roboto-BoldItalic.ttf') format('truetype'); 112 | font-weight: 700; 113 | font-style: italic; 114 | } 115 | 116 | @font-face { 117 | font-family: 'Roboto-BoldItalic'; 118 | src: url('Roboto-BoldItalic.ttf') format('truetype'); 119 | } 120 | 121 | @font-face { 122 | font-family: 'Roboto'; 123 | src: url('Roboto-Black.ttf') format('truetype'); 124 | font-weight: 900; 125 | font-style: normal; 126 | } 127 | 128 | @font-face { 129 | font-family: 'Roboto-Black'; 130 | src: url('Roboto-Black.ttf') format('truetype'); 131 | } 132 | 133 | @font-face { 134 | font-family: 'Roboto'; 135 | src: url('Roboto-BlackItalic.ttf') format('truetype'); 136 | font-weight: 900; 137 | font-style: italic; 138 | } 139 | 140 | @font-face { 141 | font-family: 'Roboto-BlackItalic'; 142 | src: url('Roboto-BlackItalic.ttf') format('truetype'); 143 | } 144 | -------------------------------------------------------------------------------- /assets/icons/app.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/icons/app.icns -------------------------------------------------------------------------------- /assets/icons/app.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/icons/app.ico -------------------------------------------------------------------------------- /assets/icons/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/icons/app.png -------------------------------------------------------------------------------- /assets/icons/app.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /assets/icons/app_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/icons/app_128.png -------------------------------------------------------------------------------- /assets/icons/app_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/icons/app_16.png -------------------------------------------------------------------------------- /assets/icons/app_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/icons/app_24.png -------------------------------------------------------------------------------- /assets/icons/app_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/icons/app_256.png -------------------------------------------------------------------------------- /assets/icons/app_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/icons/app_32.png -------------------------------------------------------------------------------- /assets/icons/app_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/icons/app_48.png -------------------------------------------------------------------------------- /assets/icons/app_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/icons/app_512.png -------------------------------------------------------------------------------- /assets/icons/app_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/icons/app_64.png -------------------------------------------------------------------------------- /assets/icons/app_96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/icons/app_96.png -------------------------------------------------------------------------------- /assets/images/ginbox_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/ginbox_icon_512.png -------------------------------------------------------------------------------- /assets/images/ginbox_mode_full_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/ginbox_mode_full_small.png -------------------------------------------------------------------------------- /assets/images/ginbox_mode_inbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/ginbox_mode_inbox.png -------------------------------------------------------------------------------- /assets/images/ginbox_mode_unreadunbundled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/ginbox_mode_unreadunbundled.png -------------------------------------------------------------------------------- /assets/images/ginbox_mode_zero_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/ginbox_mode_zero_small.png -------------------------------------------------------------------------------- /assets/images/gmail_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/gmail_icon_512.png -------------------------------------------------------------------------------- /assets/images/gmail_inbox_categories_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/gmail_inbox_categories_small.png -------------------------------------------------------------------------------- /assets/images/gmail_inbox_priority_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/gmail_inbox_priority_small.png -------------------------------------------------------------------------------- /assets/images/gmail_inbox_unread_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/gmail_inbox_unread_small.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_calendar_128px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_calendar_128px.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_calendar_32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_calendar_32px.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_calendar_48px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_calendar_48px.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_calendar_64px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_calendar_64px.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_contacts_128px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_contacts_128px.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_contacts_32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_contacts_32px.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_contacts_48px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_contacts_48px.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_contacts_64px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_contacts_64px.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_drive_128px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_drive_128px.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_drive_32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_drive_32px.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_drive_48px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_drive_48px.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_drive_64px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_drive_64px.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_hangouts_128px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_hangouts_128px.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_hangouts_16px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_hangouts_16px.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_hangouts_24px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_hangouts_24px.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_hangouts_32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_hangouts_32px.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_hangouts_48px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_hangouts_48px.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_hangouts_64px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_hangouts_64px.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_keep_128px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_keep_128px.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_keep_32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_keep_32px.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_keep_48px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_keep_48px.png -------------------------------------------------------------------------------- /assets/images/google_services/logo_keep_64px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/google_services/logo_keep_64px.png -------------------------------------------------------------------------------- /assets/images/mailbox_right_click_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thomas101/wmail/e389fc56296e74a033785b7dcc455b65a714bd46/assets/images/mailbox_right_click_settings.png -------------------------------------------------------------------------------- /assets/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const ROOT_DIR = path.resolve(path.join(__dirname, '../')) 3 | const BIN_DIR = path.join(ROOT_DIR, 'bin') 4 | const devRequire = (n) => require(path.join(ROOT_DIR, 'node_modules', n)) 5 | 6 | const CleanWebpackPlugin = devRequire('clean-webpack-plugin') 7 | const CopyWebpackPlugin = devRequire('copy-webpack-plugin') 8 | 9 | module.exports = { 10 | output: { 11 | path: BIN_DIR, 12 | filename: '__.js' 13 | }, 14 | plugins: [ 15 | new CleanWebpackPlugin(['fonts', 'icons'], { 16 | root: BIN_DIR, 17 | verbose: true, 18 | dry: false 19 | }), 20 | new CopyWebpackPlugin([ 21 | { from: path.join(__dirname, 'fonts'), to: 'fonts', force: true }, 22 | { from: path.join(__dirname, 'icons'), to: 'icons', force: true }, 23 | { from: path.join(__dirname, 'images'), to: 'images', force: true } 24 | ], { 25 | ignore: [ '.DS_Store' ] 26 | }) 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wmail", 3 | "version": "2.3.1", 4 | "prerelease": true, 5 | "description": "The missing desktop client for Gmail and Google Inbox", 6 | "scripts": { 7 | "prestart": "webpack", 8 | "start": "electron bin/app/index.js", 9 | "test": "standard", 10 | "install-all": "echo ':wmail' && npm install && cd src/app && echo ':wmail-app' && npm install && cd ../../src/scenes/mailboxes && echo ':wmail-scenes-mailboxes' && npm install", 11 | "outdated-all": "echo ':wmail' && npm outdated && cd src/app && echo ':wmail-app' && npm outdated && cd ../../src/scenes/mailboxes && echo ':wmail-scenes-mailboxes' && npm outdated", 12 | "dev:platform": "webpack --task=platform && electron bin/app/index.js", 13 | "dev:app": "webpack --task=app && electron bin/app/index.js", 14 | "dev:mailboxes": "webpack --task=mailboxes && electron bin/app/index.js", 15 | "dev:assets": "webpack --task=assets && electron bin/app/index.js", 16 | "dev:run": "electron bin/app/index.js" 17 | }, 18 | "keywords": [], 19 | "author": { 20 | "name": "Thomas Beverley", 21 | "email": "tom.beverley.wmail@gmail.com", 22 | "url": "https://github.com/Thomas101/" 23 | }, 24 | "homepage": "https://thomas101.github.io/wmail/", 25 | "license": "MPL-2.0", 26 | "repository": "https://github.com/Thomas101/wmail", 27 | "main": "bin/app/index.js", 28 | "dependencies": { 29 | "babel": "6.23.0", 30 | "babel-core": "6.23.1", 31 | "babel-loader": "6.3.2", 32 | "babel-preset-es2015": "6.22.0", 33 | "babel-preset-react": "6.23.0", 34 | "babel-preset-stage-0": "6.22.0", 35 | "clean-webpack-plugin": "0.1.15", 36 | "copy-webpack-plugin": "4.0.1", 37 | "css-loader": "0.26.1", 38 | "electron": "1.6.1", 39 | "extract-text-webpack-plugin": "1.0.1", 40 | "file-loader": "0.10.0", 41 | "json-loader": "0.5.4", 42 | "jsx-loader": "0.13.2", 43 | "less": "2.7.2", 44 | "less-loader": "2.2.3", 45 | "style-loader": "0.13.1", 46 | "uglify-js": "mishoo/UglifyJS2#3ee46e91e802fb8bf20656bce115375c5f624052", 47 | "url-loader": "0.5.7", 48 | "uuid": "3.0.1", 49 | "webpack": "1.14.0", 50 | "webpack-target-electron-renderer": "0.4.0" 51 | }, 52 | "devDependencies": { 53 | "standard": "8.6.0" 54 | }, 55 | "standard": { 56 | "ignore": [ 57 | "src/app/lib/" 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wmail-app", 3 | "keywords": [], 4 | "author": "Thomas Beverley", 5 | "license": "MPL-2.0", 6 | "repository": "https://github.com/Thomas101/wmail", 7 | "description": "The mail app code for the WMail app", 8 | "dependencies": { 9 | "appdirectory": "0.1.0", 10 | "dictionary-en-us": "1.2.0", 11 | "escape-html": "1.0.3", 12 | "fs-extra": "2.0.0", 13 | "gmail-js": "0.6.8", 14 | "googleapis": "16.1.0", 15 | "home-dir": "1.0.0", 16 | "https-proxy-agent": "1.0.0", 17 | "jquery": "3.1.1", 18 | "minivents": "2.0.2", 19 | "mkdirp": "0.5.1", 20 | "node-fetch": "1.6.3", 21 | "os-locale": "2.0.0", 22 | "request": "2.79.0", 23 | "unused-filename": "0.1.0", 24 | "uuid": "3.0.1", 25 | "windows-shortcuts": "Thomas101/windows-shortcuts#0.1.4", 26 | "wmail-spellchecker": "Thomas101/wmail-spellchecker#1.0.5", 27 | "write-file-atomic": "1.3.1", 28 | "yargs": "6.6.0" 29 | }, 30 | "devDependencies": { 31 | "standard": "8.6.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/app/src/app/AuthHTTP.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WMail Authentication Required 6 | 58 | 59 | 77 | 78 | 79 |

WMail Authentication Required

80 |

81 | requires a username and password 82 |

83 |
84 |
85 | 86 | 87 |
88 |
89 | 90 | 91 |
92 |
93 | 94 | 95 |
96 |
97 | 98 | 99 | -------------------------------------------------------------------------------- /src/app/src/app/AuthHTTP.js: -------------------------------------------------------------------------------- 1 | const {BrowserWindow} = require('electron') 2 | 3 | class AuthHTTP { 4 | 5 | /* ****************************************************************************/ 6 | // Lifecycle 7 | /* ****************************************************************************/ 8 | 9 | /** 10 | * @param callback: callback to execute with username and password 11 | */ 12 | constructor (url, callback) { 13 | this.window = new BrowserWindow({ 14 | width: 400, 15 | height: 300, 16 | frame: false, 17 | center: true, 18 | show: true, 19 | resizable: false, 20 | alwaysOnTop: true, 21 | autoHideMenuBar: true 22 | }) 23 | this.window.loadURL(`file://${__dirname}/AuthHTTP.html`) 24 | this.window.webContents.on('did-finish-load', () => { 25 | this.window.webContents.send('requestor', url) 26 | }) 27 | } 28 | } 29 | 30 | module.exports = AuthHTTP 31 | -------------------------------------------------------------------------------- /src/app/src/app/KeyboardShortcuts.js: -------------------------------------------------------------------------------- 1 | const {globalShortcut} = require('electron') 2 | 3 | /* 4 | * KeyboardShortcuts registers additional keyboard shortcuts. 5 | * Note that most keyboard shortcuts are configured with the AppPrimaryMenu. 6 | */ 7 | class KeyboardShortcuts { 8 | 9 | /* ****************************************************************************/ 10 | // Lifecycle 11 | /* ****************************************************************************/ 12 | 13 | constructor (selectors) { 14 | this._selectors = selectors 15 | this._shortcuts = [] 16 | } 17 | 18 | /* ****************************************************************************/ 19 | // Creating 20 | /* ****************************************************************************/ 21 | 22 | /** 23 | * Registers global keyboard shortcuts. 24 | */ 25 | register () { 26 | let shortcuts = new Map([ 27 | ['CmdOrCtrl+{', this._selectors.prevMailbox], 28 | ['CmdOrCtrl+}', this._selectors.nextMailbox] 29 | ]) 30 | this.unregister() 31 | shortcuts.forEach((callback, accelerator) => { 32 | globalShortcut.register(accelerator, callback) 33 | this._shortcuts.push(accelerator) 34 | }) 35 | } 36 | 37 | /** 38 | * Unregisters any previously registered global keyboard shortcuts. 39 | */ 40 | unregister () { 41 | this._shortcuts.forEach((accelerator) => { 42 | globalShortcut.unregister(accelerator) 43 | }) 44 | this._shortcuts = [] 45 | } 46 | 47 | } 48 | 49 | module.exports = KeyboardShortcuts 50 | -------------------------------------------------------------------------------- /src/app/src/app/storage/StorageBucketAppMutable.js: -------------------------------------------------------------------------------- 1 | const StorageBucket = require('./StorageBucket') 2 | 3 | class StorageBucketAppMutable extends StorageBucket { 4 | /** 5 | * @param k: the key to set 6 | * @param v: the value to set 7 | * @return v 8 | */ 9 | setItem (k, v) { return this._setItem(k, v) } 10 | 11 | /** 12 | * @param k: the key to set 13 | * @param v: the value to set 14 | * @return v 15 | */ 16 | setJSONItem (k, v) { return this._setItem(k, JSON.stringify(v)) } 17 | 18 | /** 19 | * @param k: the key to remove 20 | */ 21 | removeItem (k) { return this._removeItem(k) } 22 | } 23 | 24 | module.exports = StorageBucketAppMutable 25 | -------------------------------------------------------------------------------- /src/app/src/app/storage/appStorage.js: -------------------------------------------------------------------------------- 1 | const StorageBucket = require('./StorageBucketAppMutable') 2 | module.exports = new StorageBucket('app') 3 | -------------------------------------------------------------------------------- /src/app/src/app/storage/avatarStorage.js: -------------------------------------------------------------------------------- 1 | const StorageBucket = require('./StorageBucket') 2 | module.exports = new StorageBucket('avatar') 3 | -------------------------------------------------------------------------------- /src/app/src/app/storage/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | appStorage: require('./appStorage'), 3 | avatarStorage: require('./avatarStorage'), 4 | mailboxStorage: require('./mailboxStorage'), 5 | settingStorage: require('./settingStorage') 6 | } 7 | -------------------------------------------------------------------------------- /src/app/src/app/storage/mailboxStorage.js: -------------------------------------------------------------------------------- 1 | const StorageBucket = require('./StorageBucket') 2 | module.exports = new StorageBucket('mailboxes') 3 | -------------------------------------------------------------------------------- /src/app/src/app/storage/settingStorage.js: -------------------------------------------------------------------------------- 1 | const StorageBucket = require('./StorageBucket') 2 | module.exports = new StorageBucket('settings') 3 | -------------------------------------------------------------------------------- /src/app/src/app/stores/mailboxStore.js: -------------------------------------------------------------------------------- 1 | const persistence = require('../storage/mailboxStorage') 2 | const Minivents = require('minivents') 3 | const Mailbox = require('../../shared/Models/Mailbox/Mailbox') 4 | const { MAILBOX_INDEX_KEY } = require('../../shared/constants') 5 | 6 | class MailboxStore { 7 | /* ****************************************************************************/ 8 | // Lifecycle 9 | /* ****************************************************************************/ 10 | 11 | constructor () { 12 | Minivents(this) 13 | 14 | // Build the current data 15 | this.index = [] 16 | this.mailboxes = new Map() 17 | 18 | const allRawItems = persistence.allJSONItems() 19 | Object.keys(allRawItems).forEach((id) => { 20 | if (id === MAILBOX_INDEX_KEY) { 21 | this.index = allRawItems[id] 22 | } else { 23 | this.mailboxes.set(id, new Mailbox(id, allRawItems[id])) 24 | } 25 | }) 26 | 27 | // Listen for changes 28 | persistence.on('changed', (evt) => { 29 | if (evt.key === MAILBOX_INDEX_KEY) { 30 | this.index = persistence.getJSONItem(MAILBOX_INDEX_KEY) 31 | } else { 32 | if (evt.type === 'setItem') { 33 | this.mailboxes.set(evt.key, new Mailbox(evt.key, persistence.getJSONItem(evt.key))) 34 | } 35 | if (evt.type === 'removeItem') { 36 | this.mailboxes.delete(evt.key) 37 | } 38 | } 39 | this.emit('changed', {}) 40 | }) 41 | } 42 | 43 | /* ****************************************************************************/ 44 | // Getters 45 | /* ****************************************************************************/ 46 | 47 | /** 48 | * @return the mailboxes in an ordered list 49 | */ 50 | orderedMailboxes () { 51 | return this.index 52 | .map(id => this.mailboxes.get(id)) 53 | .filter((mailbox) => !!mailbox) 54 | } 55 | 56 | /** 57 | * @param id: the id of the mailbox 58 | * @return the mailbox record 59 | */ 60 | getMailbox (id) { 61 | return this.mailboxes.get(id) 62 | } 63 | } 64 | 65 | module.exports = new MailboxStore() 66 | -------------------------------------------------------------------------------- /src/app/src/app/stores/settingStore.js: -------------------------------------------------------------------------------- 1 | const persistence = require('../storage/settingStorage') 2 | const Minivents = require('minivents') 3 | const { 4 | Settings: { 5 | AppSettings, 6 | LanguageSettings, 7 | NewsSettings, 8 | OSSettings, 9 | ProxySettings, 10 | TraySettings, 11 | UISettings 12 | } 13 | } = require('../../shared/Models') 14 | 15 | class SettingStore { 16 | constructor () { 17 | Minivents(this) 18 | 19 | // Build the current data 20 | this.app = new AppSettings(persistence.getJSONItem('app', {})) 21 | this.language = new LanguageSettings(persistence.getJSONItem('language', {})) 22 | this.news = new NewsSettings(persistence.getJSONItem('news', {})) 23 | this.os = new OSSettings(persistence.getJSONItem('os', {})) 24 | this.proxy = new ProxySettings(persistence.getJSONItem('proxy', {})) 25 | this.tray = new TraySettings(persistence.getJSONItem('tray', {})) 26 | this.ui = new UISettings(persistence.getJSONItem('ui', {})) 27 | 28 | // Listen for changes 29 | persistence.on('changed:app', () => { 30 | const prev = this.language 31 | this.app = new AppSettings(persistence.getJSONItem('app', {})) 32 | this.emit('changed', { }) 33 | this.emit('changed:app', { prev: prev, next: this.app }) 34 | }) 35 | persistence.on('changed:language', () => { 36 | const prev = this.language 37 | this.language = new LanguageSettings(persistence.getJSONItem('language', {})) 38 | this.emit('changed', { }) 39 | this.emit('changed:language', { prev: prev, next: this.language }) 40 | }) 41 | persistence.on('changed:news', () => { 42 | const prev = this.news 43 | this.news = new NewsSettings(persistence.getJSONItem('news', {})) 44 | this.emit('changed', { }) 45 | this.emit('changed:news', { prev: prev, next: this.news }) 46 | }) 47 | persistence.on('changed:os', () => { 48 | const prev = this.os 49 | this.os = new OSSettings(persistence.getJSONItem('os', {})) 50 | this.emit('changed', { }) 51 | this.emit('changed:os', { prev: prev, next: this.os }) 52 | }) 53 | persistence.on('changed:proxy', () => { 54 | const prev = this.proxy 55 | this.proxy = new ProxySettings(persistence.getJSONItem('proxy', {})) 56 | this.emit('changed', { }) 57 | this.emit('changed:proxy', { prev: prev, next: this.proxy }) 58 | }) 59 | persistence.on('changed:tray', () => { 60 | const prev = this.tray 61 | this.tray = new TraySettings(persistence.getJSONItem('tray', {})) 62 | this.emit('changed', { }) 63 | this.emit('changed:tray', { prev: prev, next: this.tray }) 64 | }) 65 | persistence.on('changed:ui', () => { 66 | const prev = this.ui 67 | this.ui = new UISettings(persistence.getJSONItem('ui', {})) 68 | this.emit('changed', { }) 69 | this.emit('changed:ui', { prev: prev, next: this.ui }) 70 | }) 71 | } 72 | } 73 | 74 | module.exports = new SettingStore() 75 | -------------------------------------------------------------------------------- /src/app/src/app/windows/ContentWindow.js: -------------------------------------------------------------------------------- 1 | const WMailWindow = require('./WMailWindow') 2 | const {shell} = require('electron') 3 | 4 | class ContentWindow extends WMailWindow { 5 | 6 | /* ****************************************************************************/ 7 | // Creation 8 | /* ****************************************************************************/ 9 | 10 | /** 11 | * The default window preferences 12 | * @param partition: the partition to set the window to 13 | * @param extraPreferences = undefined: extra preferences to merge into the prefs 14 | * @return the settings 15 | */ 16 | defaultWindowPreferences (partition, extraPreferences = undefined) { 17 | return Object.assign(super.defaultWindowPreferences(extraPreferences), { 18 | minWidth: 400, 19 | minHeight: 400, 20 | webPreferences: { 21 | nodeIntegration: false, 22 | partition: partition 23 | } 24 | }) 25 | } 26 | 27 | /** 28 | * Starts the window 29 | * @param url: the start url 30 | * @param partition: the window partition 31 | * @param windowPreferences=undefined: additional window preferences to supply 32 | */ 33 | start (url, partition, windowPreferences = undefined) { 34 | this.createWindow(this.defaultWindowPreferences(partition, windowPreferences), url) 35 | } 36 | 37 | /** 38 | * Creates and launches the window 39 | * @arguments: passed through to super() 40 | */ 41 | createWindow () { 42 | super.createWindow.apply(this, Array.from(arguments)) 43 | this.window.webContents.on('new-window', (evt, url) => { 44 | evt.preventDefault() 45 | shell.openExternal(url) 46 | }) 47 | } 48 | } 49 | 50 | module.exports = ContentWindow 51 | -------------------------------------------------------------------------------- /src/app/src/index.js: -------------------------------------------------------------------------------- 1 | require('./app/main.js') 2 | -------------------------------------------------------------------------------- /src/app/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const ROOT_DIR = path.resolve(path.join(__dirname, '../../')) 3 | const BIN_DIR = path.join(ROOT_DIR, 'bin') 4 | const devRequire = (n) => require(path.join(ROOT_DIR, 'node_modules', n)) 5 | 6 | const CleanWebpackPlugin = devRequire('clean-webpack-plugin') 7 | const CopyWebpackPlugin = devRequire('copy-webpack-plugin') 8 | 9 | module.exports = { 10 | output: { 11 | path: BIN_DIR, 12 | filename: '__.js' 13 | }, 14 | plugins: [ 15 | new CleanWebpackPlugin(['app'], { 16 | root: BIN_DIR, 17 | verbose: true, 18 | dry: false 19 | }), 20 | new CopyWebpackPlugin([ 21 | { from: path.join(__dirname, 'src'), to: 'app', force: true }, 22 | { from: path.join(__dirname, 'node_modules'), to: 'app/node_modules', force: true }, 23 | { from: path.join(__dirname, '../shared/'), to: 'app/shared', force: true }, 24 | { from: path.join(ROOT_DIR, 'package.json'), to: 'app', force: true } 25 | ], { 26 | ignore: [ '.DS_Store' ] 27 | }) 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wmail-scenes-mailboxes", 3 | "author": "Thomas Beverley", 4 | "license": "MPL-2.0", 5 | "repository": "https://github.com/Thomas101/wmail", 6 | "description": "The mailboxes window for the WMail app", 7 | "dependencies": { 8 | "addressparser": "1.0.1", 9 | "alt": "0.18.6", 10 | "bootstrap-grid": "2.0.1", 11 | "camelcase": "4.0.0", 12 | "compare-version": "0.1.2", 13 | "fbjs": "0.8.9", 14 | "https-proxy-agent": "1.0.0", 15 | "material-ui": "0.17.0", 16 | "minivents": "2.0.2", 17 | "qs": "6.3.1", 18 | "querystring": "0.2.0", 19 | "react": "15.4.2", 20 | "react-addons-shallow-compare": "15.4.2", 21 | "react-color": "2.11.1", 22 | "react-dom": "15.4.2", 23 | "react-tap-event-plugin": "2.0.1", 24 | "react-timer-mixin": "0.13.3", 25 | "react-tooltip": "3.2.7", 26 | "urijs": "1.18.7", 27 | "uuid": "3.0.1" 28 | }, 29 | "devDependencies": { 30 | "standard": "8.6.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/Components/ColorPickerButton.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { RaisedButton, Popover } = require('material-ui') 3 | const { ChromePicker } = require('react-color') 4 | 5 | module.exports = React.createClass({ 6 | /* **************************************************************************/ 7 | // Class 8 | /* **************************************************************************/ 9 | 10 | displayName: 'ColorPickerButton', 11 | propTypes: { 12 | value: React.PropTypes.string, 13 | label: React.PropTypes.string.isRequired, 14 | disabled: React.PropTypes.bool.isRequired, 15 | anchorOrigin: React.PropTypes.object.isRequired, 16 | targetOrigin: React.PropTypes.object.isRequired, 17 | icon: React.PropTypes.node, 18 | onChange: React.PropTypes.func 19 | }, 20 | 21 | /* **************************************************************************/ 22 | // Data lifecycle 23 | /* **************************************************************************/ 24 | 25 | getInitialState () { 26 | return { 27 | open: false, 28 | anchor: null 29 | } 30 | }, 31 | 32 | getDefaultProps () { 33 | return { 34 | label: 'Pick Colour', 35 | disabled: false, 36 | anchorOrigin: {horizontal: 'left', vertical: 'bottom'}, 37 | targetOrigin: {horizontal: 'left', vertical: 'top'} 38 | } 39 | }, 40 | 41 | /* **************************************************************************/ 42 | // Rendering 43 | /* **************************************************************************/ 44 | 45 | render () { 46 | const { label, disabled, onChange, anchorOrigin, targetOrigin, icon, ...passProps } = this.props 47 | return ( 48 |
49 | this.setState({ open: true, anchor: evt.target })} /> 54 | this.setState({open: false})}> 60 | { 63 | if (onChange) { 64 | onChange(Object.assign({}, col, { 65 | rgbaStr: `rgba(${col.rgb.r}, ${col.rgb.g}, ${col.rgb.b}, ${col.rgb.a})` 66 | })) 67 | } 68 | }} /> 69 | 70 |
71 | ) 72 | } 73 | }) 74 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/Components/Grid/Col.js: -------------------------------------------------------------------------------- 1 | import 'bootstrap-grid' 2 | 3 | const React = require('react') 4 | 5 | module.exports = React.createClass({ 6 | displayName: 'GridCol', 7 | 8 | propTypes: { 9 | xs: React.PropTypes.number, 10 | sm: React.PropTypes.number, 11 | md: React.PropTypes.number, 12 | lg: React.PropTypes.number, 13 | offset: React.PropTypes.number, 14 | className: React.PropTypes.string, 15 | children: React.PropTypes.node 16 | }, 17 | 18 | render () { 19 | const {xs, sm, md, lg, offset, className, children, ...passProps} = this.props 20 | 21 | let mode = 'xs' 22 | let size = 12 23 | if (xs !== undefined) { 24 | mode = 'xs' 25 | size = xs 26 | } else if (sm !== undefined) { 27 | mode = 'sm' 28 | size = sm 29 | } else if (md !== undefined) { 30 | mode = 'md' 31 | size = md 32 | } else if (lg !== undefined) { 33 | mode = 'lg' 34 | size = lg 35 | } 36 | 37 | const classNames = [ 38 | ['col', mode, size].join('-'), 39 | offset !== undefined ? ['col', mode, 'offset', size].join('-') : undefined, 40 | className 41 | ].filter((c) => !!c).join(' ') 42 | 43 | return ( 44 |
45 | {children} 46 |
47 | ) 48 | } 49 | }) 50 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/Components/Grid/Container.js: -------------------------------------------------------------------------------- 1 | import 'bootstrap-grid' 2 | 3 | const React = require('react') 4 | 5 | module.exports = React.createClass({ 6 | displayName: 'GridContainer', 7 | 8 | propTypes: { 9 | className: React.PropTypes.string, 10 | children: React.PropTypes.node, 11 | fluid: React.PropTypes.bool 12 | }, 13 | 14 | render () { 15 | const {fluid, className, ...passProps} = this.props 16 | 17 | const classNames = [ 18 | fluid ? 'container-fluid' : 'conainer', 19 | className 20 | ].filter((c) => !!c).join(' ') 21 | 22 | return ( 23 |
24 | {this.props.children} 25 |
26 | ) 27 | } 28 | }) 29 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/Components/Grid/Row.js: -------------------------------------------------------------------------------- 1 | import 'bootstrap-grid' 2 | 3 | const React = require('react') 4 | 5 | module.exports = React.createClass({ 6 | displayName: 'GridRow', 7 | 8 | propTypes: { 9 | className: React.PropTypes.string, 10 | children: React.PropTypes.node 11 | }, 12 | 13 | render () { 14 | return ( 15 |
!!c).join(' ')}> 18 | {this.props.children} 19 |
20 | ) 21 | } 22 | }) 23 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/Components/Grid/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Container: require('./Container'), 3 | Col: require('./Col'), 4 | Row: require('./Row') 5 | } 6 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/Components/TrayPreview.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const TrayRenderer = require('./TrayRenderer') 3 | const shallowCompare = require('react-addons-shallow-compare') 4 | 5 | module.exports = React.createClass({ 6 | /* **************************************************************************/ 7 | // Class 8 | /* **************************************************************************/ 9 | 10 | displayName: 'TrayPreview', 11 | propTypes: { 12 | config: React.PropTypes.object.isRequired, 13 | size: React.PropTypes.number.isRequired 14 | }, 15 | 16 | /* **************************************************************************/ 17 | // Component Lifecycle 18 | /* **************************************************************************/ 19 | 20 | componentWillMount () { 21 | TrayRenderer.renderPNGDataImage(this.props.config) 22 | .then((png) => this.setState({ image: png })) 23 | }, 24 | 25 | componentWillReceiveProps (nextProps) { 26 | if (shallowCompare(this, nextProps, this.state)) { 27 | TrayRenderer.renderPNGDataImage(nextProps.config) 28 | .then((png) => this.setState({ image: png })) 29 | } 30 | }, 31 | 32 | /* **************************************************************************/ 33 | // Data Lifecycle 34 | /* **************************************************************************/ 35 | 36 | getInitialState () { 37 | return { image: null } 38 | }, 39 | 40 | /* **************************************************************************/ 41 | // Rendering 42 | /* **************************************************************************/ 43 | 44 | render () { 45 | const { size, style, ...passProps } = this.props 46 | delete passProps.config 47 | 48 | return ( 49 |
57 | {!this.state.image ? undefined : ( 58 | 62 | )} 63 |
64 | ) 65 | } 66 | }) 67 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/Components/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ColorPickerButton: require('./ColorPickerButton'), 3 | Grid: require('./Grid'), 4 | TrayIconEditor: require('./TrayIconEditor'), 5 | TrayPreview: require('./TrayPreview'), 6 | TrayRenderer: require('./TrayRenderer'), 7 | WebView: require('./WebView') 8 | } 9 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/Dispatch/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mailboxDispatch: require('./mailboxDispatch'), 3 | navigationDispatch: require('./navigationDispatch') 4 | } 5 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/Dispatch/navigationDispatch.js: -------------------------------------------------------------------------------- 1 | const Minivents = require('minivents') 2 | const {ipcRenderer} = window.nativeRequire('electron') 3 | 4 | class NavigationDispatch { 5 | 6 | /* **************************************************************************/ 7 | // Lifecycle 8 | /* **************************************************************************/ 9 | 10 | constructor () { 11 | Minivents(this) 12 | } 13 | 14 | /** 15 | * Binds the listeners to the ipc renderer 16 | */ 17 | bindIPCListeners () { 18 | ipcRenderer.on('launch-settings', () => { this.openSettings() }) 19 | return this 20 | } 21 | 22 | /* **************************************************************************/ 23 | // Actions 24 | /* **************************************************************************/ 25 | 26 | /** 27 | * Emits an open settings command 28 | */ 29 | openSettings () { 30 | this.emit('opensettings', {}) 31 | } 32 | 33 | /** 34 | * Opens the settings at a mailbox 35 | * @param mailboxId: the id of the mailbox 36 | */ 37 | openMailboxSettings (mailboxId) { 38 | this.emit('opensettings', { 39 | route: { 40 | tab: 'accounts', 41 | mailboxId: mailboxId 42 | } 43 | }) 44 | } 45 | 46 | /** 47 | * Opens the news 48 | */ 49 | openNews () { 50 | this.emit('opennews', {}) 51 | } 52 | } 53 | 54 | module.exports = new NavigationDispatch() 55 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/Notifications/Notification.js: -------------------------------------------------------------------------------- 1 | const { BrowserWindow } = window.nativeRequire('electron').remote 2 | const path = require('path') 3 | 4 | class Notification { 5 | constructor (text, options) { 6 | this.__options__ = Object.assign({}, options) 7 | this.browserWindow = new BrowserWindow({ 8 | x: 0, 9 | y: 0, 10 | useContentSize: true, 11 | show: false, 12 | autoHideMenuBar: true, 13 | frame: false, 14 | resizable: false, 15 | skipTaskbar: true, 16 | alwaysOnTop: true, 17 | backgroundColor: '#FFF', 18 | webPreferences: { 19 | nodeIntegration: true 20 | } 21 | }) 22 | const htmlPath = 'file://' + path.join(path.dirname(window.location.href.replace('file://', '')), 'notification.html') 23 | this.browserWindow.loadURL(htmlPath) 24 | this.browserWindow.once('ready-to-show', () => { 25 | this.browserWindow.webContents.executeJavaScript(`window.renderNotification.apply(this, ${JSON.stringify([text, options])})`) 26 | this.browserWindow.show() 27 | this.browserWindow.webContents.openDevTools() 28 | }) 29 | 30 | setTimeout(() => { 31 | this.close() 32 | }, 3000) 33 | } 34 | 35 | close () { 36 | if (!this.browserWindow) { return } 37 | this.browserWindow.close() 38 | this.browserWindow = null 39 | } 40 | } 41 | 42 | module.exports = Notification 43 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ReactComponents.less: -------------------------------------------------------------------------------- 1 | .ReactComponent-MaterialUI-Dialog-Body-Scrollbars { 2 | &::-webkit-scrollbar { 3 | -webkit-appearance: none; 4 | width: 7px; 5 | } 6 | &::-webkit-scrollbar-thumb { 7 | border-radius: 4px; 8 | background-color: rgba(0,0,0,.5); 9 | -webkit-box-shadow: 0 0 1px rgba(255,255,255,.5); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/index.js: -------------------------------------------------------------------------------- 1 | import './ReactComponents.less' 2 | const React = require('react') 3 | const ReactDOM = require('react-dom') 4 | const App = require('./ui/App') 5 | const mailboxActions = require('./stores/mailbox/mailboxActions') 6 | const settingsActions = require('./stores/settings/settingsActions') 7 | const composeActions = require('./stores/compose/composeActions') 8 | const mailboxWizardActions = require('./stores/mailboxWizard/mailboxWizardActions') 9 | const { ipcRenderer } = window.nativeRequire('electron') 10 | 11 | // See if we're offline and run a re-direct 12 | if (window.navigator.onLine === false) { 13 | window.location.href = 'offline.html' 14 | } 15 | 16 | // Load what we have in the db 17 | mailboxActions.load() 18 | mailboxWizardActions.load() 19 | settingsActions.load() 20 | composeActions.load() 21 | 22 | // Remove loading 23 | ;(() => { 24 | const loading = document.getElementById('loading') 25 | loading.parentElement.removeChild(loading) 26 | })() 27 | 28 | // Render and prepare for unrender 29 | ReactDOM.render(, document.getElementById('app')) 30 | ipcRenderer.on('prepare-reload', function () { 31 | ReactDOM.unmountComponentAtNode(document.getElementById('app')) 32 | }) 33 | ipcRenderer.send('mailboxes-js-loaded', {}) 34 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/mailboxes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WMail 6 | 7 | 8 | 9 | 49 | 50 | 51 |
52 | 53 |
54 |
55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/reporter.js: -------------------------------------------------------------------------------- 1 | const ipc = window.nativeRequire('electron').ipcRenderer 2 | 3 | class Reporter { 4 | reportError (errorStr) { 5 | ipc.send('report-error', { error: errorStr }) 6 | } 7 | } 8 | 9 | module.exports = new Reporter() 10 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/alt.js: -------------------------------------------------------------------------------- 1 | const Alt = require('alt') 2 | module.exports = new Alt() 3 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/appWizard/appWizardActions.js: -------------------------------------------------------------------------------- 1 | const alt = require('../alt') 2 | 3 | class AppWizardActions { 4 | 5 | /* **************************************************************************/ 6 | // Lifecycle 7 | /* **************************************************************************/ 8 | 9 | /** 10 | * Starts the wizard 11 | */ 12 | startWizard () { return {} } 13 | 14 | /** 15 | * Progresses the wizard to the next stage 16 | */ 17 | progressNextStep () { return {} } 18 | 19 | /** 20 | * Cancels the wizard 21 | */ 22 | cancelWizard () { return {} } 23 | 24 | /** 25 | * Cancel and discards the wizard 26 | */ 27 | discardWizard () { return {} } 28 | } 29 | 30 | module.exports = alt.createActions(AppWizardActions) 31 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/appWizard/appWizardStore.js: -------------------------------------------------------------------------------- 1 | const alt = require('../alt') 2 | const actions = require('./appWizardActions') 3 | const settingsActions = require('../settings/settingsActions') 4 | 5 | class AppWizardStore { 6 | /* **************************************************************************/ 7 | // Lifecycle 8 | /* **************************************************************************/ 9 | 10 | constructor () { 11 | this.startOpen = false 12 | this.trayConfiguratorOpen = false 13 | this.mailtoHandlerOpen = false 14 | this.completeOpen = false 15 | 16 | /** 17 | * @return true if any configuration dialogs are open 18 | */ 19 | this.hasAnyItemsOpen = () => { 20 | return this.startOpen || this.mailtoHandlerOpen || this.trayConfiguratorOpen || this.completeOpen 21 | } 22 | 23 | /* ****************************************/ 24 | // Listeners 25 | /* ****************************************/ 26 | 27 | this.bindListeners({ 28 | handleStartWizard: actions.START_WIZARD, 29 | handleProgressNextStep: actions.PROGRESS_NEXT_STEP, 30 | handleCancelWizard: actions.CANCEL_WIZARD, 31 | handleDiscardWizard: actions.DISCARD_WIZARD 32 | }) 33 | } 34 | 35 | /* **************************************************************************/ 36 | // Utils 37 | /* **************************************************************************/ 38 | 39 | clearAll () { 40 | this.startOpen = false 41 | this.trayConfiguratorOpen = false 42 | this.mailtoHandlerOpen = false 43 | this.completeOpen = false 44 | } 45 | 46 | /* **************************************************************************/ 47 | // Handlers 48 | /* **************************************************************************/ 49 | 50 | handleStartWizard () { 51 | this.clearAll() 52 | this.startOpen = true 53 | } 54 | 55 | handleProgressNextStep () { 56 | if (this.startOpen) { 57 | this.clearAll() 58 | this.trayConfiguratorOpen = true 59 | } else if (this.trayConfiguratorOpen) { 60 | this.clearAll() 61 | if (process.platform === 'darwin' || process.platform === 'win32') { 62 | this.mailtoHandlerOpen = true 63 | } else { 64 | this.completeOpen = true 65 | } 66 | } else if (this.mailtoHandlerOpen) { 67 | this.clearAll() 68 | this.completeOpen = true 69 | } else if (this.completeOpen) { 70 | this.clearAll() 71 | settingsActions.setHasSeenAppWizard.defer(true) 72 | } 73 | } 74 | 75 | handleCancelWizard () { 76 | this.clearAll() 77 | } 78 | 79 | handleDiscardWizard () { 80 | this.clearAll() 81 | settingsActions.setHasSeenAppWizard.defer(true) 82 | } 83 | } 84 | 85 | module.exports = alt.createStore(AppWizardStore, 'AppWizardStore') 86 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/appWizard/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | A: require('./appWizardActions'), 3 | appWizardActions: require('./appWizardActions'), 4 | S: require('./appWizardStore'), 5 | appWizardStore: require('./appWizardStore') 6 | } 7 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/compose/composeActions.js: -------------------------------------------------------------------------------- 1 | const alt = require('../alt') 2 | const { ipcRenderer } = window.nativeRequire('electron') 3 | const URI = require('urijs') 4 | const addressparser = require('addressparser') 5 | 6 | class ComposeActions { 7 | 8 | /* **************************************************************************/ 9 | // Lifecycle 10 | /* **************************************************************************/ 11 | 12 | load () { 13 | return {} 14 | } 15 | 16 | /* **************************************************************************/ 17 | // New Message 18 | /* **************************************************************************/ 19 | 20 | /** 21 | * Composes a new message 22 | * @param recipient=undefined: the recipient to send to 23 | * @param subject=undefined: the subject of the message 24 | * @param body=undefined: the body of the message 25 | */ 26 | composeNewMessage (recipient = undefined, subject = undefined, body = undefined) { 27 | return { recipient: recipient, subject: subject, body: body } 28 | } 29 | 30 | /** 31 | * Clears the current compose 32 | */ 33 | clearCompose () { 34 | return {} 35 | } 36 | 37 | /** 38 | * Sets the target mailbox 39 | * @param mailboxId: the id of the mailbox 40 | */ 41 | setTargetMailbox (mailboxId) { 42 | return { mailboxId: mailboxId } 43 | } 44 | 45 | /** 46 | * Opens a mailto link 47 | * @param mailtoLink='': the link to try to open 48 | */ 49 | processMailtoLink (mailtoLink = '') { 50 | if (mailtoLink.indexOf('mailto:') === 0) { 51 | const uri = URI(mailtoLink || '') 52 | const recipients = addressparser(decodeURIComponent(uri.pathname())).map((r) => r.address) 53 | const qs = uri.search(true) 54 | return this.composeNewMessage(recipients.join(','), qs.subject || qs.Subject, qs.body || qs.Body) 55 | } else { 56 | return { valid: false } 57 | } 58 | } 59 | } 60 | 61 | const actions = alt.createActions(ComposeActions) 62 | ipcRenderer.on('open-mailto-link', (evt, req) => actions.processMailtoLink(req.mailtoLink)) 63 | module.exports = actions 64 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/compose/composeStore.js: -------------------------------------------------------------------------------- 1 | const alt = require('../alt') 2 | const actions = require('./composeActions') 3 | const uuid = require('uuid') 4 | const { ipcRenderer } = window.nativeRequire('electron') 5 | 6 | class ComposeStore { 7 | /* **************************************************************************/ 8 | // Lifecycle 9 | /* **************************************************************************/ 10 | 11 | constructor () { 12 | this.composing = false 13 | this.composeRef = uuid.v4() 14 | this.recipient = undefined 15 | this.subject = undefined 16 | this.body = undefined 17 | this.targetMailbox = undefined 18 | 19 | /* ****************************************/ 20 | // Message Getters 21 | /* ****************************************/ 22 | 23 | /** 24 | * @return a dictionary containing just the message info 25 | */ 26 | this.getMessageInfo = () => { 27 | return { 28 | recipient: this.recipient, 29 | subject: this.subject, 30 | body: this.body 31 | } 32 | } 33 | 34 | /* ****************************************/ 35 | // Listeners 36 | /* ****************************************/ 37 | this.bindListeners({ 38 | handleComposeNewMessage: actions.COMPOSE_NEW_MESSAGE, 39 | handleClearCompose: actions.CLEAR_COMPOSE, 40 | handleSetTargetMailbox: actions.SET_TARGET_MAILBOX 41 | }) 42 | } 43 | 44 | /* **************************************************************************/ 45 | // New Message 46 | /* **************************************************************************/ 47 | 48 | handleComposeNewMessage ({ recipient, subject, body }) { 49 | ipcRenderer.send('focus-app', { }) 50 | this.composing = true 51 | this.composeRef = uuid.v4() 52 | this.recipient = recipient 53 | this.subject = subject 54 | this.body = body 55 | this.targetMailbox = undefined 56 | } 57 | 58 | handleClearCompose () { 59 | this.composing = false 60 | this.composeRef = uuid.v4() 61 | this.recipient = undefined 62 | this.subject = undefined 63 | this.body = undefined 64 | this.targetMailbox = undefined 65 | } 66 | 67 | handleSetTargetMailbox ({ mailboxId }) { 68 | this.targetMailbox = mailboxId 69 | } 70 | } 71 | 72 | module.exports = alt.createStore(ComposeStore, 'ComposeStore') 73 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/compose/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | A: require('./composeActions'), 3 | composeActions: require('./composeActions'), 4 | S: require('./composeStore'), 5 | composeStore: require('./composeStore') 6 | } 7 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/dictionaries/dictionariesActions.js: -------------------------------------------------------------------------------- 1 | const alt = require('../alt') 2 | const uuid = require('uuid') 3 | 4 | class DictionariesActions { 5 | 6 | /* **************************************************************************/ 7 | // Changing 8 | /* **************************************************************************/ 9 | 10 | /** 11 | * Starts the dictionary process 12 | * @return { id } 13 | */ 14 | startDictionaryInstall () { 15 | return { id: uuid.v4() } 16 | } 17 | 18 | /** 19 | * Finishes / cancels the dictionary change 20 | */ 21 | stopDictionaryInstall () { 22 | return { } 23 | } 24 | 25 | /** 26 | * Starts the dictionary process 27 | * @param id: the change id for validation 28 | * @param lang: the lang code to change to 29 | */ 30 | pickDictionaryInstallLanguage (id, lang) { 31 | return { id: id, lang: lang } 32 | } 33 | 34 | /** 35 | * Starts the dictionary install 36 | * @param id: the change id for validation 37 | */ 38 | installDictionary (id) { 39 | return { id: id } 40 | } 41 | } 42 | 43 | module.exports = alt.createActions(DictionariesActions) 44 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/dictionaries/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | A: require('./dictionariesActions'), 3 | dictionariesActions: require('./dictionariesActions'), 4 | S: require('./dictionariesStore'), 5 | dictionariesStore: require('./dictionariesStore') 6 | } 7 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/google/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | A: require('./googleActions'), 3 | googleActions: require('./googleActions'), 4 | S: require('./googleStore'), 5 | googleStore: require('./googleStore') 6 | } 7 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/http/httpActions.js: -------------------------------------------------------------------------------- 1 | const alt = require('../alt') 2 | const uuid = require('uuid') 3 | 4 | class HttpActions { 5 | 6 | /* **************************************************************************/ 7 | // Requests 8 | /* **************************************************************************/ 9 | 10 | /** 11 | * Fetches text from a remote endpoint 12 | * @param url: the url of the license 13 | * @param config: the config for the fetch request if required 14 | * @return { id, promise, ... } tracking id 15 | */ 16 | fetchText (url, config) { 17 | const id = uuid.v4() 18 | const promise = Promise.resolve() 19 | .then(() => window.fetch(url)) 20 | .then((res) => res.ok ? Promise.resolve(res) : Promise.reject(res)) 21 | .then((res) => res.text()) 22 | .then((res) => { 23 | this.requestSuccess(id, res) 24 | }, (err) => { 25 | this.requestFailure(id, err) 26 | }) 27 | 28 | return { id: uuid.v4(), promise: promise } 29 | } 30 | 31 | /** 32 | * Indicates a request ended in success 33 | * @param id: the id of the request 34 | * @param data: the data that was received 35 | */ 36 | requestSuccess (id, data) { 37 | return { id: id, data: data } 38 | } 39 | 40 | /** 41 | * Indicates a request ended in failure 42 | * @param id: the id of the request 43 | * @param err: the error that occured 44 | */ 45 | requestFailure (id, err) { 46 | return { id: id, error: err } 47 | } 48 | 49 | /* **************************************************************************/ 50 | // Clearup 51 | /* **************************************************************************/ 52 | 53 | /** 54 | * Clears the response 55 | * @param id: the id of the task 56 | */ 57 | clearResponse (id) { 58 | return { id: id } 59 | } 60 | } 61 | 62 | module.exports = alt.createActions(HttpActions) 63 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/http/httpStore.js: -------------------------------------------------------------------------------- 1 | const alt = require('../alt') 2 | const actions = require('./httpActions') 3 | 4 | class HttpStore { 5 | /* **************************************************************************/ 6 | // Lifecycle 7 | /* **************************************************************************/ 8 | 9 | constructor () { 10 | this.requests = new Map() 11 | 12 | /* ****************************************/ 13 | // Requests 14 | /* ****************************************/ 15 | 16 | /** 17 | * @param id: the id of the task 18 | * @return true if this task is inflight 19 | */ 20 | this.isInflight = (id) => { 21 | return this.tasks.has(id) ? this.tasks.get(id).inflight : false 22 | } 23 | 24 | /** 25 | * @param id: the id of the task 26 | * @return the completion error, or undefined if none 27 | */ 28 | this.error = (id) => { 29 | return this.tasks.has(id) ? this.tasks.get(id).error : undefined 30 | } 31 | 32 | /** 33 | * @param id: the id of the task 34 | * @return the completion response, or undefined if none 35 | */ 36 | this.response = (id) => { 37 | return this.tasks.has(id) ? this.tasks.get(id).response : undefined 38 | } 39 | 40 | /* ****************************************/ 41 | // Listeners 42 | /* ****************************************/ 43 | 44 | this.bindListeners({ 45 | handleFetchText: actions.FETCH_TEXT, 46 | handleRequestSuccess: actions.REQUEST_SUCCESS, 47 | handleRequestFailure: actions.REQUEST_FAILURE, 48 | handleClearResponse: actions.CLEAR_RESPONSE 49 | }) 50 | } 51 | 52 | /* **************************************************************************/ 53 | // Handlers: Fetch 54 | /* **************************************************************************/ 55 | 56 | handleFetchText ({ id }) { 57 | this.tasks.set(id, { inflight: true }) 58 | } 59 | 60 | handleRequestSuccess ({ id, data }) { 61 | this.tasks.set(id, { inflight: false, response: data }) 62 | } 63 | 64 | handleRequestFailure ({ id, error }) { 65 | this.tasks.set(id, { inflight: false, error: error }) 66 | } 67 | 68 | handleClearResponse ({ id }) { 69 | this.tasks.delete(id) 70 | } 71 | } 72 | 73 | module.exports = alt.createStore(HttpStore, 'HttpStore') 74 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/http/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | A: require('./httpActions'), 3 | httpActions: require('./httpActions'), 4 | S: require('./httpStore'), 5 | httpStore: require('./httpStore') 6 | } 7 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/mailbox/avatarPersistence.js: -------------------------------------------------------------------------------- 1 | const StorageBucket = require('../StorageBucket') 2 | module.exports = new StorageBucket('avatar') 3 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/mailbox/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | A: require('./mailboxActions'), 3 | mailboxActions: require('./mailboxActions'), 4 | S: require('./mailboxStore'), 5 | mailboxStore: require('./mailboxStore') 6 | } 7 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/mailbox/mailboxPersistence.js: -------------------------------------------------------------------------------- 1 | const StorageBucket = require('../StorageBucket') 2 | module.exports = new StorageBucket('mailboxes') 3 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/mailbox/migration.js: -------------------------------------------------------------------------------- 1 | const persistence = require('./mailboxPersistence') 2 | const {MAILBOX_INDEX_KEY} = require('shared/constants') 3 | 4 | module.exports = { 5 | /** 6 | * Migrates the data from version 1.3.1 7 | */ 8 | from_1_3_1: function () { 9 | if (window.localStorage.getItem('migrate_mailboxes_from_1_3_1') !== 'true') { 10 | const index = JSON.parse(window.localStorage.getItem('Mailbox_index') || '[]') 11 | if (index.length) { 12 | const mailboxes = index.reduce((acc, mailboxId) => { 13 | acc[mailboxId] = JSON.parse(window.localStorage.getItem('Mailbox_' + mailboxId)) 14 | delete acc[mailboxId].customAvatar // not migrating avatars. Drop these 15 | return acc 16 | }, {}) 17 | 18 | // Write the new values 19 | Object.keys(mailboxes).forEach((mailboxId) => { 20 | persistence.setJSONItemSync(mailboxId, mailboxes[mailboxId]) 21 | }) 22 | persistence.setJSONItemSync(MAILBOX_INDEX_KEY, index) 23 | 24 | // Write the completion 25 | window.localStorage.setItem('pre_1_3_1:Mailbox_index', JSON.stringify(index)) 26 | window.localStorage.removeItem('Mailbox_index') 27 | Object.keys(mailboxes).forEach((mailboxId) => { 28 | window.localStorage.setItem('pre_1_3_1:Mailbox_' + mailboxId, JSON.stringify(mailboxes[mailboxId])) 29 | window.localStorage.removeItem('Mailbox_' + mailboxId) 30 | }) 31 | } 32 | } 33 | 34 | window.localStorage.setItem('migrate_mailboxes_from_1_3_1', 'true') 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/mailboxWizard/Configurations.js: -------------------------------------------------------------------------------- 1 | const { Mailbox, Google } = require('shared/Models/Mailbox') 2 | const configurations = {} 3 | configurations[Mailbox.TYPE_GMAIL] = { 4 | DEFAULT_INBOX: { // Unread Messages in primary category 5 | googleConf: { 6 | takeLabelCountFromUI: false, 7 | unreadMode: Google.UNREAD_MODES.PRIMARY_INBOX_UNREAD// 8 | } 9 | }, 10 | PRIORIY_INBOX: { // Unread Important Messages 11 | googleConf: { 12 | takeLabelCountFromUI: false, 13 | unreadMode: Google.UNREAD_MODES.INBOX_UNREAD_IMPORTANT 14 | } 15 | }, 16 | UNREAD_INBOX: { // All Unread Messages 17 | googleConf: { 18 | takeLabelCountFromUI: false, 19 | unreadMode: Google.UNREAD_MODES.INBOX_UNREAD 20 | } 21 | } 22 | } 23 | configurations[Mailbox.TYPE_GINBOX] = { 24 | UNREAD_INBOX: { 25 | googleConf: { 26 | takeLabelCountFromUI: false, 27 | unreadMode: Google.UNREAD_MODES.INBOX_UNREAD 28 | } 29 | }, 30 | DEFAULT_INBOX: { 31 | googleConf: { 32 | takeLabelCountFromUI: false, 33 | unreadMode: Google.UNREAD_MODES.GINBOX_DEFAULT 34 | } 35 | } 36 | } 37 | 38 | module.exports = configurations 39 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/mailboxWizard/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | A: require('./mailboxWizardActions'), 3 | mailboxWizardActions: require('./mailboxWizardActions'), 4 | S: require('./mailboxWizardStore'), 5 | mailboxWizardStore: require('./mailboxWizardStore'), 6 | Configurations: require('./Configurations') 7 | } 8 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/mailboxWizard/mailboxWizardActions.js: -------------------------------------------------------------------------------- 1 | const alt = require('../alt') 2 | const { ipcRenderer } = window.nativeRequire('electron') 3 | const { Mailbox } = require('shared/Models/Mailbox') 4 | 5 | class MailboxWizardActions { 6 | 7 | /* **************************************************************************/ 8 | // Lifecycle 9 | /* **************************************************************************/ 10 | 11 | /** 12 | * Loads any start off services 13 | */ 14 | load () { return {} } 15 | 16 | /* **************************************************************************/ 17 | // Adding 18 | /* **************************************************************************/ 19 | 20 | /** 21 | * Opens the add mailbox picker 22 | */ 23 | openAddMailbox () { return {} } 24 | 25 | /** 26 | * Dismisses the add mailbox picker 27 | */ 28 | cancelAddMailbox () { return {} } 29 | 30 | /** 31 | * Starts the auth process for google inbox 32 | */ 33 | authenticateGinboxMailbox () { 34 | return { provisionalId: Mailbox.provisionId() } 35 | } 36 | 37 | /** 38 | * Starts the auth process for gmail 39 | */ 40 | authenticateGmailMailbox () { 41 | return { provisionalId: Mailbox.provisionId() } 42 | } 43 | 44 | /** 45 | * Reauthetnicates a google mailbox 46 | * @param mailboxId: the id of the mailbox 47 | */ 48 | reauthenticateGoogleMailbox (mailboxId) { 49 | return { mailboxId: mailboxId } 50 | } 51 | 52 | /* **************************************************************************/ 53 | // Authentication callbacks 54 | /* **************************************************************************/ 55 | 56 | /** 57 | * Handles a mailbox authenticating 58 | * @param evt: the event that came over the ipc 59 | * @param data: the data that came across the ipc 60 | */ 61 | authGoogleMailboxSuccess (evt, data) { 62 | return { provisionalId: data.id, type: data.type, temporaryAuth: data.temporaryAuth, mode: data.mode } 63 | } 64 | 65 | /** 66 | * Handles a mailbox authenticating error 67 | * @param evt: the ipc event that fired 68 | * @param data: the data that came across the ipc 69 | */ 70 | authGoogleMailboxFailure (evt, data) { 71 | return { evt: evt, data: data } 72 | } 73 | 74 | /* **************************************************************************/ 75 | // Config 76 | /* **************************************************************************/ 77 | 78 | /** 79 | * Configures an account 80 | * @param configuration: the additional configuration to provide 81 | */ 82 | configureMailbox (configuration) { 83 | return { configuration: configuration } 84 | } 85 | 86 | /** 87 | * Configures the enabled services 88 | * @param enabledServices: the enabled servies 89 | * @param compact: whether they should be compact or not 90 | */ 91 | configureMailboxServices (enabledServices, compact) { 92 | return { enabledServices: enabledServices, compact: compact } 93 | } 94 | 95 | /** 96 | * Completes mailbox configuration 97 | */ 98 | configurationComplete () { return {} } 99 | } 100 | 101 | const actions = alt.createActions(MailboxWizardActions) 102 | ipcRenderer.on('auth-google-complete', actions.authGoogleMailboxSuccess) 103 | ipcRenderer.on('auth-google-error', actions.authGoogleMailboxFailure) 104 | 105 | module.exports = actions 106 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/platform/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | A: require('./platformActions'), 3 | platformActions: require('./platformActions'), 4 | S: require('./platformStore'), 5 | platformStore: require('./platformStore') 6 | } 7 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/platform/platformActions.js: -------------------------------------------------------------------------------- 1 | const alt = require('../alt') 2 | 3 | class PlatformActions { 4 | 5 | /* **************************************************************************/ 6 | // Login 7 | /* **************************************************************************/ 8 | 9 | /** 10 | * @param openAtLogin: true to open at login 11 | * @param openAsHidden: true to open as hidden 12 | */ 13 | changeLoginPref (openAtLogin, openAsHidden) { 14 | return { openAtLogin: openAtLogin, openAsHidden: openAsHidden } 15 | } 16 | 17 | /* **************************************************************************/ 18 | // Mailto 19 | /* **************************************************************************/ 20 | 21 | /** 22 | * Sets if the app is the default mailto link handler 23 | * @param isCurrentApp: true if this is the handler 24 | */ 25 | changeMailtoLinkHandler (isCurrentApp) { 26 | return { isCurrentApp: isCurrentApp } 27 | } 28 | } 29 | 30 | module.exports = alt.createActions(PlatformActions) 31 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/settings/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | S: require('./settingsStore'), 3 | settingsStore: require('./settingsStore'), 4 | A: require('./settingsActions'), 5 | settingsActions: require('./settingsActions') 6 | } 7 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/settings/migration.js: -------------------------------------------------------------------------------- 1 | const persistence = require('./settingsPersistence') 2 | 3 | module.exports = { 4 | /** 5 | * Migrates the data from version 1.3.1 6 | */ 7 | from_1_3_1: function () { 8 | if (window.localStorage.getItem('migrate_settings_from_1_3_1') !== 'true') { 9 | const prev = JSON.parse(window.localStorage.getItem('App_settings') || '{}') 10 | const next = { 11 | language: {}, 12 | os: {}, 13 | proxy: prev.proxyServer || {}, 14 | tray: {}, 15 | ui: {} 16 | } 17 | const transfer = function (from, bucket, to) { 18 | if (prev[from] !== undefined) { 19 | next[bucket][to] = prev[from] 20 | } 21 | } 22 | 23 | // Language 24 | transfer('spellcheckerEnabled', 'language', 'spellcheckerEnabled') 25 | 26 | // os 27 | transfer('alwaysAskDownloadLocation', 'os', 'alwaysAskDownloadLocation') 28 | transfer('defaultDownloadLocation', 'os', 'defaultDownloadLocation') 29 | transfer('notificationsEnabled', 'os', 'notificationsEnabled') 30 | transfer('notificationsSilent', 'os', 'notificationsSilent') 31 | transfer('openLinksInBackground', 'os', 'openLinksInBackground') 32 | 33 | // tray 34 | transfer('showTrayIcon', 'tray', 'show') 35 | transfer('showTrayUnreadCount', 'tray', 'showUnreadCount') 36 | transfer('trayReadColor', 'tray', 'readColor') 37 | transfer('trayUnreadColor', 'tray', 'unreadColor') 38 | 39 | // ui 40 | transfer('showTitlebar', 'ui', 'showTitlebar') 41 | transfer('showAppBadge', 'ui', 'showAppBadge') 42 | transfer('showAppMenu', 'ui', 'showAppMenu') 43 | transfer('sidebarEnabled', 'ui', 'sidebarEnabled') 44 | 45 | // Save 46 | persistence.setJSONItemSync('language', next.language) 47 | persistence.setJSONItemSync('os', next.os) 48 | persistence.setJSONItemSync('proxy', next.proxy) 49 | persistence.setJSONItemSync('tray', next.tray) 50 | persistence.setJSONItemSync('ui', next.ui) 51 | 52 | // Save 53 | window.localStorage.setItem('pre_1_3_1:App_settings', JSON.stringify(prev)) 54 | window.localStorage.removeItem('App_settings') 55 | } 56 | 57 | window.localStorage.setItem('migrate_settings_from_1_3_1', 'true') 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/stores/settings/settingsPersistence.js: -------------------------------------------------------------------------------- 1 | const StorageBucket = require('../StorageBucket') 2 | module.exports = new StorageBucket('settings') 3 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/AppBadge.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const shallowCompare = require('react-addons-shallow-compare') 3 | const { remote } = window.nativeRequire('electron') 4 | const {nativeImage, app} = remote 5 | 6 | const AppBadge = React.createClass({ 7 | displayName: 'AppBadge', 8 | 9 | propTypes: { 10 | unreadCount: React.PropTypes.number.isRequired 11 | }, 12 | statics: { 13 | /** 14 | * @return true if the current platform supports app badges 15 | */ 16 | supportsAppBadge () { 17 | if (process.platform === 'darwin') { 18 | return true 19 | } else if (process.platform === 'linux' && app.isUnityRunning()) { 20 | return true 21 | } else { 22 | return false 23 | } 24 | }, 25 | /** 26 | * @return true if this platform supports overlay icons 27 | */ 28 | supportsAppOverlayIcon () { 29 | return process.platform === 'win32' 30 | } 31 | }, 32 | 33 | /* **************************************************************************/ 34 | // Component Lifecycle 35 | /* **************************************************************************/ 36 | 37 | componentWillUnmount () { 38 | if (AppBadge.supportsAppBadge()) { 39 | app.setBadgeCount(0) 40 | } else if (AppBadge.supportsAppOverlayIcon()) { 41 | const win = remote.getCurrentWindow() 42 | win.setOverlayIcon(null, '') 43 | } 44 | }, 45 | 46 | /* **************************************************************************/ 47 | // Rendering 48 | /* **************************************************************************/ 49 | 50 | shouldComponentUpdate (nextProps, nextState) { 51 | return shallowCompare(this, nextProps, nextState) 52 | }, 53 | 54 | render () { 55 | const { unreadCount } = this.props 56 | 57 | if (AppBadge.supportsAppBadge()) { 58 | app.setBadgeCount(unreadCount) 59 | } else if (AppBadge.supportsAppOverlayIcon()) { 60 | const win = remote.getCurrentWindow() 61 | if (unreadCount === 0) { 62 | win.setOverlayIcon(null, '') 63 | } else { 64 | const text = unreadCount.toString().length > 3 ? '+' : unreadCount.toString() 65 | const canvas = document.createElement('canvas') 66 | canvas.height = 140 67 | canvas.width = 140 68 | 69 | const ctx = canvas.getContext('2d') 70 | ctx.fillStyle = 'red' 71 | ctx.beginPath() 72 | ctx.ellipse(70, 70, 65, 65, 0, 0, 2 * Math.PI) 73 | ctx.fill() 74 | ctx.textAlign = 'center' 75 | ctx.fillStyle = 'white' 76 | 77 | if (text.length > 2) { 78 | ctx.font = '65px sans-serif' 79 | ctx.fillText(text, 70, 90) 80 | } else if (text.length > 1) { 81 | ctx.font = 'bold 80px sans-serif' 82 | ctx.fillText(text, 70, 97) 83 | } else { 84 | ctx.font = 'bold 100px sans-serif' 85 | ctx.fillText(text, 70, 106) 86 | } 87 | 88 | const badgeDataURL = canvas.toDataURL() 89 | const img = nativeImage.createFromDataURL(badgeDataURL) 90 | win.setOverlayIcon(img, text) 91 | } 92 | } 93 | 94 | return (
) 95 | } 96 | }) 97 | 98 | module.exports = AppBadge 99 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/AppWizard/AppWizard.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { appWizardStore } = require('../../stores/appWizard') 3 | const shallowCompare = require('react-addons-shallow-compare') 4 | const AppWizardStart = require('./AppWizardStart') 5 | const AppWizardComplete = require('./AppWizardComplete') 6 | const AppWizardMailto = require('./AppWizardMailto') 7 | const AppWizardTray = require('./AppWizardTray') 8 | 9 | module.exports = React.createClass({ 10 | /* **************************************************************************/ 11 | // Class 12 | /* **************************************************************************/ 13 | 14 | displayName: 'AppWizard', 15 | 16 | /* **************************************************************************/ 17 | // Component Lifecycle 18 | /* **************************************************************************/ 19 | 20 | componentDidMount () { 21 | this.renderTO = null 22 | appWizardStore.listen(this.wizardChanged) 23 | }, 24 | 25 | componentWillUnmount () { 26 | clearTimeout(this.renderTO) 27 | appWizardStore.unlisten(this.wizardChanged) 28 | }, 29 | 30 | /* **************************************************************************/ 31 | // Data lifecycle 32 | /* **************************************************************************/ 33 | 34 | getInitialState () { 35 | const wizardState = appWizardStore.getState() 36 | const itemsOpen = wizardState.hasAnyItemsOpen() 37 | return { 38 | itemsOpen: itemsOpen, 39 | render: itemsOpen, 40 | trayConfiguratorOpen: wizardState.trayConfiguratorOpen, 41 | mailtoHandlerOpen: wizardState.mailtoHandlerOpen, 42 | completeOpen: wizardState.completeOpen, 43 | startOpen: wizardState.startOpen 44 | } 45 | }, 46 | 47 | wizardChanged (wizardState) { 48 | this.setState((prevState) => { 49 | const itemsOpen = wizardState.hasAnyItemsOpen() 50 | const update = { 51 | itemsOpen: itemsOpen, 52 | trayConfiguratorOpen: wizardState.trayConfiguratorOpen, 53 | mailtoHandlerOpen: wizardState.mailtoHandlerOpen, 54 | completeOpen: wizardState.completeOpen, 55 | startOpen: wizardState.startOpen 56 | } 57 | 58 | if (prevState.itemsOpen !== itemsOpen) { 59 | clearTimeout(this.renderTO) 60 | if (prevState.itemsOpen && !itemsOpen) { 61 | this.renderTO = setTimeout(() => { 62 | this.setState({ render: false }) 63 | }, 1000) 64 | } else if (!prevState.itemsOpen && itemsOpen) { 65 | update.render = true 66 | } 67 | } 68 | return update 69 | }) 70 | }, 71 | 72 | /* **************************************************************************/ 73 | // Rendering 74 | /* **************************************************************************/ 75 | 76 | shouldComponentUpdate (nextProps, nextState) { 77 | return shallowCompare(this, nextProps, nextState) 78 | }, 79 | 80 | render () { 81 | const { render, startOpen, trayConfiguratorOpen, mailtoHandlerOpen, completeOpen } = this.state 82 | if (render) { 83 | return ( 84 |
85 | 86 | 87 | 88 | 89 |
90 | ) 91 | } else { 92 | return null 93 | } 94 | } 95 | }) 96 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/AppWizard/AppWizardMailto.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { appWizardActions } = require('../../stores/appWizard') 3 | const { platformActions } = require('../../stores/platform') 4 | const shallowCompare = require('react-addons-shallow-compare') 5 | const { Dialog, RaisedButton } = require('material-ui') 6 | 7 | module.exports = React.createClass({ 8 | /* **************************************************************************/ 9 | // Class 10 | /* **************************************************************************/ 11 | 12 | displayName: 'AppWizardMailto', 13 | propTypes: { 14 | isOpen: React.PropTypes.bool.isRequired 15 | }, 16 | 17 | /* **************************************************************************/ 18 | // Rendering 19 | /* **************************************************************************/ 20 | 21 | shouldComponentUpdate (nextProps, nextState) { 22 | return shallowCompare(this, nextProps, nextState) 23 | }, 24 | 25 | render () { 26 | const { isOpen } = this.props 27 | const actions = ( 28 |
29 | appWizardActions.cancelWizard()} /> 33 | appWizardActions.progressNextStep()} /> 36 | { 41 | platformActions.changeMailtoLinkHandler(true) 42 | appWizardActions.progressNextStep() 43 | }} /> 44 |
45 | ) 46 | 47 | return ( 48 | appWizardActions.cancelWizard()}> 55 |
56 |

57 | Would you like to make WMail your default mail client? 58 |
59 | You can always change this later 60 |

61 |
62 |
63 | ) 64 | } 65 | }) 66 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/AppWizard/AppWizardStart.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { appWizardActions } = require('../../stores/appWizard') 3 | const shallowCompare = require('react-addons-shallow-compare') 4 | const { Dialog, RaisedButton, FontIcon, Avatar } = require('material-ui') 5 | const Colors = require('material-ui/styles/colors') 6 | 7 | module.exports = React.createClass({ 8 | /* **************************************************************************/ 9 | // Class 10 | /* **************************************************************************/ 11 | 12 | displayName: 'AppWizardStart', 13 | propTypes: { 14 | isOpen: React.PropTypes.bool.isRequired 15 | }, 16 | 17 | /* **************************************************************************/ 18 | // Rendering 19 | /* **************************************************************************/ 20 | 21 | shouldComponentUpdate (nextProps, nextState) { 22 | return shallowCompare(this, nextProps, nextState) 23 | }, 24 | 25 | render () { 26 | const { isOpen } = this.props 27 | const actions = ( 28 |
29 | appWizardActions.discardWizard()} /> 33 | appWizardActions.cancelWizard()} /> 36 | appWizardActions.progressNextStep()} /> 41 |
42 | ) 43 | 44 | return ( 45 | appWizardActions.cancelWizard()}> 51 |
52 | )} 56 | size={80} /> 57 |

WMail Setup

58 |

59 | Customise WMail to work best for you by configuring a few common settings 60 |

61 |

62 | Would you like to start WMail setup now? 63 |

64 |
65 |
66 | ) 67 | } 68 | }) 69 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/AppWizard/AppWizardTray.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { appWizardActions } = require('../../stores/appWizard') 3 | const { settingsStore } = require('../../stores/settings') 4 | const shallowCompare = require('react-addons-shallow-compare') 5 | const { Dialog, RaisedButton } = require('material-ui') 6 | const { TrayIconEditor } = require('../../Components') 7 | 8 | module.exports = React.createClass({ 9 | /* **************************************************************************/ 10 | // Class 11 | /* **************************************************************************/ 12 | 13 | displayName: 'AppWizardTray', 14 | propTypes: { 15 | isOpen: React.PropTypes.bool.isRequired 16 | }, 17 | 18 | /* **************************************************************************/ 19 | // Component Lifecycle 20 | /* **************************************************************************/ 21 | 22 | componentDidMount () { 23 | settingsStore.listen(this.settingsUpdated) 24 | }, 25 | 26 | componentWillUnmount () { 27 | settingsStore.unlisten(this.settingsUpdated) 28 | }, 29 | 30 | /* **************************************************************************/ 31 | // Data Lifecycle 32 | /* **************************************************************************/ 33 | 34 | getInitialState () { 35 | return { 36 | tray: settingsStore.getState().tray 37 | } 38 | }, 39 | 40 | settingsUpdated (settingsState) { 41 | this.setState({ tray: settingsState.tray }) 42 | }, 43 | 44 | /* **************************************************************************/ 45 | // Rendering 46 | /* **************************************************************************/ 47 | 48 | shouldComponentUpdate (nextProps, nextState) { 49 | return shallowCompare(this, nextProps, nextState) 50 | }, 51 | 52 | render () { 53 | const { isOpen } = this.props 54 | const { tray } = this.state 55 | 56 | const actions = ( 57 |
58 | appWizardActions.cancelWizard()} /> 62 | appWizardActions.progressNextStep()} /> 66 |
67 | ) 68 | 69 | return ( 70 | appWizardActions.cancelWizard()}> 77 |

78 | Customise the tray icon so that it fits in with the other icons in 79 | your taskbar. You can change the way the icon appears when you have unread 80 | mail and when you have no unread mail 81 |

82 | 86 |
87 | ) 88 | } 89 | }) 90 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/AppWizard/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./AppWizard') 2 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/DictionaryInstaller/DictionaryInstallHandler.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { Dialog } = require('material-ui') 3 | const dictionariesStore = require('../../stores/dictionaries/dictionariesStore') 4 | const DictionaryInstallStepper = require('./DictionaryInstallStepper') 5 | 6 | module.exports = React.createClass({ 7 | /* **************************************************************************/ 8 | // Class 9 | /* **************************************************************************/ 10 | 11 | displayName: 'DictionaryInstallHandler', 12 | 13 | /* **************************************************************************/ 14 | // Component Lifecycle 15 | /* **************************************************************************/ 16 | 17 | componentWillMount () { 18 | dictionariesStore.listen(this.dictionariesChanged) 19 | }, 20 | 21 | componentWillUnmount () { 22 | dictionariesStore.unlisten(this.dictionariesChanged) 23 | }, 24 | 25 | /* **************************************************************************/ 26 | // Data lifecycle 27 | /* **************************************************************************/ 28 | 29 | getInitialState () { 30 | const store = dictionariesStore.getState() 31 | return { 32 | isInstalling: store.isInstalling(), 33 | installId: store.installId() 34 | } 35 | }, 36 | 37 | dictionariesChanged (store) { 38 | this.setState({ 39 | isInstalling: store.isInstalling(), 40 | installId: store.installId() 41 | }) 42 | }, 43 | 44 | /* **************************************************************************/ 45 | // Rendering 46 | /* **************************************************************************/ 47 | 48 | render () { 49 | return ( 50 | 54 | {!this.state.isInstalling ? undefined : ( 55 | 56 | )} 57 | 58 | ) 59 | } 60 | }) 61 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Mailbox/Google/GoogleMailboxCalendarTab.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const MailboxTabSleepable = require('../MailboxTabSleepable') 3 | const Mailbox = require('shared/Models/Mailbox/Mailbox') 4 | const { settingsStore } = require('../../../stores/settings') 5 | const { 6 | remote: {shell} 7 | } = window.nativeRequire('electron') 8 | 9 | const REF = 'mailbox_tab' 10 | 11 | module.exports = React.createClass({ 12 | /* **************************************************************************/ 13 | // Class 14 | /* **************************************************************************/ 15 | 16 | displayName: 'GoogleMailboxCalendarTab', 17 | propTypes: { 18 | mailboxId: React.PropTypes.string.isRequired 19 | }, 20 | 21 | /* **************************************************************************/ 22 | // Component lifecylce 23 | /* **************************************************************************/ 24 | 25 | componentDidMount () { 26 | settingsStore.listen(this.settingsChanged) 27 | }, 28 | 29 | componentWillUnmount () { 30 | settingsStore.unlisten(this.settingsChanged) 31 | }, 32 | 33 | /* **************************************************************************/ 34 | // Data lifecylce 35 | /* **************************************************************************/ 36 | 37 | getInitialState () { 38 | const settingsState = settingsStore.getState() 39 | return { 40 | os: settingsState.os 41 | } 42 | }, 43 | 44 | settingsChanged (settingsState) { 45 | this.setState({ os: settingsState.os }) 46 | }, 47 | 48 | /* **************************************************************************/ 49 | // Browser Events 50 | /* **************************************************************************/ 51 | 52 | /** 53 | * Opens a new url in the correct way 54 | * @param url: the url to open 55 | */ 56 | handleOpenNewWindow (url) { 57 | shell.openExternal(url, { activate: !this.state.os.openLinksInBackground }) 58 | }, 59 | 60 | /* **************************************************************************/ 61 | // Rendering 62 | /* **************************************************************************/ 63 | 64 | render () { 65 | const { mailboxId } = this.props 66 | 67 | return ( 68 | { this.handleOpenNewWindow(evt.url) }} /> 74 | ) 75 | } 76 | }) 77 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Mailbox/Google/GoogleMailboxCommunicationTab.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const MailboxTabSleepable = require('../MailboxTabSleepable') 3 | const Mailbox = require('shared/Models/Mailbox/Mailbox') 4 | const { settingsStore } = require('../../../stores/settings') 5 | const URL = window.nativeRequire('url') 6 | 7 | const REF = 'mailbox_tab' 8 | 9 | module.exports = React.createClass({ 10 | /* **************************************************************************/ 11 | // Class 12 | /* **************************************************************************/ 13 | 14 | displayName: 'GoogleMailboxCommunicationTab', 15 | propTypes: { 16 | mailboxId: React.PropTypes.string.isRequired 17 | }, 18 | 19 | /* **************************************************************************/ 20 | // Component lifecylce 21 | /* **************************************************************************/ 22 | 23 | componentDidMount () { 24 | settingsStore.listen(this.settingsChanged) 25 | }, 26 | 27 | componentWillUnmount () { 28 | settingsStore.unlisten(this.settingsChanged) 29 | }, 30 | 31 | /* **************************************************************************/ 32 | // Data lifecylce 33 | /* **************************************************************************/ 34 | 35 | getInitialState () { 36 | const settingsState = settingsStore.getState() 37 | return { 38 | os: settingsState.os 39 | } 40 | }, 41 | 42 | settingsChanged (settingsState) { 43 | this.setState({ os: settingsState.os }) 44 | }, 45 | 46 | /* **************************************************************************/ 47 | // Browser Events 48 | /* **************************************************************************/ 49 | 50 | /** 51 | * Opens a new url in the correct way 52 | * @param url: the url to open 53 | */ 54 | handleOpenNewWindow (url) { 55 | const purl = URL.parse(url, true) 56 | 57 | if (purl.host === 'hangouts.google.com') { 58 | this.setState({ browserSrc: url }) 59 | } 60 | }, 61 | 62 | /* **************************************************************************/ 63 | // Rendering 64 | /* **************************************************************************/ 65 | 66 | render () { 67 | const { mailboxId } = this.props 68 | 69 | return ( 70 | { this.handleOpenNewWindow(evt.url) }} /> 77 | ) 78 | } 79 | }) 80 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Mailbox/Google/GoogleMailboxContactsTab.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const MailboxTabSleepable = require('../MailboxTabSleepable') 3 | const Mailbox = require('shared/Models/Mailbox/Mailbox') 4 | const { settingsStore } = require('../../../stores/settings') 5 | const { 6 | remote: {shell} 7 | } = window.nativeRequire('electron') 8 | 9 | const REF = 'mailbox_tab' 10 | 11 | module.exports = React.createClass({ 12 | /* **************************************************************************/ 13 | // Class 14 | /* **************************************************************************/ 15 | 16 | displayName: 'GoogleMailboxContactsTab', 17 | propTypes: { 18 | mailboxId: React.PropTypes.string.isRequired 19 | }, 20 | 21 | /* **************************************************************************/ 22 | // Component lifecylce 23 | /* **************************************************************************/ 24 | 25 | componentDidMount () { 26 | settingsStore.listen(this.settingsChanged) 27 | }, 28 | 29 | componentWillUnmount () { 30 | settingsStore.unlisten(this.settingsChanged) 31 | }, 32 | 33 | /* **************************************************************************/ 34 | // Data lifecylce 35 | /* **************************************************************************/ 36 | 37 | getInitialState () { 38 | const settingsState = settingsStore.getState() 39 | return { 40 | os: settingsState.os 41 | } 42 | }, 43 | 44 | settingsChanged (settingsState) { 45 | this.setState({ os: settingsState.os }) 46 | }, 47 | 48 | /* **************************************************************************/ 49 | // Browser Events 50 | /* **************************************************************************/ 51 | 52 | /** 53 | * Opens a new url in the correct way 54 | * @param url: the url to open 55 | */ 56 | handleOpenNewWindow (url) { 57 | shell.openExternal(url, { activate: !this.state.os.openLinksInBackground }) 58 | }, 59 | 60 | /* **************************************************************************/ 61 | // Rendering 62 | /* **************************************************************************/ 63 | 64 | render () { 65 | const { mailboxId } = this.props 66 | 67 | return ( 68 | { this.handleOpenNewWindow(evt.url) }} /> 74 | ) 75 | } 76 | }) 77 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Mailbox/Google/GoogleMailboxNotesTab.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const MailboxTabSleepable = require('../MailboxTabSleepable') 3 | const Mailbox = require('shared/Models/Mailbox/Mailbox') 4 | const { settingsStore } = require('../../../stores/settings') 5 | const { 6 | remote: {shell} 7 | } = window.nativeRequire('electron') 8 | 9 | const REF = 'mailbox_tab' 10 | 11 | module.exports = React.createClass({ 12 | /* **************************************************************************/ 13 | // Class 14 | /* **************************************************************************/ 15 | 16 | displayName: 'GoogleMailboxNotesTab', 17 | propTypes: { 18 | mailboxId: React.PropTypes.string.isRequired 19 | }, 20 | 21 | /* **************************************************************************/ 22 | // Component lifecylce 23 | /* **************************************************************************/ 24 | 25 | componentDidMount () { 26 | settingsStore.listen(this.settingsChanged) 27 | }, 28 | 29 | componentWillUnmount () { 30 | settingsStore.unlisten(this.settingsChanged) 31 | }, 32 | 33 | /* **************************************************************************/ 34 | // Data lifecylce 35 | /* **************************************************************************/ 36 | 37 | getInitialState () { 38 | const settingsState = settingsStore.getState() 39 | return { 40 | os: settingsState.os 41 | } 42 | }, 43 | 44 | settingsChanged (settingsState) { 45 | this.setState({ os: settingsState.os }) 46 | }, 47 | 48 | /* **************************************************************************/ 49 | // Browser Events 50 | /* **************************************************************************/ 51 | 52 | /** 53 | * Opens a new url in the correct way 54 | * @param url: the url to open 55 | */ 56 | handleOpenNewWindow (url) { 57 | shell.openExternal(url, { activate: !this.state.os.openLinksInBackground }) 58 | }, 59 | 60 | /* **************************************************************************/ 61 | // Rendering 62 | /* **************************************************************************/ 63 | 64 | render () { 65 | const { mailboxId } = this.props 66 | 67 | return ( 68 | { this.handleOpenNewWindow(evt.url) }} /> 74 | ) 75 | } 76 | }) 77 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Mailbox/Google/GoogleMailboxStorageTab.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const MailboxTabSleepable = require('../MailboxTabSleepable') 3 | const Mailbox = require('shared/Models/Mailbox/Mailbox') 4 | const { settingsStore } = require('../../../stores/settings') 5 | const URL = window.nativeRequire('url') 6 | const { 7 | remote: {shell}, ipcRenderer 8 | } = window.nativeRequire('electron') 9 | 10 | const REF = 'mailbox_tab' 11 | 12 | module.exports = React.createClass({ 13 | /* **************************************************************************/ 14 | // Class 15 | /* **************************************************************************/ 16 | 17 | displayName: 'GoogleMailboxStorageTab', 18 | propTypes: { 19 | mailboxId: React.PropTypes.string.isRequired 20 | }, 21 | 22 | /* **************************************************************************/ 23 | // Component lifecylce 24 | /* **************************************************************************/ 25 | 26 | componentDidMount () { 27 | settingsStore.listen(this.settingsChanged) 28 | }, 29 | 30 | componentWillUnmount () { 31 | settingsStore.unlisten(this.settingsChanged) 32 | }, 33 | 34 | /* **************************************************************************/ 35 | // Data lifecylce 36 | /* **************************************************************************/ 37 | 38 | getInitialState () { 39 | const settingsState = settingsStore.getState() 40 | return { 41 | os: settingsState.os 42 | } 43 | }, 44 | 45 | settingsChanged (settingsState) { 46 | this.setState({ os: settingsState.os }) 47 | }, 48 | 49 | /* **************************************************************************/ 50 | // Browser Events 51 | /* **************************************************************************/ 52 | 53 | /** 54 | * Opens a new url in the correct way 55 | * @param url: the url to open 56 | */ 57 | handleOpenNewWindow (url) { 58 | const purl = URL.parse(url) 59 | if (purl.host === 'docs.google.com') { 60 | ipcRenderer.send('new-window', { partition: 'persist:' + this.props.mailboxId, url: url }) 61 | } else { 62 | shell.openExternal(url, { activate: !this.state.os.openLinksInBackground }) 63 | } 64 | }, 65 | 66 | /* **************************************************************************/ 67 | // Rendering 68 | /* **************************************************************************/ 69 | 70 | render () { 71 | const { mailboxId } = this.props 72 | 73 | return ( 74 | { this.handleOpenNewWindow(evt.url) }} /> 80 | ) 81 | } 82 | }) 83 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Mailbox/MailboxTargetUrl.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { Paper } = require('material-ui') 3 | 4 | module.exports = React.createClass({ 5 | /* **************************************************************************/ 6 | // Class 7 | /* **************************************************************************/ 8 | 9 | displayName: 'MailboxTargetUrl', 10 | propTypes: { 11 | url: React.PropTypes.string 12 | }, 13 | 14 | /* **************************************************************************/ 15 | // Rendering 16 | /* **************************************************************************/ 17 | 18 | render () { 19 | const { url, ...passProps } = this.props 20 | 21 | const className = [ 22 | 'ReactComponent-MailboxTargetUrl', 23 | url ? 'active' : undefined 24 | ].concat(this.props.className).filter((c) => !!c).join(' ') 25 | return ( 26 | 27 | {url} 28 | 29 | ) 30 | } 31 | }) 32 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Mailbox/mailboxWindow.less: -------------------------------------------------------------------------------- 1 | .ReactComponent-MailboxWindows { 2 | .ReactComponent-MailboxTab { 3 | position: absolute; 4 | top: 10000px; 5 | bottom: -10000px; 6 | left: 0px; 7 | right: 0px; 8 | width: 100%; 9 | height: 100%; 10 | 11 | &.active { 12 | top: 0px; 13 | bottom: 0px; 14 | } 15 | 16 | @SEARCH_HEIGHT: 48px; 17 | .ReactComponent-MailboxSearch { 18 | position: absolute; 19 | bottom: -@SEARCH_HEIGHT; 20 | left: 0px; 21 | min-width: 300px; 22 | height: @SEARCH_HEIGHT; 23 | background-color: white; 24 | transition: none !important; 25 | z-index: 10; 26 | overflow: hidden; 27 | 28 | &.active { 29 | bottom: 0px; 30 | } 31 | } 32 | 33 | @TARGET_URL_HEIGHT: 16px; 34 | .ReactComponent-MailboxTargetUrl { 35 | position: absolute; 36 | bottom: -@TARGET_URL_HEIGHT; 37 | height: @TARGET_URL_HEIGHT; 38 | max-width: 50%; 39 | right: 0px; 40 | background-color: white; 41 | z-index: 9; 42 | overflow: hidden; 43 | text-align: right; 44 | font-size: 11px; 45 | line-height: @TARGET_URL_HEIGHT; 46 | padding-left: 3px; 47 | padding-right: 3px; 48 | transition-duration: 150ms !important; 49 | white-space: nowrap; 50 | text-overflow: ellipsis; 51 | 52 | &.active { 53 | bottom: 0px; 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/MailboxWizard/ConfigureCompleteWizardDialog.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { FontIcon, Dialog, RaisedButton } = require('material-ui') 3 | const Colors = require('material-ui/styles/colors') 4 | const { mailboxWizardStore, mailboxWizardActions } = require('../../stores/mailboxWizard') 5 | const { appWizardActions } = require('../../stores/appWizard') 6 | const { settingsStore } = require('../../stores/settings') 7 | 8 | const styles = { 9 | container: { 10 | textAlign: 'center' 11 | }, 12 | tick: { 13 | color: Colors.green600, 14 | fontSize: '80px' 15 | }, 16 | instruction: { 17 | textAlign: 'center' 18 | } 19 | } 20 | 21 | module.exports = React.createClass({ 22 | /* **************************************************************************/ 23 | // Class 24 | /* **************************************************************************/ 25 | 26 | displayName: 'ConfigureCompleteWizardDialog', 27 | 28 | /* **************************************************************************/ 29 | // Component Lifecycle 30 | /* **************************************************************************/ 31 | 32 | componentDidMount () { 33 | mailboxWizardStore.listen(this.mailboxWizardChanged) 34 | settingsStore.listen(this.settingsChanged) 35 | }, 36 | 37 | componentWillUnmount () { 38 | mailboxWizardStore.unlisten(this.mailboxWizardChanged) 39 | settingsStore.unlisten(this.settingsChanged) 40 | }, 41 | 42 | /* **************************************************************************/ 43 | // Data lifecycle 44 | /* **************************************************************************/ 45 | 46 | getInitialState () { 47 | return { 48 | isOpen: mailboxWizardStore.getState().configurationCompleteOpen, 49 | hasSeenAppWizard: settingsStore.getState().app.hasSeenAppWizard 50 | } 51 | }, 52 | 53 | mailboxWizardChanged (wizardState) { 54 | this.setState({ isOpen: wizardState.configurationCompleteOpen }) 55 | }, 56 | 57 | settingsChanged (settingsState) { 58 | this.setState({ hasSeenAppWizard: settingsStore.getState().app.hasSeenAppWizard }) 59 | }, 60 | 61 | /* **************************************************************************/ 62 | // Rendering 63 | /* **************************************************************************/ 64 | 65 | render () { 66 | const { isOpen, hasSeenAppWizard } = this.state 67 | const actions = ( 68 | { 72 | mailboxWizardActions.configurationComplete() 73 | if (!hasSeenAppWizard) { 74 | setTimeout(() => { 75 | appWizardActions.startWizard() 76 | }, 500) // Feels more natural after a delay 77 | } 78 | }} /> 79 | ) 80 | 81 | return ( 82 | 88 |
89 | check_circle 90 |

All Done!

91 |

92 | You can change your mailbox settings at any time in the settings 93 |

94 |
95 |
96 | ) 97 | } 98 | }) 99 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/MailboxWizard/MailboxWizard.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { mailboxWizardStore } = require('../../stores/mailboxWizard') 3 | const shallowCompare = require('react-addons-shallow-compare') 4 | const AddMailboxWizardDialog = require('./AddMailboxWizardDialog') 5 | const ConfigureMailboxWizardDialog = require('./ConfigureMailboxWizardDialog') 6 | const ConfigureMailboxServicesDialog = require('./ConfigureMailboxServicesDialog') 7 | const ConfigureCompleteWizardDialog = require('./ConfigureCompleteWizardDialog') 8 | 9 | module.exports = React.createClass({ 10 | /* **************************************************************************/ 11 | // Class 12 | /* **************************************************************************/ 13 | 14 | displayName: 'MailboxWizard', 15 | 16 | /* **************************************************************************/ 17 | // Component Lifecycle 18 | /* **************************************************************************/ 19 | 20 | componentDidMount () { 21 | this.renderTO = null 22 | mailboxWizardStore.listen(this.wizardChanged) 23 | }, 24 | 25 | componentWillUnmount () { 26 | clearTimeout(this.renderTO) 27 | mailboxWizardStore.unlisten(this.wizardChanged) 28 | }, 29 | 30 | /* **************************************************************************/ 31 | // Data lifecycle 32 | /* **************************************************************************/ 33 | 34 | getInitialState () { 35 | const itemsOpen = mailboxWizardStore.getState().hasAnyItemsOpen() 36 | return { 37 | itemsOpen: itemsOpen, 38 | render: itemsOpen 39 | } 40 | }, 41 | 42 | wizardChanged (wizardState) { 43 | this.setState((prevState) => { 44 | const itemsOpen = wizardState.hasAnyItemsOpen() 45 | const update = { itemsOpen: itemsOpen } 46 | if (prevState.itemsOpen !== itemsOpen) { 47 | clearTimeout(this.renderTO) 48 | if (prevState.itemsOpen && !itemsOpen) { 49 | this.renderTO = setTimeout(() => { 50 | this.setState({ render: false }) 51 | }, 1000) 52 | } else if (!prevState.itemsOpen && itemsOpen) { 53 | update.render = true 54 | } 55 | } 56 | return update 57 | }) 58 | }, 59 | 60 | /* **************************************************************************/ 61 | // Rendering 62 | /* **************************************************************************/ 63 | 64 | shouldComponentUpdate (nextProps, nextState) { 65 | return shallowCompare(this, nextProps, nextState) 66 | }, 67 | 68 | render () { 69 | if (this.state.render) { 70 | return ( 71 |
72 | 73 | 74 | 75 | 76 |
77 | ) 78 | } else { 79 | return null 80 | } 81 | } 82 | }) 83 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/MailboxWizard/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./MailboxWizard') 2 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/NewsDialog.less: -------------------------------------------------------------------------------- 1 | .ReactComponent-NewsDialog-Body { 2 | padding: 0; 3 | overflow: hidden; 4 | position: relative; 5 | 6 | &:before { 7 | content: ""; 8 | margin-bottom: 100%; 9 | display: inline-block; 10 | } 11 | 12 | >iframe, >webview { 13 | border: none; 14 | width: 100%; 15 | height: 100%; 16 | position: absolute; 17 | top: 0; 18 | left: 0; 19 | right: 0; 20 | bottom: 0; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Settings/Accounts/AccountAdvancedSettings.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { Paper, Toggle } = require('material-ui') 3 | const mailboxActions = require('../../../stores/mailbox/mailboxActions') 4 | const styles = require('../settingStyles') 5 | const shallowCompare = require('react-addons-shallow-compare') 6 | 7 | module.exports = React.createClass({ 8 | /* **************************************************************************/ 9 | // Class 10 | /* **************************************************************************/ 11 | 12 | displayName: 'AccountAdvancedSettings', 13 | propTypes: { 14 | mailbox: React.PropTypes.object.isRequired, 15 | showRestart: React.PropTypes.func.isRequired 16 | }, 17 | 18 | /* **************************************************************************/ 19 | // Rendering 20 | /* **************************************************************************/ 21 | 22 | shouldComponentUpdate (nextProps, nextState) { 23 | return shallowCompare(this, nextProps, nextState) 24 | }, 25 | 26 | render () { 27 | const { mailbox, showRestart, ...passProps } = this.props 28 | 29 | return ( 30 | 31 |

Advanced

32 | { 37 | showRestart() 38 | mailboxActions.artificiallyPersistCookies(mailbox.id, toggled) 39 | }} /> 40 |
41 | ) 42 | } 43 | }) 44 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Settings/Accounts/AccountManagementSettings.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const Colors = require('material-ui/styles/colors') 3 | const { Paper, FlatButton, FontIcon } = require('material-ui') 4 | const mailboxActions = require('../../../stores/mailbox/mailboxActions') 5 | const styles = require('../settingStyles') 6 | const shallowCompare = require('react-addons-shallow-compare') 7 | const TimerMixin = require('react-timer-mixin') 8 | 9 | module.exports = React.createClass({ 10 | /* **************************************************************************/ 11 | // Class 12 | /* **************************************************************************/ 13 | 14 | displayName: 'AccountManagementSettings', 15 | mixins: [TimerMixin], 16 | propTypes: { 17 | mailbox: React.PropTypes.object.isRequired 18 | }, 19 | 20 | /* **************************************************************************/ 21 | // Component Lifecycle 22 | /* **************************************************************************/ 23 | 24 | componentWillMount () { 25 | this.confirmingDeleteTO = null 26 | }, 27 | 28 | componentWillReceiveProps (nextProps) { 29 | if (this.props.mailbox.id !== nextProps.mailbox.id) { 30 | this.setState({ confirmingDelete: false }) 31 | this.clearTimeout(this.confirmingDeleteTO) 32 | } 33 | }, 34 | 35 | /* **************************************************************************/ 36 | // Data lifecycle 37 | /* **************************************************************************/ 38 | 39 | getInitialState () { 40 | return { 41 | confirmingDelete: false 42 | } 43 | }, 44 | 45 | /* **************************************************************************/ 46 | // UI Events 47 | /* **************************************************************************/ 48 | 49 | /** 50 | * Handles the delete button being tapped 51 | */ 52 | handleDeleteTapped (evt) { 53 | if (this.state.confirmingDelete) { 54 | mailboxActions.remove(this.props.mailbox.id) 55 | } else { 56 | this.setState({ confirmingDelete: true }) 57 | this.confirmingDeleteTO = this.setTimeout(() => { 58 | this.setState({ confirmingDelete: false }) 59 | }, 4000) 60 | } 61 | }, 62 | 63 | /* **************************************************************************/ 64 | // Rendering 65 | /* **************************************************************************/ 66 | 67 | shouldComponentUpdate (nextProps, nextState) { 68 | return shallowCompare(this, nextProps, nextState) 69 | }, 70 | 71 | render () { 72 | const passProps = Object.assign({}, this.props) 73 | delete passProps.mailbox 74 | 75 | return ( 76 | 77 | delete} 80 | labelStyle={{color: Colors.red600}} 81 | onTouchTap={this.handleDeleteTapped} /> 82 | 83 | ) 84 | } 85 | }) 86 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Settings/Accounts/CustomCodeEditingModal.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { RaisedButton, FlatButton, Dialog, TextField } = require('material-ui') 3 | const shallowCompare = require('react-addons-shallow-compare') 4 | const uuid = require('uuid') 5 | 6 | module.exports = React.createClass({ 7 | /* **************************************************************************/ 8 | // Class 9 | /* **************************************************************************/ 10 | 11 | displayName: 'CustomCodeEditingModal', 12 | propTypes: { 13 | title: React.PropTypes.string, 14 | open: React.PropTypes.bool.isRequired, 15 | code: React.PropTypes.string, 16 | onCancel: React.PropTypes.func.isRequired, 17 | onSave: React.PropTypes.func.isRequired 18 | }, 19 | 20 | /* **************************************************************************/ 21 | // Component Lifecycle 22 | /* **************************************************************************/ 23 | 24 | componentWillReceiveProps (nextProps) { 25 | if (this.props.open !== nextProps.open) { 26 | this.setState({ editingKey: uuid.v4() }) 27 | } 28 | }, 29 | 30 | /* **************************************************************************/ 31 | // Data Lifecycle 32 | /* **************************************************************************/ 33 | 34 | getInitialState () { 35 | return { 36 | editingKey: uuid.v4() 37 | } 38 | }, 39 | 40 | /* **************************************************************************/ 41 | // Rendering 42 | /* **************************************************************************/ 43 | 44 | shouldComponentUpdate (nextProps, nextState) { 45 | return shallowCompare(this, nextProps, nextState) 46 | }, 47 | 48 | render () { 49 | const actions = [ 50 | ( this.props.onCancel(evt)} />), 55 | ( this.props.onSave(evt, this.refs.editor.getValue())} />) 60 | ] 61 | 62 | return ( 63 | 68 | 86 | 87 | ) 88 | } 89 | }) 90 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Settings/General/DownloadSettingsSection.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const ReactDOM = require('react-dom') 3 | const { Toggle, Paper, RaisedButton, FontIcon } = require('material-ui') 4 | const settingsActions = require('../../../stores/settings/settingsActions') 5 | const styles = require('../settingStyles') 6 | const shallowCompare = require('react-addons-shallow-compare') 7 | 8 | module.exports = React.createClass({ 9 | /* **************************************************************************/ 10 | // Class 11 | /* **************************************************************************/ 12 | 13 | displayName: 'DownloadSettingsSection', 14 | propTypes: { 15 | os: React.PropTypes.object.isRequired 16 | }, 17 | 18 | /* **************************************************************************/ 19 | // Component Lifecycle 20 | /* **************************************************************************/ 21 | 22 | componentDidMount () { 23 | ReactDOM.findDOMNode(this.refs.defaultDownloadInput).setAttribute('webkitdirectory', 'webkitdirectory') 24 | }, 25 | 26 | componentDidUpdate () { 27 | ReactDOM.findDOMNode(this.refs.defaultDownloadInput).setAttribute('webkitdirectory', 'webkitdirectory') 28 | }, 29 | 30 | /* **************************************************************************/ 31 | // Rendering 32 | /* **************************************************************************/ 33 | 34 | shouldComponentUpdate (nextProps, nextState) { 35 | return shallowCompare(this, nextProps, nextState) 36 | }, 37 | 38 | render () { 39 | const {os, ...passProps} = this.props 40 | 41 | return ( 42 | 43 |

Downloads

44 |
45 | settingsActions.setAlwaysAskDownloadLocation(toggled)} /> 50 |
51 |
52 | folder} 55 | containerElement='label' 56 | disabled={os.alwaysAskDownloadLocation} 57 | style={styles.fileInputButton}> 58 | settingsActions.setDefaultDownloadLocation(evt.target.files[0].path)} /> 64 | 65 | {os.alwaysAskDownloadLocation ? undefined : {os.defaultDownloadLocation}} 66 |
67 |
68 | ) 69 | } 70 | }) 71 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Settings/General/NotificationSettingsSection.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { Toggle, Paper } = require('material-ui') 3 | const settingsActions = require('../../../stores/settings/settingsActions') 4 | const styles = require('../settingStyles') 5 | const shallowCompare = require('react-addons-shallow-compare') 6 | 7 | module.exports = React.createClass({ 8 | /* **************************************************************************/ 9 | // Rendering 10 | /* **************************************************************************/ 11 | 12 | displayName: 'NotificationSettingsSection', 13 | propTypes: { 14 | os: React.PropTypes.object.isRequired 15 | }, 16 | 17 | /* **************************************************************************/ 18 | // Rendering 19 | /* **************************************************************************/ 20 | 21 | shouldComponentUpdate (nextProps, nextState) { 22 | return shallowCompare(this, nextProps, nextState) 23 | }, 24 | 25 | render () { 26 | const { os, ...passProps } = this.props 27 | 28 | return ( 29 | 30 |

Notifications

31 | settingsActions.setNotificationsEnabled(toggled)} /> 36 | settingsActions.setNotificationsSilent(!toggled)} /> 42 |
43 | ) 44 | } 45 | }) 46 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Settings/General/PlatformSettingsSection.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { Toggle, Paper, SelectField, MenuItem } = require('material-ui') 3 | const platformActions = require('../../../stores/platform/platformActions') 4 | const styles = require('../settingStyles') 5 | const shallowCompare = require('react-addons-shallow-compare') 6 | 7 | const LOGIN_OPEN_MODES = { 8 | OFF: 'false|false', 9 | ON: 'true|false', 10 | ON_BACKGROUND: 'true|true' 11 | } 12 | 13 | module.exports = React.createClass({ 14 | /* **************************************************************************/ 15 | // Rendering 16 | /* **************************************************************************/ 17 | 18 | displayName: 'PlatformSettingsSection', 19 | propTypes: { 20 | mailtoLinkHandlerSupported: React.PropTypes.bool.isRequired, 21 | isMailtoLinkHandler: React.PropTypes.bool.isRequired, 22 | openAtLoginSupported: React.PropTypes.bool.isRequired, 23 | openAtLogin: React.PropTypes.bool.isRequired, 24 | openAsHiddenAtLogin: React.PropTypes.bool.isRequired 25 | }, 26 | 27 | /* **************************************************************************/ 28 | // UI Events 29 | /* **************************************************************************/ 30 | 31 | /** 32 | * Handles the open at login state chaning 33 | */ 34 | handleOpenAtLoginChanged (evt, index, value) { 35 | switch (value) { 36 | case LOGIN_OPEN_MODES.OFF: 37 | platformActions.changeLoginPref(false, false) 38 | break 39 | case LOGIN_OPEN_MODES.ON: 40 | platformActions.changeLoginPref(true, false) 41 | break 42 | case LOGIN_OPEN_MODES.ON_BACKGROUND: 43 | platformActions.changeLoginPref(true, true) 44 | break 45 | } 46 | }, 47 | 48 | /* **************************************************************************/ 49 | // Rendering 50 | /* **************************************************************************/ 51 | 52 | shouldComponentUpdate (nextProps, nextState) { 53 | return shallowCompare(this, nextProps, nextState) 54 | }, 55 | 56 | render () { 57 | const { 58 | mailtoLinkHandlerSupported, 59 | isMailtoLinkHandler, 60 | openAtLoginSupported, 61 | openAtLogin, 62 | openAsHiddenAtLogin, 63 | ...passProps 64 | } = this.props 65 | 66 | if (!mailtoLinkHandlerSupported && !openAtLoginSupported) { return null } 67 | 68 | return ( 69 | 70 |

Platform

71 | {mailtoLinkHandlerSupported ? ( 72 | platformActions.changeMailtoLinkHandler(toggled)} /> 77 | ) : undefined} 78 | {openAtLoginSupported ? ( 79 | 84 | 85 | 86 | 87 | 88 | ) : undefined} 89 |
90 | ) 91 | } 92 | }) 93 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Settings/General/TraySettingsSection.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { Toggle, Paper, SelectField, MenuItem } = require('material-ui') 3 | const { TrayIconEditor } = require('../../../Components') 4 | const settingsActions = require('../../../stores/settings/settingsActions') 5 | const styles = require('../settingStyles') 6 | const shallowCompare = require('react-addons-shallow-compare') 7 | const Tray = require('../../Tray') 8 | 9 | module.exports = React.createClass({ 10 | /* **************************************************************************/ 11 | // Class 12 | /* **************************************************************************/ 13 | 14 | displayName: 'TraySettingsSection', 15 | propTypes: { 16 | tray: React.PropTypes.object.isRequired 17 | }, 18 | 19 | /* **************************************************************************/ 20 | // Rendering 21 | /* **************************************************************************/ 22 | 23 | shouldComponentUpdate (nextProps, nextState) { 24 | return shallowCompare(this, nextProps, nextState) 25 | }, 26 | 27 | render () { 28 | const {tray, ...passProps} = this.props 29 | 30 | return ( 31 | 32 |

{process.platform === 'darwin' ? 'Menu Bar' : 'Tray'}

33 |
34 | settingsActions.setShowTrayIcon(toggled)} /> 39 | settingsActions.setShowTrayUnreadCount(toggled)} /> 45 | {Tray.platformSupportsDpiMultiplier() ? ( 46 | settingsActions.setDpiMultiplier(value)}> 50 | 51 | 52 | 53 | 54 | 55 | 56 | ) : undefined } 57 |
58 |
59 | 60 |
61 | ) 62 | } 63 | }) 64 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Settings/settingStyles.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /* **************************************************************************/ 3 | // Modal 4 | /* **************************************************************************/ 5 | dialog: { 6 | width: '90%', 7 | maxWidth: 1200 8 | }, 9 | tabToggles: { 10 | display: 'flex', 11 | flexDirection: 'row', 12 | alignContent: 'stretch' 13 | }, 14 | tabToggle: { 15 | height: 50, 16 | borderRadius: 0, 17 | flex: 1, 18 | borderBottomWidth: 2, 19 | borderBottomStyle: 'solid' 20 | }, 21 | 22 | /* **************************************************************************/ 23 | // General 24 | /* **************************************************************************/ 25 | paper: { 26 | padding: 15, 27 | marginBottom: 5, 28 | marginTop: 5 29 | }, 30 | subheading: { 31 | marginTop: 0, 32 | marginBottom: 10, 33 | color: '#CCC', 34 | fontWeight: '300', 35 | fontSize: 16 36 | }, 37 | fileInputButton: { 38 | marginRight: 15, 39 | position: 'relative', 40 | overflow: 'hidden' 41 | }, 42 | fileInput: { 43 | position: 'absolute', 44 | top: 0, 45 | left: 0, 46 | right: 0, 47 | bottom: 0, 48 | opacity: 0, 49 | width: '100%', 50 | cursor: 'pointer' 51 | }, 52 | button: { 53 | marginTop: 5, 54 | marginBottom: 5 55 | }, 56 | 57 | /* **************************************************************************/ 58 | // Account 59 | /* **************************************************************************/ 60 | 61 | accountPicker: { 62 | position: 'relative', 63 | height: 100 64 | }, 65 | accountPickerAvatar: { 66 | position: 'absolute', 67 | top: 20, 68 | left: 20, 69 | boxShadow: 'rgba(0, 0, 0, 0.117647) 0px 1px 6px, rgba(0, 0, 0, 0.117647) 0px 1px 4px' // copied from paper 70 | }, 71 | accountPickerContainer: { 72 | position: 'absolute', 73 | top: 25, 74 | left: 100, 75 | right: 0 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Sidelist/SidelistItemAddMailbox.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { IconButton } = require('material-ui') 3 | const Colors = require('material-ui/styles/colors') 4 | const styles = require('./SidelistStyles') 5 | const ReactTooltip = require('react-tooltip') 6 | const { mailboxWizardActions } = require('../../stores/mailboxWizard') 7 | 8 | module.exports = React.createClass({ 9 | 10 | /* **************************************************************************/ 11 | // Class 12 | /* **************************************************************************/ 13 | 14 | displayName: 'SidelistItemAddMailbox', 15 | 16 | /* **************************************************************************/ 17 | // Rendering 18 | /* **************************************************************************/ 19 | 20 | render () { 21 | const { style, ...passProps } = this.props 22 | return ( 23 |
28 | mailboxWizardActions.openAddMailbox()} 31 | iconStyle={{ color: Colors.blueGrey400 }}> 32 | add_circle 33 | 34 | 39 |
40 | ) 41 | } 42 | }) 43 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Sidelist/SidelistItemMailbox/SidelistItemMailboxAvatar.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { Avatar } = require('material-ui') 3 | const { mailboxStore } = require('../../../stores/mailbox') 4 | const shallowCompare = require('react-addons-shallow-compare') 5 | const styles = require('../SidelistStyles') 6 | 7 | module.exports = React.createClass({ 8 | /* **************************************************************************/ 9 | // Class 10 | /* **************************************************************************/ 11 | 12 | displayName: 'SidelistItemMailboxAvatar', 13 | propTypes: { 14 | isActive: React.PropTypes.bool.isRequired, 15 | isHovering: React.PropTypes.bool.isRequired, 16 | mailbox: React.PropTypes.object.isRequired, 17 | index: React.PropTypes.number.isRequired, 18 | onClick: React.PropTypes.func.isRequired 19 | }, 20 | 21 | /* **************************************************************************/ 22 | // Rendering 23 | /* **************************************************************************/ 24 | 25 | shouldComponentUpdate (nextProps, nextState) { 26 | return shallowCompare(this, nextProps, nextState) 27 | }, 28 | 29 | render () { 30 | const { isActive, isHovering, mailbox, index, ...passProps } = this.props 31 | 32 | let url 33 | let children 34 | let backgroundColor 35 | const borderColor = isActive || isHovering ? mailbox.color : 'white' 36 | if (mailbox.hasCustomAvatar) { 37 | url = mailboxStore.getState().getAvatar(mailbox.customAvatarId) 38 | backgroundColor = 'white' 39 | } else if (mailbox.avatarURL) { 40 | url = mailbox.avatarURL 41 | backgroundColor = 'white' 42 | } else { 43 | children = index 44 | backgroundColor = mailbox.color 45 | } 46 | 47 | return ( 48 | 56 | {children} 57 | 58 | ) 59 | } 60 | }) 61 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Sidelist/SidelistItemMailbox/SidelistItemMailboxServices.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const shallowCompare = require('react-addons-shallow-compare') 3 | const styles = require('../SidelistStyles') 4 | const SidelistItemMailboxService = require('./SidelistItemMailboxService') 5 | 6 | module.exports = React.createClass({ 7 | /* **************************************************************************/ 8 | // Class 9 | /* **************************************************************************/ 10 | 11 | displayName: 'SidelistItemMailboxServices', 12 | propTypes: { 13 | mailbox: React.PropTypes.object.isRequired, 14 | isActiveMailbox: React.PropTypes.bool.isRequired, 15 | activeService: React.PropTypes.string.isRequired, 16 | onOpenService: React.PropTypes.func.isRequired 17 | }, 18 | 19 | /* **************************************************************************/ 20 | // Rendering 21 | /* **************************************************************************/ 22 | 23 | shouldComponentUpdate (nextProps, nextState) { 24 | return shallowCompare(this, nextProps, nextState) 25 | }, 26 | 27 | render () { 28 | const { mailbox, isActiveMailbox, activeService, onOpenService, onContextMenu } = this.props 29 | if (!mailbox.hasEnabledServices) { return null } 30 | 31 | return ( 32 |
33 | {mailbox.enabledServies.map((service) => { 34 | return ( 35 | 43 | ) 44 | })} 45 |
46 | ) 47 | } 48 | }) 49 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Sidelist/SidelistItemMailbox/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./SidelistItemMailbox') 2 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Sidelist/SidelistItemNews.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { IconButton, Badge } = require('material-ui') 3 | const Colors = require('material-ui/styles/colors') 4 | const {navigationDispatch} = require('../../Dispatch') 5 | const styles = require('./SidelistStyles') 6 | const ReactTooltip = require('react-tooltip') 7 | const { settingsStore } = require('../../stores/settings') 8 | 9 | module.exports = React.createClass({ 10 | 11 | /* **************************************************************************/ 12 | // Class 13 | /* **************************************************************************/ 14 | 15 | displayName: 'SidelistItemNews', 16 | 17 | /* **************************************************************************/ 18 | // Component Lifecycle 19 | /* **************************************************************************/ 20 | 21 | componentDidMount () { 22 | settingsStore.listen(this.settingsUpdated) 23 | }, 24 | 25 | componentWillUnmount () { 26 | settingsStore.unlisten(this.settingsUpdated) 27 | }, 28 | 29 | /* **************************************************************************/ 30 | // Data Lifecycle 31 | /* **************************************************************************/ 32 | 33 | getInitialState () { 34 | return { 35 | hasUnopenedNewsId: settingsStore.getState().news.hasUnopenedNewsId, 36 | newsLevel: settingsStore.getState().news.newsLevel 37 | } 38 | }, 39 | 40 | settingsUpdated (settingsState) { 41 | this.setState({ 42 | hasUnopenedNewsId: settingsState.news.hasUnopenedNewsId, 43 | newsLevel: settingsState.news.newsLevel 44 | }) 45 | }, 46 | 47 | /* **************************************************************************/ 48 | // UI Events 49 | /* **************************************************************************/ 50 | 51 | handleClick () { 52 | navigationDispatch.openNews() 53 | }, 54 | 55 | /* **************************************************************************/ 56 | // Rendering 57 | /* **************************************************************************/ 58 | 59 | render () { 60 | const { style, ...passProps } = this.props 61 | const { hasUnopenedNewsId, newsLevel } = this.state 62 | return ( 63 |
68 | 72 | {hasUnopenedNewsId && newsLevel === 'notify' ? ( 73 | 78 | ) : undefined} 79 | 84 |
85 | ) 86 | } 87 | }) 88 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Sidelist/SidelistItemSettings.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { IconButton } = require('material-ui') 3 | const Colors = require('material-ui/styles/colors') 4 | const {navigationDispatch} = require('../../Dispatch') 5 | const styles = require('./SidelistStyles') 6 | const ReactTooltip = require('react-tooltip') 7 | 8 | module.exports = React.createClass({ 9 | 10 | /* **************************************************************************/ 11 | // Class 12 | /* **************************************************************************/ 13 | 14 | displayName: 'SidelistItemSettings', 15 | 16 | /* **************************************************************************/ 17 | // Rendering 18 | /* **************************************************************************/ 19 | 20 | /** 21 | * Renders the app 22 | */ 23 | render () { 24 | const { style, ...passProps } = this.props 25 | return ( 26 |
31 | navigationDispatch.openSettings()} 34 | iconStyle={{ color: Colors.blueGrey400 }}> 35 | settings 36 | 37 | 42 |
43 | ) 44 | } 45 | }) 46 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Sidelist/SidelistItemWizard.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { IconButton } = require('material-ui') 3 | const Colors = require('material-ui/styles/colors') 4 | const { appWizardActions } = require('../../stores/appWizard') 5 | const styles = require('./SidelistStyles') 6 | const ReactTooltip = require('react-tooltip') 7 | 8 | module.exports = React.createClass({ 9 | 10 | /* **************************************************************************/ 11 | // Class 12 | /* **************************************************************************/ 13 | 14 | displayName: 'SidelistItemWizard', 15 | 16 | /* **************************************************************************/ 17 | // Rendering 18 | /* **************************************************************************/ 19 | 20 | /** 21 | * Renders the app 22 | */ 23 | render () { 24 | const { style, ...passProps } = this.props 25 | return ( 26 |
31 | appWizardActions.startWizard()} 34 | iconStyle={{ color: Colors.yellow600 }} /> 35 | 40 |
41 | ) 42 | } 43 | }) 44 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Sidelist/SidelistMailboxes.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { mailboxStore } = require('../../stores/mailbox') 3 | const SidelistItemMailbox = require('./SidelistItemMailbox') 4 | 5 | module.exports = React.createClass({ 6 | 7 | /* **************************************************************************/ 8 | // Class 9 | /* **************************************************************************/ 10 | 11 | displayName: 'SidelistMailboxes', 12 | 13 | /* **************************************************************************/ 14 | // Lifecycle 15 | /* **************************************************************************/ 16 | 17 | componentDidMount () { 18 | mailboxStore.listen(this.mailboxesChanged) 19 | }, 20 | 21 | componentWillUnmount () { 22 | mailboxStore.unlisten(this.mailboxesChanged) 23 | }, 24 | 25 | /* **************************************************************************/ 26 | // Data lifecycle 27 | /* **************************************************************************/ 28 | 29 | getInitialState () { 30 | return { 31 | mailboxIds: mailboxStore.getState().mailboxIds() 32 | } 33 | }, 34 | 35 | mailboxesChanged (store) { 36 | this.setState({ 37 | mailboxIds: store.mailboxIds() 38 | }) 39 | }, 40 | 41 | /* **************************************************************************/ 42 | // Rendering 43 | /* **************************************************************************/ 44 | 45 | shouldComponentUpdate (nextProps, nextState) { 46 | if (JSON.stringify(this.state.mailboxIds) !== JSON.stringify(nextState.mailboxIds)) { return true } 47 | return false 48 | }, 49 | 50 | render () { 51 | const { styles, ...passProps } = this.props 52 | const { mailboxIds } = this.state 53 | return ( 54 |
55 | {mailboxIds.map((mailboxId, index, arr) => { 56 | return ( 57 | ) 63 | })} 64 |
65 | ) 66 | } 67 | }) 68 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Sidelist/SidelistStyles.less: -------------------------------------------------------------------------------- 1 | .ReactComponent-Sidelist-Scroller { 2 | &::-webkit-scrollbar { display: none; } 3 | } 4 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Sidelist/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./Sidelist') 2 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/Welcome/Welcome.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { mailboxWizardActions } = require('../../stores/mailboxWizard') 3 | const { RaisedButton, FontIcon } = require('material-ui') 4 | const Colors = require('material-ui/styles/colors') 5 | 6 | const styles = { 7 | icon: { 8 | height: 80, 9 | width: 80, 10 | display: 'inline-block', 11 | marginLeft: 10, 12 | marginRight: 10, 13 | backgroundSize: 'contain', 14 | backgroundPosition: 'center', 15 | backgroundRepeat: 'no-repeat', 16 | backgroundImage: 'url("../../icons/app_512.png")' 17 | }, 18 | container: { 19 | textAlign: 'center', 20 | overflow: 'auto' 21 | }, 22 | heading: { 23 | backgroundColor: Colors.red400, 24 | color: 'white', 25 | paddingTop: 40, 26 | paddingBottom: 20 27 | }, 28 | headingTitle: { 29 | fontWeight: '200', 30 | fontSize: '30px', 31 | marginBottom: 0 32 | }, 33 | headingSubtitle: { 34 | fontWeight: '200', 35 | fontSize: '18px' 36 | }, 37 | setupItem: { 38 | marginTop: 32 39 | }, 40 | setupItemExtended: { 41 | fontSize: '85%', 42 | color: Colors.grey600 43 | } 44 | } 45 | 46 | module.exports = React.createClass({ 47 | 48 | /* **************************************************************************/ 49 | // Class 50 | /* **************************************************************************/ 51 | 52 | displayName: 'Welcome', 53 | 54 | /* **************************************************************************/ 55 | // Rendering 56 | /* **************************************************************************/ 57 | 58 | render () { 59 | return ( 60 |
61 |
62 |
63 |

Welcome to WMail

64 |

...the open-source desktop client for Gmail and Google Inbox

65 |
66 |
67 | mail_outline)} 70 | primary 71 | onClick={() => mailboxWizardActions.openAddMailbox()} /> 72 |

73 | Get started by adding your first Gmail or Google Inbox accout 74 |

75 |
76 |
77 | ) 78 | } 79 | }) 80 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/appContent.less: -------------------------------------------------------------------------------- 1 | #app { 2 | &, .master, .detail, .titlebar { 3 | position: fixed; 4 | top: 0px; 5 | left: 0px; 6 | right: 0px; 7 | bottom: 0px; 8 | } 9 | & { 10 | overflow: hidden; 11 | } 12 | 13 | .titlebar { 14 | bottom: auto; 15 | height: 16px; 16 | -webkit-app-region: drag; 17 | z-index: 100; 18 | } 19 | 20 | .master { 21 | width: 70px; 22 | right: auto; 23 | z-index: 100; 24 | -webkit-app-region: drag; 25 | } 26 | 27 | .detail { 28 | left: 70px; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/appTheme.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Spacing, zIndex } = require('material-ui/styles') 4 | const Colors = require('material-ui/styles/colors') 5 | const colorManipulator = require('material-ui/utils/colorManipulator') 6 | const getMuiTheme = require('material-ui/styles/getMuiTheme').default 7 | 8 | module.exports = getMuiTheme({ 9 | spacing: Spacing, 10 | zIndex: zIndex, 11 | fontFamily: 'Roboto, sans-serif', 12 | palette: { 13 | primary1Color: Colors.lightBlue600, 14 | primary2Color: Colors.lightBlue500, 15 | primary3Color: Colors.blueGrey100, 16 | accent1Color: Colors.redA200, 17 | accent2Color: Colors.grey100, 18 | accent3Color: Colors.grey600, 19 | textColor: Colors.darkBlack, 20 | alternateTextColor: Colors.white, 21 | canvasColor: Colors.white, 22 | borderColor: Colors.grey300, 23 | disabledColor: colorManipulator.fade(Colors.darkBlack, 0.3), 24 | pickerHeaderColor: Colors.cyan500 25 | } 26 | }) 27 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/src/ui/layout.less: -------------------------------------------------------------------------------- 1 | html, body { 2 | position: fixed; 3 | top: 0px; 4 | left: 0px; 5 | right: 0px; 6 | bottom: 0px; 7 | overflow: visible; 8 | } 9 | -------------------------------------------------------------------------------- /src/scenes/mailboxes/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const ROOT_DIR = path.resolve(path.join(__dirname, '../../../')) 3 | const BIN_DIR = path.join(ROOT_DIR, 'bin') 4 | const OUT_DIR = path.join(BIN_DIR, 'scenes/mailboxes') 5 | const devRequire = (n) => require(path.join(ROOT_DIR, 'node_modules', n)) 6 | 7 | const webpack = devRequire('webpack') 8 | const CopyWebpackPlugin = devRequire('copy-webpack-plugin') 9 | const webpackTargetElectronRenderer = devRequire('webpack-target-electron-renderer') 10 | const CleanWebpackPlugin = devRequire('clean-webpack-plugin') 11 | 12 | const isProduction = process.env.NODE_ENV === 'production' 13 | 14 | const options = { 15 | devtool: isProduction ? undefined : (process.env.WEBPACK_DEVTOOL || 'source-map'), 16 | entry: { 17 | mailboxes: [ 18 | path.join(__dirname, 'src') 19 | ] 20 | }, 21 | output: { 22 | path: OUT_DIR, 23 | filename: 'mailboxes.js' 24 | }, 25 | plugins: [ 26 | !isProduction ? undefined : new webpack.DefinePlugin({ 27 | 'process.env': { 28 | 'NODE_ENV': JSON.stringify('production') 29 | } 30 | }), 31 | 32 | // Clean out our bin dir 33 | new CleanWebpackPlugin([path.relative(BIN_DIR, OUT_DIR)], { 34 | root: BIN_DIR, 35 | verbose: true, 36 | dry: false 37 | }), 38 | 39 | // Ignore electron modules and other modules we don't want to compile in 40 | new webpack.IgnorePlugin(new RegExp('^(electron)$')), 41 | 42 | // Copy our static assets 43 | new CopyWebpackPlugin([ 44 | { from: path.join(__dirname, 'src/mailboxes.html'), to: 'mailboxes.html', force: true }, 45 | { from: path.join(__dirname, 'src/offline.html'), to: 'offline.html', force: true }, 46 | { from: path.join(__dirname, 'src/notification.html'), to: 'notification.html', force: true } 47 | ], { 48 | ignore: [ '.DS_Store' ] 49 | }), 50 | 51 | // Minify in production 52 | isProduction ? new webpack.optimize.UglifyJsPlugin({ 53 | compress: { warnings: false } 54 | }) : undefined 55 | ].filter((p) => !!p), 56 | resolve: { 57 | extensions: ['', '.js', '.jsx', '.less', '.css'], 58 | alias: { 59 | shared: path.resolve(path.join(__dirname, '../../shared')) 60 | }, 61 | root: [ 62 | __dirname, 63 | path.resolve(path.join(__dirname, 'src')) 64 | ], 65 | modulesDirectories: [path.join(__dirname, 'node_modules')] 66 | }, 67 | module: { 68 | loaders: [ 69 | { 70 | test: /(\.jsx|\.js)$/, 71 | loader: 'babel', 72 | exclude: /node_modules/, 73 | include: [ 74 | __dirname, 75 | path.resolve(path.join(__dirname, '../../shared')) 76 | ], 77 | query: { 78 | cacheDirectory: true, 79 | presets: ['react', 'stage-0', 'es2015'] 80 | } 81 | }, 82 | { 83 | test: /(\.less|\.css)$/, 84 | loaders: ['style', 'css', 'less'] 85 | }, 86 | { 87 | test: /(\.json)$/, 88 | loader: 'json' 89 | } 90 | ] 91 | } 92 | } 93 | 94 | options.target = webpackTargetElectronRenderer(options) 95 | module.exports = options 96 | -------------------------------------------------------------------------------- /src/scenes/platform/src/nativeRequire.js: -------------------------------------------------------------------------------- 1 | window.nativeRequire = function (name) { 2 | return require(name) 3 | } 4 | 5 | window.remoteRequire = function (name) { 6 | const path = require('path') 7 | const {remote} = require('electron') 8 | return remote.require(path.join(__dirname, '../../app/app/', name)) 9 | } 10 | 11 | window.appNodeModulesRequire = function (name) { 12 | return require('../../app/node_modules/' + name) 13 | } 14 | 15 | window.appPackage = function () { 16 | return require('../../app/package.json') 17 | } 18 | -------------------------------------------------------------------------------- /src/scenes/platform/src/webviewInjection/Browser/Browser.js: -------------------------------------------------------------------------------- 1 | const KeyboardNavigator = require('./KeyboardNavigator') 2 | const Spellchecker = require('./Spellchecker') 3 | const ContextMenu = require('./ContextMenu') 4 | const { ipcRenderer } = require('electron') 5 | 6 | class Browser { 7 | 8 | /* **************************************************************************/ 9 | // Lifecycle 10 | /* **************************************************************************/ 11 | 12 | constructor () { 13 | this.keyboardNavigator = new KeyboardNavigator() 14 | this.spellchecker = new Spellchecker() 15 | this.contextMenu = new ContextMenu(this.spellchecker) 16 | 17 | ipcRenderer.on('get-process-memory-info', (evt, data) => { 18 | ipcRenderer.sendToHost({ 19 | data: process.getProcessMemoryInfo(), 20 | type: data.__respond__ 21 | }) 22 | }) 23 | } 24 | } 25 | 26 | module.exports = Browser 27 | -------------------------------------------------------------------------------- /src/scenes/platform/src/webviewInjection/Browser/DictionaryLoad.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const LanguageSettings = require('../../../../app/shared/Models/Settings/LanguageSettings') 4 | const enUS = require('../../../../app/node_modules/dictionary-en-us') 5 | const pkg = require('../../../../app/package.json') 6 | const AppDirectory = require('../../../../app/node_modules/appdirectory') 7 | 8 | const appDirectory = new AppDirectory(pkg.name).userData() 9 | const userDictionariesPath = LanguageSettings.userDictionariesPath(appDirectory) 10 | 11 | class DictionaryLoad { 12 | /* **************************************************************************/ 13 | // Loader Utils 14 | /* **************************************************************************/ 15 | 16 | /** 17 | * Loads a custom dictionary from disk 18 | * @param language: the language to load 19 | * @return promise 20 | */ 21 | static _loadCustomDictionary_ (language) { 22 | return new Promise((resolve, reject) => { 23 | const tasks = [ 24 | { path: path.join(userDictionariesPath, language + '.aff'), type: 'aff' }, 25 | { path: path.join(userDictionariesPath, language + '.dic'), type: 'dic' } 26 | ].map((desc) => { 27 | return new Promise((resolve, reject) => { 28 | fs.readFile(desc.path, (err, data) => { 29 | err ? reject(Object.assign({ error: err }, desc)) : resolve(Object.assign({ data: data }, desc)) 30 | }) 31 | }) 32 | }) 33 | 34 | Promise.all(tasks) 35 | .then((loaded) => { 36 | const loadObj = loaded.reduce((acc, load) => { 37 | acc[load.type] = load.data 38 | return acc 39 | }, {}) 40 | resolve(loadObj) 41 | }, (err) => { 42 | reject(err) 43 | }) 44 | }) 45 | } 46 | 47 | /** 48 | * Loads an inbuilt language 49 | * @param language: the language to load 50 | * @return promise 51 | */ 52 | static _loadInbuiltDictionary_ (language) { 53 | if (language === 'en_US') { 54 | return new Promise((resolve, reject) => { 55 | enUS((err, load) => { 56 | if (err) { 57 | reject(err) 58 | } else { 59 | resolve({ aff: load.aff, dic: load.dic }) 60 | } 61 | }) 62 | }) 63 | } else { 64 | return Promise.reject(new Error('Unknown Dictionary')) 65 | } 66 | } 67 | 68 | /* **************************************************************************/ 69 | // Loaders 70 | /* **************************************************************************/ 71 | 72 | /** 73 | * Loads a dictionary 74 | * @param language: the language to load 75 | * @return promise 76 | */ 77 | static load (language) { 78 | return new Promise((resolve, reject) => { 79 | DictionaryLoad._loadInbuiltDictionary_(language).then( 80 | (dic) => resolve(dic), 81 | (_err) => { 82 | DictionaryLoad._loadCustomDictionary_(language).then( 83 | (dic) => resolve(dic), 84 | (_err) => reject(new Error('Unknown Dictionary')) 85 | ) 86 | } 87 | ) 88 | }) 89 | } 90 | } 91 | 92 | module.exports = DictionaryLoad 93 | -------------------------------------------------------------------------------- /src/scenes/platform/src/webviewInjection/Browser/KeyboardNavigator.js: -------------------------------------------------------------------------------- 1 | const injector = require('../injector') 2 | 3 | class KeyboardNavigator { 4 | /* **************************************************************************/ 5 | // Lifecycle 6 | /* **************************************************************************/ 7 | 8 | constructor () { 9 | injector.injectBodyEvent('keydown', this._handleKeydown_) 10 | } 11 | 12 | /* **************************************************************************/ 13 | // Event handlers 14 | /* **************************************************************************/ 15 | 16 | _handleKeydown_ (evt) { 17 | if (evt.keyCode === 8) { // Backspace 18 | // Look for reasons to cancel 19 | if (evt.target.tagName === 'INPUT') { return } 20 | if (evt.target.tagName === 'TEXTAREA') { return } 21 | if (evt.target.tagName === 'SELECT') { return } 22 | if (evt.target.tagName === 'OPTION') { return } 23 | if (evt.path.findIndex((e) => e.getAttribute && e.getAttribute('contentEditable') === 'true') !== -1) { return } 24 | 25 | window.history.back() 26 | } 27 | } 28 | } 29 | 30 | module.exports = KeyboardNavigator 31 | -------------------------------------------------------------------------------- /src/scenes/platform/src/webviewInjection/Google/GinboxApi.js: -------------------------------------------------------------------------------- 1 | const escapeHTML = require('../../../../app/node_modules/escape-html') 2 | 3 | class GinboxApi { 4 | 5 | /** 6 | * @return true if the API is ready 7 | */ 8 | static isReady () { return document.readyState === 'complete' } 9 | 10 | /** 11 | * Gets the visible unread count. Ensures that clusters are only counted once/ 12 | * May throw a dom exception if things go wrong 13 | * @return the unread count 14 | */ 15 | static getVisibleUnreadCount () { 16 | const unread = Array.from(document.querySelectorAll('[data-item-id] [email]')).reduce((acc, elm) => { 17 | const isUnread = elm.tagName !== 'IMG' && window.getComputedStyle(elm).fontWeight === 'bold' 18 | if (isUnread) { 19 | const clusterElm = elm.closest('[data-item-id^="#clusters"]') 20 | if (clusterElm) { 21 | acc.openClusters.add(clusterElm) 22 | } else { 23 | acc.messages.add(elm) 24 | } 25 | } 26 | return acc 27 | }, { messages: new Set(), openClusters: new Set() }) 28 | return unread.messages.size + unread.openClusters.size 29 | } 30 | 31 | /** 32 | * Checks if the inbox tab is visble 33 | * May throw a dom exception if things go wrong 34 | * @return true or false 35 | */ 36 | static isInboxTabVisible () { 37 | const elm = document.querySelector('nav [role="menuitem"]') // The first item 38 | return window.getComputedStyle(elm).backgroundColor.substr(-4) !== ', 0)' 39 | } 40 | 41 | /** 42 | * Checks if the pinned setting is toggled 43 | * May throw a dom exception if things go wrong 44 | * @return true or false 45 | */ 46 | static isInboxPinnedToggled () { 47 | const elm = document.querySelector('[jsaction="global.toggle_pinned"]') 48 | return elm ? elm.getAttribute('aria-pressed') === 'true' : false 49 | } 50 | 51 | /** 52 | * Handles opening the compose ui and prefills relevant items 53 | * @param data: the data that was sent with the event 54 | */ 55 | static composeMessage (data) { 56 | const composeButton = document.querySelector('button.y.hC') || document.querySelector('[jsaction="jsl._"]') 57 | if (!composeButton) { return } 58 | composeButton.click() 59 | 60 | setTimeout(() => { 61 | // Grab elements 62 | const bodyEl = document.querySelector('[g_editable="true"][role="textbox"]') 63 | if (!bodyEl) { return } 64 | const dialogEl = bodyEl.closest('[role="dialog"]') 65 | if (!dialogEl) { return } 66 | const recipientEl = dialogEl.querySelector('input') // first input 67 | const subjectEl = dialogEl.querySelector('[jsaction*="subject"]') 68 | let focusableEl 69 | 70 | // Recipient 71 | if (data.recipient && recipientEl) { 72 | recipientEl.value = escapeHTML(data.recipient) 73 | focusableEl = subjectEl 74 | } 75 | 76 | // Subject 77 | if (data.subject && subjectEl) { 78 | subjectEl.value = escapeHTML(data.subject) 79 | focusableEl = bodyEl 80 | } 81 | 82 | // Body 83 | if (data.body && bodyEl) { 84 | bodyEl.innerHTML = escapeHTML(data.body) + bodyEl.innerHTML 85 | const labelEl = bodyEl.parentElement.querySelector('label') 86 | if (labelEl) { labelEl.style.display = 'none' } 87 | focusableEl = bodyEl 88 | } 89 | 90 | if (focusableEl) { 91 | setTimeout(() => focusableEl.focus(), 500) 92 | } 93 | }) 94 | } 95 | } 96 | 97 | module.exports = GinboxApi 98 | -------------------------------------------------------------------------------- /src/scenes/platform/src/webviewInjection/Google/GinboxChangeEmitter.js: -------------------------------------------------------------------------------- 1 | const {ipcRenderer} = require('electron') 2 | const injector = require('../injector') 3 | const GinboxApi = require('./GinboxApi') 4 | const MAX_MESSAGE_HASH_TIME = 1000 * 60 * 10 // 10 mins 5 | 6 | class GinboxChangeEmitter { 7 | 8 | /* **************************************************************************/ 9 | // Lifecycle 10 | /* **************************************************************************/ 11 | 12 | constructor () { 13 | this.state = { 14 | messageHash: this.currentMessageHash, 15 | messageHashTime: new Date().getTime() 16 | } 17 | 18 | this.latestMessageInterval = setInterval(this.recheckMessageHash.bind(this), 2000) 19 | this.clickThrottle = null 20 | injector.injectBodyEvent('click', this.handleBodyClick) 21 | } 22 | 23 | /* **************************************************************************/ 24 | // Properties 25 | /* **************************************************************************/ 26 | 27 | get currentMessageHash () { 28 | const topItem = document.querySelector('[data-item-id]') 29 | const topHash = topItem ? topItem.getAttribute('data-item-id') : '_' 30 | const unreadCount = GinboxApi.getVisibleUnreadCount() 31 | return unreadCount + ':' + topHash 32 | } 33 | 34 | /* **************************************************************************/ 35 | // Event Handlers 36 | /* **************************************************************************/ 37 | 38 | /** 39 | * Re-checks the top message id and fires an unread-count-changed event when 40 | * changed 41 | */ 42 | recheckMessageHash () { 43 | const now = new Date().getTime() 44 | const nextMessageHash = this.currentMessageHash 45 | if (this.state.messageHash !== nextMessageHash || now - this.state.messageHashTime > MAX_MESSAGE_HASH_TIME) { 46 | this.state.messageHash = nextMessageHash 47 | this.state.messageHashTime = now 48 | clearTimeout(this.clickThrottle) 49 | ipcRenderer.sendToHost({ 50 | type: 'unread-count-changed', 51 | data: { trigger: 'periodic-diff' } 52 | }) 53 | } 54 | } 55 | 56 | /** 57 | * Adds a click event into the body which fires off an unread-count-changed 58 | * event rather lazily to catch the message hash not working correctly 59 | */ 60 | handleBodyClick () { 61 | clearTimeout(this.clickThrottle) 62 | this.clickThrottle = setTimeout(() => { 63 | ipcRenderer.sendToHost({ 64 | type: 'unread-count-changed', 65 | data: { trigger: 'delayed-click' } 66 | }) 67 | }, 10000) 68 | } 69 | } 70 | 71 | module.exports = GinboxChangeEmitter 72 | -------------------------------------------------------------------------------- /src/scenes/platform/src/webviewInjection/Google/GmailApiExtras.js: -------------------------------------------------------------------------------- 1 | const escapeHTML = require('../../../../app/node_modules/escape-html') 2 | 3 | class GmailApiExtras { 4 | /** 5 | * Handles opening the compose ui and prefills relevant items 6 | * @param gmailApi: the gmail api 7 | * @param data: the data that was sent with the event 8 | */ 9 | static composeMessage (gmailApi, data) { 10 | if (!gmailApi) { return } 11 | 12 | gmailApi.compose.start_compose() 13 | 14 | if (data.recipient || data.subject || data.body) { 15 | setTimeout(() => { 16 | // Grab elements 17 | const subjectEl = Array.from(document.querySelectorAll('[name="subjectbox"]')).slice(-1)[0] 18 | if (!subjectEl) { return } 19 | const dialogEl = subjectEl.closest('[role="dialog"]') 20 | if (!dialogEl) { return } 21 | const bodyEl = dialogEl.querySelector('[g_editable="true"][role="textbox"]') 22 | const recipientEl = dialogEl.querySelector('[name="to"]') 23 | let focusableEl 24 | 25 | // Recipient 26 | if (data.recipient && recipientEl) { 27 | recipientEl.value = escapeHTML(data.recipient) 28 | focusableEl = subjectEl 29 | } 30 | 31 | // Subject 32 | if (data.subject && subjectEl) { 33 | subjectEl.value = escapeHTML(data.subject) 34 | focusableEl = bodyEl 35 | } 36 | 37 | // Body 38 | if (data.body && bodyEl) { 39 | bodyEl.innerHTML = escapeHTML(data.body) + bodyEl.innerHTML 40 | focusableEl = bodyEl 41 | } 42 | 43 | if (focusableEl) { 44 | setTimeout(() => focusableEl.focus(), 500) 45 | } 46 | }) 47 | } 48 | } 49 | } 50 | 51 | module.exports = GmailApiExtras 52 | -------------------------------------------------------------------------------- /src/scenes/platform/src/webviewInjection/Google/GmailChangeEmitter.js: -------------------------------------------------------------------------------- 1 | const {ipcRenderer} = require('electron') 2 | const MAX_MESSAGE_HASH_TIME = 1000 * 60 * 10 // 10 mins 3 | 4 | class GmailChangeEmitter { 5 | 6 | /* **************************************************************************/ 7 | // Lifecycle 8 | /* **************************************************************************/ 9 | 10 | /** 11 | * @param gmailApi: the gmail api instance 12 | */ 13 | constructor (gmailApi) { 14 | this.gmailApi = gmailApi 15 | this.state = { 16 | count: this.gmailApi.get.unread_inbox_emails(), 17 | countTime: new Date().getTime() 18 | } 19 | 20 | this.gmailApi.observe.on('http_event', this.handleHTTPEvent.bind(this)) 21 | } 22 | 23 | /* **************************************************************************/ 24 | // Event Handlers 25 | /* **************************************************************************/ 26 | 27 | /** 28 | * Handles gmail firing a http event by checking if the unread count has changed 29 | * and passing this event up across the bridge 30 | */ 31 | handleHTTPEvent () { 32 | const now = new Date().getTime() 33 | const nextCount = this.gmailApi.get.unread_inbox_emails() 34 | if (this.state.count !== nextCount || now - this.state.messageHashTime > MAX_MESSAGE_HASH_TIME) { 35 | this.state.count = nextCount 36 | this.state.countTime = now 37 | ipcRenderer.sendToHost({ 38 | type: 'unread-count-changed', 39 | data: { trigger: 'http-event' } 40 | }) 41 | } 42 | } 43 | } 44 | 45 | module.exports = GmailChangeEmitter 46 | -------------------------------------------------------------------------------- /src/scenes/platform/src/webviewInjection/Google/GoogleService.js: -------------------------------------------------------------------------------- 1 | const injector = require('../injector') 2 | const Browser = require('../Browser/Browser') 3 | const WMail = require('../WMail/WMail') 4 | 5 | class GoogleService { 6 | 7 | /* **************************************************************************/ 8 | // Lifecycle 9 | /* **************************************************************************/ 10 | 11 | constructor () { 12 | this.browser = new Browser() 13 | this.wmail = new WMail() 14 | 15 | injector.injectStyle(` 16 | a[href*="/SignOutOptions"] { 17 | visibility: hidden !important; 18 | } 19 | `) 20 | } 21 | } 22 | 23 | module.exports = GoogleService 24 | -------------------------------------------------------------------------------- /src/scenes/platform/src/webviewInjection/Google/GoogleWindowOpen.js: -------------------------------------------------------------------------------- 1 | const ipcRenderer = require('electron').ipcRenderer 2 | 3 | class GoogleWindowOpen { 4 | /* **************************************************************************/ 5 | // Lifecycle 6 | /* **************************************************************************/ 7 | 8 | constructor () { 9 | this.gmailApi = undefined 10 | 11 | // Inject into the main window 12 | this.injectWindow(window) 13 | document.addEventListener('DOMContentLoaded', function () { 14 | const frames = document.querySelectorAll('iframe') 15 | for (let i = 0; i < frames.length; i++) { 16 | try { 17 | this.injectWindow(frames[i].contentWindow) 18 | } catch (ex) { } 19 | } 20 | }, false) 21 | } 22 | 23 | /* **************************************************************************/ 24 | // Utils 25 | /* **************************************************************************/ 26 | 27 | /** 28 | * @param url: the url to get the qs argument from 29 | * @param key: the key of the value to get 30 | * @param defaultValue=undefined: the default value to return 31 | * @return the string value 32 | */ 33 | getQSArg (url, key, defaultValue = undefined) { 34 | const regex = new RegExp('[\\?&]' + key + '=([^&#]*)') 35 | const results = regex.exec(url) 36 | return results === null ? defaultValue : results[1] 37 | } 38 | 39 | /* **************************************************************************/ 40 | // Injection 41 | /* **************************************************************************/ 42 | 43 | /** 44 | * Injects the window.open polyfill. Gmail opens new windows. They do.... 45 | * w = window.open("", "_blank", "") 46 | * w.document.write('') 47 | * this proxies that request and sends it up to be opened 48 | * @param w: the window to inject into 49 | */ 50 | injectWindow (w) { 51 | const defaultfn = w.open 52 | const handlerInst = this 53 | w.open = function () { 54 | // Open message in new window -- old style 55 | if (arguments[0] === '' && arguments[1] === '_blank') { 56 | return { 57 | document: { 58 | write: function (value) { 59 | const parser = new window.DOMParser() 60 | const xml = parser.parseFromString(value, 'text/xml') 61 | if (xml.firstChild && xml.firstChild.getAttribute('HTTP-EQUIV') === 'refresh') { 62 | const content = xml.firstChild.getAttribute('content') 63 | const url = content.replace('0; url=', '') 64 | ipcRenderer.sendToHost({ type: 'js-new-window', url: url }) 65 | } 66 | } 67 | } 68 | } 69 | } else if (handlerInst.gmailApi && arguments[0].indexOf('ui=2') !== -1 && arguments[0].indexOf('view=btop') !== -1) { 70 | const ik = handlerInst.gmailApi.tracker.ik 71 | const currentUrlMsgId = window.location.hash.split('/').pop().replace(/#/, '').split('?')[0] 72 | const th = handlerInst.getQSArg(arguments[0], 'th', currentUrlMsgId) 73 | 74 | ipcRenderer.sendToHost({ 75 | type: 'js-new-window', 76 | url: 'https://mail.google.com/mail?ui=2&view=lg&ik=' + ik + '&msg=' + th 77 | }) 78 | return { closed: false, focus: function () { } } 79 | } else { 80 | return defaultfn.apply(this, Array.from(arguments)) 81 | } 82 | } 83 | } 84 | } 85 | 86 | module.exports = GoogleWindowOpen 87 | -------------------------------------------------------------------------------- /src/scenes/platform/src/webviewInjection/WMail/CustomCode.js: -------------------------------------------------------------------------------- 1 | const ipcRenderer = require('electron').ipcRenderer 2 | const injector = require('../injector') 3 | 4 | class CustomCode { 5 | constructor () { 6 | ipcRenderer.on('inject-custom-content', this._handleInject_.bind(this)) 7 | } 8 | 9 | _handleInject_ (evt, data) { 10 | if (data.js) { 11 | injector.injectJavaScript(data.js) 12 | } 13 | if (data.css) { 14 | injector.injectStyle(data.css) 15 | } 16 | } 17 | } 18 | 19 | module.exports = CustomCode 20 | -------------------------------------------------------------------------------- /src/scenes/platform/src/webviewInjection/WMail/WMail.js: -------------------------------------------------------------------------------- 1 | const CustomCode = require('./CustomCode') 2 | 3 | class WMail { 4 | constructor () { 5 | this.customCode = new CustomCode() 6 | } 7 | } 8 | 9 | module.exports = WMail 10 | -------------------------------------------------------------------------------- /src/scenes/platform/src/webviewInjection/elconsole.js: -------------------------------------------------------------------------------- 1 | const ipcRenderer = require('electron').ipcRenderer 2 | 3 | class ELConsole { 4 | /** 5 | * Logs the supplied arguments and also logs them to the parent frame 6 | */ 7 | log () { 8 | ipcRenderer.sendToHost({ 9 | type: 'elevated-log', 10 | messages: Array.from(arguments) 11 | }) 12 | console.log.apply(this, Array.from(arguments)) 13 | } 14 | 15 | /** 16 | * Logs the supplied arguments as errors and also logs them to the parent frame 17 | */ 18 | error () { 19 | ipcRenderer.sendToHost({ 20 | type: 'elevated-error', 21 | messages: Array.from(arguments) 22 | }) 23 | console.error.apply(this, Array.from(arguments)) 24 | } 25 | } 26 | 27 | module.exports = new ELConsole() 28 | -------------------------------------------------------------------------------- /src/scenes/platform/src/webviewInjection/googleMail.js: -------------------------------------------------------------------------------- 1 | const elconsole = require('./elconsole') 2 | try { 3 | const GoogleMail = require('./Google/GoogleMail') 4 | /*eslint-disable */ 5 | const googleMail = new GoogleMail() 6 | /*eslint-enable */ 7 | } catch (ex) { 8 | elconsole.error('Error', ex) 9 | } 10 | -------------------------------------------------------------------------------- /src/scenes/platform/src/webviewInjection/googleService.js: -------------------------------------------------------------------------------- 1 | const elconsole = require('./elconsole') 2 | try { 3 | const GoogleService = require('./Google/GoogleService') 4 | /*eslint-disable */ 5 | const googleService = new GoogleService() 6 | /*eslint-enable */ 7 | } catch (ex) { 8 | elconsole.error('Error', ex) 9 | } 10 | -------------------------------------------------------------------------------- /src/scenes/platform/src/webviewInjection/injector.js: -------------------------------------------------------------------------------- 1 | class Injector { 2 | /* **************************************************************************/ 3 | // Lifecycle 4 | /* **************************************************************************/ 5 | 6 | constructor () { 7 | this.scripts = { pending: [], interval: null } 8 | this.bodyEvents = { pending: [], interval: null } 9 | } 10 | 11 | /* **************************************************************************/ 12 | // Script injection 13 | /* **************************************************************************/ 14 | 15 | /** 16 | * Injects an element into the dom 17 | * @param el: the element to inject 18 | * @param callback=undefined: executed when injected 19 | */ 20 | injectScriptElement (el, callback) { 21 | if (document.head) { 22 | document.head.appendChild(el) 23 | if (callback) { callback() } 24 | } else { 25 | this.scripts.pending.push([el, callback]) 26 | if (this.scripts.interval === null) { 27 | this.scripts.interval = setInterval(() => { 28 | if (document.head) { 29 | clearInterval(this.scripts.interval) 30 | this.scripts.interval = null 31 | this.scripts.pending.forEach((inf) => { 32 | document.head.appendChild(inf[0]) 33 | if (inf[1]) { inf[1]() } 34 | }) 35 | this.scripts.pending = [] 36 | } 37 | }, 10) 38 | } 39 | } 40 | } 41 | 42 | /** 43 | * Injects javascript into the head 44 | * @param js: the js code to inject 45 | * @param callback=undefined: executed when injected 46 | */ 47 | injectJavaScript (js, callback) { 48 | const el = document.createElement('script') 49 | el.type = 'text/javascript' 50 | el.innerHTML = js 51 | this.injectScriptElement(el, callback) 52 | } 53 | 54 | /** 55 | * Injects a stylesheet into the head 56 | * @param css: the css code to inject 57 | * @param callback=undefined: executed when injected 58 | */ 59 | injectStyle (css, callback) { 60 | const el = document.createElement('style') 61 | el.innerHTML = css 62 | this.injectScriptElement(el, callback) 63 | } 64 | 65 | /* **************************************************************************/ 66 | // Event injection 67 | /* **************************************************************************/ 68 | 69 | /** 70 | * Injects a body event listener 71 | * @param eventName: the name of the event 72 | * @param fn: the function to call 73 | */ 74 | injectBodyEvent (eventName, fn) { 75 | if (document.body) { 76 | document.body.addEventListener(eventName, fn, false) 77 | } else { 78 | this.bodyEvents.pending.push([eventName, fn]) 79 | if (this.bodyEvents.interval === null) { 80 | this.bodyEvents.interval = setInterval(() => { 81 | if (document.body) { 82 | clearInterval(this.bodyEvents.interval) 83 | this.bodyEvents.interval = null 84 | this.bodyEvents.pending.forEach((inf) => { 85 | document.body.addEventListener(inf[0], inf[1], false) 86 | }) 87 | this.bodyEvents.pending = [] 88 | } 89 | }, 100) 90 | } 91 | } 92 | } 93 | } 94 | 95 | module.exports = new Injector() 96 | -------------------------------------------------------------------------------- /src/scenes/platform/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const ROOT_DIR = path.resolve(path.join(__dirname, '../../../')) 3 | const BIN_DIR = path.join(ROOT_DIR, 'bin') 4 | const OUT_DIR = path.join(BIN_DIR, 'scenes/platform') 5 | const devRequire = (n) => require(path.join(ROOT_DIR, 'node_modules', n)) 6 | 7 | const CleanWebpackPlugin = devRequire('clean-webpack-plugin') 8 | const CopyWebpackPlugin = devRequire('copy-webpack-plugin') 9 | 10 | module.exports = { 11 | output: { 12 | path: OUT_DIR, 13 | filename: '__.js' 14 | }, 15 | plugins: [ 16 | new CleanWebpackPlugin([path.relative(BIN_DIR, OUT_DIR)], { 17 | root: BIN_DIR, 18 | verbose: true, 19 | dry: false 20 | }), 21 | new CopyWebpackPlugin([ 22 | { from: path.join(__dirname, 'src'), to: '', force: true } 23 | ], { 24 | ignore: [ '.DS_Store' ] 25 | }) 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/shared/Models/Mailbox/MailboxServices.js: -------------------------------------------------------------------------------- 1 | // Try to keep these generic to support n-types of account 2 | module.exports = Object.freeze({ 3 | DEFAULT: 'mail', 4 | STORAGE: 'storage', // google drive 5 | CONTACTS: 'contacts', // google contacts 6 | NOTES: 'notes', // google keep 7 | CALENDAR: 'calendar', // google calendar 8 | COMMUNICATION: 'communication' // google hangouts 9 | }) 10 | -------------------------------------------------------------------------------- /src/shared/Models/Mailbox/MailboxTypes.js: -------------------------------------------------------------------------------- 1 | module.exports = Object.freeze({ 2 | GMAIL: 'gmail', 3 | GINBOX: 'ginbox' 4 | }) 5 | -------------------------------------------------------------------------------- /src/shared/Models/Mailbox/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Google: require('./Google'), 3 | Mailbox: require('./Mailbox') 4 | } 5 | -------------------------------------------------------------------------------- /src/shared/Models/Model.js: -------------------------------------------------------------------------------- 1 | class Model { 2 | 3 | /* **************************************************************************/ 4 | // Lifecycle 5 | /* **************************************************************************/ 6 | 7 | constructor (data) { 8 | this.__data__ = Object.freeze(JSON.parse(JSON.stringify(data))) 9 | } 10 | 11 | /* **************************************************************************/ 12 | // Cloning 13 | /* **************************************************************************/ 14 | 15 | /** 16 | * Clones a copy of the data 17 | * @return a new copy of the data copied deeply. 18 | */ 19 | cloneData () { 20 | return JSON.parse(JSON.stringify(this.__data__)) 21 | } 22 | 23 | /** 24 | * Clones the data and merges the given changeset 25 | * @param changeset: { k->v } to merge. Only 1 level deep 26 | */ 27 | changeData (changeset) { 28 | return Object.assign(this.cloneData(), changeset) 29 | } 30 | 31 | /* **************************************************************************/ 32 | // Utils 33 | /* **************************************************************************/ 34 | 35 | /** 36 | * @param the key to get 37 | * @param defaultValue: the value to return if undefined 38 | * @return the value or defaultValue 39 | */ 40 | _value_ (key, defaultValue) { 41 | return this.__data__[key] === undefined ? defaultValue : this.__data__[key] 42 | } 43 | } 44 | 45 | module.exports = Model 46 | -------------------------------------------------------------------------------- /src/shared/Models/Settings/AppSettings.js: -------------------------------------------------------------------------------- 1 | const Model = require('../Model') 2 | 3 | class AppSettings extends Model { 4 | get ignoreGPUBlacklist () { return this._value_('ignoreGPUBlacklist', true) } 5 | get disableSmoothScrolling () { return this._value_('disableSmoothScrolling', false) } 6 | get enableUseZoomForDSF () { return this._value_('enableUseZoomForDSF', true) } 7 | get checkForUpdates () { return this._value_('checkForUpdates', true) } 8 | get hasSeenAppWizard () { return this._value_('hasSeenAppWizard', false) } 9 | } 10 | 11 | module.exports = AppSettings 12 | -------------------------------------------------------------------------------- /src/shared/Models/Settings/LanguageSettings.js: -------------------------------------------------------------------------------- 1 | const Model = require('../Model') 2 | const path = require('path') 3 | 4 | class LanguageSettings extends Model { 5 | 6 | /* ****************************************************************************/ 7 | // Class 8 | /* ****************************************************************************/ 9 | 10 | /** 11 | * @param root: the root directory of the app 12 | * @return the paths to add user dictionaries 13 | */ 14 | static userDictionariesPath (root) { return path.join(root, 'user_dictionaries') } 15 | static get defaultSpellcheckerLanguage () { return 'en_US' } 16 | 17 | /* ****************************************************************************/ 18 | // Properties: Spellchecker 19 | /* ****************************************************************************/ 20 | 21 | get spellcheckerEnabled () { return this._value_('spellcheckerEnabled', true) } 22 | get spellcheckerLanguage () { return this._value_('spellcheckerLanguage', LanguageSettings.defaultSpellcheckerLanguage) } 23 | get hasSecondarySpellcheckerLanguage () { return this.secondarySpellcheckerLanguage === null } 24 | get secondarySpellcheckerLanguage () { return this._value_('secondarySpellcheckerLanguage', null) } 25 | 26 | } 27 | 28 | module.exports = LanguageSettings 29 | -------------------------------------------------------------------------------- /src/shared/Models/Settings/NewsSettings.js: -------------------------------------------------------------------------------- 1 | const Model = require('../Model') 2 | 3 | class NewsSettings extends Model { 4 | get newsId () { return this._value_('newsId', 0) } 5 | get newsLevel () { return this._value_('newsLevel', 'none') } 6 | get newsFeed () { return this._value_('newsFeed', undefined) } 7 | get openedNewsId () { return this._value_('openedNewsId', 0) } 8 | 9 | get hasUpdateInfo () { return !!this.newsFeed } 10 | get hasUnopenedNewsId () { return this.openedNewsId < this.newsId } 11 | get showNewsInSidebar () { return this._value_('showNewsInSidebar', true) } 12 | } 13 | 14 | module.exports = NewsSettings 15 | -------------------------------------------------------------------------------- /src/shared/Models/Settings/OSSettings.js: -------------------------------------------------------------------------------- 1 | const Model = require('../Model') 2 | 3 | class OSSettings extends Model { 4 | get alwaysAskDownloadLocation () { return this._value_('alwaysAskDownloadLocation', true) } 5 | get defaultDownloadLocation () { return this._value_('defaultDownloadLocation', undefined) } 6 | get notificationsEnabled () { return this._value_('notificationsEnabled', true) } 7 | get notificationsSilent () { return this._value_('notificationsSilent', false) } 8 | get openLinksInBackground () { return this._value_('openLinksInBackground', false) } 9 | } 10 | 11 | module.exports = OSSettings 12 | -------------------------------------------------------------------------------- /src/shared/Models/Settings/ProxySettings.js: -------------------------------------------------------------------------------- 1 | const Model = require('../Model') 2 | 3 | class ProxySettings extends Model { 4 | get enabled () { return this._value_('enabled', false) } 5 | get host () { return this.__data__.host } 6 | get port () { return this.__data__.port } 7 | get url () { return this.host + ':' + this.port } 8 | } 9 | 10 | module.exports = ProxySettings 11 | -------------------------------------------------------------------------------- /src/shared/Models/Settings/SettingsIdent.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | SEGMENTS: { 3 | APP: 'app', 4 | LANGUAGE: 'language', 5 | NEWS: 'news', 6 | OS: 'os', 7 | PROXY: 'proxy', 8 | TRAY: 'tray', 9 | UI: 'ui' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/shared/Models/Settings/TraySettings.js: -------------------------------------------------------------------------------- 1 | const Model = require('../Model') 2 | 3 | class TraySettings extends Model { 4 | 5 | /* **************************************************************************/ 6 | // Lifecycle 7 | /* **************************************************************************/ 8 | 9 | /** 10 | * @param data: the tray data 11 | * @param themedDefaults: the default values for the tray 12 | */ 13 | constructor (data, themedDefaults = {}) { 14 | super(data) 15 | this.__themedDefaults__ = Object.freeze(Object.assign({}, themedDefaults)) 16 | } 17 | 18 | /* **************************************************************************/ 19 | // Properties 20 | /* **************************************************************************/ 21 | 22 | get show () { return this._value_('show', true) } 23 | get showUnreadCount () { return this._value_('showUnreadCount', true) } 24 | get readColor () { return this._value_('readColor', this.__themedDefaults__.readColor) } 25 | get readBackgroundColor () { return this._value_('readBackgroundColor', this.__themedDefaults__.readBackgroundColor) } 26 | get unreadColor () { return this._value_('unreadColor', this.__themedDefaults__.unreadColor) } 27 | get unreadBackgroundColor () { return this._value_('unreadBackgroundColor', this.__themedDefaults__.unreadBackgroundColor) } 28 | 29 | get dpiMultiplier () { 30 | let defaultValue = 1 31 | try { 32 | defaultValue = window.devicePixelRatio 33 | } catch (ex) { } 34 | return this._value_('dpiMultiplier', defaultValue) 35 | } 36 | } 37 | 38 | module.exports = TraySettings 39 | -------------------------------------------------------------------------------- /src/shared/Models/Settings/UISettings.js: -------------------------------------------------------------------------------- 1 | const Model = require('../Model') 2 | 3 | class UISettings extends Model { 4 | get showTitlebar () { return this._value_('showTitlebar', false) } 5 | get showAppBadge () { return this._value_('showAppBadge', true) } 6 | get showTitlebarCount () { return this._value_('showTitlebarCount', true) } 7 | get showAppMenu () { return this._value_('showAppMenu', process.platform !== 'win32') } 8 | get sidebarEnabled () { return this._value_('sidebarEnabled', true) } 9 | get openHidden () { return this._value_('openHidden', false) } 10 | } 11 | 12 | module.exports = UISettings 13 | -------------------------------------------------------------------------------- /src/shared/Models/Settings/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | AppSettings: require('./AppSettings'), 3 | LanguageSettings: require('./LanguageSettings'), 4 | NewsSettings: require('./NewsSettings'), 5 | OSSettings: require('./OSSettings'), 6 | ProxySettings: require('./ProxySettings'), 7 | TraySettings: require('./TraySettings'), 8 | UISettings: require('./UISettings'), 9 | 10 | SettingsIdent: require('./SettingsIdent') 11 | } 12 | -------------------------------------------------------------------------------- /src/shared/Models/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Mailbox: require('./Mailbox'), 3 | Settings: require('./Settings') 4 | } 5 | -------------------------------------------------------------------------------- /src/shared/b64Assets.js: -------------------------------------------------------------------------------- 1 | module.exports = Object.freeze({ 2 | BLANK_PNG: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAAtJREFUCB1jYAACAAAFAAGNu5vzAAAAAElFTkSuQmCC', 3 | MAIL_SVG: 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjMDAwMDAwIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPiAgICA8cGF0aCBkPSJNMjAgNEg0Yy0xLjEgMC0xLjk5LjktMS45OSAyTDIgMThjMCAxLjEuOSAyIDIgMmgxNmMxLjEgMCAyLS45IDItMlY2YzAtMS4xLS45LTItMi0yem0wIDE0SDRWOGw4IDUgOC01djEwem0tOC03TDQgNmgxNmwtOCA1eiIvPjwvc3ZnPg==' 4 | }) 5 | -------------------------------------------------------------------------------- /src/shared/constants.js: -------------------------------------------------------------------------------- 1 | module.exports = Object.freeze({ 2 | APP_ID: 'tombeverley.wmail', 3 | 4 | MAILBOX_INDEX_KEY: '__index__', 5 | MAILBOX_SLEEP_WAIT: 1000 * 30, // 30 seconds 6 | 7 | WEB_URL: 'https://thomas101.github.io/wmail/', 8 | GITHUB_URL: 'https://github.com/Thomas101/wmail/', 9 | GITHUB_ISSUE_URL: 'https://github.com/Thomas101/wmail/issues', 10 | UPDATE_DOWNLOAD_URL: 'http://thomas101.github.io/wmail/download', 11 | UPDATE_CHECK_URL: 'https://thomas101.github.io/wmail/version.json', 12 | PRIVACY_URL: 'https://thomas101.github.io/wmail/privacy', 13 | USER_SCRIPTS_WEB_URL: 'https://github.com/Thomas101/wmail-user-scripts', 14 | UPDATE_CHECK_INTERVAL: 1000 * 60 * 60 * 24, // 24 hours 15 | 16 | GMAIL_PROFILE_SYNC_MS: 1000 * 60 * 60, // 60 mins 17 | GMAIL_UNREAD_SYNC_MS: 1000 * 60, // 60 seconds 18 | GMAIL_NOTIFICATION_MAX_MESSAGE_AGE_MS: 1000 * 60 * 60 * 2, // 2 hours 19 | GMAIL_NOTIFICATION_FIRST_RUN_GRACE_MS: 1000 * 30, // 30 seconds 20 | 21 | REFOCUS_MAILBOX_INTERVAL_MS: 300, 22 | 23 | DB_EXTENSION: 'wmaildb', 24 | DB_WRITE_DELAY_MS: 500 // 0.5secs 25 | }) 26 | -------------------------------------------------------------------------------- /src/shared/dictionaryExcludes.js: -------------------------------------------------------------------------------- 1 | const EN = new Set([ 2 | 'ain', 3 | 'can', 4 | 'couldn', 5 | 'didn', 6 | 'don', 7 | 'doesn', 8 | 'hadn', 9 | 'hasn', 10 | 'havn', 11 | 'isn', 12 | 'mightn', 13 | 'mustn', 14 | 'needn', 15 | 'oughtn', 16 | 'shan', 17 | 'shouldn', 18 | 'wasn', 19 | 'weren', 20 | 'won', 21 | 'wouldn' 22 | ]) 23 | 24 | module.exports = { 25 | en_AU: EN, 26 | en_CA: EN, 27 | en_GB: EN, 28 | en_US: EN, 29 | en_ZA: EN 30 | } 31 | -------------------------------------------------------------------------------- /src/shared/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | b64Assets: require('./b64Assets'), 3 | credentials: require('./credentials'), 4 | constants: require('./constants'), 5 | Models: require('./Models'), 6 | dictionaries: require('./dictionaries.js') 7 | } 8 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | function getArg (name, defaultValue) { 2 | const arg = process.argv.find((a) => a.indexOf(name) === 0) 3 | return arg === undefined ? defaultValue : arg 4 | } 5 | 6 | // Production 7 | if (getArg('-p') !== undefined) { 8 | console.log('[PRODUCTION BUILD]') 9 | process.env.NODE_ENV = 'production' 10 | } else { 11 | console.log('[DEVELOPMENT BUILD]') 12 | } 13 | 14 | // Cheap / expensive source maps 15 | if (getArg('--fast') !== undefined) { 16 | console.log('[CHEAP SOURCEMAPS]') 17 | process.env.WEBPACK_DEVTOOL = 'eval-cheap-module-source-map' 18 | } else { 19 | console.log('[FULL SOURCEMAPS]') 20 | } 21 | 22 | // Task export 23 | const task = getArg('--task=', '--task=all').substr(7) 24 | if (task === 'app') { 25 | console.log('[TASK=app]') 26 | module.exports = [ require('./src/app/webpack.config.js') ] 27 | } else if (task === 'mailboxes') { 28 | console.log('[TASK=mailboxes]') 29 | module.exports = [ require('./src/scenes/mailboxes/webpack.config.js') ] 30 | } else if (task === 'platform') { 31 | console.log('[TASK=platform]') 32 | module.exports = [ require('./src/scenes/platform/webpack.config.js') ] 33 | } else if (task === 'assets') { 34 | console.log('[TASK=assets]') 35 | module.exports = [ require('./assets/webpack.config.js') ] 36 | } else { 37 | console.log('[TASK=all]') 38 | module.exports = [ 39 | require('./assets/webpack.config.js'), 40 | require('./src/app/webpack.config.js'), 41 | require('./src/scenes/mailboxes/webpack.config.js'), 42 | require('./src/scenes/platform/webpack.config.js') 43 | ] 44 | } 45 | --------------------------------------------------------------------------------