From cff437e90052f610e72aa9668b98e380e67ff2a5 Mon Sep 17 00:00:00 2001 From: Ben Gotow Date: Sun, 14 Feb 2021 15:58:22 -0600 Subject: [PATCH] Upgrade to Electron 8, improve TS usage and TS errors outside calendar [requires re- npm install] (#2284) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Shfit away from default exports and PropTypes for better TS support * localize strings and expand use of types in WeekView, create new EventOccurence distinct from Event * Remove calendar wrap, use TS enum for view type + consistent prop interface * Bump Typescript to 3.8.3 and improve query / attribute / search typings * Re-use the Autolinker for calendar event descriptions with aggressive phone detection * Clean up WeekView and the editing popover, lots of cruft here * Update ScrollRegion to initialize scrollbar provided by external ref * Expose ScrollRegion’s resizeObserver to clean up tick interval tracking * Simply tickGenerator and move it to a helper * Bump to Electron 8.x for Chrome 75+ CSS features * Bump Handlebars dep to fix annoying npm audit noise * Remove electron-remote from electron-spellchecker * Explicitly add node-gyp, why is this necessary? * Fix lesslint issues * Bump eslint and let it fix 133 issues * Satisfy remaining eslint@2020 errors by hand * Add tsc-watch npm script and fix all TS errors outside calendar * Configure appveyor to publish all the pdb files it gets * Log sync exit codes and signals for easier triage on Windows * Upgrade npm, mark that the build process supports Node 11+ not just Node 11 * Resolve more errors * Upgrade sqlite to be a context-aware native module * Fix: Tab key no longer navigating into contenteditable because tabIndex not inferred * Fix: Bad print styles because Chrome now adds more CSS of it’s own when doctype is missing * Fix: before-navigate is now called after beforeunload --- .appveyor.yml | 2 +- .travis.yml | 2 +- .vscode/settings.json | 5 +- .vscode/tasks.json | 11 + .../account-sidebar/lib/sidebar-item.ts | 4 +- .../account-sidebar/lib/sidebar-section.ts | 13 +- .../account-sidebar/lib/types.ts | 2 +- .../specs/sidebar-item-spec.es6.ts | 12 +- .../activity/lib/dashboard/share-button.tsx | 1 + .../lib/preferences-signatures.tsx | 2 +- .../composer-templates/lib/template-store.ts | 2 +- .../composer/lib/composer-view.tsx | 10 +- .../composer/styles/composer.less | 6 +- .../contacts/lib/FoundInMailEnabledBar.tsx | 7 +- .../contacts/lib/GoogleSupport.ts | 2 +- .../draft-list/lib/draft-list-store.ts | 13 +- .../lib/github-user-store.ts | 2 +- .../localizer-help/lib/main.tsx | 2 +- .../main-calendar/lib/calendar-wrapper.tsx | 104 - .../lib/core/calendar-constants.ts | 9 +- .../lib/core/calendar-data-source.ts | 96 +- .../lib/core/calendar-event-container.tsx | 28 +- .../lib/core/calendar-event-popover.tsx | 266 +- .../main-calendar/lib/core/calendar-event.tsx | 19 +- .../lib/core/calendar-helpers.tsx | 2 +- .../lib/core/calendar-source-list.tsx | 17 +- .../lib/core/current-time-indicator.tsx | 19 +- .../lib/core/event-attendees-input.tsx | 12 +- .../lib/core/event-grid-background.tsx | 20 +- .../lib/core/event-search-bar.tsx | 64 +- .../lib/core/event-timerange-picker.tsx | 66 + .../lib/core/footer-controls.tsx | 30 - .../lib/core/header-controls.tsx | 9 +- .../lib/core/mailspring-calendar.tsx | 275 +- .../lib/core/mini-month-view.tsx | 138 - .../main-calendar/lib/core/month-view.tsx | 7 +- .../main-calendar/lib/core/top-banner.tsx | 17 - .../lib/core/week-view-all-day-events.tsx | 49 +- .../lib/core/week-view-event-column.tsx | 72 +- .../lib/core/week-view-helpers.ts | 132 + .../main-calendar/lib/core/week-view.tsx | 505 +- .../lib/event-description-frame.tsx | 10 +- .../main-calendar/lib/main.tsx | 24 +- .../main-calendar/lib/quick-event-button.tsx | 4 +- .../main-calendar/lib/quick-event-popover.tsx | 41 +- .../main-calendar/styles/main-calendar.less | 4 - .../main-calendar/styles/nylas-calendar.less | 110 +- .../message-list/lib/email-frame.tsx | 15 +- .../specs/message-item-body-spec.jsx | 2 +- .../lib/decorators/create-page-for-form.tsx | 2 +- .../lib/mailcore-provider-settings.json | 57 - .../onboarding/lib/oauth-signin-page.tsx | 23 +- .../onboarding/lib/page-top-bar.tsx | 22 +- .../open-tracking-composer-extension-spec.ts | 1 - .../lib/tabs/preferences-account-list.tsx | 4 +- .../lib/tabs/preferences-general.tsx | 2 +- .../lib/tabs/preferences-identity.tsx | 3 +- .../lib/tabs/preferences-keymaps.tsx | 2 +- .../print/lib/print-window.ts | 3 +- .../print/static/print-preload.js | 37 +- .../specs/tracking-pixels-extension-spec.ts | 5 +- .../send-later/lib/send-later-status.tsx | 2 +- .../thread-list/lib/message-list-toolbar.tsx | 2 +- .../lib/thread-list-data-source.ts | 2 +- .../lib/thread-list-participants.tsx | 4 +- .../thread-list/lib/thread-list-store.ts | 2 +- .../specs/thread-list-participants-spec.jsx | 2 +- .../thread-search/lib/thread-search-bar.tsx | 41 +- .../translation/lib/message-header.tsx | 4 +- .../translation/lib/service.ts | 3 +- .../undo-redo/lib/undo-redo-toast.tsx | 2 +- app/package-lock.json | 4591 ++++- app/package.json | 9 +- app/spec/database-object-registry-spec.ts | 2 +- app/spec/fixtures/db-test-model.ts | 7 +- app/spec/models/model-spec.ts | 2 +- app/spec/models/query-spec.ts | 2 +- .../both-email-and-url-in.html | 0 .../both-email-and-url-out.html | 0 .../autolinker-fixtures/gmail-in.html | 0 .../autolinker-fixtures/gmail-out.html | 0 .../autolinker-fixtures/linkedin-in.html | 0 .../autolinker-fixtures/linkedin-out.html | 0 .../autolinker-fixtures/medium-post-in.html | 0 .../autolinker-fixtures/medium-post-out.html | 0 .../autolinker-fixtures/nylas-url-in.html | 0 .../autolinker-fixtures/nylas-url-out.html | 0 .../autolinker-fixtures/plaintext-in.html | 0 .../autolinker-fixtures/plaintext-out.html | 0 .../autolinker-fixtures/readme-in.html | 0 .../autolinker-fixtures/readme-out.html | 0 .../autolinker-fixtures/strangeemails-in.html | 0 .../strangeemails-out.html | 0 .../autolinker-fixtures/strangephones-in.html | 0 .../strangephones-out.html | 0 .../autolinker-fixtures/twitter-in.html | 0 .../autolinker-fixtures/twitter-out.html | 0 .../autolinker-fixtures/url-with-port-in.html | 0 .../url-with-port-out.html | 0 .../services}/autolinker-spec.ts | 7 +- app/src/app-env.ts | 56 +- app/src/browser/application-touch-bar.ts | 4 +- app/src/browser/application.ts | 43 +- app/src/browser/autoupdate-manager.ts | 13 +- app/src/browser/config-persistence-manager.ts | 16 +- app/src/browser/mailspring-window.ts | 5 +- app/src/browser/main.js | 17 + app/src/browser/move-to-applications.ts | 2 +- app/src/browser/system-tray-manager.ts | 2 +- app/src/browser/types/global-ext.d.ts | 5 +- .../composer-editor/base-block-plugins.tsx | 10 +- .../composer-editor/base-mark-plugins.tsx | 4 +- .../composer-editor/composer-editor.tsx | 8 +- .../components/composer-editor/conversion.tsx | 9 +- .../composer-editor/emoji-plugins.tsx | 4 +- .../composer-editor/link-plugins.tsx | 10 +- .../composer-editor/template-plugins.tsx | 2 +- app/src/components/composer-editor/types.ts | 19 +- app/src/components/date-picker.tsx | 2 +- .../decorators/listens-to-observable.tsx | 19 +- app/src/components/drop-zone.tsx | 2 +- app/src/components/empty-list-state.tsx | 2 +- app/src/components/flexbox.tsx | 2 +- app/src/components/injected-component.tsx | 2 +- app/src/components/list-data-source.ts | 4 +- app/src/components/list-selection.ts | 4 +- app/src/components/list-tabular-item.tsx | 5 +- app/src/components/list-tabular.tsx | 10 +- app/src/components/menu.tsx | 2 +- app/src/components/mini-month-view.tsx | 120 + app/src/components/multiselect-list.tsx | 2 +- app/src/components/resizable-region.tsx | 2 +- app/src/components/retina-img.tsx | 2 +- app/src/components/scroll-region.tsx | 153 +- app/src/components/spinner.tsx | 2 +- app/src/components/tab-group-region.tsx | 2 +- app/src/config-utils.ts | 65 +- app/src/config.ts | 13 +- app/src/date-utils.ts | 10 +- app/src/default-client-helper.ts | 116 +- app/src/error-logger.js | 4 +- app/src/flux/actions.ts | 3 +- app/src/flux/attributes.ts | 65 +- app/src/flux/attributes/attribute-boolean.ts | 4 +- .../flux/attributes/attribute-collection.ts | 29 +- app/src/flux/attributes/attribute-datetime.ts | 4 +- .../flux/attributes/attribute-joined-data.ts | 15 +- app/src/flux/attributes/attribute-number.ts | 4 +- app/src/flux/attributes/attribute-object.ts | 11 +- app/src/flux/attributes/attribute-string.ts | 4 +- app/src/flux/attributes/attribute.ts | 16 +- app/src/flux/attributes/matcher.ts | 32 +- app/src/flux/attributes/sort-order.ts | 9 +- app/src/flux/mailsync-bridge.ts | 8 +- app/src/flux/models/account.ts | 15 +- app/src/flux/models/calendar.ts | 2 +- app/src/flux/models/category.ts | 4 +- app/src/flux/models/contact-book.ts | 2 +- app/src/flux/models/contact-group.ts | 2 +- app/src/flux/models/contact.ts | 4 +- app/src/flux/models/event.ts | 2 +- app/src/flux/models/file.ts | 2 +- app/src/flux/models/message.ts | 4 +- app/src/flux/models/model-with-metadata.ts | 8 +- app/src/flux/models/model.ts | 9 +- .../flux/models/mutable-query-result-set.ts | 2 +- .../flux/models/provider-syncback-request.ts | 8 +- .../flux/models/query-subscription-pool.ts | 3 +- app/src/flux/models/query-subscription.ts | 30 +- app/src/flux/models/query.ts | 93 +- app/src/flux/models/thread.ts | 2 +- app/src/flux/models/utils.ts | 6 +- app/src/flux/stores/account-store.ts | 9 +- app/src/flux/stores/attachment-store.ts | 92 +- app/src/flux/stores/database-agent.js | 30 +- app/src/flux/stores/database-store.ts | 45 +- app/src/flux/stores/draft-store.ts | 2 +- app/src/flux/stores/feature-usage-store.tsx | 35 +- app/src/flux/stores/identity-store.ts | 28 +- app/src/flux/stores/task-queue.ts | 26 +- app/src/flux/stores/workspace-store.ts | 37 +- .../change-contactgroup-membership-task.ts | 4 +- app/src/flux/tasks/change-folder-task.ts | 6 +- app/src/flux/tasks/change-labels-task.ts | 2 +- app/src/flux/tasks/change-mail-task.ts | 2 +- .../flux/tasks/change-role-mapping-task.ts | 2 +- app/src/flux/tasks/change-starred-task.ts | 2 +- app/src/flux/tasks/change-unread-task.ts | 2 +- app/src/flux/tasks/destroy-category-task.ts | 2 +- app/src/flux/tasks/destroy-contact-task.ts | 2 +- .../flux/tasks/destroy-contactgroup-task.ts | 4 +- app/src/flux/tasks/destroy-draft-task.ts | 2 +- app/src/flux/tasks/event-rsvp-task.ts | 2 +- .../flux/tasks/expunge-all-in-folder-task.ts | 4 +- .../flux/tasks/get-message-rfc2822-task.ts | 2 +- app/src/flux/tasks/send-draft-task.ts | 6 +- .../tasks/send-feature-usage-event-task.ts | 2 +- app/src/flux/tasks/syncback-category-task.ts | 4 +- app/src/flux/tasks/syncback-contact-task.ts | 4 +- .../flux/tasks/syncback-contactgroup-task.ts | 4 +- app/src/flux/tasks/syncback-draft-task.ts | 4 +- app/src/flux/tasks/syncback-metadata-task.ts | 6 +- app/src/flux/tasks/task.ts | 7 +- app/src/global/mailspring-component-kit.d.ts | 3 +- app/src/global/mailspring-component-kit.js | 1 + app/src/global/mailspring-exports.d.ts | 4 +- app/src/global/mailspring-exports.js | 1 + app/src/global/mailspring-observables.ts | 11 +- app/src/global/mailspring-store.ts | 44 +- app/src/key-manager.ts | 2 +- app/src/mailsync-process.ts | 15 +- app/src/quickpreview/index.ts | 2 +- app/src/regexp-utils.ts | 12 +- .../lib => src/services}/autolinker.ts | 33 +- .../search/search-query-backend-local.ts | 5 +- .../services/search/search-query-parser.ts | 36 +- app/src/spellchecker.ts | 2 +- app/src/types/window-ext.d.ts | 2 +- app/src/window-event-handler.ts | 4 +- app/static/index.html | 2 +- .../style/components/mini-month-view.less | 80 + app/static/style/index.less | 1 + package-lock.json | 14221 +++++++++++++++- package.json | 15 +- 224 files changed, 20037 insertions(+), 3123 deletions(-) create mode 100644 .vscode/tasks.json delete mode 100644 app/internal_packages/main-calendar/lib/calendar-wrapper.tsx create mode 100644 app/internal_packages/main-calendar/lib/core/event-timerange-picker.tsx delete mode 100644 app/internal_packages/main-calendar/lib/core/footer-controls.tsx delete mode 100644 app/internal_packages/main-calendar/lib/core/mini-month-view.tsx delete mode 100644 app/internal_packages/main-calendar/lib/core/top-banner.tsx create mode 100644 app/internal_packages/main-calendar/lib/core/week-view-helpers.ts rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/both-email-and-url-in.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/both-email-and-url-out.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/gmail-in.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/gmail-out.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/linkedin-in.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/linkedin-out.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/medium-post-in.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/medium-post-out.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/nylas-url-in.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/nylas-url-out.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/plaintext-in.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/plaintext-out.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/readme-in.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/readme-out.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/strangeemails-in.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/strangeemails-out.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/strangephones-in.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/strangephones-out.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/twitter-in.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/twitter-out.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/url-with-port-in.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-fixtures/url-with-port-out.html (100%) rename app/{internal_packages/message-list/specs => spec/services}/autolinker-spec.ts (86%) create mode 100644 app/src/components/mini-month-view.tsx rename app/{internal_packages/message-list/lib => src/services}/autolinker.ts (70%) create mode 100644 app/static/style/components/mini-month-view.less diff --git a/.appveyor.yml b/.appveyor.yml index ecabd841d..747505faa 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -32,7 +32,7 @@ build_script: - cmd: node app/build/create-signed-windows-installer.js before_deploy: - - cmd: 7z -ttar a dummy %APPVEYOR_BUILD_FOLDER%\app\dist\*.dll %APPVEYOR_BUILD_FOLDER%\app\dist\mailsync.exe -so | 7z -si -tgzip a .\app\dist\mailsync.tar.gz + - cmd: 7z -ttar a dummy %APPVEYOR_BUILD_FOLDER%\app\dist\*.dll %APPVEYOR_BUILD_FOLDER%\app\dist\*.pdb %APPVEYOR_BUILD_FOLDER%\app\dist\mailsync.exe -so | 7z -si -tgzip a .\app\dist\mailsync.tar.gz - ps: Get-ChildItem .\app\dist\*.tar.gz | % { Push-AppveyorArtifact $_.FullName -FileName "win-ia32/$($_.Name)" -DeploymentName s3-deployment } - ps: Get-ChildItem .\app\dist\MailspringSetup.exe | % { Push-AppveyorArtifact $_.FullName -FileName "win-ia32/$($_.Name)" -DeploymentName s3-deployment } - ps: Get-ChildItem .\app\dist\*.nupkg | % { Push-AppveyorArtifact $_.FullName -FileName "win-ia32/$($_.Name)" -DeploymentName s3-deployment } diff --git a/.travis.yml b/.travis.yml index 90d5e9463..87bfd9fc7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ git: language: node_js node_js: - - '11' + - '12' addons: artifacts: diff --git a/.vscode/settings.json b/.vscode/settings.json index 3d78108a3..d7323ae77 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -29,5 +29,6 @@ "git.ignoreLimitWarning": true, "files.exclude": { "**/*.dll": true - } -} \ No newline at end of file + }, + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..f3a937be1 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,11 @@ +{ + "version": "2.0.0", + "command": "./node_modules/.bin/tsc", + "type": "shell", + "args": ["-w", "-p", "./app", "--noEmit"], + "presentation": { + "reveal": "silent" + }, + "isBackground": true, + "problemMatcher": "$tsc-watch" +} diff --git a/app/internal_packages/account-sidebar/lib/sidebar-item.ts b/app/internal_packages/account-sidebar/lib/sidebar-item.ts index b1cf591ee..55f673015 100644 --- a/app/internal_packages/account-sidebar/lib/sidebar-item.ts +++ b/app/internal_packages/account-sidebar/lib/sidebar-item.ts @@ -52,7 +52,7 @@ const onDeleteItem = function(item) { return; } - const chosen = remote.dialog.showMessageBox({ + const response = remote.dialog.showMessageBoxSync({ type: 'info', message: localized('Are you sure?'), detail: localized( @@ -62,7 +62,7 @@ const onDeleteItem = function(item) { defaultId: 0, }); - if (chosen !== 0) { + if (response !== 0) { return; } diff --git a/app/internal_packages/account-sidebar/lib/sidebar-section.ts b/app/internal_packages/account-sidebar/lib/sidebar-section.ts index 25648c966..26b99a8ff 100644 --- a/app/internal_packages/account-sidebar/lib/sidebar-section.ts +++ b/app/internal_packages/account-sidebar/lib/sidebar-section.ts @@ -18,7 +18,7 @@ import { import SidebarItem from './sidebar-item'; import * as SidebarActions from './sidebar-actions'; -import { ISidebarSection } from './types'; +import { ISidebarSection, ISidebarItem } from './types'; function isSectionCollapsed(title) { if (AppEnv.savedState.sidebarKeysCollapsed[title] !== undefined) { @@ -106,8 +106,8 @@ class SidebarSection { ]; const items = []; - for (let names of standardNames) { - names = Array.isArray(names) ? names : [names]; + for (const nameOrNames of standardNames) { + const names = Array.isArray(nameOrNames) ? nameOrNames : [nameOrNames]; const categories = CategoryStore.getCategoriesWithRoles(accounts, ...names); if (categories.length === 0) { continue; @@ -192,15 +192,16 @@ class SidebarSection { // Inbox.FolderA.FolderB // Inbox.FolderB // - const items = []; - const seenItems = {}; + const items: ISidebarItem[] = []; + const seenItems: { [key: string]: ISidebarItem } = {}; for (const category of CategoryStore.userCategories(account)) { // https://regex101.com/r/jK8cC2/1 - let item, parentKey; + let item: ISidebarItem = null; const re = RegExpUtils.subcategorySplitRegex(); const itemKey = category.displayName.replace(re, '/'); let parent = null; + let parentKey: string = null; const parentComponents = itemKey.split('/'); for (let i = parentComponents.length; i >= 1; i--) { parentKey = parentComponents.slice(0, i).join('/'); diff --git a/app/internal_packages/account-sidebar/lib/types.ts b/app/internal_packages/account-sidebar/lib/types.ts index 8a8a3985d..989ffe17d 100644 --- a/app/internal_packages/account-sidebar/lib/types.ts +++ b/app/internal_packages/account-sidebar/lib/types.ts @@ -12,7 +12,7 @@ export interface ISidebarItem { collapsed: boolean; counterStyle: string; onDelete?: () => void; - onEdited?: () => void; + onEdited?: (item, name: string) => void; onCollapseToggled: () => void; onDrop: (item, event) => void; shouldAcceptDrop: (item, event) => void; diff --git a/app/internal_packages/account-sidebar/specs/sidebar-item-spec.es6.ts b/app/internal_packages/account-sidebar/specs/sidebar-item-spec.es6.ts index 52e7b75ce..98c7a59af 100644 --- a/app/internal_packages/account-sidebar/specs/sidebar-item-spec.es6.ts +++ b/app/internal_packages/account-sidebar/specs/sidebar-item-spec.es6.ts @@ -3,25 +3,25 @@ import SidebarItem from '../lib/sidebar-item'; describe('sidebar-item', function sidebarItemSpec() { it('preserves nested labels on rename', () => { - spyOn(Actions, 'queueTask'); - const categories = [new Folder({ path: 'a.b/c', accountId: window.TEST_ACCOUNT_ID })]; + const queueTask = spyOn(Actions, 'queueTask'); + const categories = [new Folder({ path: 'a.b/c', accountId: TEST_ACCOUNT_ID })]; AppEnv.savedState.sidebarKeysCollapsed = {}; const item = SidebarItem.forCategories(categories) as any; item.onEdited(item, 'd'); - const task = Actions.queueTask.calls[0].args[0]; + const task = queueTask.calls[0].args[0]; const { existingPath, path } = task; expect(existingPath).toBe('a.b/c'); expect(path).toBe('a.b/d'); }); it('preserves labels on rename', () => { - spyOn(Actions, 'queueTask'); - const categories = [new Folder({ path: 'a', accountId: window.TEST_ACCOUNT_ID })]; + const queueTask = spyOn(Actions, 'queueTask'); + const categories = [new Folder({ path: 'a', accountId: TEST_ACCOUNT_ID })]; AppEnv.savedState.sidebarKeysCollapsed = {}; const item = SidebarItem.forCategories(categories); item.onEdited(item, 'b') as any; - const task = Actions.queueTask.calls[0].args[0]; + const task = queueTask.calls[0].args[0]; const { existingPath, path } = task; expect(existingPath).toBe('a'); expect(path).toBe('b'); diff --git a/app/internal_packages/activity/lib/dashboard/share-button.tsx b/app/internal_packages/activity/lib/dashboard/share-button.tsx index 1ce5bb2c0..384ca5b2f 100644 --- a/app/internal_packages/activity/lib/dashboard/share-button.tsx +++ b/app/internal_packages/activity/lib/dashboard/share-button.tsx @@ -5,6 +5,7 @@ import { RetinaImg } from 'mailspring-component-kit'; function buildShareHTML(htmlEl, styleEl) { return ` + diff --git a/app/internal_packages/composer-signature/lib/preferences-signatures.tsx b/app/internal_packages/composer-signature/lib/preferences-signatures.tsx index 7382925e3..b21836cc6 100644 --- a/app/internal_packages/composer-signature/lib/preferences-signatures.tsx +++ b/app/internal_packages/composer-signature/lib/preferences-signatures.tsx @@ -57,7 +57,7 @@ class SignatureEditor extends React.Component sig.body === RenderSignatureData({ ...sig.data, templateName: t.name }) ); if (!htmlMatchesATemplate) { - const idx = remote.dialog.showMessageBox({ + const idx = remote.dialog.showMessageBoxSync({ type: 'warning', buttons: [localized('Cancel'), localized('Continue')], message: localized('Revert custom HTML?'), diff --git a/app/internal_packages/composer-templates/lib/template-store.ts b/app/internal_packages/composer-templates/lib/template-store.ts index 0326e0acc..afa2ee1ef 100644 --- a/app/internal_packages/composer-templates/lib/template-store.ts +++ b/app/internal_packages/composer-templates/lib/template-store.ts @@ -155,7 +155,7 @@ class TemplateStore extends MailspringStore { _displayDialog(title, message, buttons) { return ( - remote.dialog.showMessageBox({ + remote.dialog.showMessageBoxSync({ title: title, message: title, detail: message, diff --git a/app/internal_packages/composer/lib/composer-view.tsx b/app/internal_packages/composer/lib/composer-view.tsx index 837af0130..3eb0a1389 100644 --- a/app/internal_packages/composer/lib/composer-view.tsx +++ b/app/internal_packages/composer/lib/composer-view.tsx @@ -62,7 +62,9 @@ export default class ComposerView extends React.Component(); sendButton = React.createRef(); focusContainer = React.createRef(); - editor = React.createRef(); + editor: + | React.RefObject + | React.RefObject = React.createRef(); header = React.createRef(); _keymapHandlers = { @@ -158,7 +160,7 @@ export default class ComposerView extends React.Component {draft.plaintext ? ( } value={draft.body} propsForPlugins={{ draft, session }} onFileReceived={this._onFileReceived} @@ -170,7 +172,7 @@ export default class ComposerView extends React.Component } value={draft.bodyEditorState} className={quotedTextHidden && 'hiding-quoted-text'} propsForPlugins={{ draft, session }} @@ -364,7 +366,7 @@ export default class ComposerView extends React.Component 0 && !options.force) { - const response = dialog.showMessageBox({ + const response = dialog.showMessageBoxSync({ type: 'warning', buttons: [localized('Send Anyway'), localized('Cancel')], message: localized('Are you sure?'), diff --git a/app/internal_packages/composer/styles/composer.less b/app/internal_packages/composer/styles/composer.less index 32fb357fb..6c319f401 100644 --- a/app/internal_packages/composer/styles/composer.less +++ b/app/internal_packages/composer/styles/composer.less @@ -385,7 +385,11 @@ body.platform-win32 { .composer-body-wrap { padding: 0 0 5px 0; } - + .composer-header { + .key-commands-region { + height: initial; + } + } .composer-header-actions { position: relative; float: right; diff --git a/app/internal_packages/contacts/lib/FoundInMailEnabledBar.tsx b/app/internal_packages/contacts/lib/FoundInMailEnabledBar.tsx index c2886bb51..55d0d51f2 100644 --- a/app/internal_packages/contacts/lib/FoundInMailEnabledBar.tsx +++ b/app/internal_packages/contacts/lib/FoundInMailEnabledBar.tsx @@ -14,7 +14,12 @@ class FoundInMailEnabledBarWithData extends React.Component { - const accountId = this.props.perspective.accountId; + const { perspective } = this.props; + if (!perspective || perspective.type !== 'found-in-mail') { + return false; + } + + const accountId = perspective.accountId; let disabled = AppEnv.config.get(CONFIG_KEY); if (disabled.includes(accountId)) { disabled = disabled.filter(i => i !== accountId); diff --git a/app/internal_packages/contacts/lib/GoogleSupport.ts b/app/internal_packages/contacts/lib/GoogleSupport.ts index 621c6829e..68bcd91fb 100644 --- a/app/internal_packages/contacts/lib/GoogleSupport.ts +++ b/app/internal_packages/contacts/lib/GoogleSupport.ts @@ -10,7 +10,7 @@ export const showGPeopleReadonlyNotice = (accountId: string) => { acct.provider === 'gmail' && (!acct.authedAt || acct.authedAt < CONTACTS_OAUTH_SCOPE_ADDED) ) { - remote.dialog.showMessageBox({ + remote.dialog.showMessageBoxSync({ message: localized(`Please re-authenticate with Google`), detail: localized( `To make changes to contacts in this account, you'll need to re-authorize Mailspring to access your data.\n\n` + diff --git a/app/internal_packages/draft-list/lib/draft-list-store.ts b/app/internal_packages/draft-list/lib/draft-list-store.ts index f4da5aac8..4cabecbaf 100644 --- a/app/internal_packages/draft-list/lib/draft-list-store.ts +++ b/app/internal_packages/draft-list/lib/draft-list-store.ts @@ -61,11 +61,12 @@ class DraftListStore extends MailspringStore { const subscription = new MutableQuerySubscription(query, { emitResultSet: true }); const $resultSet = Rx.Observable.combineLatest( - [ - Rx.Observable.fromNamedQuerySubscription('draft-list', subscription), - Rx.Observable.fromStore(OutboxStore) as any, - ], - (resultSet: QueryResultSet, outbox) => { + Rx.Observable.fromNamedQuerySubscription('draft-list', subscription), + Rx.Observable.fromStore(OutboxStore), + (resultSet, outboxStore) => { + if (!(resultSet instanceof QueryResultSet)) { + throw 'Set emitResultSet=true and did not receive a QueryResultSet'; + } // Generate a new result set that includes additional information on // the draft objects. This is similar to what we do in the thread-list, // where we set thread.__messages to the message array. @@ -73,7 +74,7 @@ class DraftListStore extends MailspringStore { // TODO BG modelWithId: task.headerMessageId does not work mailboxPerspective.accountIds.forEach(aid => { - OutboxStore.itemsForAccount(aid).forEach(task => { + outboxStore.itemsForAccount(aid).forEach(task => { let draft = resultSet.modelWithId(task.headerMessageId) as any; if (draft) { draft = draft.clone(); diff --git a/app/internal_packages/github-contact-card/lib/github-user-store.ts b/app/internal_packages/github-contact-card/lib/github-user-store.ts index 86d59be5f..e8ce32cd0 100644 --- a/app/internal_packages/github-contact-card/lib/github-user-store.ts +++ b/app/internal_packages/github-contact-card/lib/github-user-store.ts @@ -82,7 +82,7 @@ class GithubUserStore extends MailspringStore { `https://api.github.com/search/repositories?q=user:${profile.login}&sort=stars&order=desc` ); // Sort the repositories by their stars (`-` for descending order) - profile.repos = _.sortBy(repos.items, repo => -repo.stargazers_count); + profile.repos = _.sortBy(repos.items, repo => -repo['stargazers_count']); // Trigger so that our React components refresh their state and display // the updated data. this.trigger(this); diff --git a/app/internal_packages/localizer-help/lib/main.tsx b/app/internal_packages/localizer-help/lib/main.tsx index 84d6e4134..a3a1c76df 100644 --- a/app/internal_packages/localizer-help/lib/main.tsx +++ b/app/internal_packages/localizer-help/lib/main.tsx @@ -33,7 +33,7 @@ class SubmitLocalizationsBar extends React.Component { json: true, }); if (status === 'success') { - remote.dialog.showMessageBox({ + remote.dialog.showMessageBoxSync({ type: 'info', buttons: [localized('OK')], message: localized('Thank you!'), diff --git a/app/internal_packages/main-calendar/lib/calendar-wrapper.tsx b/app/internal_packages/main-calendar/lib/calendar-wrapper.tsx deleted file mode 100644 index 30259d763..000000000 --- a/app/internal_packages/main-calendar/lib/calendar-wrapper.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { Actions, DestroyModelTask, Event } from 'mailspring-exports'; -import React from 'react'; -import { remote } from 'electron'; - -import { KeyCommandsRegion } from 'mailspring-component-kit'; -import CalendarDataSource from './core/calendar-data-source'; -import CalendarEventPopover from './core/calendar-event-popover'; -import MailspringCalendar from './core/mailspring-calendar'; - -export default class CalendarWrapper extends React.Component<{}, { selectedEvents: Event[] }> { - static displayName = 'CalendarWrapper'; - static containerRequired = false; - - _dataSource = new CalendarDataSource(); - - constructor(props) { - super(props); - this.state = { selectedEvents: [] }; - } - - _openEventPopover(eventModel: Event) { - const eventEl = document.getElementById(eventModel.id); - if (!eventEl) { - return; - } - const eventRect = eventEl.getBoundingClientRect(); - - Actions.openPopover(, { - originRect: eventRect, - direction: 'right', - fallbackDirection: 'left', - }); - } - - _onEventClick = (e: React.MouseEvent, event: Event) => { - let next = [...this.state.selectedEvents]; - - if (e.shiftKey || e.metaKey) { - const idx = next.findIndex(({ id }) => event.id === id); - if (idx === -1) { - next.push(event); - } else { - next.splice(idx, 1); - } - } else { - next = [event]; - } - - this.setState({ - selectedEvents: next, - }); - }; - - _onEventDoubleClick = (eventModel: Event) => { - this._openEventPopover(eventModel); - }; - - _onEventFocused = (eventModel: Event) => { - this._openEventPopover(eventModel); - }; - - _onDeleteSelectedEvents = () => { - if (this.state.selectedEvents.length === 0) { - return; - } - const response = remote.dialog.showMessageBox({ - type: 'warning', - buttons: ['Delete', 'Cancel'], - message: 'Delete or decline these events?', - detail: `Are you sure you want to delete or decline invitations for the selected event(s)?`, - }); - if (response === 0) { - // response is button array index - for (const event of this.state.selectedEvents) { - const task = new DestroyModelTask({ - modelId: event.id, - modelName: event.constructor.name, - endpoint: '/events', - accountId: event.accountId, - }); - Actions.queueTask(task); - } - } - }; - - render() { - return ( - - - - ); - } -} diff --git a/app/internal_packages/main-calendar/lib/core/calendar-constants.ts b/app/internal_packages/main-calendar/lib/core/calendar-constants.ts index d82021ee4..91e66815e 100644 --- a/app/internal_packages/main-calendar/lib/core/calendar-constants.ts +++ b/app/internal_packages/main-calendar/lib/core/calendar-constants.ts @@ -1,4 +1,5 @@ -export const DAY_VIEW = 'day'; -export const WEEK_VIEW = 'week'; -export const MONTH_VIEW = 'month'; -export const YEAR_VIEW = 'year'; +export enum CalendarView { + DAY = 'day', + WEEK = 'week', + MONTH = 'month', +} diff --git a/app/internal_packages/main-calendar/lib/core/calendar-data-source.ts b/app/internal_packages/main-calendar/lib/core/calendar-data-source.ts index 9fb97e86b..c4367b8dd 100644 --- a/app/internal_packages/main-calendar/lib/core/calendar-data-source.ts +++ b/app/internal_packages/main-calendar/lib/core/calendar-data-source.ts @@ -2,18 +2,32 @@ import Rx from 'rx-lite'; import { Event, Matcher, DatabaseStore } from 'mailspring-exports'; import IcalExpander from 'ical-expander'; -export default class CalendarDataSource { - observable: Rx.Observable<{ events: Event[] }>; +export interface EventOccurrence { + start: number; // unix + end: number; // unix + id: string; + accountId: string; + calendarId: string; + title: string; + location: string; + description: string; + isAllDay: boolean; + organizer: { email: string } | null; + attendees: { email: string; name: string }[]; +} - buildObservable({ startTime, endTime, disabledCalendars }) { +export class CalendarDataSource { + observable: Rx.Observable<{ events: EventOccurrence[] }>; + + buildObservable({ startUnix, endUnix, disabledCalendars }) { const end = Event.attributes.recurrenceEnd; const start = Event.attributes.recurrenceStart; const matcher = new Matcher.Or([ - new Matcher.And([start.lte(endTime), end.gte(startTime)]), - new Matcher.And([start.lte(endTime), start.gte(startTime)]), - new Matcher.And([end.gte(startTime), end.lte(endTime)]), - new Matcher.And([end.gte(endTime), start.lte(startTime)]), + new Matcher.And([start.lte(endUnix), end.gte(startUnix)]), + new Matcher.And([start.lte(endUnix), start.gte(startUnix)]), + new Matcher.And([end.gte(startUnix), end.lte(endUnix)]), + new Matcher.And([end.gte(endUnix), start.lte(startUnix)]), ]); if (disabledCalendars && disabledCalendars.length) { @@ -21,35 +35,9 @@ export default class CalendarDataSource { } const query = DatabaseStore.findAll(Event).where(matcher); - this.observable = Rx.Observable.fromQuery(query).flatMapLatest(results => { - const events = []; - results.forEach(result => { - const icalExpander = new IcalExpander({ ics: result.ics, maxIterations: 100 }); - const expanded = icalExpander.between(new Date(startTime * 1000), new Date(endTime * 1000)); - - [...expanded.events, ...expanded.occurrences].forEach((e, idx) => { - const start = e.startDate.toJSDate().getTime() / 1000; - const end = e.endDate.toJSDate().getTime() / 1000; - const item = e.item || e; - events.push({ - start, - end, - id: `${result.id}-e${idx}`, - calendarId: result.calendarId, - title: item.summary, - displayTitle: item.summary, - description: item.description, - isAllDay: end - start >= 86400 - 1, - organizer: item.organizer ? { email: item.organizer } : null, - attendees: item.attendees.map(a => ({ - ...a.jCal[1], - email: a.getFirstValue(), - })), - }); - }); - }); - return Rx.Observable.from([{ events }]); - }); + this.observable = Rx.Observable.fromQuery(query).flatMapLatest(results => + Rx.Observable.from([{ events: occurrencesForEvents(results, { startUnix, endUnix }) }]) + ); return this.observable; } @@ -57,3 +45,39 @@ export default class CalendarDataSource { return this.observable.subscribe(callback); } } + +export function occurrencesForEvents( + results: Event[], + { startUnix, endUnix }: { startUnix: number; endUnix: number } +) { + const occurences: EventOccurrence[] = []; + + results.forEach(result => { + const icalExpander = new IcalExpander({ ics: result.ics, maxIterations: 100 }); + const expanded = icalExpander.between(new Date(startUnix * 1000), new Date(endUnix * 1000)); + + [...expanded.events, ...expanded.occurrences].forEach((e, idx) => { + const start = e.startDate.toJSDate().getTime() / 1000; + const end = e.endDate.toJSDate().getTime() / 1000; + const item = e.item || e; + occurences.push({ + start, + end, + id: `${result.id}-e${idx}`, + accountId: result.accountId, + calendarId: result.calendarId, + title: item.summary, + location: item.location, + description: item.description, + isAllDay: end - start >= 86400 - 1, + organizer: item.organizer ? { email: item.organizer } : null, + attendees: item.attendees.map(a => ({ + ...a.jCal[1], + email: a.getFirstValue(), + })), + }); + }); + }); + + return occurences; +} diff --git a/app/internal_packages/main-calendar/lib/core/calendar-event-container.tsx b/app/internal_packages/main-calendar/lib/core/calendar-event-container.tsx index fa2c0e490..215604108 100644 --- a/app/internal_packages/main-calendar/lib/core/calendar-event-container.tsx +++ b/app/internal_packages/main-calendar/lib/core/calendar-event-container.tsx @@ -1,18 +1,28 @@ import moment from 'moment'; - import React from 'react'; import ReactDOM from 'react-dom'; -import PropTypes from 'prop-types'; -export default class CalendarEventContainer extends React.Component { +import { EventOccurrence } from './calendar-data-source'; + +export interface CalendarEventArgs { + event?: EventOccurrence; + time: number; + x: number; + y: number; + width: number; + height: number; + mouseIsDown: boolean; +} + +interface CalendarEventContainerProps { + onCalendarMouseDown: (args: CalendarEventArgs) => void; + onCalendarMouseMove: (args: CalendarEventArgs) => void; + onCalendarMouseUp: (args: CalendarEventArgs) => void; +} + +export class CalendarEventContainer extends React.Component { static displayName = 'CalendarEventContainer'; - static propTypes = { - onCalendarMouseUp: PropTypes.func, - onCalendarMouseDown: PropTypes.func, - onCalendarMouseMove: PropTypes.func, - }; - _DOMCache: { eventColumn?: any; gridWrap?: any; diff --git a/app/internal_packages/main-calendar/lib/core/calendar-event-popover.tsx b/app/internal_packages/main-calendar/lib/core/calendar-event-popover.tsx index d8769c071..4e00c94cc 100644 --- a/app/internal_packages/main-calendar/lib/core/calendar-event-popover.tsx +++ b/app/internal_packages/main-calendar/lib/core/calendar-event-popover.tsx @@ -1,12 +1,12 @@ -import moment from 'moment'; +import moment, { Moment } from 'moment'; import React from 'react'; import { - PropTypes, - Event, Actions, - DatabaseStore, DateUtils, SyncbackEventTask, + localized, + RegExpUtils, + Autolink, } from 'mailspring-exports'; import { DatePicker, @@ -15,10 +15,12 @@ import { TabGroupRegion, TimePicker, } from 'mailspring-component-kit'; -import EventAttendeesInput from './event-attendees-input'; +import { EventAttendeesInput } from './event-attendees-input'; +import { EventOccurrence } from './calendar-data-source'; +import { EventTimerangePicker } from './event-timerange-picker'; interface CalendarEventPopoverProps { - event: Event; + event: EventOccurrence; } interface CalendarEventPopoverState { @@ -31,36 +33,28 @@ interface CalendarEventPopoverState { title: string; } -export default class CalendarEventPopover extends React.Component< +export class CalendarEventPopover extends React.Component< CalendarEventPopoverProps, CalendarEventPopoverState > { - static propTypes = { - event: PropTypes.object.isRequired, - }; - constructor(props) { super(props); - const { description, start, end, location, attendees } = this.props.event; + const { description, start, end, location, attendees, title } = this.props.event; this.state = { description, start, end, location, - title: this.props.event.displayTitle, + title, editing: false, - attendees: attendees || [], + attendees, }; } componentWillReceiveProps = nextProps => { - const { description, start, end, location, attendees } = nextProps.event; - this.setState({ description, start, end, location }); - this.setState({ - attendees: attendees || [], - title: nextProps.event.displayTitle, - }); + const { description, start, end, location, attendees, title } = nextProps.event; + this.setState({ description, start, end, location, attendees, title }); }; onEdit = () => { @@ -89,34 +83,7 @@ export default class CalendarEventPopover extends React.Component< Actions.queueTask(task); }; - extractNotesFromDescription(node) { - const els = node.querySelectorAll('meta[itemprop=description]'); - let notes: string = null; - if (els.length) { - notes = Array.from(els) - .map((el: any) => el.content) - .join('\n'); - } else { - notes = node.innerText; - } - while (true) { - const nextNotes = notes.replace('\n\n', '\n'); - if (nextNotes === notes) { - break; - } - notes = nextNotes; - } - return notes; - } - // If on the hour, formats as "3 PM", else formats as "3:15 PM" - formatTime(momentTime) { - const min = momentTime.minutes(); - if (min === 0) { - return momentTime.format('h A'); - } - return momentTime.format('h:mm A'); - } updateAttendees = attendees => { this.setState({ attendees }); @@ -128,86 +95,10 @@ export default class CalendarEventPopover extends React.Component< this.setState(updates); }; - _onChangeDay = newTimestamp => { - const newDay = moment(newTimestamp); - const start = this.getStartMoment(); - const end = this.getEndMoment(); - start.year(newDay.year()); - end.year(newDay.year()); - start.dayOfYear(newDay.dayOfYear()); - end.dayOfYear(newDay.dayOfYear()); - this.setState({ start: start.unix(), end: end.unix() }); - }; - - _onChangeStartTime = newTimestamp => { - const newStart = moment(newTimestamp); - let newEnd = this.getEndMoment(); - if (newEnd.isSameOrBefore(newStart)) { - const leftInDay = moment(newStart) - .endOf('day') - .diff(newStart); - const move = Math.min(leftInDay, moment.duration(1, 'hour').asMilliseconds()); - newEnd = moment(newStart).add(move, 'ms'); - } - this.setState({ start: newStart.unix(), end: newEnd.unix() }); - }; - - _onChangeEndTime = newTimestamp => { - const newEnd = moment(newTimestamp); - let newStart = this.getStartMoment(); - if (newStart.isSameOrAfter(newEnd)) { - const sinceDay = moment(newEnd).diff(newEnd.startOf('day')); - const move = Math.min(sinceDay, moment.duration(1, 'hour').asMilliseconds()); - newStart = moment(newEnd).subtract(move, 'ms'); - } - this.setState({ end: newEnd.unix(), start: newStart.unix() }); - }; - - renderTime() { - const startMoment = this.getStartMoment(); - const endMoment = this.getEndMoment(); - const date = startMoment.format('dddd, MMMM D'); // e.g. Tuesday, February 22 - const timeRange = `${this.formatTime(startMoment)} - ${this.formatTime(endMoment)}`; - return ( -
- {date} -
- {timeRange} -
- ); - } - - renderEditableTime() { - const startVal = this.state.start * 1000; - const endVal = this.state.end * 1000; - return ( -
- - - - to - - - {moment() - .tz(DateUtils.timeZone) - .format('z')} - -   on   - - -
- ); - } - renderEditable = () => { const { title, description, start, end, location, attendees } = this.state; - const fragment = document.createDocumentFragment(); - const descriptionRoot = document.createElement('root'); - fragment.appendChild(descriptionRoot); - descriptionRoot.innerHTML = description; - - const notes = this.extractNotesFromDescription(descriptionRoot); + const notes = extractNotesFromDescription(description); return (
@@ -230,9 +121,15 @@ export default class CalendarEventPopover extends React.Component< this.updateField('location', e.target.value); }} /> -
{this.renderEditableTime()}
-
Invitees:
+ this.setState({ start, end })} + /> +
+
+
{localized(`Invitees`)}:
-
Notes:
+
{localized(`Notes`)}:
- Save - Actions.closePopover()}>Cancel + {localized(`Save`)} + Actions.closePopover()}>{localized(`Cancel`)}
); @@ -262,14 +159,56 @@ export default class CalendarEventPopover extends React.Component< if (this.state.editing) { return this.renderEditable(); } - const { title, description, location, attendees } = this.state; + return ( + this.setState({ editing: true })} + /> + ); + } +} - const fragment = document.createDocumentFragment(); - const descriptionRoot = document.createElement('root'); - fragment.appendChild(descriptionRoot); - descriptionRoot.innerHTML = description; +class CalendarEventPopoverUnenditable extends React.Component<{ + event: EventOccurrence; + onEdit: () => void; +}> { + descriptionRef = React.createRef(); - const notes = this.extractNotesFromDescription(descriptionRoot); + renderTime() { + const startMoment = moment(this.props.event.start * 1000); + const endMoment = moment(this.props.event.end * 1000); + const date = startMoment.format('dddd, MMMM D'); // e.g. Tuesday, February 22 + const timeRange = `${formatTime(startMoment)} - ${formatTime(endMoment)}`; + return ( +
+ {date} +
+ {timeRange} +
+ ); + } + + componentDidMount() { + this.autolink(); + } + + componentDidUpdate() { + this.autolink(); + } + + autolink() { + if (!this.descriptionRef.current) return; + Autolink(this.descriptionRef.current, { + async: false, + telAggressiveMatch: true, + }); + } + + render() { + const { event, onEdit } = this.props; + const { title, description, location, attendees } = event; + + const notes = extractNotesFromDescription(description); return (
@@ -280,22 +219,67 @@ export default class CalendarEventPopover extends React.Component< name="edit-icon.png" title="Edit Item" mode={RetinaImg.Mode.ContentIsMask} - onClick={this.onEdit} + onClick={onEdit} />
-
{location}
+ {location && ( +
+ {location.startsWith('http') || location.startsWith('tel:') ? ( + {location} + ) : ( + location + )} +
+ )}
{this.renderTime()}
-
Invitees:
-
{attendees.map((a, idx) =>
{a.cn}
)}
+
{localized(`Invitees`)}:
+
+ {attendees.map((a, idx) => ( +
{a.cn}
+ ))} +
-
Notes:
-
{notes}
+
{localized(`Notes`)}:
+
{notes}
); } } + +function extractNotesFromDescription(description: string) { + const fragment = document.createDocumentFragment(); + const descriptionRoot = document.createElement('root'); + fragment.appendChild(descriptionRoot); + descriptionRoot.innerHTML = description; + + const els = descriptionRoot.querySelectorAll('meta[itemprop=description]'); + let notes: string = null; + if (els.length) { + notes = Array.from(els) + .map((el: any) => el.content) + .join('\n'); + } else { + notes = descriptionRoot.innerText; + } + while (true) { + const nextNotes = notes.replace('\n\n', '\n'); + if (nextNotes === notes) { + break; + } + notes = nextNotes; + } + return notes; +} + +function formatTime(momentTime: Moment) { + const min = momentTime.minutes(); + if (min === 0) { + return momentTime.format('h A'); + } + return momentTime.format('h:mm A'); +} diff --git a/app/internal_packages/main-calendar/lib/core/calendar-event.tsx b/app/internal_packages/main-calendar/lib/core/calendar-event.tsx index 8772e3d8e..e835c29cb 100644 --- a/app/internal_packages/main-calendar/lib/core/calendar-event.tsx +++ b/app/internal_packages/main-calendar/lib/core/calendar-event.tsx @@ -1,25 +1,26 @@ import React, { CSSProperties } from 'react'; import ReactDOM from 'react-dom'; -import { Event } from 'mailspring-exports'; import { InjectedComponentSet } from 'mailspring-component-kit'; +import { EventOccurrence } from './calendar-data-source'; import { calcColor } from './calendar-helpers'; interface CalendarEventProps { - event: Event; + event: EventOccurrence; order: number; - selected?: boolean; + selected: boolean; scopeEnd: number; scopeStart: number; direction: 'horizontal' | 'vertical'; fixedSize: number; - focused?: boolean; + focused: boolean; concurrentEvents: number; - onClick: (e: React.MouseEvent, event: Event) => void; - onDoubleClick: (event: Event) => void; - onFocused: (event: Event) => void; + + onClick: (e: React.MouseEvent, event: EventOccurrence) => void; + onDoubleClick: (event: EventOccurrence) => void; + onFocused: (event: EventOccurrence) => void; } -export default class CalendarEvent extends React.Component { +export class CalendarEvent extends React.Component { static displayName = 'CalendarEvent'; static defaultProps = { @@ -116,7 +117,7 @@ export default class CalendarEvent extends React.Component { onDoubleClick={() => onDoubleClick(event)} > - {event.displayTitle} + {event.title} { const calendarId = calendar.id; const onClick = () => { @@ -43,7 +43,12 @@ function renderCalendarToggles(calendars, disabledCalendars) { }); } -export default function CalendarSourceList(props) { +interface CalendarSourceListProps { + accounts: Account[]; + calendars: Calendar[]; + disabledCalendars: string[]; +} +export function CalendarSourceList(props: CalendarSourceListProps) { const calsByAccountId = _.groupBy(props.calendars, 'accountId'); const accountSections = []; for (const accountId of Object.keys(calsByAccountId)) { @@ -61,9 +66,3 @@ export default function CalendarSourceList(props) { } return
{accountSections}
; } - -CalendarSourceList.propTypes = { - accounts: PropTypes.array, - calendars: PropTypes.array, - disabledCalendars: PropTypes.array, -}; diff --git a/app/internal_packages/main-calendar/lib/core/current-time-indicator.tsx b/app/internal_packages/main-calendar/lib/core/current-time-indicator.tsx index 8a957c380..213ea4885 100644 --- a/app/internal_packages/main-calendar/lib/core/current-time-indicator.tsx +++ b/app/internal_packages/main-calendar/lib/core/current-time-indicator.tsx @@ -1,16 +1,17 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import PropTypes from 'prop-types'; import Moment from 'moment'; import classNames from 'classnames'; -export default class CurrentTimeIndicator extends React.Component< - { - gridHeight: number; - numColumns: number; - todayColumnIdx: number; - visible: boolean; - }, +interface CurrentTimeIndicatorProps { + gridHeight: number; + numColumns: number; + todayColumnIdx: number; + visible: boolean; +} + +export class CurrentTimeIndicator extends React.Component< + CurrentTimeIndicatorProps, { msecIntoDay: number } > { _movementTimer = null; @@ -48,7 +49,7 @@ export default class CurrentTimeIndicator extends React.Component< const todayMarker = todayColumnIdx !== -1 ? ( -
+
) : null; return ( diff --git a/app/internal_packages/main-calendar/lib/core/event-attendees-input.tsx b/app/internal_packages/main-calendar/lib/core/event-attendees-input.tsx index 2c350b393..8d22e0249 100644 --- a/app/internal_packages/main-calendar/lib/core/event-attendees-input.tsx +++ b/app/internal_packages/main-calendar/lib/core/event-attendees-input.tsx @@ -1,10 +1,10 @@ import React from 'react'; import _ from 'underscore'; import { remote, clipboard } from 'electron'; -import { PropTypes, Utils, Contact, ContactStore, RegExpUtils } from 'mailspring-exports'; +import { Utils, Contact, ContactStore, RegExpUtils, localized } from 'mailspring-exports'; import { TokenizingTextField, Menu, InjectedComponentSet } from 'mailspring-component-kit'; -const TokenRenderer = props => { +const TokenRenderer = (props: { token: Contact }) => { const { email, cn } = props.token; let chipText = email; if (cn && cn.length > 0 && cn !== email) { @@ -23,10 +23,6 @@ const TokenRenderer = props => { ); }; -TokenRenderer.propTypes = { - token: PropTypes.object, -}; - interface EventAttendeesInputProps { attendees: any[]; change: (next: any[]) => void; @@ -35,7 +31,7 @@ interface EventAttendeesInputProps { onFocus?: () => void; } -export default class EventAttendeesInput extends React.Component { +export class EventAttendeesInput extends React.Component { static displayName = 'EventAttendeesInput'; shouldComponentUpdate(nextProps, nextState) { @@ -123,7 +119,7 @@ export default class EventAttendeesInput extends React.Component clipboard.writeText(participant.email), }) ); diff --git a/app/internal_packages/main-calendar/lib/core/event-grid-background.tsx b/app/internal_packages/main-calendar/lib/core/event-grid-background.tsx index f1741acee..ae247e89f 100644 --- a/app/internal_packages/main-calendar/lib/core/event-grid-background.tsx +++ b/app/internal_packages/main-calendar/lib/core/event-grid-background.tsx @@ -1,15 +1,15 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { PropTypes, Utils } from 'mailspring-exports'; +import { Utils } from 'mailspring-exports'; +import { tickGenerator } from './week-view-helpers'; interface EventGridBackgroundProps { height: number; numColumns: number; - tickGenerator: (arg: { type: string }) => Array<{ yPos }>; intervalHeight: number; } -export default class EventGridBackground extends React.Component { +export class EventGridBackground extends React.Component { static displayName = 'EventGridBackground'; _lastHoverRect: { x?; y?; width?; height? } = {}; @@ -36,9 +36,9 @@ export default class EventGridBackground extends React.Component { ctx.strokeStyle = strokeStyle; ctx.beginPath(); - for (const { yPos } of this.props.tickGenerator({ type })) { - ctx.moveTo(0, yPos); - ctx.lineTo(canvas.width, yPos); + for (const { y } of tickGenerator(type, this.props.intervalHeight)) { + ctx.moveTo(0, y); + ctx.lineTo(canvas.width, y); } ctx.stroke(); }; @@ -47,7 +47,11 @@ export default class EventGridBackground extends React.Component) { + const width = e.currentTarget.clientWidth; + const x = e.clientX; + const y = e.clientY; + if (!width || x == null || y == null) { return; } @@ -77,7 +81,7 @@ export default class EventGridBackground extends React.Component +
diff --git a/app/internal_packages/main-calendar/lib/core/event-search-bar.tsx b/app/internal_packages/main-calendar/lib/core/event-search-bar.tsx index 054b60403..332525e1b 100644 --- a/app/internal_packages/main-calendar/lib/core/event-search-bar.tsx +++ b/app/internal_packages/main-calendar/lib/core/event-search-bar.tsx @@ -1,22 +1,19 @@ import React, { Component } from 'react'; -import { Event, DatabaseStore } from 'mailspring-exports'; -import { SearchBar } from 'mailspring-component-kit'; -import PropTypes from 'prop-types'; +import { Event, DatabaseStore, localized } from 'mailspring-exports'; +import { EventOccurrence, occurrencesForEvents } from './calendar-data-source'; +import moment from 'moment'; -class EventSearchBar extends Component< - { - disabledCalendars: string[]; - onSelectEvent: (event: Event) => void; - }, - { query: string; suggestions: Event[] } +interface EventSearchBarProps { + disabledCalendars: string[]; + onSelectEvent: (event: EventOccurrence) => void; +} + +export class EventSearchBar extends Component< + EventSearchBarProps, + { query: string; suggestions: EventOccurrence[] } > { static displayName = 'EventSearchBar'; - static defaultProps = { - disabledCalendars: [], - onSelectEvent: (event: Event) => {}, - }; - constructor(props) { super(props); this.state = { @@ -40,7 +37,16 @@ class EventSearchBar extends Component< .search(query) .limit(10) .then(events => { - this.setState({ suggestions: events }); + this.setState({ + suggestions: occurrencesForEvents(events, { + startUnix: moment() + .add(-2, 'years') + .unix(), + endUnix: moment() + .add(2, 'years') + .unix(), + }), + }); }); }; @@ -69,20 +75,18 @@ class EventSearchBar extends Component< // TODO BG return ; - return ( - event.id} - suggestionRenderer={this.renderEvent} - onSearchQueryChanged={this.onSearchQueryChanged} - onSelectSuggestion={this.onSelectEvent} - onClearSearchQuery={this.onClearSearchQuery} - onClearSearchSuggestions={this.onClearSearchSuggestions} - /> - ); + // return ( + // event.id} + // suggestionRenderer={this.renderEvent} + // onSearchQueryChanged={this.onSearchQueryChanged} + // onSelectSuggestion={this.onSelectEvent} + // onClearSearchQuery={this.onClearSearchQuery} + // onClearSearchSuggestions={this.onClearSearchSuggestions} + // /> + // ); } } - -export default EventSearchBar; diff --git a/app/internal_packages/main-calendar/lib/core/event-timerange-picker.tsx b/app/internal_packages/main-calendar/lib/core/event-timerange-picker.tsx new file mode 100644 index 000000000..2ff7a2e9b --- /dev/null +++ b/app/internal_packages/main-calendar/lib/core/event-timerange-picker.tsx @@ -0,0 +1,66 @@ +import moment from 'moment'; +import React from 'react'; +import { DateUtils } from 'mailspring-exports'; +import { DatePicker, RetinaImg, TimePicker } from 'mailspring-component-kit'; + +export const EventTimerangePicker: React.FunctionComponent<{ + start: number; + end: number; + onChange: ({ start, end }) => void; +}> = ({ start, end, onChange }) => { + const onChangeStartTime = newTimestamp => { + const newStart = moment(newTimestamp); + let newEnd = moment(end); + if (newEnd.isSameOrBefore(newStart)) { + const leftInDay = moment(newStart) + .endOf('day') + .diff(newStart); + const move = Math.min(leftInDay, moment.duration(1, 'hour').asMilliseconds()); + newEnd = moment(newStart).add(move, 'ms'); + } + onChange({ start: newStart.unix(), end: newEnd.unix() }); + }; + + const onChangeEndTime = (newTimestamp: number) => { + const newEnd = moment(newTimestamp); + let newStart = moment(start); + if (newStart.isSameOrAfter(newEnd)) { + const sinceDay = moment(newEnd).diff(newEnd.startOf('day')); + const move = Math.min(sinceDay, moment.duration(1, 'hour').asMilliseconds()); + newStart = moment(newEnd).subtract(move, 'ms'); + } + onChange({ end: newEnd.unix(), start: newStart.unix() }); + }; + + const onChangeDay = (newTimestamp: number) => { + const newDay = moment(newTimestamp); + + const newStart = moment(start); + newStart.year(newDay.year()); + newStart.dayOfYear(newDay.dayOfYear()); + + const newEnd = moment(end); + newEnd.year(newDay.year()); + newEnd.dayOfYear(newDay.dayOfYear()); + + this.setState({ start: newStart.unix(), end: newEnd.unix() }); + }; + + return ( +
+ + + + to + + + {moment() + .tz(DateUtils.timeZone) + .format('z')} + +   on   + + +
+ ); +}; diff --git a/app/internal_packages/main-calendar/lib/core/footer-controls.tsx b/app/internal_packages/main-calendar/lib/core/footer-controls.tsx deleted file mode 100644 index 184298aac..000000000 --- a/app/internal_packages/main-calendar/lib/core/footer-controls.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import { PropTypes, Utils } from 'mailspring-exports'; - -export default class FooterControls extends React.Component<{ - footerComponents: React.ReactChildren; -}> { - static displayName = 'FooterControls'; - - static defaultProps = { - footerComponents: false, - }; - - shouldComponentUpdate(nextProps, nextState) { - return !Utils.isEqualReact(nextProps, this.props) || !Utils.isEqualReact(nextState, this.state); - } - - render() { - if (!this.props.footerComponents) { - return false; - } - return ( -
-
-   -
- {this.props.footerComponents} -
- ); - } -} diff --git a/app/internal_packages/main-calendar/lib/core/header-controls.tsx b/app/internal_packages/main-calendar/lib/core/header-controls.tsx index d61872871..8e87c20bd 100644 --- a/app/internal_packages/main-calendar/lib/core/header-controls.tsx +++ b/app/internal_packages/main-calendar/lib/core/header-controls.tsx @@ -2,18 +2,13 @@ import React from 'react'; import { Utils } from 'mailspring-exports'; import { RetinaImg } from 'mailspring-component-kit'; -export default class HeaderControls extends React.Component<{ +export class HeaderControls extends React.Component<{ title: string; - headerComponents: React.ReactNode; nextAction: () => void; prevAction: () => void; }> { static displayName = 'HeaderControls'; - static defaultProps = { - headerComonents: false, - }; - shouldComponentUpdate(nextProps, nextState) { return !Utils.isEqualReact(nextProps, this.props) || !Utils.isEqualReact(nextState, this.state); } @@ -48,7 +43,7 @@ export default class HeaderControls extends React.Component<{ {this.props.title} {this._renderNextAction()}
- {this.props.headerComponents} + {this.props.children}
); } diff --git a/app/internal_packages/main-calendar/lib/core/mailspring-calendar.tsx b/app/internal_packages/main-calendar/lib/core/mailspring-calendar.tsx index e4ec4fa03..32be477fa 100644 --- a/app/internal_packages/main-calendar/lib/core/mailspring-calendar.tsx +++ b/app/internal_packages/main-calendar/lib/core/mailspring-calendar.tsx @@ -1,90 +1,74 @@ import moment, { Moment } from 'moment'; import React from 'react'; -import { Rx, DatabaseStore, AccountStore, Calendar, Account, Event } from 'mailspring-exports'; -import { ScrollRegion, ResizableRegion } from 'mailspring-component-kit'; -import WeekView from './week-view'; -import MonthView from './month-view'; -import EventSearchBar from './event-search-bar'; -import CalendarSourceList from './calendar-source-list'; -import CalendarDataSource from './calendar-data-source'; -import { WEEK_VIEW, MONTH_VIEW } from './calendar-constants'; -import MiniMonthView from './mini-month-view'; +import { + Rx, + DatabaseStore, + AccountStore, + Calendar, + Account, + Actions, + localized, + DestroyModelTask, +} from 'mailspring-exports'; +import { + ScrollRegion, + ResizableRegion, + KeyCommandsRegion, + MiniMonthView, +} from 'mailspring-component-kit'; +import { WeekView } from './week-view'; +import { MonthView } from './month-view'; +import { EventSearchBar } from './event-search-bar'; +import { CalendarSourceList } from './calendar-source-list'; +import { CalendarDataSource, EventOccurrence } from './calendar-data-source'; +import { CalendarView } from './calendar-constants'; import { Disposable } from 'rx-core'; +import { CalendarEventArgs } from './calendar-event-container'; +import { CalendarEventPopover } from './calendar-event-popover'; +import { remote } from 'electron'; const DISABLED_CALENDARS = 'mailspring.disabledCalendars'; +const VIEWS = { + [CalendarView.WEEK]: WeekView, + [CalendarView.MONTH]: MonthView, +}; + +export interface EventRendererProps { + focusedEvent: EventOccurrence; + selectedEvents: EventOccurrence[]; + onEventClick: (e: React.MouseEvent, event: EventOccurrence) => void; + onEventDoubleClick: (event: EventOccurrence) => void; + onEventFocused: (event: EventOccurrence) => void; +} + +export interface MailspringCalendarViewProps extends EventRendererProps { + dataSource: CalendarDataSource; + disabledCalendars: string[]; + focusedMoment: Moment; + onChangeView: (view: CalendarView) => void; + onChangeFocusedMoment: (moment: Moment) => void; + onCalendarMouseUp: (args: CalendarEventArgs) => void; + onCalendarMouseDown: (args: CalendarEventArgs) => void; + onCalendarMouseMove: (args: CalendarEventArgs) => void; +} + /* * Mailspring Calendar */ -interface MailspringCalendarProps { - /* - * The data source that powers all of the views of the MailspringCalendar - */ - dataSource: CalendarDataSource; - - currentMoment?: Moment; - - /* - * Any extra info you want to display on the top banner of calendar - * components - */ - bannerComponents?: { - day: React.ReactChild; - week: React.ReactChild; - month: React.ReactChild; - year: React.ReactChild; - }; - - /* - * Any extra header components for each of the supported View types of - * the MailspringCalendar - */ - headerComponents?: { - day: React.ReactChild; - week: React.ReactChild; - month: React.ReactChild; - year: React.ReactChild; - }; - - /* - * Any extra footer components for each of the supported View types of - * the MailspringCalendar - */ - footerComponents?: { - day: React.ReactChild; - week: React.ReactChild; - month: React.ReactChild; - year: React.ReactChild; - }; - - /* - * The following are a set of supported interaction handlers. - * - * These are passed a custom set of arguments in a single object that - * includes the `currentView` as well as things like the `time` at the - * click coordinate. - */ - onCalendarMouseUp?: () => void; - onCalendarMouseDown?: () => void; - onCalendarMouseMove?: () => void; - - onEventClick: (e: React.MouseEvent, event: Event) => void; - onEventDoubleClick: (event: Event) => void; - onEventFocused: (event: Event) => void; - - selectedEvents: Event[]; -} +interface MailspringCalendarProps {} interface MailspringCalendarState { - currentView: string; - focusedEvent: Event | null; + view: CalendarView; + selectedEvents: EventOccurrence[]; + focusedEvent: EventOccurrence | null; accounts?: Account[]; calendars: Calendar[]; - currentMoment: Moment; + focusedMoment: Moment; disabledCalendars: string[]; } -export default class MailspringCalendar extends React.Component< +export class MailspringCalendar extends React.Component< MailspringCalendarProps, MailspringCalendarState > { @@ -92,26 +76,21 @@ export default class MailspringCalendar extends React.Component< static WeekView = WeekView; - static defaultProps = { - bannerComponents: { day: false, week: false, month: false, year: false }, - headerComponents: { day: false, week: false, month: false, year: false }, - footerComponents: { day: false, week: false, month: false, year: false }, - selectedEvents: [], - }; - static containerStyles = { height: '100%', }; _disposable?: Disposable; + _dataSource = new CalendarDataSource(); constructor(props) { super(props); this.state = { calendars: [], focusedEvent: null, - currentView: WEEK_VIEW, - currentMoment: props.currentMoment || this._now(), + selectedEvents: [], + view: CalendarView.WEEK, + focusedMoment: moment(), disabledCalendars: AppEnv.config.get(DISABLED_CALENDARS) || [], }; } @@ -129,49 +108,109 @@ export default class MailspringCalendar extends React.Component< const calQueryObs = Rx.Observable.fromQuery(calQuery); const accQueryObs = Rx.Observable.fromStore(AccountStore); const configObs = Rx.Observable.fromConfig(DISABLED_CALENDARS); - return Rx.Observable.combineLatest([calQueryObs, accQueryObs, configObs]).subscribe( - ([calendars, accountStore, disabledCalendars]: [Calendar[], any, string[] | undefined]) => { + + return Rx.Observable.combineLatest(calQueryObs, accQueryObs, configObs).subscribe( + ([calendars, accountStore, disabledCalendars]) => { this.setState({ - accounts: accountStore.accounts() as Account[], calendars: calendars, + accounts: accountStore.accounts(), disabledCalendars: disabledCalendars || [], }); } ); } - _now() { - return moment(); + onChangeView = (view: CalendarView) => { + this.setState({ view }); + }; + + onChangeFocusedMoment = (focusedMoment: Moment) => { + this.setState({ focusedMoment, focusedEvent: null }); + }; + + _focusEvent = (event: EventOccurrence) => { + this.setState({ focusedMoment: moment(event.start * 1000), focusedEvent: event }); + }; + + _openEventPopover(eventModel: EventOccurrence) { + const eventEl = document.getElementById(eventModel.id); + if (!eventEl) { + return; + } + Actions.openPopover(, { + originRect: eventEl.getBoundingClientRect(), + direction: 'right', + fallbackDirection: 'left', + }); } - _getCurrentViewComponent() { - const components = {}; - components[WEEK_VIEW] = WeekView; - components[MONTH_VIEW] = MonthView; - return components[this.state.currentView]; - } + _onEventClick = (e: React.MouseEvent, event: EventOccurrence) => { + let next = [...this.state.selectedEvents]; - _changeCurrentView = currentView => { - this.setState({ currentView }); + if (e.shiftKey || e.metaKey) { + const idx = next.findIndex(({ id }) => event.id === id); + if (idx === -1) { + next.push(event); + } else { + next.splice(idx, 1); + } + } else { + next = [event]; + } + + this.setState({ + selectedEvents: next, + }); }; - _changeCurrentMoment = currentMoment => { - this.setState({ currentMoment, focusedEvent: null }); + _onEventDoubleClick = (occurrence: EventOccurrence) => { + this._openEventPopover(occurrence); }; - _changeCurrentMomentFromValue = value => { - this.setState({ currentMoment: moment(value), focusedEvent: null }); + _onEventFocused = (occurrence: EventOccurrence) => { + this._openEventPopover(occurrence); }; - _focusEvent = event => { - const value = event.start * 1000; - this.setState({ currentMoment: moment(value), focusedEvent: event }); + _onDeleteSelectedEvents = () => { + if (this.state.selectedEvents.length === 0) { + return; + } + const response = remote.dialog.showMessageBoxSync({ + type: 'warning', + buttons: [localized('Delete'), localized('Cancel')], + message: localized('Delete or decline these events?'), + detail: localized( + `Are you sure you want to delete or decline invitations for the selected event(s)?` + ), + }); + if (response === 0) { + // response is button array index + for (const event of this.state.selectedEvents) { + const task = new DestroyModelTask({ + modelId: event.id, + modelName: event.constructor.name, + endpoint: '/events', + accountId: event.accountId, + }); + Actions.queueTask(task); + } + } }; + _onCalendarMouseDown = () => {}; + _onCalendarMouseMove = () => {}; + _onCalendarMouseUp = () => {}; + render() { - const CurrentView = this._getCurrentViewComponent(); + const CurrentView = VIEWS[this.state.view]; + return ( -
+
- +
-
+ ); } } diff --git a/app/internal_packages/main-calendar/lib/core/mini-month-view.tsx b/app/internal_packages/main-calendar/lib/core/mini-month-view.tsx deleted file mode 100644 index 2c455c710..000000000 --- a/app/internal_packages/main-calendar/lib/core/mini-month-view.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import _ from 'underscore'; -import React from 'react'; -import PropTypes from 'prop-types'; -import moment from 'moment'; -import classnames from 'classnames'; - -export default class MiniMonthView extends React.Component< - { value: number; onChange: (val: number) => void }, - { shownYear: number; shownMonth: number } -> { - static displayName = 'MiniMonthView'; - - static propTypes = { - value: PropTypes.number, - onChange: PropTypes.func, - }; - - static defaultProps = { - value: moment().valueOf(), - onChange: () => {}, - }; - - today = moment(); - - constructor(props) { - super(props); - this.state = this._stateFromProps(props); - } - - componentWillReceiveProps(newProps) { - this.setState(this._stateFromProps(newProps)); - } - - _stateFromProps(props) { - const m = props.value ? moment(props.value) : moment(); - return { - shownYear: m.year(), - shownMonth: m.month(), - }; - } - - _shownMonthMoment() { - return moment([this.state.shownYear, this.state.shownMonth]); - } - - _changeMonth = by => { - const newMonth = this.state.shownMonth + by; - const newMoment = this._shownMonthMoment().month(newMonth); - this.setState({ - shownYear: newMoment.year(), - shownMonth: newMoment.month(), - }); - }; - - _renderLegend() { - const weekdayGen = moment([2016]); - const legendEls = []; - for (let i = 0; i < 7; i++) { - const dayStr = weekdayGen.weekday(i).format('dd'); // Locale aware! - legendEls.push( - - {dayStr} - - ); - } - return
{legendEls}
; - } - - _onClickDay = event => { - if (!event.target.dataset.timestamp) { - return; - } - const newVal = moment(parseInt(event.target.dataset.timestamp, 10)).valueOf(); - this.props.onChange(newVal); - }; - - _isSameDay(m1, m2) { - return m1.dayOfYear() === m2.dayOfYear() && m1.year() === m2.year(); - } - - _renderDays() { - const dayIter = this._shownMonthMoment().date(1); - const startWeek = dayIter.week(); - const curMonth = this.state.shownMonth; - const endWeek = moment(dayIter) - .date(dayIter.daysInMonth()) - .week(); - const weekEls = []; - const valDay = moment(this.props.value); - for (let week = startWeek; week <= endWeek; week++) { - dayIter.week(week); // Locale aware! - const dayEls = []; - for (let weekday = 0; weekday < 7; weekday++) { - dayIter.weekday(weekday); // Locale aware! - const dayStr = dayIter.format('D'); - const className = classnames({ - day: true, - today: this._isSameDay(dayIter, this.today), - 'cur-day': this._isSameDay(dayIter, valDay), - 'cur-month': dayIter.month() === curMonth, - }); - dayEls.push( -
- {dayStr} -
- ); - } - weekEls.push( -
- {dayEls} -
- ); - } - return ( -
- {weekEls} -
- ); - } - - render() { - return ( -
-
-
- ‹ -
- {this._shownMonthMoment().format('MMMM YYYY')} -
- › -
-
- {this._renderLegend()} - {this._renderDays()} -
- ); - } -} diff --git a/app/internal_packages/main-calendar/lib/core/month-view.tsx b/app/internal_packages/main-calendar/lib/core/month-view.tsx index 37358283a..d63554e7a 100644 --- a/app/internal_packages/main-calendar/lib/core/month-view.tsx +++ b/app/internal_packages/main-calendar/lib/core/month-view.tsx @@ -1,11 +1,12 @@ import React from 'react'; -import PropTypes from 'prop-types'; +import { MailspringCalendarViewProps } from './mailspring-calendar'; +import { CalendarView } from './calendar-constants'; -export default class MonthView extends React.Component<{ changeView: (view: string) => void }> { +export class MonthView extends React.Component { static displayName = 'MonthView'; _onClick = () => { - this.props.changeView('WeekView'); + this.props.onChangeView(CalendarView.WEEK); }; render() { diff --git a/app/internal_packages/main-calendar/lib/core/top-banner.tsx b/app/internal_packages/main-calendar/lib/core/top-banner.tsx deleted file mode 100644 index 09e96a251..000000000 --- a/app/internal_packages/main-calendar/lib/core/top-banner.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -export default class TopBanner extends React.Component<{ bannerComponents: React.ReactNode }> { - static displayName = 'TopBanner'; - - static propTypes = { - bannerComponents: PropTypes.node, - }; - - render() { - if (!this.props.bannerComponents) { - return false; - } - return
{this.props.bannerComponents}
; - } -} diff --git a/app/internal_packages/main-calendar/lib/core/week-view-all-day-events.tsx b/app/internal_packages/main-calendar/lib/core/week-view-all-day-events.tsx index 376ac72c2..6cd982541 100644 --- a/app/internal_packages/main-calendar/lib/core/week-view-all-day-events.tsx +++ b/app/internal_packages/main-calendar/lib/core/week-view-all-day-events.tsx @@ -1,6 +1,9 @@ import React from 'react'; import { Event, Utils } from 'mailspring-exports'; -import CalendarEvent from './calendar-event'; +import { CalendarEvent } from './calendar-event'; +import { EventOccurrence } from './calendar-data-source'; +import { OverlapByEventId } from './week-view-helpers'; +import { EventRendererProps } from './mailspring-calendar'; /* * Displays the all day events across the top bar of the week event view. @@ -9,16 +12,16 @@ import CalendarEvent from './calendar-event'; * we can use `shouldComponentUpdate` to selectively re-render these * events. */ -interface WeekViewAllDayEventsProps { +interface WeekViewAllDayEventsProps extends EventRendererProps { end: number; start: number; height: number; minorDim: number; - allDayEvents: Event[]; - allDayOverlap: any; + allDayEvents: EventOccurrence[]; + allDayOverlap: OverlapByEventId; } -export default class WeekViewAllDayEvents extends React.Component { +export class WeekViewAllDayEvents extends React.Component { static displayName = 'WeekViewAllDayEvents'; shouldComponentUpdate(nextProps, nextState) { @@ -26,23 +29,27 @@ export default class WeekViewAllDayEvents extends React.Component { - return ( - - ); - }); + const { height, allDayEvents, allDayOverlap, selectedEvents, focusedEvent } = this.props; + return ( -
- {eventComponents} +
+ {allDayEvents.map(e => ( + + ))}
); } diff --git a/app/internal_packages/main-calendar/lib/core/week-view-event-column.tsx b/app/internal_packages/main-calendar/lib/core/week-view-event-column.tsx index a0594c49b..3789ff1c6 100644 --- a/app/internal_packages/main-calendar/lib/core/week-view-event-column.tsx +++ b/app/internal_packages/main-calendar/lib/core/week-view-event-column.tsx @@ -1,8 +1,10 @@ import React from 'react'; import moment, { Moment } from 'moment'; import classnames from 'classnames'; -import { PropTypes, Utils, Event } from 'mailspring-exports'; -import CalendarEvent from './calendar-event'; +import { Utils, Event } from 'mailspring-exports'; +import { CalendarEvent } from './calendar-event'; +import { EventOccurrence } from './calendar-data-source'; +import { overlapForEvents } from './week-view-helpers'; /* * This display a single column of events in the Week View. @@ -11,71 +13,63 @@ import CalendarEvent from './calendar-event'; * column-by-column basis. */ interface WeekViewEventColumnProps { - events: Event[]; + events: EventOccurrence[]; day: Moment; dayEnd: number; - focusedEvent: Event; - eventOverlap: any; - onEventClick: () => void; - onEventDoubleClick: () => void; - onEventFocused: () => void; - selectedEvents: Event[]; + focusedEvent: EventOccurrence; + onEventClick: (e: React.MouseEvent, event: EventOccurrence) => void; + onEventDoubleClick: (event: EventOccurrence) => void; + onEventFocused: (event: EventOccurrence) => void; + selectedEvents: EventOccurrence[]; } -export default class WeekViewEventColumn extends React.Component { +export class WeekViewEventColumn extends React.Component { static displayName = 'WeekViewEventColumn'; shouldComponentUpdate(nextProps, nextState) { return !Utils.isEqualReact(nextProps, this.props) || !Utils.isEqualReact(nextState, this.state); } - renderEvents() { + render() { const { events, focusedEvent, selectedEvents, - eventOverlap, dayEnd, day, onEventClick, onEventDoubleClick, onEventFocused, } = this.props; - return events.map(e => ( - - )); - } - render() { const className = classnames({ 'event-column': true, - weekend: this.props.day.day() === 0 || this.props.day.day() === 6, + weekend: day.day() === 0 || day.day() === 6, }); - const end = moment(this.props.day) + const overlap = overlapForEvents(events); + const end = moment(day) .add(1, 'day') .subtract(1, 'millisecond') .valueOf(); + return ( -
- {this.renderEvents()} +
+ {events.map(e => ( + + ))}
); } diff --git a/app/internal_packages/main-calendar/lib/core/week-view-helpers.ts b/app/internal_packages/main-calendar/lib/core/week-view-helpers.ts new file mode 100644 index 000000000..15ed3162d --- /dev/null +++ b/app/internal_packages/main-calendar/lib/core/week-view-helpers.ts @@ -0,0 +1,132 @@ +import { EventOccurrence } from './calendar-data-source'; +import moment, { Moment } from 'moment'; +import { Utils } from 'mailspring-exports'; + +// This pre-fetches from Utils to prevent constant disc access +const overlapsBounds = Utils.overlapsBounds; + +export interface OverlapByEventId { + [id: string]: { concurrentEvents: number; order: null | number }; +} + +/* + * Computes the overlap between a set of events in not O(n^2). + * + * Returns a hash keyed by event id whose value is an object: + * - concurrentEvents: number of concurrent events + * - order: the order in that series of concurrent events + */ +export function overlapForEvents(events: EventOccurrence[]) { + const eventsByTime: { [unix: number]: EventOccurrence[] } = {}; + + for (const event of events) { + if (!eventsByTime[event.start]) { + eventsByTime[event.start] = []; + } + if (!eventsByTime[event.end]) { + eventsByTime[event.end] = []; + } + eventsByTime[event.start].push(event); + eventsByTime[event.end].push(event); + } + const sortedTimes = Object.keys(eventsByTime) + .map(Number) + .sort(); + + const overlapById: OverlapByEventId = {}; + let ongoingEvents: EventOccurrence[] = []; + + for (const t of sortedTimes) { + // Process all event start/ends during this time to keep our + // "ongoingEvents" set correct. + for (const e of eventsByTime[t]) { + if (e.start === t) { + overlapById[e.id] = { concurrentEvents: 1, order: null }; + ongoingEvents.push(e); + } + if (e.end === t) { + ongoingEvents = ongoingEvents.filter(o => o.id !== e.id); + } + } + + // Write concurrency for all the events currently ongoing if they haven't + // been assigned values already + for (const e of ongoingEvents) { + const numEvents = findMaxConcurrent(ongoingEvents, overlapById); + overlapById[e.id].concurrentEvents = numEvents; + if (overlapById[e.id].order === null) { + // Don't re-assign the order. + const order = findAvailableOrder(ongoingEvents, overlapById); + overlapById[e.id].order = order; + } + } + } + return overlapById; +} + +export function findMaxConcurrent(ongoing: EventOccurrence[], overlapById: OverlapByEventId) { + return Math.max(1, ongoing.length, ...ongoing.map(e => overlapById[e.id].concurrentEvents)); +} + +export function findAvailableOrder(ongoing: EventOccurrence[], overlapById: OverlapByEventId) { + const orders = ongoing.map(e => overlapById[e.id].order); + let order = 1; + while (true) { + if (!orders.includes(order)) { + return order; + } + order += 1; + } +} + +export function maxConcurrentEvents(eventOverlap: OverlapByEventId) { + return Math.max(-1, ...Object.values(eventOverlap).map(o => o.concurrentEvents)); +} + +export function eventsGroupedByDay(events: EventOccurrence[], days: Moment[]) { + const map: { allDay: EventOccurrence[]; [dayUnix: string]: EventOccurrence[] } = { allDay: [] }; + + const unixDays = days.map(d => d.unix()); + unixDays.forEach(day => { + map[`${day}`] = []; + }); + + events.forEach(event => { + if (event.isAllDay) { + map.allDay.push(event); + } else { + for (const day of unixDays) { + const bounds = { + start: day, + end: day + 24 * 60 * 60 - 1, + }; + if (overlapsBounds(bounds, event)) { + map[`${day}`].push(event); + } + } + } + }); + + return map; +} + +export const DAY_DUR = 24 * 60 * 60; +export const TICK_STEP = 30 * 60; +export const TICKS_PER_DAY = DAY_DUR / TICK_STEP; + +export function* tickGenerator(type: 'major' | 'minor', tickHeight: number) { + const step = TICK_STEP * 2; + const skip = TICK_STEP * 2; + const stepStart = type === 'minor' ? TICK_STEP : 0; + + // We only use a moment object so we can properly localize the "time" + // part. The day is irrelevant. We just need to make sure we're + // picking a non-DST boundary day. + const time = moment([2015, 1, 1]).add(stepStart, 'seconds'); + + for (let tsec = stepStart; tsec <= DAY_DUR; tsec += step) { + const y = (tsec / TICK_STEP) * tickHeight; + yield { time, y }; + time.add(skip, 'seconds'); + } +} diff --git a/app/internal_packages/main-calendar/lib/core/week-view.tsx b/app/internal_packages/main-calendar/lib/core/week-view.tsx index bc4d7f6df..fe3efd0ef 100644 --- a/app/internal_packages/main-calendar/lib/core/week-view.tsx +++ b/app/internal_packages/main-calendar/lib/core/week-view.tsx @@ -3,65 +3,43 @@ import _ from 'underscore'; import moment, { Moment } from 'moment-timezone'; import classnames from 'classnames'; import React from 'react'; -import ReactDOM from 'react-dom'; -import { PropTypes, Utils, Event } from 'mailspring-exports'; -import { ScrollRegion } from 'mailspring-component-kit'; -import TopBanner from './top-banner'; -import HeaderControls from './header-controls'; -import FooterControls from './footer-controls'; -import CalendarDataSource from './calendar-data-source'; -import EventGridBackground from './event-grid-background'; -import WeekViewEventColumn from './week-view-event-column'; -import WeekViewAllDayEvents from './week-view-all-day-events'; -import CalendarEventContainer from './calendar-event-container'; -import CurrentTimeIndicator from './current-time-indicator'; +import { ScrollRegion, InjectedComponentSet } from 'mailspring-component-kit'; +import { HeaderControls } from './header-controls'; +import { EventOccurrence } from './calendar-data-source'; +import { EventGridBackground } from './event-grid-background'; +import { WeekViewEventColumn } from './week-view-event-column'; +import { WeekViewAllDayEvents } from './week-view-all-day-events'; +import { CalendarEventContainer } from './calendar-event-container'; +import { CurrentTimeIndicator } from './current-time-indicator'; import { Disposable } from 'rx-core'; +import { + overlapForEvents, + maxConcurrentEvents, + eventsGroupedByDay, + TICKS_PER_DAY, + tickGenerator, +} from './week-view-helpers'; +import { MailspringCalendarViewProps } from './mailspring-calendar'; const BUFFER_DAYS = 7; // in each direction const DAYS_IN_VIEW = 7; const MIN_INTERVAL_HEIGHT = 21; -const DAY_DUR = moment.duration(1, 'day').as('seconds'); -const INTERVAL_TIME = moment.duration(30, 'minutes').as('seconds'); +const DAY_PORTION_SHOWN_VERTICALLY = 11 / 24; -// This pre-fetches from Utils to prevent constant disc access -const overlapsBounds = Utils.overlapsBounds; - -interface WeekViewProps { - dataSource: CalendarDataSource; - currentMoment: Moment; - focusedEvent: Event; - bannerComponents: React.ReactChildren; - headerComponents: React.ReactChildren; - footerComponents: React.ReactChildren; - disabledCalendars: string[]; - changeCurrentView: () => void; - changeCurrentMoment: (moment: Moment) => void; - onCalendarMouseUp: () => void; - onCalendarMouseDown: () => void; - onCalendarMouseMove: () => void; - onEventClick: () => void; - onEventDoubleClick: () => void; - onEventFocused: () => void; - selectedEvents: Event[]; -} - -export default class WeekView extends React.Component< - WeekViewProps, - { intervalHeight: number; events: Event[] } +export class WeekView extends React.Component< + MailspringCalendarViewProps, + { intervalHeight: number; events: EventOccurrence[] } > { static displayName = 'WeekView'; - static defaultProps = { - changeCurrentView: () => {}, - bannerComponents: false, - headerComponents: false, - footerComponents: false, - }; - _waitingForShift = 0; _mounted = false; + _scrollbar = React.createRef(); _sub?: Disposable; - _lastWrapHeight: number; + + _legendWrapEl = React.createRef(); + _calendarWrapEl = React.createRef(); + _gridScrollRegion = React.createRef(); constructor(props) { super(props); @@ -74,20 +52,23 @@ export default class WeekView extends React.Component< componentDidMount() { this._mounted = true; this._centerScrollRegion(); - this._setIntervalHeight(); - window.addEventListener('resize', this._setIntervalHeight, true); - const wrap = ReactDOM.findDOMNode(this.refs.calendarAreaWrap) as HTMLElement; + + // Shift ourselves right by a week because we preload 7 days on either side + const wrap = this._calendarWrapEl.current; wrap.scrollLeft += wrap.clientWidth; + this.updateSubscription(); + this._setIntervalHeight(); } componentDidUpdate(prevProps) { - this._setIntervalHeight(); - const wrap = ReactDOM.findDOMNode(this.refs.calendarAreaWrap) as HTMLElement; - wrap.scrollLeft += this._waitingForShift; - this._waitingForShift = 0; + if (this._waitingForShift) { + const wrap = this._calendarWrapEl.current; + wrap.scrollLeft += this._waitingForShift; + this._waitingForShift = 0; + } if ( - prevProps.currentMoment !== this.props.currentMoment || + prevProps.focusedMoment !== this.props.focusedMoment || prevProps.disabledCalendars !== this.props.disabledCalendars ) { this.updateSubscription(); @@ -96,8 +77,7 @@ export default class WeekView extends React.Component< componentWillUnmount() { this._mounted = false; - this._sub.dispose(); - window.removeEventListener('resize', this._setIntervalHeight); + this._sub && this._sub.dispose(); } // Indirection for testing purposes @@ -106,17 +86,14 @@ export default class WeekView extends React.Component< } updateSubscription() { - if (this._sub) { - this._sub.dispose(); - } - - const { start, end } = this._calculateMomentRange(); + const { bufferedStart, bufferedEnd } = this._calculateMomentRange(); + this._sub && this._sub.dispose(); this._sub = this.props.dataSource .buildObservable({ disabledCalendars: this.props.disabledCalendars, - startTime: start.unix(), - endTime: end.unix(), + startUnix: bufferedStart.unix(), + endUnix: bufferedEnd.unix(), }) .subscribe(state => { this.setState(state); @@ -124,37 +101,39 @@ export default class WeekView extends React.Component< } _calculateMomentRange() { - const { currentMoment } = this.props; - let start; + const { focusedMoment } = this.props; // NOTE: Since we initialize a new time from one of the properties of - // the props.currentMomet, we need to check for the timezone! + // the props.focusedMoment, we need to check for the timezone! // // Other relative operations (like adding or subtracting time) are // independent of a timezone. - const tz = currentMoment.tz(); - if (tz) { - start = moment.tz([currentMoment.year()], tz); - } else { - start = moment([currentMoment.year()]); - } - - start = start + const tz = focusedMoment.tz(); + const start = (tz ? moment.tz([focusedMoment.year()], tz) : moment([focusedMoment.year()])) .weekday(0) - .week(currentMoment.week()) - .subtract(BUFFER_DAYS, 'days'); + .week(focusedMoment.week()); - const end = moment(start) - .add(BUFFER_DAYS * 2 + DAYS_IN_VIEW, 'days') + const end = start + .clone() + .add(DAYS_IN_VIEW, 'days') .subtract(1, 'millisecond'); - return { start, end }; + return { + visibleStart: start, + visibleEnd: end, + + bufferedStart: start.clone().subtract(BUFFER_DAYS, 'days'), + bufferedEnd: moment(end) + .add(BUFFER_DAYS, 'days') + .subtract(1, 'millisecond'), + }; } - _renderDateLabel = (day, idx) => { + _renderDateLabel = (day: Moment, idx: number) => { const className = classnames({ 'day-label-wrap': true, 'is-today': this._isToday(day), + 'is-hard-stop': day.weekday() == 1, }); return (
@@ -171,246 +150,79 @@ export default class WeekView extends React.Component< return todayDayOfYear === day.dayOfYear() && todayYear === day.year(); } - _renderEventColumn = (eventsByDay, day) => { - const dayUnix = day.unix(); - const events = eventsByDay[dayUnix]; - return ( - - ); - }; - - _allDayEventHeight(allDayOverlap) { - if (_.size(allDayOverlap) === 0) { - return 0; - } - return this._maxConcurrentEvents(allDayOverlap) * MIN_INTERVAL_HEIGHT + 1; - } - - /* - * Computes the overlap between a set of events in not O(n^2). - * - * Returns a hash keyed by event id whose value is an object: - * - concurrentEvents: number of concurrent events - * - order: the order in that series of concurrent events - */ - _eventOverlap(events) { - const times = {}; - for (const event of events) { - if (!times[event.start]) { - times[event.start] = []; - } - if (!times[event.end]) { - times[event.end] = []; - } - times[event.start].push(event); - times[event.end].push(event); - } - const sortedTimes = Object.keys(times) - .map(k => parseInt(k, 10)) - .sort(); - const overlapById = {}; - let startedEvents = []; - for (const t of sortedTimes) { - for (const e of times[t]) { - if (e.start === t) { - overlapById[e.id] = { concurrentEvents: 1, order: null }; - startedEvents.push(e); - } - if (e.end === t) { - startedEvents = _.reject(startedEvents, o => o.id === e.id); - } - } - for (const e of startedEvents) { - if (!overlapById[e.id]) { - overlapById[e.id] = {}; - } - const numEvents = this._findMaxConcurrent(startedEvents, overlapById); - overlapById[e.id].concurrentEvents = numEvents; - if (overlapById[e.id].order === null) { - // Dont' re-assign the order. - const order = this._findAvailableOrder(startedEvents, overlapById); - overlapById[e.id].order = order; - } - } - } - return overlapById; - } - - _findMaxConcurrent(startedEvents, overlapById) { - let max = 1; - for (const e of startedEvents) { - max = Math.max(overlapById[e.id].concurrentEvents || 1, max); - } - return Math.max(max, startedEvents.length); - } - - _findAvailableOrder(startedEvents, overlapById) { - const orders = startedEvents.map(e => overlapById[e.id].order); - let order = 1; - while (true) { - if (orders.indexOf(order) === -1) { - return order; - } - order += 1; - } - } - - _maxConcurrentEvents(eventOverlap) { - let maxConcurrent = -1; - _.each(eventOverlap, ({ concurrentEvents }) => { - maxConcurrent = Math.max(concurrentEvents, maxConcurrent); - }); - return maxConcurrent; - } - _daysInView() { - const { start } = this._calculateMomentRange(); - const days = []; + const { bufferedStart } = this._calculateMomentRange(); + const days: Moment[] = []; for (let i = 0; i < DAYS_IN_VIEW + BUFFER_DAYS * 2; i++) { // moment::weekday is locale aware since some weeks start on diff // days. See http://momentjs.com/docs/#/get-set/weekday/ - days.push(moment(start).weekday(i)); + days.push(moment(bufferedStart).weekday(i)); } return days; } - _headerComponents() { - const left = ( - - ); - const right = false; - return [left, right, this.props.headerComponents]; - } - _onClickToday = () => { - this.props.changeCurrentMoment(this._now()); + this.props.onChangeFocusedMoment(this._now()); }; _onClickNextWeek = () => { - const newMoment = moment(this.props.currentMoment).add(1, 'week'); - this.props.changeCurrentMoment(newMoment); + const newMoment = moment(this.props.focusedMoment).add(1, 'week'); + this.props.onChangeFocusedMoment(newMoment); }; _onClickPrevWeek = () => { - const newMoment = moment(this.props.currentMoment).subtract(1, 'week'); - this.props.changeCurrentMoment(newMoment); + const newMoment = moment(this.props.focusedMoment).subtract(1, 'week'); + this.props.onChangeFocusedMoment(newMoment); }; - _gridHeight() { - return DAY_DUR / INTERVAL_TIME * this.state.intervalHeight; - } - _centerScrollRegion() { - const wrap = ReactDOM.findDOMNode(this.refs.eventGridWrap) as HTMLElement; - wrap.scrollTop = this._gridHeight() / 2 - wrap.getBoundingClientRect().height / 2; - } - - // This generates the ticks used mark the event grid and the - // corresponding legend in the week view. - *_tickGenerator({ type }) { - const height = this._gridHeight(); - - let step = INTERVAL_TIME; - let stepStart = 0; - - // We only use a moment object so we can properly localize the "time" - // part. The day is irrelevant. We just need to make sure we're - // picking a non-DST boundary day. - const start = moment([2015, 1, 1]); - - let duration = INTERVAL_TIME; - if (type === 'major') { - step = INTERVAL_TIME * 2; - duration += INTERVAL_TIME; - } else if (type === 'minor') { - step = INTERVAL_TIME * 2; - stepStart = INTERVAL_TIME; - duration += INTERVAL_TIME; - start.add(INTERVAL_TIME, 'seconds'); - } - - const curTime = moment(start); - for (let tsec = stepStart; tsec <= DAY_DUR; tsec += step) { - const y = tsec / DAY_DUR * height; - yield { time: curTime, yPos: y }; - curTime.add(duration, 'seconds'); - } + const wrap = this._gridScrollRegion.current.viewportEl; + wrap.scrollTop = wrap.scrollHeight / 2 - wrap.clientHeight / 2; } _setIntervalHeight = () => { if (!this._mounted) { return; - } // Resize unmounting is delayed in tests - const wrap = ReactDOM.findDOMNode(this.refs.eventGridWrap) as HTMLElement; - const wrapHeight = wrap.getBoundingClientRect().height; - if (this._lastWrapHeight === wrapHeight) { - return; } - this._lastWrapHeight = wrapHeight; - const numIntervals = Math.floor(DAY_DUR / INTERVAL_TIME); - (ReactDOM.findDOMNode( - this.refs.eventGridLegendWrap - ) as HTMLElement).style.height = `${wrapHeight}px`; + const viewportHeight = this._gridScrollRegion.current.viewportEl.clientHeight; + this._legendWrapEl.current.style.height = `${viewportHeight}px`; + this.setState({ - intervalHeight: Math.max(wrapHeight / numIntervals, MIN_INTERVAL_HEIGHT), + intervalHeight: Math.max( + viewportHeight / (TICKS_PER_DAY * DAY_PORTION_SHOWN_VERTICALLY), + MIN_INTERVAL_HEIGHT + ), }); }; - _onScrollGrid = event => { - (ReactDOM.findDOMNode(this.refs.eventGridLegendWrap) as HTMLElement).scrollTop = - event.target.scrollTop; - }; + _onScrollCalendarArea = (event: React.UIEvent) => { + console.log(event.currentTarget.scrollLeft); + // if (!event.currentTarget.scrollLeft || this._waitingForShift) { + // return; + // } - _onScrollCalendarArea = event => { - if (!event.currentTarget.scrollLeft || this._waitingForShift) { - return; - } + // const edgeWidth = (event.currentTarget.clientWidth / DAYS_IN_VIEW) * 2; - const edgeWidth = event.currentTarget.clientWidth / DAYS_IN_VIEW * 2; - - if (event.currentTarget.scrollLeft < edgeWidth) { - this._waitingForShift = event.currentTarget.clientWidth; - this._onClickPrevWeek(); - } else if ( - event.currentTarget.scrollLeft > - event.currentTarget.scrollWidth - event.currentTarget.clientWidth - edgeWidth - ) { - this._waitingForShift = -event.currentTarget.clientWidth; - this._onClickNextWeek(); - } + // if (event.currentTarget.scrollLeft < edgeWidth) { + // this._waitingForShift = event.currentTarget.clientWidth; + // this._onClickPrevWeek(); + // } else if ( + // event.currentTarget.scrollLeft > + // event.currentTarget.scrollWidth - event.currentTarget.clientWidth - edgeWidth + // ) { + // this._waitingForShift = -event.currentTarget.clientWidth; + // this._onClickNextWeek(); + // } }; _renderEventGridLabels() { const labels = []; - let centering = 0; - for (const { time, yPos } of this._tickGenerator({ type: 'major' })) { - const hr = time.format('LT'); // Locale time. 2:00 pm or 14:00 - const style = { top: yPos - centering }; + for (const { time, y } of tickGenerator('major', this.state.intervalHeight)) { labels.push( - - {hr} + + {time.format('LT')} ); - centering = 8; // center all except the 1st one. } return labels.slice(0, labels.length - 1); } @@ -419,75 +231,57 @@ export default class WeekView extends React.Component< return (BUFFER_DAYS * 2 + DAYS_IN_VIEW) / DAYS_IN_VIEW; } - // We calculate events by days so we only need to iterate through all - // events in the span once. - _eventsByDay(days) { - const map = { allDay: [] }; - const unixDays = days.map(d => d.unix()); - unixDays.forEach(d => { - map[d] = []; - return; - }); - for (const event of this.state.events) { - if (event.isAllDay) { - map.allDay.push(event); - } else { - for (const day of unixDays) { - const bounds = { - start: day, - end: day + DAY_DUR - 1, - }; - if (overlapsBounds(bounds, event)) { - map[day].push(event); - } - } - } - } - return map; - } - render() { const days = this._daysInView(); + const eventsByDay = eventsGroupedByDay(this.state.events, days); const todayColumnIdx = days.findIndex(d => this._isToday(d)); - const eventsByDay = this._eventsByDay(days); - const allDayOverlap = this._eventOverlap(eventsByDay.allDay); - const tickGen = this._tickGenerator.bind(this); - const gridHeight = this._gridHeight(); + const totalHeight = TICKS_PER_DAY * this.state.intervalHeight; - const { start: startMoment, end: endMoment } = this._calculateMomentRange(); + const range = this._calculateMomentRange(); - const start = moment(startMoment).add(BUFFER_DAYS, 'days'); - const end = moment(endMoment).subtract(BUFFER_DAYS, 'days'); - const headerText = `${start.format('MMMM D')} - ${end.format('MMMM D YYYY')}`; + const headerText = [ + range.visibleStart.format('MMMM D'), + range.visibleEnd.format('MMMM D YYYY'), + ].join(' - '); + + const allDayOverlap = overlapForEvents(eventsByDay.allDay); + const allDayBarHeight = eventsByDay.allDay.length + ? maxConcurrentEvents(allDayOverlap) * MIN_INTERVAL_HEIGHT + 1 + : 0; return (
- +
+ +
+ > + +
-
+
All Day
-
-
+
+
{this._renderEventGridLabels()}
@@ -495,56 +289,69 @@ export default class WeekView extends React.Component<
{days.map(this._renderDateLabel)}
this.refs.scrollbar} - onScroll={this._onScrollGrid} + ref={this._gridScrollRegion} + scrollbarRef={this._scrollbar} + onScroll={event => (this._legendWrapEl.current.scrollTop = event.target.scrollTop)} + onViewportResize={this._setIntervalHeight} style={{ width: `${this._bufferRatio() * 100}%` }} > -
- {days.map(_.partial(this._renderEventColumn, eventsByDay))} +
+ {days.map(day => ( + + ))} BUFFER_DAYS && todayColumnIdx <= BUFFER_DAYS + DAYS_IN_VIEW } - gridHeight={gridHeight} + gridHeight={totalHeight} numColumns={BUFFER_DAYS * 2 + DAYS_IN_VIEW} todayColumnIdx={todayColumnIdx} />
this.refs.eventGridWrap} + ref={this._scrollbar} + getScrollRegion={() => this._gridScrollRegion.current} />
- -
); diff --git a/app/internal_packages/main-calendar/lib/event-description-frame.tsx b/app/internal_packages/main-calendar/lib/event-description-frame.tsx index f074893da..f158390f2 100644 --- a/app/internal_packages/main-calendar/lib/event-description-frame.tsx +++ b/app/internal_packages/main-calendar/lib/event-description-frame.tsx @@ -1,15 +1,11 @@ import { EventedIFrame } from 'mailspring-component-kit'; import React from 'react'; import ReactDOM from 'react-dom'; -import { PropTypes, Utils } from 'mailspring-exports'; +import { Utils } from 'mailspring-exports'; -export default class EmailFrame extends React.Component<{ content: string }> { +export class EmailFrame extends React.Component<{ content: string }> { static displayName = 'EmailFrame'; - static propTypes = { - content: PropTypes.string.isRequired, - }; - _mounted = false; _unlisten?: () => void; _iframeComponent: EventedIFrame; @@ -54,7 +50,7 @@ export default class EmailFrame extends React.Component<{ content: string }> { ); doc.close(); - // autolink(doc, {async: true}); + // autolink(doc.body, {async: true}); // autoscaleImages(doc); // addInlineDownloadPrompts(doc); diff --git a/app/internal_packages/main-calendar/lib/main.tsx b/app/internal_packages/main-calendar/lib/main.tsx index a74c73499..622e3d19b 100644 --- a/app/internal_packages/main-calendar/lib/main.tsx +++ b/app/internal_packages/main-calendar/lib/main.tsx @@ -1,18 +1,22 @@ import React from 'react'; import { WorkspaceStore, ComponentRegistry } from 'mailspring-exports'; -import CalendarWrapper from './calendar-wrapper'; -import QuickEventButton from './quick-event-button'; +import { QuickEventButton } from './quick-event-button'; +import { MailspringCalendar } from './core/mailspring-calendar'; + +const Notice = () => + AppEnv.inDevMode() ? ( + + ) : ( +
+ Calendar is launching later this year! This preview is read-only and only supports Google + calendar. +
+ ); -const Notice = () => ( -
- Calendar is launching later this year! This preview is read-only and only supports Google - calendar. -
-); Notice.displayName = 'Notice'; export function activate() { - ComponentRegistry.register(CalendarWrapper, { + ComponentRegistry.register(MailspringCalendar, { location: WorkspaceStore.Location.Center, }); ComponentRegistry.register(Notice, { @@ -24,6 +28,6 @@ export function activate() { } export function deactivate() { - ComponentRegistry.unregister(CalendarWrapper); + ComponentRegistry.unregister(MailspringCalendar); ComponentRegistry.unregister(QuickEventButton); } diff --git a/app/internal_packages/main-calendar/lib/quick-event-button.tsx b/app/internal_packages/main-calendar/lib/quick-event-button.tsx index 571230188..6493304fd 100644 --- a/app/internal_packages/main-calendar/lib/quick-event-button.tsx +++ b/app/internal_packages/main-calendar/lib/quick-event-button.tsx @@ -1,9 +1,9 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Actions } from 'mailspring-exports'; -import QuickEventPopover from './quick-event-popover'; +import { QuickEventPopover } from './quick-event-popover'; -export default class QuickEventButton extends React.Component { +export class QuickEventButton extends React.Component<{}> { static displayName = 'QuickEventButton'; onClick = event => { diff --git a/app/internal_packages/main-calendar/lib/quick-event-popover.tsx b/app/internal_packages/main-calendar/lib/quick-event-popover.tsx index d5ff84430..a5553f62a 100644 --- a/app/internal_packages/main-calendar/lib/quick-event-popover.tsx +++ b/app/internal_packages/main-calendar/lib/quick-event-popover.tsx @@ -1,13 +1,14 @@ import React from 'react'; -import { Actions, Calendar, DatabaseStore, DateUtils, Event } from 'mailspring-exports'; +import { Actions, Calendar, DatabaseStore, DateUtils, Event, localized } from 'mailspring-exports'; import { Moment } from 'moment'; -interface QuickEventPopoverSttae { +interface QuickEventPopoverState { start: Moment | null; end: Moment | null; leftoverText: string | null; } -export default class QuickEventPopover extends React.Component<{}, QuickEventPopoverSttae> { + +export class QuickEventPopover extends React.Component<{}, QuickEventPopoverState> { constructor(props) { super(props); this.state = { @@ -18,7 +19,10 @@ export default class QuickEventPopover extends React.Component<{}, QuickEventPop } onInputKeyDown = event => { - const { key, target: { value } } = event; + const { + key, + target: { value }, + } = event; if (value.length > 0 && ['Enter', 'Return'].includes(key)) { // This prevents onInputChange from being fired event.stopPropagation(); @@ -31,24 +35,29 @@ export default class QuickEventPopover extends React.Component<{}, QuickEventPop this.setState(DateUtils.parseDateString(event.target.value)); }; - createEvent = async ({ leftoverText, start, end }) => { + createEvent = async ({ + leftoverText, + start, + end, + }: { + leftoverText: string; + start: Moment; + end: Moment; + }) => { const allCalendars = await DatabaseStore.findAll(Calendar); - if (allCalendars.length === 0) { - throw new Error("Can't create an event, you have no calendars"); - } - const cals = allCalendars.filter(c => !c.readOnly); - if (cals.length === 0) { + const editableCals = allCalendars.filter(c => !c.readOnly); + if (editableCals.length === 0) { AppEnv.showErrorDialog( - "This account has no editable calendars. We can't " + - 'create an event for you. Please make sure you have an editable calendar ' + - 'with your account provider.' + localized( + "This account has no editable calendars. We can't create an event for you. Please make sure you have an editable calendar with your account provider." + ) ); return; } const event = new Event({ - calendarId: cals[0].id, - accountId: cals[0].accountId, + calendarId: editableCals[0].id, + accountId: editableCals[0].accountId, start: start.unix(), end: end.unix(), when: { @@ -86,7 +95,7 @@ export default class QuickEventPopover extends React.Component<{}, QuickEventPop diff --git a/app/internal_packages/main-calendar/styles/main-calendar.less b/app/internal_packages/main-calendar/styles/main-calendar.less index a16076102..b0559ed8c 100644 --- a/app/internal_packages/main-calendar/styles/main-calendar.less +++ b/app/internal_packages/main-calendar/styles/main-calendar.less @@ -2,10 +2,6 @@ @import 'ui-variables'; @import 'ui-mixins'; -.main-calendar { - height: 100%; -} - .fixed-popover .calendar-event-popover { color: fadeout(@text-color, 20%); background-color: @background-primary; diff --git a/app/internal_packages/main-calendar/styles/nylas-calendar.less b/app/internal_packages/main-calendar/styles/nylas-calendar.less index 3d4d2da59..87cd572d2 100644 --- a/app/internal_packages/main-calendar/styles/nylas-calendar.less +++ b/app/internal_packages/main-calendar/styles/nylas-calendar.less @@ -91,7 +91,7 @@ body.platform-win32 { .default-header { overflow: hidden; text-overflow: ellipsis; - margin: 4px 5px 5px 6px; + margin: 4px 1px 3px 4px; cursor: default; opacity: 0.7; flex: 1; @@ -141,6 +141,7 @@ body.platform-win32 { overflow-x: auto; overflow-y: hidden; position: relative; + scroll-snap-type: x mandatory; } .calendar-legend { @@ -181,14 +182,14 @@ body.platform-win32 { .legend-text { font-size: 11px; position: absolute; - color: #bfbfbf; + color: @text-color-very-subtle; right: 10px; } .date-label-legend { width: @legend-width; position: relative; - border-bottom: 1px solid #dddddd; + border-bottom: 1px solid @border-color-divider; box-shadow: 0 1px 2.5px rgba(0, 0, 0, 0.15); z-index: 3; .legend-text { @@ -201,6 +202,10 @@ body.platform-win32 { text-align: center; flex: 1; box-shadow: inset 1px 0 0 rgba(177, 177, 177, 0.15); + scroll-snap-align: start; + &.is-hard-stop { + scroll-snap-stop: always; + } &.is-today { .date-label { color: @accent-primary; @@ -214,7 +219,7 @@ body.platform-win32 { display: block; font-size: 16px; font-weight: 300; - color: #808080; + color: @text-color-subtle; } .weekday-label { display: block; @@ -222,7 +227,7 @@ body.platform-win32 { text-transform: uppercase; margin-top: 3px; font-size: 12px; - color: #ccd4d8; + color: @text-color-subtle; } .event-grid-wrap { @@ -277,6 +282,7 @@ body.platform-win32 { } .month-view { + color: #000; } .current-time-indicator { @@ -304,7 +310,7 @@ body.platform-win32 { .top-banner { color: rgba(33, 99, 146, 0.6); - background: #e0eff6; + background: @gray-lighter; font-size: 12px; line-height: 25px; text-align: center; @@ -314,7 +320,7 @@ body.platform-win32 { .header-controls { padding: 10px; display: flex; - color: #808080; + color: @text-color-subtle; border-bottom: 1px solid @border-color-divider; box-shadow: inset 0 -1px 1px rgba(191, 191, 191, 0.12); flex-shrink: 0; @@ -338,99 +344,9 @@ body.platform-win32 { } } - .footer-controls { - padding: 10px; - min-height: 45px; - display: flex; - color: #808080; - background: @background-primary; - border-top: @border-color-divider; - box-shadow: 0 -3px 16px rgba(0, 0, 0, 0.11); - z-index: 2; - } - .center-controls { text-align: center; flex: 1; order: 0; } } - -.mini-month-view { - width: 100%; - height: 100%; - min-width: 200px; - min-height: 200px; - text-align: center; - display: flex; - flex-direction: column; - background: @background-primary; - border: 1px solid @border-color-divider; - border-radius: @border-radius-base; - - .header { - display: flex; - background: @background-secondary; - padding: 4px 0 3px 0; - .month-title { - padding-top: 3px; - color: @text-color; - flex: 1; - } - border-bottom: 1px solid @border-color-divider; - .btn.btn-icon { - line-height: 27px; - height: 27px; - margin-top: -1px; - margin-right: 0; - &:active { - background: transparent; - } - } - } - - .legend { - display: flex; - .weekday { - flex: 1; - } - padding: 3px 0; - border-bottom: 1px solid @border-color-divider; - } - - .day-grid { - display: flex; - flex-direction: column; - flex: 1; - .week { - flex: 1; - display: flex; - min-height: 28px; - } - .day { - display: flex; - flex-direction: column; - justify-content: center; - flex: 1; - min-height: 28px; - color: @text-color-very-subtle; - &.cur-month { - color: @text-color; - } - &:hover { - background: rgba(0, 0, 0, 0.05); - cursor: pointer; - } - &.today { - border: 1px solid @accent-primary; - } - &.cur-day { - background: @accent-primary; - color: @text-color-inverse; - &:hover { - background: darken(@accent-primary, 5%); - } - } - } - } -} diff --git a/app/internal_packages/message-list/lib/email-frame.tsx b/app/internal_packages/message-list/lib/email-frame.tsx index 63471f1b1..3d0ab038f 100644 --- a/app/internal_packages/message-list/lib/email-frame.tsx +++ b/app/internal_packages/message-list/lib/email-frame.tsx @@ -1,8 +1,14 @@ import { EventedIFrame } from 'mailspring-component-kit'; import React from 'react'; import ReactDOM from 'react-dom'; -import { PropTypes, Utils, QuotedHTMLTransformer, MessageStore, Message } from 'mailspring-exports'; -import { autolink } from './autolinker'; +import { + PropTypes, + Utils, + QuotedHTMLTransformer, + MessageStore, + Message, + Autolink, +} from 'mailspring-exports'; import { adjustImages } from './adjust-images'; import EmailFrameStylesStore from './email-frame-styles-store'; @@ -118,7 +124,10 @@ export default class EmailFrame extends React.Component { this._iframeDocObserver.observe(iframeEl.contentDocument.firstElementChild); window.requestAnimationFrame(() => { - autolink(doc, { async: true }); + Autolink(doc.body, { + async: true, + telAggressiveMatch: false, + }); adjustImages(doc); for (const extension of MessageStore.extensions()) { diff --git a/app/internal_packages/message-list/specs/message-item-body-spec.jsx b/app/internal_packages/message-list/specs/message-item-body-spec.jsx index dee461ee0..5e9f6fe6d 100644 --- a/app/internal_packages/message-list/specs/message-item-body-spec.jsx +++ b/app/internal_packages/message-list/specs/message-item-body-spec.jsx @@ -122,7 +122,7 @@ xdescribe('MessageItem', function() { snippet: 'snippet one...', subject: 'Subject One', threadId: 'thread_12345', - accountId: window.TEST_ACCOUNT_ID, + accountId: TEST_ACCOUNT_ID, }); // Generate the test component. Should be called after @message is configured diff --git a/app/internal_packages/onboarding/lib/decorators/create-page-for-form.tsx b/app/internal_packages/onboarding/lib/decorators/create-page-for-form.tsx index c501a2fa5..1653ac5c9 100644 --- a/app/internal_packages/onboarding/lib/decorators/create-page-for-form.tsx +++ b/app/internal_packages/onboarding/lib/decorators/create-page-for-form.tsx @@ -148,7 +148,7 @@ const CreatePageForForm = FormComponent => { account.settings.imap_host.includes('imap.gmail.com') ) { didWarnAboutGmailIMAP = true; - const buttonIndex = remote.dialog.showMessageBox({ + const buttonIndex = remote.dialog.showMessageBoxSync({ type: 'warning', buttons: [localized('Go Back'), localized('Continue')], message: localized('Are you sure?'), diff --git a/app/internal_packages/onboarding/lib/mailcore-provider-settings.json b/app/internal_packages/onboarding/lib/mailcore-provider-settings.json index af5f7e523..f3c18bb06 100644 --- a/app/internal_packages/onboarding/lib/mailcore-provider-settings.json +++ b/app/internal_packages/onboarding/lib/mailcore-provider-settings.json @@ -162,25 +162,6 @@ "mx-match": ["mx\\.zoho\\.com", "mx[0-9]*\\.zoho\\.com"], "domain-match": ["zoho\\.com"] }, - "juno": { - "servers": { - "pop": [ - { - "port": 995, - "hostname": "pop.juno.com", - "ssl": true - } - ], - "smtp": [ - { - "port": 465, - "hostname": "smtp.juno.com", - "starttls": true - } - ] - }, - "domain-match": ["juno\\.com"] - }, "mobileme": { "servers": { "imap": [ @@ -1109,44 +1090,6 @@ }, "mx-match": ["mx1\\.comcast\\.net", "mx2\\.comcast\\.net"] }, - "verizon": { - "servers": { - "pop": [ - { - "port": 995, - "hostname": "pop.verizon.net", - "ssl": true - } - ], - "smtp": [ - { - "port": 465, - "hostname": "smtp.verizon.net", - "ssl": true - } - ] - }, - "mx-match": ["relay\\.verizon\\.net"] - }, - "rcn": { - "servers": { - "pop": [ - { - "port": 110, - "hostname": "pop.rcn.com", - "ssl": true - } - ], - "smtp": [ - { - "port": 25, - "hostname": "smtp.rcn.com", - "ssl": true - } - ] - }, - "mx-match": ["mx\\.rcn\\.com"] - }, "ukrnet": { "servers": { "imap": [ diff --git a/app/internal_packages/onboarding/lib/oauth-signin-page.tsx b/app/internal_packages/onboarding/lib/oauth-signin-page.tsx index ca19adbfb..e2ca0d642 100644 --- a/app/internal_packages/onboarding/lib/oauth-signin-page.tsx +++ b/app/internal_packages/onboarding/lib/oauth-signin-page.tsx @@ -7,13 +7,14 @@ import url from 'url'; import FormErrorMessage from './form-error-message'; import { LOCAL_SERVER_PORT } from './onboarding-helpers'; +import AccountProviders from './account-providers'; interface OAuthSignInPageProps { providerAuthPageUrl: string; buildAccountFromAuthResponse: (rep: any) => Account | Promise; onSuccess: (account: Account) => void; onTryAgain: () => void; - providerConfig: object; + providerConfig: (typeof AccountProviders)[0]; serviceName: string; } @@ -79,18 +80,16 @@ export default class OAuthSignInPage extends React.Component< response.end('Unknown Request'); } }); - this._server.listen(LOCAL_SERVER_PORT, err => { - if (err) { - AppEnv.showErrorDialog({ - title: localized('Unable to Start Local Server'), - message: localized( - `To listen for the Gmail Oauth response, Mailspring needs to start a webserver on port ${LOCAL_SERVER_PORT}. Please go back and try linking your account again. If this error persists, use the IMAP/SMTP option with a Gmail App Password.\n\n%@`, - err - ), - }); - return; - } + this._server.once('error', err => { + AppEnv.showErrorDialog({ + title: localized('Unable to Start Local Server'), + message: localized( + `To listen for the Gmail Oauth response, Mailspring needs to start a webserver on port ${LOCAL_SERVER_PORT}. Please go back and try linking your account again. If this error persists, use the IMAP/SMTP option with a Gmail App Password.\n\n%@`, + err + ), + }); }); + this._server.listen(LOCAL_SERVER_PORT); } componentWillUnmount() { diff --git a/app/internal_packages/onboarding/lib/page-top-bar.tsx b/app/internal_packages/onboarding/lib/page-top-bar.tsx index a688057b7..c99b014af 100644 --- a/app/internal_packages/onboarding/lib/page-top-bar.tsx +++ b/app/internal_packages/onboarding/lib/page-top-bar.tsx @@ -33,19 +33,17 @@ const PageTopBar = props => { backButton = null; } + const style: any = { + top: 0, + left: 26, + right: 0, + height: 27, + zIndex: 100, + position: 'absolute', + WebkitAppRegion: 'drag', + }; return ( -
+
{backButton}
); diff --git a/app/internal_packages/open-tracking/specs/open-tracking-composer-extension-spec.ts b/app/internal_packages/open-tracking/specs/open-tracking-composer-extension-spec.ts index 83886a6af..c528f255b 100644 --- a/app/internal_packages/open-tracking/specs/open-tracking-composer-extension-spec.ts +++ b/app/internal_packages/open-tracking/specs/open-tracking-composer-extension-spec.ts @@ -20,7 +20,6 @@ xdescribe('Open tracking composer extension', function openTrackingComposerExten beforeEach(() => { this.draftBodyRootNode = nodeForHTML(beforeBody); this.draft = new Message({ - clientId: clientId, accountId: accountId, body: beforeBody, }); diff --git a/app/internal_packages/preferences/lib/tabs/preferences-account-list.tsx b/app/internal_packages/preferences/lib/tabs/preferences-account-list.tsx index 9821c44eb..4d79dd705 100644 --- a/app/internal_packages/preferences/lib/tabs/preferences-account-list.tsx +++ b/app/internal_packages/preferences/lib/tabs/preferences-account-list.tsx @@ -23,7 +23,7 @@ class PreferencesAccountList extends Component { onRemoveAccount: PropTypes.func.isRequired, }; - _renderAccountStateIcon(account) { + _renderAccountStateIcon(account: Account) { if (account.syncState !== 'running') { return (
@@ -38,7 +38,7 @@ class PreferencesAccountList extends Component { return null; } - _renderAccount = account => { + _renderAccount = (account: Account) => { const label = account.label; const accountSub = `${account.name || localized('No name provided')} <${account.emailAddress}>`; const syncError = account.hasSyncStateError(); diff --git a/app/internal_packages/preferences/lib/tabs/preferences-general.tsx b/app/internal_packages/preferences/lib/tabs/preferences-general.tsx index b8d434fdf..00614197b 100644 --- a/app/internal_packages/preferences/lib/tabs/preferences-general.tsx +++ b/app/internal_packages/preferences/lib/tabs/preferences-general.tsx @@ -28,7 +28,7 @@ class PreferencesGeneral extends React.Component<{ }; _onResetAccountsAndSettings = () => { - const chosen = remote.dialog.showMessageBox({ + const chosen = remote.dialog.showMessageBoxSync({ type: 'info', message: localized('Are you sure?'), buttons: [localized('Cancel'), localized('Reset')], diff --git a/app/internal_packages/preferences/lib/tabs/preferences-identity.tsx b/app/internal_packages/preferences/lib/tabs/preferences-identity.tsx index 5c2593949..25387bdab 100644 --- a/app/internal_packages/preferences/lib/tabs/preferences-identity.tsx +++ b/app/internal_packages/preferences/lib/tabs/preferences-identity.tsx @@ -5,6 +5,7 @@ import { localized, localizedReactFragment, IIdentity, + EMPTY_IDENTITY, } from 'mailspring-exports'; import { OpenIdentityPageButton, BillingModal, RetinaImg } from 'mailspring-component-kit'; import { shell } from 'electron'; @@ -158,7 +159,7 @@ class PreferencesIdentity extends React.Component<{}, { identity: IIdentity }> { _getStateFromStores() { return { - identity: IdentityStore.identity() || {}, + identity: IdentityStore.identity() || { ...EMPTY_IDENTITY }, }; } diff --git a/app/internal_packages/preferences/lib/tabs/preferences-keymaps.tsx b/app/internal_packages/preferences/lib/tabs/preferences-keymaps.tsx index 05d15f133..dcdea57d4 100644 --- a/app/internal_packages/preferences/lib/tabs/preferences-keymaps.tsx +++ b/app/internal_packages/preferences/lib/tabs/preferences-keymaps.tsx @@ -74,7 +74,7 @@ export default class PreferencesKeymaps extends React.Component< } _onDeleteUserKeymap() { - const chosen = remote.dialog.showMessageBox({ + const chosen = remote.dialog.showMessageBoxSync({ type: 'info', message: localized('Are you sure?'), detail: localized('Delete your custom key bindings and reset to the template defaults?'), diff --git a/app/internal_packages/print/lib/print-window.ts b/app/internal_packages/print/lib/print-window.ts index 98e886c12..c619bb7db 100644 --- a/app/internal_packages/print/lib/print-window.ts +++ b/app/internal_packages/print/lib/print-window.ts @@ -26,6 +26,7 @@ export default class PrintWindow { .join(''); const content = ` + @@ -79,7 +80,7 @@ export default class PrintWindow { contextIsolation: false, }, }); - this.browserWin.setMenu(null); + this.browserWin.removeMenu(); fs.writeFileSync(tmpMessagesPath, `window.printMessages = ${printMessages}`); fs.writeFileSync(this.tmpFile, content); } diff --git a/app/internal_packages/print/static/print-preload.js b/app/internal_packages/print/static/print-preload.js index 8e8773cb1..35e13abc7 100644 --- a/app/internal_packages/print/static/print-preload.js +++ b/app/internal_packages/print/static/print-preload.js @@ -7,31 +7,28 @@ win.addListener('page-title-updated', event => { event.preventDefault(); }); -global.printToPDF = () => { - remote.dialog.showSaveDialog( +global.printToPDF = async () => { + const { filePath } = await remote.dialog.showSaveDialog({ + defaultPath: `${win.getTitle()}.pdf`, + }); + + if (!filePath) { + return; + } + webcontents.printToPDF( { - defaultPath: `${win.getTitle()}.pdf`, + marginsType: 0, + pageSize: 'Letter', + printBackground: true, + landscape: false, }, - filename => { - if (!filename) { + (error, data) => { + if (error) { + remote.dialog.showErrorBox('An Error Occurred', `${error}`); return; } - webcontents.printToPDF( - { - marginsType: 0, - pageSize: 'Letter', - printBackground: true, - landscape: false, - }, - (error, data) => { - if (error) { - remote.dialog.showErrorBox('An Error Occurred', `${error}`); - return; - } - fs.writeFileSync(filename, data); - } - ); + fs.writeFileSync(filename, data); } ); }; diff --git a/app/internal_packages/remove-tracking-pixels/specs/tracking-pixels-extension-spec.ts b/app/internal_packages/remove-tracking-pixels/specs/tracking-pixels-extension-spec.ts index 8a77aa826..2b4bc1707 100644 --- a/app/internal_packages/remove-tracking-pixels/specs/tracking-pixels-extension-spec.ts +++ b/app/internal_packages/remove-tracking-pixels/specs/tracking-pixels-extension-spec.ts @@ -1,6 +1,7 @@ /* eslint no-irregular-whitespace: 0 */ import fs from 'fs'; import { removeTrackingPixels } from '../lib/main'; +import { Message } from 'mailspring-exports'; const readFixture = name => { return fs @@ -19,7 +20,7 @@ describe('TrackingPixelsExtension', function trackingPixelsExtension() { accountId: '1234', isFromMe: () => true, }; - removeTrackingPixels(message); + removeTrackingPixels(message as Message); expect(message.body).toEqual(expected); }); @@ -32,7 +33,7 @@ describe('TrackingPixelsExtension', function trackingPixelsExtension() { accountId: '1234', isFromMe: () => false, }; - removeTrackingPixels(message); + removeTrackingPixels(message as Message); expect(message.body).toEqual(expected); }); }); diff --git a/app/internal_packages/send-later/lib/send-later-status.tsx b/app/internal_packages/send-later/lib/send-later-status.tsx index 0b295c272..e71981004 100644 --- a/app/internal_packages/send-later/lib/send-later-status.tsx +++ b/app/internal_packages/send-later/lib/send-later-status.tsx @@ -68,7 +68,7 @@ export default class SendLaterStatus extends Component ({ + (store, items) => ({ focusedThread: store.focused('thread'), items, }) diff --git a/app/internal_packages/thread-list/lib/thread-list-data-source.ts b/app/internal_packages/thread-list/lib/thread-list-data-source.ts index 2979982d0..20cd80447 100644 --- a/app/internal_packages/thread-list/lib/thread-list-data-source.ts +++ b/app/internal_packages/thread-list/lib/thread-list-data-source.ts @@ -9,7 +9,7 @@ import { } from 'mailspring-exports'; const _observableForThreadMessages = (id, initialModels) => { - const subscription = new QuerySubscription( + const subscription = new QuerySubscription( DatabaseStore.findAll(Message, { threadId: id }), { initialModels: initialModels, diff --git a/app/internal_packages/thread-list/lib/thread-list-participants.tsx b/app/internal_packages/thread-list/lib/thread-list-participants.tsx index 6f8b997ad..07b3b8dad 100644 --- a/app/internal_packages/thread-list/lib/thread-list-participants.tsx +++ b/app/internal_packages/thread-list/lib/thread-list-participants.tsx @@ -59,7 +59,7 @@ class ThreadListParticipants extends React.Component<{ thread: ThreadWithMessage if (spacer) { accumulate('...'); } else { - let short = ''; + let short = contact.email; if (contact.name && contact.name.length > 0) { if (items.length > 1) { short = contact.displayName({ @@ -69,8 +69,6 @@ class ThreadListParticipants extends React.Component<{ thread: ThreadWithMessage } else { short = contact.displayName({ includeAccountLabel: false }); } - } else { - short = contact.email; } if (idx < items.length - 1 && !items[idx + 1].spacer) { short += ', '; diff --git a/app/internal_packages/thread-list/lib/thread-list-store.ts b/app/internal_packages/thread-list/lib/thread-list-store.ts index dd2334a35..956c72ab1 100644 --- a/app/internal_packages/thread-list/lib/thread-list-store.ts +++ b/app/internal_packages/thread-list/lib/thread-list-store.ts @@ -49,7 +49,7 @@ class ThreadListStore extends MailspringStore { }; selectionObservable = () => { - return Rx.Observable.fromListSelection(this); + return Rx.Observable.fromListSelection(this); }; // Inbound Events diff --git a/app/internal_packages/thread-list/specs/thread-list-participants-spec.jsx b/app/internal_packages/thread-list/specs/thread-list-participants-spec.jsx index 59a16d835..30a57bff6 100644 --- a/app/internal_packages/thread-list/specs/thread-list-participants-spec.jsx +++ b/app/internal_packages/thread-list/specs/thread-list-participants-spec.jsx @@ -191,7 +191,7 @@ describe('ThreadListParticipants', function() { }, ]; - for (let scenario of scenarios) { + for (const scenario of scenarios) { const thread = new Thread(); thread.__messages = scenario.in; const participants = ReactTestUtils.renderIntoDocument( diff --git a/app/internal_packages/thread-search/lib/thread-search-bar.tsx b/app/internal_packages/thread-search/lib/thread-search-bar.tsx index f35ed4365..5657df197 100644 --- a/app/internal_packages/thread-search/lib/thread-search-bar.tsx +++ b/app/internal_packages/thread-search/lib/thread-search-bar.tsx @@ -23,26 +23,27 @@ import { wrapInQuotes, } from './search-bar-util'; -class ThreadSearchBar extends Component< - { - query: string; - isSearching: boolean; - perspective: MailboxPerspective; - }, - { - suggestions: { - token: string; - term: string; - description: string; - termSuggestions: string[] | ((term: string, accountIds: string[]) => Promise); - }[]; - focused: boolean; - selected?: { - description: any; - }; - selectedIdx: number; - } -> { +interface ThreadSearchBarProps { + query: string; + isSearching: boolean; + perspective: MailboxPerspective; +} + +interface ThreadSearchBarState { + suggestions: { + token: string; + term: string; + description: string; + termSuggestions: string[] | ((term: string, accountIds: string[]) => Promise); + }[]; + focused: boolean; + selected?: { + description: any; + }; + selectedIdx: number; +} + +class ThreadSearchBar extends Component { static displayName = 'ThreadSearchBar'; static propTypes = { diff --git a/app/internal_packages/translation/lib/message-header.tsx b/app/internal_packages/translation/lib/message-header.tsx index de5e0dc7c..4dc996438 100644 --- a/app/internal_packages/translation/lib/message-header.tsx +++ b/app/internal_packages/translation/lib/message-header.tsx @@ -1,6 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import cld from '@paulcbetts/cld'; +import cld from 'cld'; import { remote } from 'electron'; import { localized, @@ -237,7 +237,7 @@ export class TranslateMessageHeader extends React.Component< _onNeverForLanguage = () => { if (!this.state.detected) return; - const response = remote.dialog.showMessageBox({ + const response = remote.dialog.showMessageBoxSync({ type: 'warning', buttons: [localized('Yes'), localized('Cancel')], message: localized('Are you sure?'), diff --git a/app/internal_packages/translation/lib/service.ts b/app/internal_packages/translation/lib/service.ts index 83e848740..0f7d3a987 100644 --- a/app/internal_packages/translation/lib/service.ts +++ b/app/internal_packages/translation/lib/service.ts @@ -5,6 +5,7 @@ import { Actions, MailspringAPIRequest, RegExpUtils, + FeatureLexicon, } from 'mailspring-exports'; export const TranslatePopupOptions = { @@ -118,7 +119,7 @@ export const AllLanguages = { ms: 'Malay', }; -export const TranslationsUsedLexicon = { +export const TranslationsUsedLexicon: FeatureLexicon = { headerText: localized('All Translations Used'), rechargeText: `${localized( 'You can translate up to %1$@ emails each %2$@ with Mailspring Basic.' diff --git a/app/internal_packages/undo-redo/lib/undo-redo-toast.tsx b/app/internal_packages/undo-redo/lib/undo-redo-toast.tsx index 3a4ee6f73..bce28220d 100644 --- a/app/internal_packages/undo-redo/lib/undo-redo-toast.tsx +++ b/app/internal_packages/undo-redo/lib/undo-redo-toast.tsx @@ -7,7 +7,7 @@ function isUndoSend(block) { return ( block.tasks.length === 1 && block.tasks[0] instanceof SyncbackMetadataTask && - block.tasks[0].value.isUndoSend + (block.tasks[0].value as any).isUndoSend ); } diff --git a/app/package-lock.json b/app/package-lock.json index 4179a0644..192296b7e 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -1,8 +1,4027 @@ { "name": "mailspring", "version": "1.8.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "mailspring", + "version": "1.8.0", + "license": "GPL-3.0", + "dependencies": { + "@bengotow/slate-edit-list": "github:bengotow/slate-edit-list#b868e108", + "better-sqlite3": "^7.1.2", + "chromium-net-errors": "1.0.3", + "chrono-node": "^1.1.2", + "classnames": "1.2.1", + "cld": "2.6.0", + "collapse-whitespace": "^1.1.6", + "debug": "github:emorikawa/debug#nylas", + "deep-extend": "0.6.0", + "electron-spellchecker": "github:bengotow/electron-spellchecker#267df08", + "emoji-data": "^0.2.0", + "enzyme": "^3.8.0", + "enzyme-adapter-react-16": "^1.9.0", + "event-kit": "^1.0.2", + "fs-plus": "^2.3.2", + "getmac": "^1.2.1", + "graceful-fs": "^4.1.11", + "ical-expander": "^2.0.0", + "ical.js": "^1.3.0", + "immutable": "^3.8.2", + "ini": "^1.3.5", + "jasmine-json": "~0.0", + "jasmine-react-helpers": "^0.2", + "jasmine-reporters": "1.x.x", + "juice": "^5.2.0", + "keytar": "5.5.0", + "less-cache": "1.1.0", + "lru-cache": "^4.0.1", + "mammoth": "1.4.7", + "mkdirp": "^0.5", + "moment": "^2.24.0", + "moment-round": "^1.0.1", + "moment-timezone": "^0.5.32", + "mousetrap": "^1.5.3", + "node-emoji": "^1.2.1", + "optimist": "0.4.0", + "pick-react-known-prop": "0.x.x", + "proxyquire": "1.3.1", + "raven": "2.1.2", + "react": "16.6.0", + "react-color": "^2.17.0", + "react-dom": "16.6.0", + "react-test-renderer": "16.6.0", + "react-transition-group": "1.2.1", + "reflux": "0.1.13", + "rimraf": "2.5.2", + "rtlcss": "2.4.0", + "rx-lite": "4.0.8", + "slate": "github:bengotow/slate#cd6f40e8", + "slate-auto-replace": "0.12.1", + "slate-base64-serializer": "0.2.100", + "slate-html-serializer": "0.7.39", + "slate-plain-serializer": "0.6.39", + "slate-prop-types": "0.5.30", + "slate-react": "github:bengotow/slate#0.45.1-react", + "slate-soft-break": "^0.9.0", + "slate-when": "^0.2.0", + "snarkdown": "1.2.2", + "source-map-support": "^0.3.2", + "temp": "^0.8", + "tld": "^0.0.2", + "underscore": "1.8.x", + "underscore.string": "^3.3.5", + "utf7": "^1.0.2", + "uuid": "^3.0.0", + "vcf": "^2.0.5", + "windows-iana": "^4.2.1", + "windows-shortcuts": "emorikawa/windows-shortcuts#b0a0fc7", + "xlsx": "0.14.1" + }, + "optionalDependencies": { + "macos-notification-state": "^1.1.0", + "node-mac-notifier": "1.1.0", + "windows-quiet-hours": "^1.2.5" + } + }, + "node_modules/@babel/runtime": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz", + "integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==", + "dependencies": { + "regenerator-runtime": "0.13.2" + } + }, + "node_modules/@bengotow/slate-edit-list": { + "resolved": "git+ssh://git@github.com/bengotow/slate-edit-list.git#b868e1088b44ad1adb2cb06bbbb847a2fef7edb5" + }, + "node_modules/@felixrieseberg/spellchecker": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@felixrieseberg/spellchecker/-/spellchecker-4.0.12.tgz", + "integrity": "sha512-jLAPnRALB1I6Un8ldHVJfJid7m2R1qXoafFF/95sdm7R5VPOsZ3xTreZ/wLKO5x9AdsD2t9zpOcjDFTsCf3VzQ==", + "dependencies": { + "nan": "2.14.0" + } + }, + "node_modules/@icons/material": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", + "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==" + }, + "node_modules/@types/node": { + "version": "12.0.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.7.tgz", + "integrity": "sha512-1YKeT4JitGgE4SOzyB9eMwO0nGVNkNEsm9qlIt1Lqm/tG2QEiSMTD4kS3aO6L+w5SClLVxALmIBESK6Mk5wX0A==" + }, + "node_modules/adler-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.2.0.tgz", + "integrity": "sha1-aj5r8KY5ALoVZSgIyxXGgT0aXyU=", + "dependencies": { + "exit-on-epipe": "1.0.1", + "printj": "1.1.2" + }, + "bin": { + "adler32": "bin/adler32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/airbnb-prop-types": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.13.2.tgz", + "integrity": "sha512-2FN6DlHr6JCSxPPi25EnqGaXC4OC3/B3k1lCd6MMYrZ51/Gf/1qDfaR+JElzWa+Tl7cY2aYOlsYJGFeQyVHIeQ==", + "dependencies": { + "array.prototype.find": "2.1.0", + "function.prototype.name": "1.1.0", + "has": "1.0.3", + "is-regex": "1.0.4", + "object-is": "1.0.1", + "object.assign": "4.1.0", + "object.entries": "1.1.0", + "prop-types": "15.7.2", + "prop-types-exact": "1.2.0", + "react-is": "16.8.6" + } + }, + "node_modules/ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "dependencies": { + "fast-deep-equal": "2.0.1", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.2.2" + } + }, + "node_modules/amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "engines": { + "node": ">=0.4.2" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "1.9.3" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "node_modules/are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dependencies": { + "delegates": "1.0.0", + "readable-stream": "2.3.7" + } + }, + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "node_modules/are-we-there-yet/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "5.1.2" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "1.0.3" + } + }, + "node_modules/array-filter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", + "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=" + }, + "node_modules/array.prototype.find": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.0.tgz", + "integrity": "sha512-Wn41+K1yuO5p7wRZDl7890c3xvv5UBrfVXTVIe28rSQb6LS0fZMDrQB6PAcxQFRFy6vJTLDc3A2+3CjQdzVKRg==", + "dependencies": { + "define-properties": "1.1.3", + "es-abstract": "1.13.0" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.1.tgz", + "integrity": "sha512-rVqIs330nLJvfC7JqYvEWwqVr5QjYF1ib02i3YJtR/fICO6527Tjpc/e4Mvmxh3GIePPreRXMdaGyC99YphWEw==", + "dependencies": { + "define-properties": "1.1.3", + "es-abstract": "1.13.0", + "function-bind": "1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "optional": true + }, + "node_modules/asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dependencies": { + "safer-buffer": "2.1.2" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "node_modules/base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "node_modules/bcp47": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/bcp47/-/bcp47-1.1.2.tgz", + "integrity": "sha1-NUvjMH/9CEM6ePXh4glYRfifx/4=", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dependencies": { + "tweetnacl": "0.14.5" + } + }, + "node_modules/better-sqlite3": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-7.1.2.tgz", + "integrity": "sha512-8FWYnJ6Bx94MBX03J5Ka7sTRlvXXMEm4FW2Op7nM8ErQZeyALYLmSlbMBnfr4cMpS0tj0aYZv0a+26G2YJuIjg==", + "dependencies": { + "bindings": "1.5.0", + "prebuild-install": "5.3.3", + "tar": "6.1.0" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz", + "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==", + "dependencies": { + "buffer": "5.6.0", + "inherits": "2.0.4", + "readable-stream": "3.4.0" + } + }, + "node_modules/bl/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/block-elements": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/block-elements/-/block-elements-1.2.0.tgz", + "integrity": "sha1-jgTMq2OMfiWW9QZftsHHUYyQWl0=" + }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=" + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "node_modules/boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "optional": true, + "dependencies": { + "hoek": "2.16.3" + }, + "engines": { + "node": ">=0.10.40" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "dependencies": { + "base64-js": "1.3.1", + "ieee754": "1.1.13" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "node_modules/cfb": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.1.1.tgz", + "integrity": "sha512-eov9L63s9/uMiiDvWCiTYbK3AcM5x5AYo4NL1q75G0AZBrD2X0+Lm9hubk94qy1MJP7pYFuM6/xn0h3grAvBXQ==", + "dependencies": { + "adler-32": "1.2.0", + "commander": "2.20.0", + "crc-32": "1.2.0", + "printj": "1.1.2" + }, + "bin": { + "cfb": "bin/cfb.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/chain-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.1.tgz", + "integrity": "sha512-SxltgMwL9uCko5/ZCLiyG2B7R9fY4pDZUw7hJ4MhirdjBLosoDqkWABi3XMucddHdLiFJMb7PD2MZifZriuMTg==" + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", + "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", + "dependencies": { + "css-select": "1.2.0", + "dom-serializer": "0.1.1", + "entities": "1.1.2", + "htmlparser2": "3.10.1", + "lodash": "4.17.11", + "parse5": "3.0.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/chromium-net-errors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chromium-net-errors/-/chromium-net-errors-1.0.3.tgz", + "integrity": "sha1-lcI2Hvv7HrLThZM6V9k1WdnHvwE=" + }, + "node_modules/chrono-node": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/chrono-node/-/chrono-node-1.3.11.tgz", + "integrity": "sha512-jDWRnY6nYvzfV3HPYBqo+tot7tcsUs9i3arGbMdI0TouPSXP2C2y/Ctp27rxKTQDi6yuTxAB2cw+Q6igGhOhdQ==", + "dependencies": { + "moment": "2.21.0" + } + }, + "node_modules/chrono-node/node_modules/moment": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.21.0.tgz", + "integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ==", + "engines": { + "node": "*" + } + }, + "node_modules/classnames": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-1.2.1.tgz", + "integrity": "sha1-xwx4j8kS4GhL2XruFn0u/WgtzfM=" + }, + "node_modules/cld": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/cld/-/cld-2.6.0.tgz", + "integrity": "sha512-2U8Uiv7Bvl1v4fNWFGB3RYtPvhUWXQJ1MoNKJNVuoALfandEt9oVqK64S+3ZLvQPjDiYjsohtTep/wIs0xOXkw==", + "dependencies": { + "glob": "5.0.15", + "node-addon-api": "2.0.0", + "rimraf": "2.5.2", + "underscore": "1.8.3" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "optional": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/codepage": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.14.0.tgz", + "integrity": "sha1-jL4lSBMjVZ19MHVxsP/5HnodL5k=", + "dependencies": { + "commander": "2.14.1", + "exit-on-epipe": "1.0.1" + }, + "bin": { + "codepage": "bin/codepage.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/codepage/node_modules/commander": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", + "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==" + }, + "node_modules/collapse-whitespace": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/collapse-whitespace/-/collapse-whitespace-1.1.7.tgz", + "integrity": "sha512-24up1hbQSsnaDSGHPOvGQT84vmxvG0QUrI8tguiQpo9I5irrnypCKwddXindXMyXhoTe+9V6LYj3aFIhTQ4UCg==", + "dependencies": { + "block-elements": "1.2.0", + "void-elements": "2.0.1" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "node_modules/colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "node_modules/cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "node_modules/crc-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", + "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", + "dependencies": { + "exit-on-epipe": "1.0.1", + "printj": "1.1.2" + }, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dependencies": { + "nice-try": "1.0.5", + "path-key": "2.0.1", + "semver": "5.7.0", + "shebang-command": "1.2.0", + "which": "1.3.1" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "optional": true, + "dependencies": { + "boom": "2.10.1" + }, + "engines": { + "node": ">=0.10.40" + } + }, + "node_modules/css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dependencies": { + "boolbase": "1.0.0", + "css-what": "2.1.3", + "domutils": "1.5.1", + "nth-check": "1.0.2" + } + }, + "node_modules/css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "engines": { + "node": "*" + } + }, + "node_modules/d": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz", + "integrity": "sha1-2hhMU10Y2O57oqoim5FACfrhEwk=", + "dependencies": { + "es5-ext": "0.10.50" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dependencies": { + "assert-plus": "1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/datauri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/datauri/-/datauri-2.0.0.tgz", + "integrity": "sha512-zS2HSf9pI5XPlNZgIqJg/wCJpecgU/HA6E/uv2EfaWnW1EiTGLfy/EexTIsC9c99yoCOTXlqeeWk4FkCSuO3/g==", + "dependencies": { + "image-size": "0.7.4", + "mimer": "1.0.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/datauri/node_modules/image-size": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.7.4.tgz", + "integrity": "sha512-GqPgxs+VkOr12aWwjSkyRzf5atzObWpFtiRuDgxCl2I/SDpZOKZFRD3iIAeAN6/usmn8SeLWRt7a8JRYK0Whbw==", + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/debug": { + "resolved": "git+ssh://git@github.com/emorikawa/debug.git#29685485665b44c42d33ebd7ed62570b21c6e4bd", + "dependencies": { + "ms": "0.7.3" + } + }, + "node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dependencies": { + "mimic-response": "2.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dependencies": { + "object-keys": "1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/direction": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/direction/-/direction-0.1.5.tgz", + "integrity": "sha1-zl15f5fib4vnvv9T99xA4cGp7Ew=", + "bin": { + "direction": "cli.js" + } + }, + "node_modules/discontinuous-range": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=" + }, + "node_modules/dom-helpers": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", + "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "dependencies": { + "@babel/runtime": "7.4.5" + } + }, + "node_modules/dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "dependencies": { + "domelementtype": "1.3.1", + "entities": "1.1.2" + } + }, + "node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dependencies": { + "domelementtype": "1.3.1" + } + }, + "node_modules/domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dependencies": { + "dom-serializer": "0.1.1", + "domelementtype": "1.3.1" + } + }, + "node_modules/duck": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/duck/-/duck-0.1.11.tgz", + "integrity": "sha1-OtwaPS+91Yef/TvaBc4PaTVekJM=", + "dependencies": { + "underscore": "1.4.4" + } + }, + "node_modules/duck/node_modules/underscore": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", + "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=" + }, + "node_modules/eachr": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eachr/-/eachr-3.2.0.tgz", + "integrity": "sha1-LDXkPqCGUW95l8+At6pk1VpKRIQ=", + "dependencies": { + "editions": "1.3.4", + "typechecker": "4.7.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/eachr/node_modules/editions": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/editions/-/editions-1.3.4.tgz", + "integrity": "sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dependencies": { + "jsbn": "0.1.1", + "safer-buffer": "2.1.2" + } + }, + "node_modules/editions": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/editions/-/editions-2.1.3.tgz", + "integrity": "sha512-xDZyVm0A4nLgMNWVVLJvcwMjI80ShiH/27RyLiCnW1L273TcJIA25C4pwJ33AWV01OX6UriP35Xu+lH4S7HWQw==", + "dependencies": { + "errlop": "1.1.1", + "semver": "5.7.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/electron-spellchecker": { + "resolved": "git+ssh://git@github.com/bengotow/electron-spellchecker.git#267df085fa4c1a5f5631e5f0ebd627890d59d670", + "dependencies": { + "@felixrieseberg/spellchecker": "4.0.12", + "bcp47": "1.1.2", + "cld": "2.6.0", + "debug": "4.3.1", + "keyboard-layout": "2.0.17", + "lru-cache": "5.1.1", + "mkdirp": "0.5.1", + "pify": "4.0.1", + "rxjs": "5.5.12", + "rxjs-serial-subscription": "0.1.1", + "spawn-rx": "2.0.12" + } + }, + "node_modules/electron-spellchecker/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/electron-spellchecker/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "3.1.1" + } + }, + "node_modules/electron-spellchecker/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/electron-spellchecker/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/emissary": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/emissary/-/emissary-1.3.3.tgz", + "integrity": "sha1-phjZLWgrIy0xER3DYlpd9mF5lgY=", + "dependencies": { + "es6-weak-map": "0.1.4", + "mixto": "1.0.0", + "property-accessors": "1.1.3", + "underscore-plus": "1.7.0" + } + }, + "node_modules/emoji-data": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/emoji-data/-/emoji-data-0.2.0.tgz", + "integrity": "sha1-pGxmC5ExPoZuvJkAmI6ztktGh+g=", + "dependencies": { + "underscore.string": "2.4.0" + }, + "engines": { + "node": ">=0.10.0", + "npm": ">=1.2.10" + } + }, + "node_modules/emoji-data/node_modules/underscore.string": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", + "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=", + "engines": { + "node": "*" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "1.4.0" + } + }, + "node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "node_modules/enzyme": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.10.0.tgz", + "integrity": "sha512-p2yy9Y7t/PFbPoTvrWde7JIYB2ZyGC+NgTNbVEGvZ5/EyoYSr9aG/2rSbVvyNvMHEhw9/dmGUJHWtfQIEiX9pg==", + "dependencies": { + "array.prototype.flat": "1.2.1", + "cheerio": "1.0.0-rc.3", + "function.prototype.name": "1.1.0", + "has": "1.0.3", + "html-element-map": "1.0.1", + "is-boolean-object": "1.0.0", + "is-callable": "1.1.4", + "is-number-object": "1.0.3", + "is-regex": "1.0.4", + "is-string": "1.0.4", + "is-subset": "0.1.1", + "lodash.escape": "4.0.1", + "lodash.isequal": "4.5.0", + "object-inspect": "1.6.0", + "object-is": "1.0.1", + "object.assign": "4.1.0", + "object.entries": "1.1.0", + "object.values": "1.1.0", + "raf": "3.4.1", + "rst-selector-parser": "2.2.3", + "string.prototype.trim": "1.1.2" + } + }, + "node_modules/enzyme-adapter-react-16": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.14.0.tgz", + "integrity": "sha512-7PcOF7pb4hJUvjY7oAuPGpq3BmlCig3kxXGi2kFx0YzJHppqX1K8IIV9skT1IirxXlu8W7bneKi+oQ10QRnhcA==", + "dependencies": { + "enzyme-adapter-utils": "1.12.0", + "has": "1.0.3", + "object.assign": "4.1.0", + "object.values": "1.1.0", + "prop-types": "15.7.2", + "react-is": "16.8.6", + "react-test-renderer": "16.6.0", + "semver": "5.7.0" + } + }, + "node_modules/enzyme-adapter-utils": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.12.0.tgz", + "integrity": "sha512-wkZvE0VxcFx/8ZsBw0iAbk3gR1d9hK447ebnSYBf95+r32ezBq+XDSAvRErkc4LZosgH8J7et7H7/7CtUuQfBA==", + "dependencies": { + "airbnb-prop-types": "2.13.2", + "function.prototype.name": "1.1.0", + "object.assign": "4.1.0", + "object.fromentries": "2.0.0", + "prop-types": "15.7.2", + "semver": "5.7.0" + } + }, + "node_modules/errlop": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/errlop/-/errlop-1.1.1.tgz", + "integrity": "sha512-WX7QjiPHhsny7/PQvrhS5VMizXXKoKCS3udaBp8gjlARdbn+XmK300eKBAAN0hGyRaTCtRpOaxK+xFVPUJ3zkw==", + "dependencies": { + "editions": "2.1.3" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "optional": true, + "dependencies": { + "prr": "1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "dependencies": { + "es-to-primitive": "1.2.0", + "function-bind": "1.1.1", + "has": "1.0.3", + "is-callable": "1.1.4", + "is-regex": "1.0.4", + "object-keys": "1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dependencies": { + "is-callable": "1.1.4", + "is-date-object": "1.0.1", + "is-symbol": "1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es5-ext": { + "version": "0.10.50", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz", + "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==", + "dependencies": { + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1", + "next-tick": "1.0.0" + } + }, + "node_modules/es5-ext/node_modules/d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "dependencies": { + "es5-ext": "0.10.50" + } + }, + "node_modules/es5-ext/node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dependencies": { + "d": "1.0.0", + "es5-ext": "0.10.50", + "es6-symbol": "3.1.1" + } + }, + "node_modules/es5-ext/node_modules/es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dependencies": { + "d": "1.0.0", + "es5-ext": "0.10.50" + } + }, + "node_modules/es6-iterator": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-0.1.3.tgz", + "integrity": "sha1-1vWLjE/EE8JJtLqhl2j45NfIlE4=", + "dependencies": { + "d": "0.1.1", + "es5-ext": "0.10.50", + "es6-symbol": "2.0.1" + } + }, + "node_modules/es6-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-2.0.1.tgz", + "integrity": "sha1-dhtcZ8/U8dGK+yNPaR1nhoLLO/M=", + "dependencies": { + "d": "0.1.1", + "es5-ext": "0.10.50" + } + }, + "node_modules/es6-weak-map": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-0.1.4.tgz", + "integrity": "sha1-cGzvnpmqI2undmwjnIueKG6n0ig=", + "dependencies": { + "d": "0.1.1", + "es5-ext": "0.10.50", + "es6-iterator": "0.1.3", + "es6-symbol": "2.0.1" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esrever": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/esrever/-/esrever-0.2.0.tgz", + "integrity": "sha1-lunSj08bGnZ4TNXUkOquAQ50B7g=", + "bin": { + "esrever": "bin/esrever" + } + }, + "node_modules/event-kit": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/event-kit/-/event-kit-1.5.0.tgz", + "integrity": "sha1-Ek72qtgyjcsmtxxHWQtbjmPrxIc=", + "dependencies": { + "grim": "1.5.0" + } + }, + "node_modules/event-target-shim": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-1.1.1.tgz", + "integrity": "sha1-qG5e5r2qFgVEddp5fM3fDFVphJE=", + "optional": true + }, + "node_modules/eventemitter3": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-0.1.6.tgz", + "integrity": "sha1-jHrES4e6q1XNUMgo3Dh3jqwFLqU=" + }, + "node_modules/exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extract-opts": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/extract-opts/-/extract-opts-3.3.1.tgz", + "integrity": "sha1-WrvtyYwNUgLjJ4cn+Rktfghsa+E=", + "dependencies": { + "eachr": "3.2.0", + "editions": "1.3.4", + "typechecker": "4.7.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/extract-opts/node_modules/editions": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/editions/-/editions-1.3.4.tgz", + "integrity": "sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "node_modules/findup": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/findup/-/findup-0.1.5.tgz", + "integrity": "sha1-itkpozk7rGJ5V6fl3kYjsGsOLOs=", + "dependencies": { + "colors": "0.6.2", + "commander": "2.1.0" + }, + "bin": { + "findup": "bin/findup.js" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/findup/node_modules/commander": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.1.0.tgz", + "integrity": "sha1-0SG7roYNmZKj1Re6lvVliOR8Z4E=", + "engines": { + "node": ">= 0.6.x" + } + }, + "node_modules/foldline": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/foldline/-/foldline-1.1.0.tgz", + "integrity": "sha512-9SheyADS50hjvFYjFJ3OB/GlDz2mD1T2CHd7auIk4Uto5YYWPBcw8iYo3F+gENJ+/SOeH9tT0loHZSqlUlumTA==" + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dependencies": { + "asynckit": "0.4.0", + "combined-stream": "1.0.8", + "mime-types": "2.1.24" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "3.1.3" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-plus": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-2.10.1.tgz", + "integrity": "sha1-MgR4HXhAYR5jZOe2+wWMljJ8WqU=", + "dependencies": { + "async": "1.5.2", + "mkdirp": "0.5.1", + "rimraf": "2.5.2", + "underscore-plus": "1.7.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/function.prototype.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.0.tgz", + "integrity": "sha512-Bs0VRrTz4ghD8pTmbJQD1mZ8A/mN0ur/jGz+A6FBxPDUPkm1tNfF6bhTYPA7i7aF4lZJVr+OXTNNrnnIl58Wfg==", + "dependencies": { + "define-properties": "1.1.3", + "function-bind": "1.1.1", + "is-callable": "1.1.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dependencies": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.3", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.3" + } + }, + "node_modules/get-document": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-document/-/get-document-1.0.0.tgz", + "integrity": "sha1-SCG85m8cJMsDMWAr5strEsTwHEs=" + }, + "node_modules/get-window": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/get-window/-/get-window-1.1.2.tgz", + "integrity": "sha512-yjWpFcy9fjhLQHW1dPtg9ga4pmizLY8y4ZSHdGrAQ1NU277MRhnGnnLPxe19X8W5lWVsCZz++5xEuNozWMVmTw==", + "dependencies": { + "get-document": "1.0.0" + } + }, + "node_modules/getmac": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/getmac/-/getmac-1.4.6.tgz", + "integrity": "sha512-3JPwiIr4P6Sgr6y6SVXX0+l2mrB6pyf4Cdyua7rvEV7SveWQkAp11vrkNym8wvRxzLrBenKRcwe93asdghuwWg==", + "dependencies": { + "editions": "2.1.3", + "extract-opts": "3.3.1" + }, + "bin": { + "getmac-node": "bin.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dependencies": { + "assert-plus": "1.0.0" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=" + }, + "node_modules/glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dependencies": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" + }, + "node_modules/grim": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/grim/-/grim-1.5.0.tgz", + "integrity": "sha1-sysI71Z88YUvgXWe2caLDXE5ajI=", + "dependencies": { + "emissary": "1.3.3" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dependencies": { + "ajv": "6.10.0", + "har-schema": "2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dependencies": { + "ansi-regex": "2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "node_modules/hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "optional": true, + "dependencies": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + }, + "engines": { + "node": ">=0.10.32" + } + }, + "node_modules/hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "optional": true, + "engines": { + "node": ">=0.10.40" + } + }, + "node_modules/html-attributes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/html-attributes/-/html-attributes-1.1.0.tgz", + "integrity": "sha1-ggJ6T6x6YHDqbBjMOIauoY1t6gk=", + "engines": { + "node": ">= 0.10.26", + "npm": ">=1.4.3" + } + }, + "node_modules/html-element-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.0.1.tgz", + "integrity": "sha512-BZSfdEm6n706/lBfXKWa4frZRZcT5k1cOusw95ijZsHlI+GdgY0v95h6IzO3iIDf2ROwq570YTwqNPqHcNMozw==", + "dependencies": { + "array-filter": "1.0.0" + } + }, + "node_modules/htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dependencies": { + "domelementtype": "1.3.1", + "domhandler": "2.4.2", + "domutils": "1.5.1", + "entities": "1.1.2", + "inherits": "2.0.3", + "readable-stream": "3.4.0" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dependencies": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.16.1" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/ical-expander": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ical-expander/-/ical-expander-2.0.0.tgz", + "integrity": "sha512-DABFhfEl0c58MeiTbG8hO5VEWQfKlFA7jSqdoIbS2caxS4nGyA73nVBGsFtIbmTrDO7Osgw9/up2ZCnvuxGcKA==", + "dependencies": { + "ical.js": "1.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ical.js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ical.js/-/ical.js-1.3.0.tgz", + "integrity": "sha512-wQ0w77MGOe6vNhZMBQuZAtZoTzDEOQ/QoCDofWL7yfkQ/HYWX5NyWPYjN+yj+4JahOvTkhXjTlrZtiQKv+BSOA==" + }, + "node_modules/ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immutable": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", + "integrity": "sha1-wkOZUUVbs5kT2vKBN28VMOEErfM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dependencies": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "node_modules/ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "engines": { + "node": "*" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "1.4.0" + } + }, + "node_modules/is-boolean-object": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.0.tgz", + "integrity": "sha1-mPiygDBoQhmpXzdc+9iM40Bd/5M=", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dependencies": { + "number-is-nan": "1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hotkey": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.0.3.tgz", + "integrity": "sha512-qpMe/7DxFXs65E3f8u5Zu+3qOXmqlKlAF/cpYlRiPzg1UKC9clzQcwcuf/2RC4WMFhh56DQC+FfK63Qp3lM35w==" + }, + "node_modules/is-in-browser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", + "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" + }, + "node_modules/is-number-object": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.3.tgz", + "integrity": "sha1-8mWrian0RQNO9q/xWo8AsA9VF5k=", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dependencies": { + "has": "1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-string": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.4.tgz", + "integrity": "sha1-zDqbaYV9Yh6WNyWiTK7shzuCbmQ=", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=" + }, + "node_modules/is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dependencies": { + "has-symbols": "1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "node_modules/is-window": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-window/-/is-window-1.0.2.tgz", + "integrity": "sha1-LIlspT25feRdPDMTOmXYyfVjSA0=" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isomorphic-base64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/isomorphic-base64/-/isomorphic-base64-1.0.2.tgz", + "integrity": "sha1-9Caq6CVpuopOxcpzrSGkSrHueAM=" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "node_modules/jasmine-json": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/jasmine-json/-/jasmine-json-0.0.3.tgz", + "integrity": "sha1-Xi6P1QqlhXAOjzWa9pawupZPg4c=" + }, + "node_modules/jasmine-react-helpers": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/jasmine-react-helpers/-/jasmine-react-helpers-0.2.2.tgz", + "integrity": "sha1-GkFuRw6okFTrCDa7nGGpE6wmhns=" + }, + "node_modules/jasmine-reporters": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-1.1.0.tgz", + "integrity": "sha1-8zUIhYkMntqtEqCHxi8swZ3PZsA=", + "dependencies": { + "mkdirp": "0.3.5" + } + }, + "node_modules/jasmine-reporters/node_modules/mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "node_modules/json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "optional": true, + "dependencies": { + "jsonify": "0.0.0" + } + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "node_modules/jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "optional": true + }, + "node_modules/jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "node_modules/jszip": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-2.5.0.tgz", + "integrity": "sha1-dET9hVHd8+XacZj+oMkbyDCMwnQ=", + "dependencies": { + "pako": "0.2.9" + } + }, + "node_modules/juice": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/juice/-/juice-5.2.0.tgz", + "integrity": "sha512-0l6GZmT3efexyaaay3SchKT5kG311N59TEFP5lfvEy0nz9SNqjx311plJ3b4jze7arsmDsiHQLh/xnAuk0HFTQ==", + "dependencies": { + "cheerio": "0.22.0", + "commander": "2.20.0", + "cross-spawn": "6.0.5", + "deep-extend": "0.6.0", + "mensch": "0.3.3", + "slick": "1.12.2", + "web-resource-inliner": "4.3.2" + }, + "bin": { + "juice": "bin/juice" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/juice/node_modules/cheerio": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", + "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", + "dependencies": { + "css-select": "1.2.0", + "dom-serializer": "0.1.1", + "entities": "1.1.2", + "htmlparser2": "3.10.1", + "lodash.assignin": "4.2.0", + "lodash.bind": "4.2.1", + "lodash.defaults": "4.2.0", + "lodash.filter": "4.6.0", + "lodash.flatten": "4.4.0", + "lodash.foreach": "4.5.0", + "lodash.map": "4.6.0", + "lodash.merge": "4.6.1", + "lodash.pick": "4.4.0", + "lodash.reduce": "4.6.0", + "lodash.reject": "4.6.0", + "lodash.some": "4.6.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/keyboard-layout": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/keyboard-layout/-/keyboard-layout-2.0.17.tgz", + "integrity": "sha512-W9LL+1e8CS9fi0s8ZHINDN1HZ6QpYjE4yLi4+faed7ozppNOAxINjv5w16zG9tJv8Jp5LJrCfO5PZ9aV1m5d4g==", + "dependencies": { + "event-kit": "2.5.3", + "nan": "2.14.0" + } + }, + "node_modules/keyboard-layout/node_modules/event-kit": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/event-kit/-/event-kit-2.5.3.tgz", + "integrity": "sha512-b7Qi1JNzY4BfAYfnIRanLk0DOD1gdkWHT4GISIn8Q2tAf3LpU8SP2CMwWaq40imYoKWbtN4ZhbSRxvsnikooZQ==" + }, + "node_modules/keytar": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-5.5.0.tgz", + "integrity": "sha512-1d/F2qAL/qijpm25wNq8eez4mE+/J4eBvqyLfspIYIeCEHn3nttFwplowIZfbdNCjFGWh68MzGZOd2vS61Ffew==", + "dependencies": { + "nan": "2.14.0", + "prebuild-install": "5.3.3" + } + }, + "node_modules/less": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/less/-/less-2.7.3.tgz", + "integrity": "sha512-KPdIJKWcEAb02TuJtaLrhue0krtRLoRoo7x6BNJIBelO00t/CCdJQUnHW5V34OnHMWzIktSalJxRO+FvytQlCQ==", + "dependencies": { + "graceful-fs": "4.1.15", + "mkdirp": "0.5.1" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=0.12" + }, + "optionalDependencies": { + "errno": "0.1.7", + "image-size": "0.5.5", + "mime": "1.6.0", + "promise": "7.3.1", + "request": "2.81.0", + "source-map": "0.5.7" + } + }, + "node_modules/less-cache": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/less-cache/-/less-cache-1.1.0.tgz", + "integrity": "sha1-fi9rOV+lx6l0N0kFyFjy0+nRUyA=", + "dependencies": { + "fs-plus": "3.1.1", + "less": "2.7.3", + "underscore-plus": "1.7.0", + "walkdir": "0.0.11" + } + }, + "node_modules/less-cache/node_modules/fs-plus": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-3.1.1.tgz", + "integrity": "sha512-Se2PJdOWXqos1qVTkvqqjb0CSnfBnwwD+pq+z4ksT+e97mEShod/hrNg0TRCCsXPbJzcIq+NuzQhigunMWMJUA==", + "dependencies": { + "async": "1.5.2", + "mkdirp": "0.5.1", + "rimraf": "2.5.2", + "underscore-plus": "1.7.0" + } + }, + "node_modules/less/node_modules/ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "optional": true, + "dependencies": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "node_modules/less/node_modules/assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/less/node_modules/aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/less/node_modules/form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "optional": true, + "dependencies": { + "asynckit": "0.4.0", + "combined-stream": "1.0.8", + "mime-types": "2.1.24" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/less/node_modules/har-schema": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/less/node_modules/har-validator": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "optional": true, + "dependencies": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/less/node_modules/http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "optional": true, + "dependencies": { + "assert-plus": "0.2.0", + "jsprim": "1.4.1", + "sshpk": "1.16.1" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/less/node_modules/oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/less/node_modules/performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", + "optional": true + }, + "node_modules/less/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "optional": true + }, + "node_modules/less/node_modules/qs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", + "optional": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/less/node_modules/request": { + "version": "2.81.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "optional": true, + "dependencies": { + "aws-sign2": "0.6.0", + "aws4": "1.8.0", + "caseless": "0.12.0", + "combined-stream": "1.0.8", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.24", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.1.2", + "stringstream": "0.0.6", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.6.0", + "uuid": "3.3.2" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/less/node_modules/tough-cookie": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "optional": true, + "dependencies": { + "punycode": "1.4.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "node_modules/lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" + }, + "node_modules/lodash.assignin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", + "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=" + }, + "node_modules/lodash.bind": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", + "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "node_modules/lodash.escape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", + "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=" + }, + "node_modules/lodash.filter": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", + "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=" + }, + "node_modules/lodash.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "node_modules/lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=" + }, + "node_modules/lodash.merge": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz", + "integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==" + }, + "node_modules/lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" + }, + "node_modules/lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" + }, + "node_modules/lodash.reject": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", + "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=" + }, + "node_modules/lodash.some": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", + "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=" + }, + "node_modules/lodash.toarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", + "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=" + }, + "node_modules/lodash.unescape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", + "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lop": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/lop/-/lop-0.4.0.tgz", + "integrity": "sha1-Tw5DhNXE9FXQuG0lT9UqnQVZPCw=", + "dependencies": { + "duck": "0.1.11", + "option": "0.2.4", + "underscore": "1.4.4" + } + }, + "node_modules/lop/node_modules/underscore": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", + "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=" + }, + "node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dependencies": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "node_modules/lsmod": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz", + "integrity": "sha1-mgD3bco26yP6BTUK/htYXUKZ5ks=" + }, + "node_modules/macos-notification-state": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/macos-notification-state/-/macos-notification-state-1.3.1.tgz", + "integrity": "sha512-Zd4fezXN4WaXw0fJnO3dr8Eg72AKI4C+g0Jnm5KcX8o7WWvIMV9rzsl8mtuOL8FT+QPYv1xiaLpElqhXubf3Yw==", + "optional": true, + "dependencies": { + "bindings": "1.5.0", + "nan": "2.14.0" + } + }, + "node_modules/mammoth": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/mammoth/-/mammoth-1.4.7.tgz", + "integrity": "sha512-vN13//bwnOhYPYArerIHXgDV6hhSzpyQjdtoRaJjMU9z60DBQlb/A2Ug59QBVgjWiJXpVUH0sLmXunbFg5J0EQ==", + "dependencies": { + "argparse": "1.0.10", + "bluebird": "3.4.7", + "jszip": "2.5.0", + "lop": "0.4.0", + "path-is-absolute": "1.0.1", + "sax": "1.1.6", + "underscore": "1.8.3", + "xmlbuilder": "10.1.1" + }, + "bin": { + "mammoth": "bin/mammoth" + } + }, + "node_modules/material-colors": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", + "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==" + }, + "node_modules/memoize-one": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-4.1.0.tgz", + "integrity": "sha512-2GApq0yI/b22J2j9rhbrAlsHb0Qcz+7yWxeLG8h+95sl1XPUgeLimQSOdur4Vw7cUhrBHwaUZxWFZueojqNRzA==" + }, + "node_modules/mensch": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/mensch/-/mensch-0.3.3.tgz", + "integrity": "sha1-4gD/TdgjcX+OBWOzLj9UgfyiYrI=" + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dependencies": { + "mime-db": "1.40.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mimer/-/mimer-1.0.0.tgz", + "integrity": "sha512-4ZJvCzfcwsBgPbkKXUzGoVZMWjv8IDIygkGzVc7uUYhgnK0t2LmGxxjdgH1i+pn0/KQfB5F/VKUJlfyTSOFQjg==", + "bin": { + "mimer": "bin/mimer" + }, + "engines": { + "node": ">= 6.0" + } + }, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dependencies": { + "brace-expansion": "1.1.11" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "node_modules/minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "3.1.3" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mixto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mixto/-/mixto-1.0.0.tgz", + "integrity": "sha1-wyDvYbUvKJj1IuF9i7xtUG2EJbY=" + }, + "node_modules/mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz", + "integrity": "sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g==" + }, + "node_modules/moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-round": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/moment-round/-/moment-round-1.0.1.tgz", + "integrity": "sha1-W68rdS6F0PaiJ8Chb2++EHwqLTk=" + }, + "node_modules/moment-timezone": { + "version": "0.5.32", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.32.tgz", + "integrity": "sha512-Z8QNyuQHQAmWucp8Knmgei8YNo28aLjJq6Ma+jy1ZSpSk5nyfRT8xgUbSQvD2+2UajISfenndwvFuH3NGS+nvA==", + "dependencies": { + "moment": "2.24.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/moo": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.4.3.tgz", + "integrity": "sha512-gFD2xGCl8YFgGHsqJ9NKRVdwlioeW3mI1iqfLNYQOv0+6JRwG58Zk9DIGQgyIaffSYaO1xsKnMaYzzNr1KyIAw==" + }, + "node_modules/mousetrap": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.3.tgz", + "integrity": "sha512-bd+nzwhhs9ifsUrC2tWaSgm24/oo2c83zaRyZQF06hYA6sANfsXHtnZ19AbbbDXCDzeH5nZBSQ4NvCjgD62tJA==" + }, + "node_modules/ms": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.3.tgz", + "integrity": "sha1-cIFVpeROM/X9D8U+gdDUCpG+H/8=" + }, + "node_modules/nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, + "node_modules/nearley": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.16.0.tgz", + "integrity": "sha512-Tr9XD3Vt/EujXbZBv6UAHYoLUSMQAxSsTnm9K3koXzjzNWY195NqALeyrzLZBKzAkL3gl92BcSogqrHjD8QuUg==", + "dependencies": { + "commander": "2.20.0", + "moo": "0.4.3", + "railroad-diagrams": "1.0.0", + "randexp": "0.4.6", + "semver": "5.7.0" + }, + "bin": { + "nearley-railroad": "bin/nearley-railroad.js", + "nearley-test": "bin/nearley-test.js", + "nearley-unparse": "bin/nearley-unparse.js", + "nearleyc": "bin/nearleyc.js" + } + }, + "node_modules/next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, + "node_modules/node-abi": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.15.0.tgz", + "integrity": "sha512-FeLpTS0F39U7hHZU1srAK4Vx+5AHNVOTP+hxBNQknR/54laTHSFIJkDWDqiquY1LeLUgTfPN7sLPhMubx0PLAg==", + "dependencies": { + "semver": "5.7.0" + } + }, + "node_modules/node-addon-api": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz", + "integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA==" + }, + "node_modules/node-emoji": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", + "integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==", + "dependencies": { + "lodash.toarray": "4.4.0" + } + }, + "node_modules/node-mac-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/node-mac-notifier/-/node-mac-notifier-1.1.0.tgz", + "integrity": "sha512-Fwv09eKMGkM4xn+Eby5g7lvmYF+1KYWX4V5QNn27l4cVFVS7MNYOxhbvGGHT5VuTWbWDQIi/Lr7l6so5vvmqVw==", + "optional": true, + "dependencies": { + "bindings": "1.5.0", + "event-target-shim": "1.1.1", + "uuid": "3.3.2" + } + }, + "node_modules/noop-logger": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", + "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=" + }, + "node_modules/npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dependencies": { + "are-we-there-yet": "1.1.5", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "node_modules/nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dependencies": { + "boolbase": "1.0.0" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==" + }, + "node_modules/object-is": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", + "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dependencies": { + "define-properties": "1.1.3", + "function-bind": "1.1.1", + "has-symbols": "1.0.0", + "object-keys": "1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.entries": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", + "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", + "dependencies": { + "define-properties": "1.1.3", + "es-abstract": "1.13.0", + "function-bind": "1.1.1", + "has": "1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz", + "integrity": "sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==", + "dependencies": { + "define-properties": "1.1.3", + "es-abstract": "1.13.0", + "function-bind": "1.1.1", + "has": "1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", + "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "dependencies": { + "define-properties": "1.1.3", + "es-abstract": "1.13.0", + "function-bind": "1.1.1", + "has": "1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1.0.2" + } + }, + "node_modules/optimist": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.4.0.tgz", + "integrity": "sha1-y47Dfy/jqphky2eidSUOfhliCiU=", + "dependencies": { + "wordwrap": "0.0.2" + } + }, + "node_modules/option": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/option/-/option-0.2.4.tgz", + "integrity": "sha1-/Udc35jcq7PLOXo7pShP60Xtv+Q=" + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=" + }, + "node_modules/parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "dependencies": { + "@types/node": "12.0.7" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "engines": { + "node": ">=4" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "node_modules/pick-react-known-prop": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/pick-react-known-prop/-/pick-react-known-prop-0.1.5.tgz", + "integrity": "sha512-SnDf64AVdvqoAFpHeZUKT9kdn40Ellj84CPALRxYWqNJ6r6f44eAAT+Jtkb0Suhiw7yg5BdOFAQ25OJnjG+afw==", + "dependencies": { + "html-attributes": "1.1.0", + "lodash.isplainobject": "4.0.6", + "svg-attributes": "1.0.0" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dependencies": { + "chalk": "2.4.2", + "source-map": "0.6.1", + "supports-color": "5.5.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/postcss/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prebuild-install": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz", + "integrity": "sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==", + "dependencies": { + "detect-libc": "1.0.3", + "expand-template": "2.0.3", + "github-from-package": "0.0.0", + "minimist": "1.2.5", + "mkdirp": "0.5.1", + "napi-build-utils": "1.0.2", + "node-abi": "2.15.0", + "noop-logger": "0.1.1", + "npmlog": "4.1.2", + "pump": "3.0.0", + "rc": "1.2.8", + "simple-get": "3.1.0", + "tar-fs": "2.0.1", + "tunnel-agent": "0.6.0", + "which-pm-runs": "1.0.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/prebuild-install/node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "node_modules/printj": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", + "bin": { + "printj": "bin/printj.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "optional": true, + "dependencies": { + "asap": "2.0.6" + } + }, + "node_modules/prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "dependencies": { + "loose-envify": "1.4.0", + "object-assign": "4.1.1", + "react-is": "16.8.6" + } + }, + "node_modules/prop-types-exact": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/prop-types-exact/-/prop-types-exact-1.2.0.tgz", + "integrity": "sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==", + "dependencies": { + "has": "1.0.3", + "object.assign": "4.1.0", + "reflect.ownkeys": "0.2.0" + } + }, + "node_modules/property-accessors": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/property-accessors/-/property-accessors-1.1.3.tgz", + "integrity": "sha1-Hd6EAkYxhlkJ7zBwM2VoDF+SixU=", + "dependencies": { + "es6-weak-map": "0.1.4", + "mixto": "1.0.0" + } + }, + "node_modules/proxyquire": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-1.3.1.tgz", + "integrity": "sha1-EQecCx2R+A74z3phxlDNB7tF2To=" + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "optional": true + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "node_modules/psl": { + "version": "1.1.32", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.32.tgz", + "integrity": "sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "1.4.4", + "once": "1.4.0" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "dependencies": { + "performance-now": "2.1.0" + } + }, + "node_modules/railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=" + }, + "node_modules/randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "dependencies": { + "discontinuous-range": "1.0.0", + "ret": "0.1.15" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/raven": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/raven/-/raven-2.1.2.tgz", + "integrity": "sha512-pBopqPVAuzEP5jyyqEPxJvXg788dAJ9p8NXoHVLF598Ih7MApu2AWT/QHNk2Fa4Rx/xV9yTYDEtAujLXX1FvRg==", + "dependencies": { + "cookie": "0.3.1", + "json-stringify-safe": "5.0.1", + "lsmod": "1.0.0", + "stack-trace": "0.0.9", + "timed-out": "4.0.1", + "uuid": "3.0.0" + }, + "bin": { + "raven": "bin/raven" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/raven/node_modules/uuid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz", + "integrity": "sha1-Zyj8BFnEUNeWqZwxg3VpvfZy1yg=", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "0.6.0", + "ini": "1.3.5", + "minimist": "1.2.5", + "strip-json-comments": "2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "node_modules/react": { + "version": "16.6.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.6.0.tgz", + "integrity": "sha512-zJPnx/jKtuOEXCbQ9BKaxDMxR0001/hzxXwYxG8septeyYGfsgAei6NgfbVgOhbY1WOP2o3VPs/E9HaN+9hV3Q==", + "dependencies": { + "loose-envify": "1.4.0", + "object-assign": "4.1.1", + "prop-types": "15.7.2", + "scheduler": "0.10.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-color": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.17.3.tgz", + "integrity": "sha512-1dtO8LqAVotPIChlmo6kLtFS1FP89ll8/OiA8EcFRDR+ntcK+0ukJgByuIQHRtzvigf26dV5HklnxDIvhON9VQ==", + "dependencies": { + "@icons/material": "0.2.4", + "lodash": "4.17.11", + "material-colors": "1.2.6", + "prop-types": "15.7.2", + "reactcss": "1.2.3", + "tinycolor2": "1.4.1" + } + }, + "node_modules/react-dom": { + "version": "16.6.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.0.tgz", + "integrity": "sha512-Stm2D9dXEUUAQdvpvhvFj/DEXwC2PAL/RwEMhoN4dvvD2ikTlJegEXf97xryg88VIAU22ZAP7n842l+9BTz6+w==", + "dependencies": { + "loose-envify": "1.4.0", + "object-assign": "4.1.1", + "prop-types": "15.7.2", + "scheduler": "0.10.0" + } + }, + "node_modules/react-immutable-proptypes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/react-immutable-proptypes/-/react-immutable-proptypes-2.2.0.tgz", + "integrity": "sha512-Vf4gBsePlwdGvSZoLSBfd4HAP93HDauMY4fDjXhreg/vg6F3Fj/MXDNyTbltPC/xZKmZc+cjLu3598DdYK6sgQ==", + "dependencies": { + "invariant": "2.2.4" + } + }, + "node_modules/react-is": { + "version": "16.8.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", + "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==" + }, + "node_modules/react-test-renderer": { + "version": "16.6.0", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.6.0.tgz", + "integrity": "sha512-w+Y3YT7OX1LP5KO7HCd0YR34Ol1qmISHaooPNMRYa6QzmwtcWhEGuZPr34wO8UCBIokswuhyLQUq7rjPDcEtJA==", + "dependencies": { + "object-assign": "4.1.1", + "prop-types": "15.7.2", + "react-is": "16.8.6", + "scheduler": "0.10.0" + } + }, + "node_modules/react-transition-group": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.1.tgz", + "integrity": "sha512-CWaL3laCmgAFdxdKbhhps+c0HRGF4c+hdM4H23+FI1QBNUyx/AMeIJGWorehPNSaKnQNOAxL7PQmqMu78CDj3Q==", + "dependencies": { + "chain-function": "1.0.1", + "dom-helpers": "3.4.0", + "loose-envify": "1.4.0", + "prop-types": "15.7.2", + "warning": "3.0.0" + } + }, + "node_modules/reactcss": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", + "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", + "dependencies": { + "lodash": "4.17.11" + } + }, + "node_modules/readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "dependencies": { + "inherits": "2.0.3", + "string_decoder": "1.2.0", + "util-deprecate": "1.0.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/reflect.ownkeys": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz", + "integrity": "sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=" + }, + "node_modules/reflux": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflux/-/reflux-0.1.13.tgz", + "integrity": "sha1-ZLAsuL5IYDOYhViRsQG7uq1Cllo=", + "dependencies": { + "eventemitter3": "0.1.6" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", + "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" + }, + "node_modules/request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dependencies": { + "aws-sign2": "0.7.0", + "aws4": "1.8.0", + "caseless": "0.12.0", + "combined-stream": "1.0.8", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.3.3", + "har-validator": "5.1.3", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.24", + "oauth-sign": "0.9.0", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.4.3", + "tunnel-agent": "0.6.0", + "uuid": "3.3.2" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "engines": { + "node": ">=0.12" + } + }, + "node_modules/rimraf": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.2.tgz", + "integrity": "sha1-YrqUf6TAtDY4Oa7+zU8PutYFlyY=", + "dependencies": { + "glob": "7.1.4" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dependencies": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rst-selector-parser": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", + "integrity": "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=", + "dependencies": { + "lodash.flattendeep": "4.4.0", + "nearley": "2.16.0" + } + }, + "node_modules/rtlcss": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-2.4.0.tgz", + "integrity": "sha512-hdjFhZ5FCI0ABOfyXOMOhBtwPWtANLCG7rOiOcRf+yi5eDdxmDjqBruWouEnwVdzfh/TWF6NNncIEsigOCFZOA==", + "dependencies": { + "chalk": "2.4.2", + "findup": "0.1.5", + "mkdirp": "0.5.1", + "postcss": "6.0.23", + "strip-json-comments": "2.0.1" + }, + "bin": { + "rtlcss": "bin/rtlcss.js" + } + }, + "node_modules/rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=" + }, + "node_modules/rxjs": { + "version": "5.5.12", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz", + "integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==", + "dependencies": { + "symbol-observable": "1.0.1" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/rxjs-serial-subscription": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/rxjs-serial-subscription/-/rxjs-serial-subscription-0.1.1.tgz", + "integrity": "sha1-pCsdsL8QlLCSMRkeJ3jKP8+e0Uc=", + "dependencies": { + "rxjs": "5.5.12" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sax": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.1.6.tgz", + "integrity": "sha1-XWFr6KXmB9VOEUr65Vt+ry/MMkA=" + }, + "node_modules/scheduler": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.10.0.tgz", + "integrity": "sha512-+TSTVTCBAA3h8Anei3haDc1IRwMeDmtI/y/o3iBe3Mjl2vwYF9DtPDt929HyRmV/e7au7CLu8sc4C4W0VOs29w==", + "dependencies": { + "loose-envify": "1.4.0", + "object-assign": "4.1.1" + } + }, + "node_modules/selection-is-backward": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/selection-is-backward/-/selection-is-backward-1.0.0.tgz", + "integrity": "sha1-l6VGMxiKURq6ZBn8XB+pG0Z+a+E=" + }, + "node_modules/semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dependencies": { + "shebang-regex": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "node_modules/simple-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", + "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" + }, + "node_modules/simple-get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", + "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", + "dependencies": { + "decompress-response": "4.2.1", + "once": "1.4.0", + "simple-concat": "1.0.0" + } + }, + "node_modules/slate": { + "resolved": "git+ssh://git@github.com/bengotow/slate.git#cd6f40e8db9058f518c091cffe364608b3c858e6", + "dependencies": { + "debug": "3.2.7", + "direction": "0.1.5", + "esrever": "0.2.0", + "is-plain-object": "2.0.4", + "lodash": "4.17.11", + "tiny-invariant": "1.1.0", + "tiny-warning": "0.0.3", + "type-of": "2.0.1" + } + }, + "node_modules/slate-auto-replace": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/slate-auto-replace/-/slate-auto-replace-0.12.1.tgz", + "integrity": "sha512-4TBjWk/NKk2ef8FvPM0kYiOY+fClPieqyxAzAa2lpFbUnVX01I9Up4X34d/cmAlyX2jojn5AvBA/GG5xDschBg==", + "dependencies": { + "is-hotkey": "0.0.3", + "type-of": "2.0.1" + } + }, + "node_modules/slate-base64-serializer": { + "version": "0.2.100", + "resolved": "https://registry.npmjs.org/slate-base64-serializer/-/slate-base64-serializer-0.2.100.tgz", + "integrity": "sha512-LpESVGnN3twV/8Sen12YgIEN/CPY1rpff+w7jRBwy/ux/42R68/BanyEOQbKLscEMb//19Dwd6TvrG6ECMqLyQ==", + "dependencies": { + "isomorphic-base64": "1.0.2" + } + }, + "node_modules/slate-dev-environment": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/slate-dev-environment/-/slate-dev-environment-0.2.2.tgz", + "integrity": "sha512-JZ09llrRQu6JUsLJCUlGC0lB1r1qIAabAkSd454iyYBq6lDuY//Bypi3Jo8yzIfzZ4+mRLdQvl9e8MbeM9l48Q==", + "dependencies": { + "is-in-browser": "1.1.3" + } + }, + "node_modules/slate-hotkeys": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/slate-hotkeys/-/slate-hotkeys-0.2.9.tgz", + "integrity": "sha512-y+C/s5vJEmBxo8fIqHmUcdViGwALL/A6Qow3sNG1OHYD5SI11tC2gfYtGbPh+2q0H7O4lufffCmFsP5bMaDHqA==", + "dependencies": { + "is-hotkey": "0.1.4", + "slate-dev-environment": "0.2.2" + } + }, + "node_modules/slate-hotkeys/node_modules/is-hotkey": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.1.4.tgz", + "integrity": "sha512-Py+aW4r5mBBY18TGzGz286/gKS+fCQ0Hee3qkaiSmEPiD0PqFpe0wuA3l7rTOUKyeXl8Mxf3XzJxIoTlSv+kxA==" + }, + "node_modules/slate-html-serializer": { + "version": "0.7.39", + "resolved": "https://registry.npmjs.org/slate-html-serializer/-/slate-html-serializer-0.7.39.tgz", + "integrity": "sha512-ZNyVjqCasSa0M80+4W1bFXMhW4KJM7EE6dkyBOhQmIcuRX529xOahcz+6ZYK7fQn0Cyrye899OtX26LulAh/Ew==", + "dependencies": { + "type-of": "2.0.1" + } + }, + "node_modules/slate-plain-serializer": { + "version": "0.6.39", + "resolved": "https://registry.npmjs.org/slate-plain-serializer/-/slate-plain-serializer-0.6.39.tgz", + "integrity": "sha512-EGl+Y+9Fw9IULtPg8sttydaeiAoaibJolMXNfqI79+5GWTQwJFIbg24keKvsTw+3f2RieaPu8fcrKyujKtZ7ZQ==" + }, + "node_modules/slate-prop-types": { + "version": "0.5.30", + "resolved": "https://registry.npmjs.org/slate-prop-types/-/slate-prop-types-0.5.30.tgz", + "integrity": "sha512-zm3gVmnGn0a9l/75REkAC0zYQGl3U/j2Z4p4pZ4rLupCptLM8iLWeGdn07V3JVCUA6n/80y9Mtv43Z9b1G/BAA==" + }, + "node_modules/slate-react": { + "resolved": "git+ssh://git@github.com/bengotow/slate.git#4eb9d59ec29bca6d7edf8e61283249cf81034d65", + "dependencies": { + "debug": "3.2.7", + "get-window": "1.1.2", + "is-window": "1.0.2", + "lodash": "4.17.11", + "memoize-one": "4.1.0", + "prop-types": "15.7.2", + "react-immutable-proptypes": "2.2.0", + "selection-is-backward": "1.0.0", + "slate-base64-serializer": "0.2.100", + "slate-dev-environment": "0.2.2", + "slate-hotkeys": "0.2.9", + "slate-plain-serializer": "0.6.39", + "slate-prop-types": "0.5.30", + "slate-react-placeholder": "0.1.20", + "tiny-invariant": "1.1.0", + "tiny-warning": "0.0.3" + } + }, + "node_modules/slate-react-placeholder": { + "version": "0.1.20", + "resolved": "https://registry.npmjs.org/slate-react-placeholder/-/slate-react-placeholder-0.1.20.tgz", + "integrity": "sha512-A4xq1kS3V3YetFbLE/1dv+/SDVjx9zsZZepJqjcmkGK+evHU2yNkWjZXCg8MLMRtZJpGtYT/BE3+kHbkgT5Q4A==" + }, + "node_modules/slate-react/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "2.1.3" + } + }, + "node_modules/slate-react/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/slate-soft-break": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/slate-soft-break/-/slate-soft-break-0.9.0.tgz", + "integrity": "sha512-iTy2bl/G+tphN10YQBOPDRxDqgM46ZH1UUz0ublmL6PLtwJ3jJK1n08w37YxnY/ge4aW31UN386KV6qvFx6Hsw==" + }, + "node_modules/slate-when": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/slate-when/-/slate-when-0.2.0.tgz", + "integrity": "sha512-FtM9N1lrgWSlo4YHME6p2EnNlcDFbn0W8INvq0e4ithbvOZgFwqwrVWbgaFeGTntcEi934Lz8nqLwcVvxDt/zQ==" + }, + "node_modules/slate/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "2.1.3" + } + }, + "node_modules/slate/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/slick": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/slick/-/slick-1.12.2.tgz", + "integrity": "sha1-vQSN23TefRymkV+qSldXCzVQwtc=", + "engines": { + "node": "*" + } + }, + "node_modules/snarkdown": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/snarkdown/-/snarkdown-1.2.2.tgz", + "integrity": "sha1-DP4vMBK4BN4SD8DJ93kehpxZzHQ=" + }, + "node_modules/sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "optional": true, + "dependencies": { + "hoek": "2.16.3" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.3.3.tgz", + "integrity": "sha1-NJAJd9W6PwfHdX7nLnO7GptTdU8=", + "dependencies": { + "source-map": "0.1.32" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.1.32", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.32.tgz", + "integrity": "sha1-yLbBZ3l7pHQKjqMyUhYv8IWRsmY=", + "dependencies": { + "amdefine": "1.0.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/spawn-rx": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-2.0.12.tgz", + "integrity": "sha512-gOPXiQQFQ9lTOLuys0iMn3jfxxv9c7zzwhbYLOEbQGvEShHVJ5sSR1oD3Daj88os7jKArDYT7rbOKdvNhe7iEg==", + "dependencies": { + "debug": "github:emorikawa/debug#29685485665b44c42d33ebd7ed62570b21c6e4bd", + "lodash.assign": "4.2.0", + "rxjs": "5.5.12" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "node_modules/ssf": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.10.2.tgz", + "integrity": "sha512-rDhAPm9WyIsY8eZEKyE8Qsotb3j/wBdvMWBUsOhJdfhKGLfQidRjiBUV0y/MkyCLiXQ38FG6LWW/VYUtqlIDZQ==", + "dependencies": { + "frac": "1.1.2" + }, + "bin": { + "ssf": "bin/ssf.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dependencies": { + "asn1": "0.2.4", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.2", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.2", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "safer-buffer": "2.1.2", + "tweetnacl": "0.14.5" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stack-trace": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", + "integrity": "sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU=", + "engines": { + "node": "*" + } + }, + "node_modules/string_decoder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", + "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", + "dependencies": { + "safe-buffer": "5.1.2" + } + }, + "node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dependencies": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz", + "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=", + "dependencies": { + "define-properties": "1.1.3", + "es-abstract": "1.13.0", + "function-bind": "1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/stringstream": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", + "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==", + "optional": true + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dependencies": { + "ansi-regex": "2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/svg-attributes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-attributes/-/svg-attributes-1.0.0.tgz", + "integrity": "sha1-tcWWjzYke32+OFMgfyqcaK2Aa/w=", + "engines": { + "node": ">= 0.10.26", + "npm": ">=1.4.3" + } + }, + "node_modules/symbol-observable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", + "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tar": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz", + "integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==", + "dependencies": { + "fs-minipass": "2.1.0", + "minipass": "3.1.3", + "minizlib": "2.1.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/tar-fs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", + "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==", + "dependencies": { + "chownr": "1.1.4", + "mkdirp-classic": "0.5.2", + "pump": "3.0.0", + "tar-stream": "2.1.2" + } + }, + "node_modules/tar-stream": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz", + "integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==", + "dependencies": { + "bl": "4.0.2", + "end-of-stream": "1.4.4", + "fs-constants": "1.0.0", + "inherits": "2.0.3", + "readable-stream": "3.4.0" + } + }, + "node_modules/temp": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", + "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=", + "engines": [ + "node >=0.8.0" + ], + "dependencies": { + "os-tmpdir": "1.0.2", + "rimraf": "2.2.8" + } + }, + "node_modules/temp/node_modules/rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tiny-invariant": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", + "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" + }, + "node_modules/tiny-warning": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-0.0.3.tgz", + "integrity": "sha512-r0SSA5Y5IWERF9Xh++tFPx0jITBgGggOsRLDWWew6YRw/C2dr4uNO1fw1vanrBmHsICmPyMLNBZboTlxUmUuaA==" + }, + "node_modules/tinycolor2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", + "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=", + "engines": { + "node": "*" + } + }, + "node_modules/tld": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/tld/-/tld-0.0.2.tgz", + "integrity": "sha1-9g442CzaB14NzHlgHOXWgpzASd4=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dependencies": { + "psl": "1.1.32", + "punycode": "1.4.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tough-cookie/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "node_modules/type-of": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-of/-/type-of-2.0.1.tgz", + "integrity": "sha1-5yoXQYllaOn2KDeNgW1pEvfyOXI=" + }, + "node_modules/typechecker": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/typechecker/-/typechecker-4.7.0.tgz", + "integrity": "sha512-4LHc1KMNJ6NDGO+dSM/yNfZQRtp8NN7psYrPHUblD62Dvkwsp3VShsbM78kOgpcmMkRTgvwdKOTjctS+uMllgQ==", + "dependencies": { + "editions": "2.1.3" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" + }, + "node_modules/underscore-plus": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/underscore-plus/-/underscore-plus-1.7.0.tgz", + "integrity": "sha512-A3BEzkeicFLnr+U/Q3EyWwJAQPbA19mtZZ4h+lLq3ttm9kn8WC4R3YpuJZEXmWdLjYP47Zc8aLZm9kwdv+zzvA==", + "dependencies": { + "underscore": "1.9.1" + } + }, + "node_modules/underscore-plus/node_modules/underscore": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", + "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" + }, + "node_modules/underscore.string": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.5.tgz", + "integrity": "sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg==", + "dependencies": { + "sprintf-js": "1.0.3", + "util-deprecate": "1.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dependencies": { + "punycode": "2.1.1" + } + }, + "node_modules/utf7": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utf7/-/utf7-1.0.2.tgz", + "integrity": "sha1-lV9JCq5lO6IguUVqCod2wZk2CZE=", + "dependencies": { + "semver": "5.3.0" + } + }, + "node_modules/utf7/node_modules/semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/valid-data-url": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-0.1.6.tgz", + "integrity": "sha512-FXg2qXMzfAhZc0y2HzELNfUeiOjPr+52hU1DNBWiJJ2luXD+dD1R9NA48Ug5aj0ibbxroeGDc/RJv6ThiGgkDw==" + }, + "node_modules/vcf": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/vcf/-/vcf-2.0.5.tgz", + "integrity": "sha512-HnTtrJ1xBYmjbCyB24/2Vbugu884e0TI5QL/6QevfNuw4cmhXYaSxTVscEgfK35em6Zehmz3Jc3N/a1K4QmZ9A==", + "dependencies": { + "camelcase": "5.3.1", + "foldline": "1.1.0" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + } + }, + "node_modules/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/walkdir": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.11.tgz", + "integrity": "sha1-oW0CXrkxvQO1LzCMrtD0D86+lTI=", + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", + "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", + "dependencies": { + "loose-envify": "1.4.0" + } + }, + "node_modules/web-resource-inliner": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-4.3.2.tgz", + "integrity": "sha512-eVnNqwG20sbAgqv2JONwyr57UNZFJP4oauioeUjpCMY83AM11956eIhxlCGGXfSMi7bRBjR9Vao05bXFzslh7w==", + "dependencies": { + "async": "2.6.2", + "chalk": "1.1.3", + "datauri": "2.0.0", + "htmlparser2": "3.10.1", + "lodash.unescape": "4.0.1", + "request": "2.88.0", + "safer-buffer": "2.1.2", + "valid-data-url": "0.1.6", + "xtend": "4.0.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/web-resource-inliner/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/web-resource-inliner/node_modules/async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "dependencies": { + "lodash": "4.17.11" + } + }, + "node_modules/web-resource-inliner/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dependencies": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/web-resource-inliner/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-pm-runs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", + "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" + }, + "node_modules/wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dependencies": { + "string-width": "1.0.2" + } + }, + "node_modules/windows-iana": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/windows-iana/-/windows-iana-4.2.1.tgz", + "integrity": "sha512-ewNtGiWhAh66r5lYeWwiYWgg3ERcD+KiLMOqA8UCAKnKkFieb9M0VprLt7HD37bRFZKP7CLyXksj2L5hbrtnnw==" + }, + "node_modules/windows-quiet-hours": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/windows-quiet-hours/-/windows-quiet-hours-1.2.7.tgz", + "integrity": "sha512-PjKM2/RQhZ3ikG4COq0vPpXvmJsxckcg2YN3xmQv8kZl5l1uu00a/epSGDeY6tczDPkzBhoThBz1FKRqxBA7cQ==", + "optional": true, + "dependencies": { + "bindings": "1.5.0", + "nan": "2.14.0" + } + }, + "node_modules/windows-shortcuts": { + "resolved": "git+ssh://git@github.com/emorikawa/windows-shortcuts.git#b0a0fc7fb86fb03e06ddceb9cbd6c9c5c29e571e" + }, + "node_modules/wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/xlsx": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.14.1.tgz", + "integrity": "sha512-7hjB5YuyJo1fuuzXQjwuxD8LSUzE4Rxu5ToC3fB5JSunZxGjLcgKg69bEFG9GYoxeVDx5GL0k1dUodlvaQNRQw==", + "dependencies": { + "adler-32": "1.2.0", + "cfb": "1.1.1", + "codepage": "1.14.0", + "commander": "2.17.1", + "crc-32": "1.2.0", + "exit-on-epipe": "1.0.1", + "ssf": "0.10.2" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xlsx/node_modules/commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" + }, + "node_modules/xmlbuilder": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", + "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + } + }, "dependencies": { "@babel/runtime": { "version": "7.4.5", @@ -13,32 +4032,22 @@ } }, "@bengotow/slate-edit-list": { - "version": "github:bengotow/slate-edit-list#b868e1088b44ad1adb2cb06bbbb847a2fef7edb5" + "version": "git+ssh://git@github.com/bengotow/slate-edit-list.git#b868e1088b44ad1adb2cb06bbbb847a2fef7edb5", + "from": "@bengotow/slate-edit-list@github:bengotow/slate-edit-list#b868e108" + }, + "@felixrieseberg/spellchecker": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@felixrieseberg/spellchecker/-/spellchecker-4.0.12.tgz", + "integrity": "sha512-jLAPnRALB1I6Un8ldHVJfJid7m2R1qXoafFF/95sdm7R5VPOsZ3xTreZ/wLKO5x9AdsD2t9zpOcjDFTsCf3VzQ==", + "requires": { + "nan": "2.14.0" + } }, "@icons/material": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==" }, - "@paulcbetts/cld": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/@paulcbetts/cld/-/cld-2.4.6.tgz", - "integrity": "sha1-qZL2vEPKshKsLESIpnHPMC+LYuc=", - "requires": { - "glob": "5.0.15", - "nan": "2.14.0", - "rimraf": "2.5.2", - "underscore": "1.8.3" - } - }, - "@paulcbetts/spellchecker": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@paulcbetts/spellchecker/-/spellchecker-4.0.6.tgz", - "integrity": "sha512-9lhLEvWfAB00n2oOM/S08sna9AuFk+b+bPk8ficpSa2X0Ll40PahMwfFS3G54nqQBIFFZgTPrhoHtCLAao0xmg==", - "requires": { - "nan": "2.14.0" - } - }, "@types/node": { "version": "12.0.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.7.tgz", @@ -110,18 +4119,18 @@ "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "requires": { "delegates": "1.0.0", - "readable-stream": "2.3.6" + "readable-stream": "2.3.7" }, "dependencies": { "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", - "process-nextick-args": "2.0.0", + "process-nextick-args": "2.0.1", "safe-buffer": "5.1.2", "string_decoder": "1.1.1", "util-deprecate": "1.0.2" @@ -213,6 +4222,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, "bcp47": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/bcp47/-/bcp47-1.1.2.tgz", @@ -227,11 +4241,13 @@ } }, "better-sqlite3": { - "version": "github:bengotow/better-sqlite3#dcd5b6e73c9a5329fd72c85be3316131fcfb83ab", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-7.1.2.tgz", + "integrity": "sha512-8FWYnJ6Bx94MBX03J5Ka7sTRlvXXMEm4FW2Op7nM8ErQZeyALYLmSlbMBnfr4cMpS0tj0aYZv0a+26G2YJuIjg==", "requires": { "bindings": "1.5.0", - "nan": "2.14.0", - "to-descriptor": "1.0.1" + "prebuild-install": "5.3.3", + "tar": "6.1.0" } }, "bindings": { @@ -243,35 +4259,19 @@ } }, "bl": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz", + "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==", "requires": { - "readable-stream": "2.3.6", - "safe-buffer": "5.1.2" + "buffer": "5.6.0", + "inherits": "2.0.4", + "readable-stream": "3.4.0" }, "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "5.1.2" - } + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" } } }, @@ -294,6 +4294,7 @@ "version": "2.10.1", "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "optional": true, "requires": { "hoek": "2.16.3" } @@ -307,25 +4308,15 @@ "concat-map": "0.0.1" } }, - "buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", "requires": { - "buffer-alloc-unsafe": "1.1.0", - "buffer-fill": "1.0.0" + "base64-js": "1.3.1", + "ieee754": "1.1.13" } }, - "buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" - }, - "buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" - }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -376,9 +4367,9 @@ } }, "chownr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", - "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, "chromium-net-errors": { "version": "1.0.3", @@ -405,6 +4396,17 @@ "resolved": "https://registry.npmjs.org/classnames/-/classnames-1.2.1.tgz", "integrity": "sha1-xwx4j8kS4GhL2XruFn0u/WgtzfM=" }, + "cld": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/cld/-/cld-2.6.0.tgz", + "integrity": "sha512-2U8Uiv7Bvl1v4fNWFGB3RYtPvhUWXQJ1MoNKJNVuoALfandEt9oVqK64S+3ZLvQPjDiYjsohtTep/wIs0xOXkw==", + "requires": { + "glob": "5.0.15", + "node-addon-api": "2.0.0", + "rimraf": "2.5.2", + "underscore": "1.8.3" + } + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -571,17 +4573,18 @@ } }, "debug": { - "version": "github:emorikawa/debug#29685485665b44c42d33ebd7ed62570b21c6e4bd", + "version": "git+ssh://git@github.com/emorikawa/debug.git#29685485665b44c42d33ebd7ed62570b21c6e4bd", + "from": "debug@github:emorikawa/debug#nylas", "requires": { "ms": "0.7.3" } }, "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", "requires": { - "mimic-response": "1.0.1" + "mimic-response": "2.1.0" } }, "deep-extend": { @@ -710,34 +4713,49 @@ "semver": "5.7.0" } }, - "electron-remote": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/electron-remote/-/electron-remote-1.3.0.tgz", - "integrity": "sha512-i00MD42fzlmyhsYRUDrMM104OQTT/soEmBmZ707CZ3k/nwa0rrB3a3mpxvR0EI2Q+Xw2VBdhWbk2gYmyg0PS0g==", - "requires": { - "debug": "github:emorikawa/debug#29685485665b44c42d33ebd7ed62570b21c6e4bd", - "hashids": "1.2.2", - "lodash.get": "4.4.2", - "pify": "2.3.0", - "rxjs": "5.5.12", - "xmlhttprequest": "1.8.0" - } - }, "electron-spellchecker": { - "version": "github:bengotow/electron-spellchecker#de319e18db19e497a6add6cc086b243d8e0461db", + "version": "git+ssh://git@github.com/bengotow/electron-spellchecker.git#267df085fa4c1a5f5631e5f0ebd627890d59d670", + "from": "electron-spellchecker@github:bengotow/electron-spellchecker#267df08", "requires": { - "@paulcbetts/cld": "2.4.6", - "@paulcbetts/spellchecker": "4.0.6", + "@felixrieseberg/spellchecker": "4.0.12", "bcp47": "1.1.2", - "debug": "github:emorikawa/debug#29685485665b44c42d33ebd7ed62570b21c6e4bd", - "electron-remote": "1.3.0", - "keyboard-layout": "2.0.16", - "lru-cache": "4.1.5", + "cld": "2.6.0", + "debug": "4.3.1", + "keyboard-layout": "2.0.17", + "lru-cache": "5.1.1", "mkdirp": "0.5.1", - "pify": "2.3.0", + "pify": "4.0.1", "rxjs": "5.5.12", "rxjs-serial-subscription": "0.1.1", "spawn-rx": "2.0.12" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "3.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } } }, "emissary": { @@ -767,9 +4785,9 @@ } }, "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "requires": { "once": "1.4.0" } @@ -1071,6 +5089,14 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "3.1.3" + } + }, "fs-plus": { "version": "2.10.1", "resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-2.10.1.tgz", @@ -1111,7 +5137,7 @@ "console-control-strings": "1.1.0", "has-unicode": "2.0.1", "object-assign": "4.1.1", - "signal-exit": "3.0.2", + "signal-exit": "3.0.3", "string-width": "1.0.2", "strip-ansi": "3.0.1", "wide-align": "1.1.3" @@ -1222,11 +5248,6 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, - "hashids": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/hashids/-/hashids-1.2.2.tgz", - "integrity": "sha512-dEHCG2LraR6PNvSGxosZHIRgxF5sNLOIBFEHbj8lfP9WWmu/PWPMzsip1drdVSOFi51N2pU7gZavrgn7sbGFuw==" - }, "hawk": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", @@ -1242,7 +5263,8 @@ "hoek": { "version": "2.16.3", "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "optional": true }, "html-attributes": { "version": "1.1.0", @@ -1293,6 +5315,11 @@ "resolved": "https://registry.npmjs.org/ical.js/-/ical.js-1.3.0.tgz", "integrity": "sha512-wQ0w77MGOe6vNhZMBQuZAtZoTzDEOQ/QoCDofWL7yfkQ/HYWX5NyWPYjN+yj+4JahOvTkhXjTlrZtiQKv+BSOA==" }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, "image-size": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", @@ -1323,6 +5350,14 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "1.4.0" + } + }, "is-boolean-object": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.0.tgz", @@ -1554,9 +5589,9 @@ } }, "keyboard-layout": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/keyboard-layout/-/keyboard-layout-2.0.16.tgz", - "integrity": "sha512-eGrxmlV6jbm/mbPEOpYGuH53XEC7wIUj9ZxKcT2z9QHJ/RwrT9iVkvxka9zRxqHZHwQzcffgsa5OxoVAKnhK9w==", + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/keyboard-layout/-/keyboard-layout-2.0.17.tgz", + "integrity": "sha512-W9LL+1e8CS9fi0s8ZHINDN1HZ6QpYjE4yLi4+faed7ozppNOAxINjv5w16zG9tJv8Jp5LJrCfO5PZ9aV1m5d4g==", "requires": { "event-kit": "2.5.3", "nan": "2.14.0" @@ -1570,19 +5605,12 @@ } }, "keytar": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/keytar/-/keytar-4.3.0.tgz", - "integrity": "sha512-pd++/v+fS0LQKmzWlW6R1lziTXFqhfGeS6sYLfuTIqEy2pDzAbjutbSW8f9tnJdEEMn/9XhAQlT34VAtl9h4MQ==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-5.5.0.tgz", + "integrity": "sha512-1d/F2qAL/qijpm25wNq8eez4mE+/J4eBvqyLfspIYIeCEHn3nttFwplowIZfbdNCjFGWh68MzGZOd2vS61Ffew==", "requires": { - "nan": "2.8.0", - "prebuild-install": "5.3.0" - }, - "dependencies": { - "nan": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", - "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=" - } + "nan": "2.14.0", + "prebuild-install": "5.3.3" } }, "less": { @@ -1799,11 +5827,6 @@ "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" - }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -1958,9 +5981,9 @@ "integrity": "sha512-4ZJvCzfcwsBgPbkKXUzGoVZMWjv8IDIygkGzVc7uUYhgnK0t2LmGxxjdgH1i+pn0/KQfB5F/VKUJlfyTSOFQjg==" }, "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" }, "minimatch": { "version": "3.0.4", @@ -1975,6 +5998,19 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==" + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "3.1.3" + } + }, "mixto": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/mixto/-/mixto-1.0.0.tgz", @@ -1988,6 +6024,11 @@ "minimist": "0.0.8" } }, + "mkdirp-classic": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz", + "integrity": "sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g==" + }, "moment": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", @@ -2027,9 +6068,9 @@ "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" }, "napi-build-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.1.tgz", - "integrity": "sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" }, "nearley": { "version": "2.16.0", @@ -2054,13 +6095,18 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "node-abi": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.8.0.tgz", - "integrity": "sha512-1/aa2clS0pue0HjckL62CsbhWWU35HARvBDXcJtYKbYR7LnIutmpxmXbuDMV9kEviD2lP/wACOgWmmwljghHyQ==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.15.0.tgz", + "integrity": "sha512-FeLpTS0F39U7hHZU1srAK4Vx+5AHNVOTP+hxBNQknR/54laTHSFIJkDWDqiquY1LeLUgTfPN7sLPhMubx0PLAg==", "requires": { "semver": "5.7.0" } }, + "node-addon-api": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz", + "integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA==" + }, "node-emoji": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", @@ -2199,11 +6245,6 @@ "resolved": "https://registry.npmjs.org/option/-/option-0.2.4.tgz", "integrity": "sha1-/Udc35jcq7PLOXo7pShP60Xtv+Q=" }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -2248,9 +6289,9 @@ } }, "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" }, "postcss": { "version": "6.0.23", @@ -2270,32 +6311,31 @@ } }, "prebuild-install": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.0.tgz", - "integrity": "sha512-aaLVANlj4HgZweKttFNUVNRxDukytuIuxeK2boIMHjagNJCiVKWFsKF4tCE3ql3GbrD2tExPQ7/pwtEJcHNZeg==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz", + "integrity": "sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==", "requires": { "detect-libc": "1.0.3", "expand-template": "2.0.3", "github-from-package": "0.0.0", - "minimist": "1.2.0", + "minimist": "1.2.5", "mkdirp": "0.5.1", - "napi-build-utils": "1.0.1", - "node-abi": "2.8.0", + "napi-build-utils": "1.0.2", + "node-abi": "2.15.0", "noop-logger": "0.1.1", "npmlog": "4.1.2", - "os-homedir": "1.0.2", - "pump": "2.0.1", + "pump": "3.0.0", "rc": "1.2.8", - "simple-get": "2.8.1", - "tar-fs": "1.16.3", + "simple-get": "3.1.0", + "tar-fs": "2.0.1", "tunnel-agent": "0.6.0", "which-pm-runs": "1.0.0" }, "dependencies": { "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" } } }, @@ -2305,9 +6345,9 @@ "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==" }, "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "promise": { "version": "7.3.1", @@ -2369,11 +6409,11 @@ "integrity": "sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g==" }, "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "requires": { - "end-of-stream": "1.4.1", + "end-of-stream": "1.4.4", "once": "1.4.0" } }, @@ -2436,14 +6476,14 @@ "requires": { "deep-extend": "0.6.0", "ini": "1.3.5", - "minimist": "1.2.0", + "minimist": "1.2.5", "strip-json-comments": "2.0.1" }, "dependencies": { "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" } } }, @@ -2483,9 +6523,12 @@ } }, "react-immutable-proptypes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/react-immutable-proptypes/-/react-immutable-proptypes-2.1.0.tgz", - "integrity": "sha1-Aj1vObsVyXwHHp5g0A0TbqxfoLQ=" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/react-immutable-proptypes/-/react-immutable-proptypes-2.2.0.tgz", + "integrity": "sha512-Vf4gBsePlwdGvSZoLSBfd4HAP93HDauMY4fDjXhreg/vg6F3Fj/MXDNyTbltPC/xZKmZc+cjLu3598DdYK6sgQ==", + "requires": { + "invariant": "2.2.4" + } }, "react-is": { "version": "16.8.6", @@ -2627,47 +6670,6 @@ "strip-json-comments": "2.0.1" } }, - "runas": { - "version": "github:getflywheel/node-runas#ca4f0714a926319f53df4851b007f2e4fd5381e5", - "requires": { - "nan": "2.14.0", - "prebuild-install": "2.5.3" - }, - "dependencies": { - "expand-template": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.1.tgz", - "integrity": "sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg==" - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "prebuild-install": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.5.3.tgz", - "integrity": "sha512-/rI36cN2g7vDQnKWN8Uzupi++KjyqS9iS+/fpwG4Ea8d0Pip0PQ5bshUNzVwt+/D2MRfhVAplYMMvWLqWrCF/g==", - "requires": { - "detect-libc": "1.0.3", - "expand-template": "1.1.1", - "github-from-package": "0.0.0", - "minimist": "1.2.5", - "mkdirp": "0.5.1", - "node-abi": "2.8.0", - "noop-logger": "0.1.1", - "npmlog": "4.1.2", - "os-homedir": "1.0.2", - "pump": "2.0.1", - "rc": "1.2.8", - "simple-get": "2.8.1", - "tar-fs": "1.16.3", - "tunnel-agent": "0.6.0", - "which-pm-runs": "1.0.0" - } - } - } - }, "rx-lite": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", @@ -2742,9 +6744,9 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, "simple-concat": { "version": "1.0.0", @@ -2752,40 +6754,41 @@ "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" }, "simple-get": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz", - "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", + "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", "requires": { - "decompress-response": "3.3.0", + "decompress-response": "4.2.1", "once": "1.4.0", "simple-concat": "1.0.0" } }, "slate": { - "version": "github:bengotow/slate#cd6f40e8db9058f518c091cffe364608b3c858e6", + "version": "git+ssh://git@github.com/bengotow/slate.git#cd6f40e8db9058f518c091cffe364608b3c858e6", + "from": "slate@github:bengotow/slate#cd6f40e8", "requires": { - "debug": "3.2.6", + "debug": "3.2.7", "direction": "0.1.5", "esrever": "0.2.0", "is-plain-object": "2.0.4", "lodash": "4.17.11", - "tiny-invariant": "1.0.4", + "tiny-invariant": "1.1.0", "tiny-warning": "0.0.3", "type-of": "2.0.1" }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "requires": { - "ms": "2.1.2" + "ms": "2.1.3" } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" } } }, @@ -2849,15 +6852,16 @@ "integrity": "sha512-zm3gVmnGn0a9l/75REkAC0zYQGl3U/j2Z4p4pZ4rLupCptLM8iLWeGdn07V3JVCUA6n/80y9Mtv43Z9b1G/BAA==" }, "slate-react": { - "version": "github:bengotow/slate#4eb9d59ec29bca6d7edf8e61283249cf81034d65", + "version": "git+ssh://git@github.com/bengotow/slate.git#4eb9d59ec29bca6d7edf8e61283249cf81034d65", + "from": "slate-react@github:bengotow/slate#0.45.1-react", "requires": { - "debug": "3.2.6", + "debug": "3.2.7", "get-window": "1.1.2", "is-window": "1.0.2", "lodash": "4.17.11", "memoize-one": "4.1.0", "prop-types": "15.7.2", - "react-immutable-proptypes": "2.1.0", + "react-immutable-proptypes": "2.2.0", "selection-is-backward": "1.0.0", "slate-base64-serializer": "0.2.100", "slate-dev-environment": "0.2.2", @@ -2865,22 +6869,22 @@ "slate-plain-serializer": "0.6.39", "slate-prop-types": "0.5.30", "slate-react-placeholder": "0.1.20", - "tiny-invariant": "1.0.4", + "tiny-invariant": "1.1.0", "tiny-warning": "0.0.3" }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "requires": { - "ms": "2.1.2" + "ms": "2.1.3" } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" } } }, @@ -3051,64 +7055,37 @@ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=" }, - "tar-fs": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", - "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==", + "tar": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz", + "integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==", "requires": { - "chownr": "1.1.1", - "mkdirp": "0.5.1", - "pump": "1.0.3", - "tar-stream": "1.6.2" - }, - "dependencies": { - "pump": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", - "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", - "requires": { - "end-of-stream": "1.4.1", - "once": "1.4.0" - } - } + "fs-minipass": "2.1.0", + "minipass": "3.1.3", + "minizlib": "2.1.2" + } + }, + "tar-fs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", + "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==", + "requires": { + "chownr": "1.1.4", + "mkdirp-classic": "0.5.2", + "pump": "3.0.0", + "tar-stream": "2.1.2" } }, "tar-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz", + "integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==", "requires": { - "bl": "1.2.2", - "buffer-alloc": "1.2.0", - "end-of-stream": "1.4.1", + "bl": "4.0.2", + "end-of-stream": "1.4.4", "fs-constants": "1.0.0", - "readable-stream": "2.3.6", - "to-buffer": "1.1.1", - "xtend": "4.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "5.1.2" - } - } + "inherits": "2.0.3", + "readable-stream": "3.4.0" } }, "temp": { @@ -3133,9 +7110,9 @@ "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" }, "tiny-invariant": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.4.tgz", - "integrity": "sha512-lMhRd/djQJ3MoaHEBrw8e2/uM4rs9YMNk0iOr8rHQ0QdbM7D4l0gFl3szKdeixrlyfm9Zqi4dxHCM2qVG8ND5g==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", + "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" }, "tiny-warning": { "version": "0.0.3", @@ -3152,16 +7129,6 @@ "resolved": "https://registry.npmjs.org/tld/-/tld-0.0.2.tgz", "integrity": "sha1-9g442CzaB14NzHlgHOXWgpzASd4=" }, - "to-buffer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" - }, - "to-descriptor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-descriptor/-/to-descriptor-1.0.1.tgz", - "integrity": "sha1-oOZ4w068fS2uRk2DcrwhR52cK80=" - }, "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", @@ -3393,7 +7360,8 @@ } }, "windows-shortcuts": { - "version": "github:emorikawa/windows-shortcuts#b0a0fc7fb86fb03e06ddceb9cbd6c9c5c29e571e" + "version": "git+ssh://git@github.com/emorikawa/windows-shortcuts.git#b0a0fc7fb86fb03e06ddceb9cbd6c9c5c29e571e", + "from": "windows-shortcuts@emorikawa/windows-shortcuts#b0a0fc7" }, "wordwrap": { "version": "0.0.2", @@ -3431,11 +7399,6 @@ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==" }, - "xmlhttprequest": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", - "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" - }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/app/package.json b/app/package.json index a7141883d..50492881a 100644 --- a/app/package.json +++ b/app/package.json @@ -12,15 +12,15 @@ "main": "./src/browser/main.js", "dependencies": { "@bengotow/slate-edit-list": "github:bengotow/slate-edit-list#b868e108", - "@paulcbetts/cld": "2.4.6", - "better-sqlite3": "bengotow/better-sqlite3#dcd5b6e73c9a5329fd72c85be3316131fcfb83ab", + "better-sqlite3": "^7.1.2", "chromium-net-errors": "1.0.3", "chrono-node": "^1.1.2", "classnames": "1.2.1", + "cld": "2.6.0", "collapse-whitespace": "^1.1.6", "debug": "github:emorikawa/debug#nylas", "deep-extend": "0.6.0", - "electron-spellchecker": "github:bengotow/electron-spellchecker#de319e18db19e497a6add6cc086b243d8e0461db", + "electron-spellchecker": "github:bengotow/electron-spellchecker#267df08", "emoji-data": "^0.2.0", "enzyme": "^3.8.0", "enzyme-adapter-react-16": "^1.9.0", @@ -36,7 +36,7 @@ "jasmine-react-helpers": "^0.2", "jasmine-reporters": "1.x.x", "juice": "^5.2.0", - "keytar": "4.3.0", + "keytar": "5.5.0", "less-cache": "1.1.0", "lru-cache": "^4.0.1", "mammoth": "1.4.7", @@ -58,7 +58,6 @@ "reflux": "0.1.13", "rimraf": "2.5.2", "rtlcss": "2.4.0", - "runas": "getflywheel/node-runas#ca4f0714", "rx-lite": "4.0.8", "slate": "github:bengotow/slate#cd6f40e8", "slate-auto-replace": "0.12.1", diff --git a/app/spec/database-object-registry-spec.ts b/app/spec/database-object-registry-spec.ts index 5ef1d6cac..e4f0bb962 100644 --- a/app/spec/database-object-registry-spec.ts +++ b/app/spec/database-object-registry-spec.ts @@ -1,7 +1,7 @@ /* eslint quote-props: 0 */ import _ from 'underscore'; import { Model } from '../src/flux/models/model'; -import Attributes from '../src/flux/attributes'; +import * as Attributes from '../src/flux/attributes'; import DatabaseObjectRegistry from '../src/registries/database-object-registry'; class GoodTest extends Model { diff --git a/app/spec/fixtures/db-test-model.ts b/app/spec/fixtures/db-test-model.ts index 3fbd8e06b..46a3196e1 100644 --- a/app/spec/fixtures/db-test-model.ts +++ b/app/spec/fixtures/db-test-model.ts @@ -5,14 +5,11 @@ */ import { Model } from '../../src/flux/models/model'; import { Category } from '../../src/flux/models/category'; -import Attributes from '../../src/flux/attributes'; +import * as Attributes from '../../src/flux/attributes'; class TestModel extends Model { static attributes = { - id: Attributes.String({ - queryable: true, - modelKey: 'id', - }), + ...Model.attributes, clientId: Attributes.String({ queryable: true, diff --git a/app/spec/models/model-spec.ts b/app/spec/models/model-spec.ts index bf5d5d350..1af9e0638 100644 --- a/app/spec/models/model-spec.ts +++ b/app/spec/models/model-spec.ts @@ -1,6 +1,6 @@ /* eslint quote-props: 0 */ import { Model } from '../../src/flux/models/model'; -import Attributes from '../../src/flux/attributes'; +import * as Attributes from '../../src/flux/attributes'; describe('Model', function modelSpecs() { describe('constructor', () => { diff --git a/app/spec/models/query-spec.ts b/app/spec/models/query-spec.ts index a321c5c8b..98646d44a 100644 --- a/app/spec/models/query-spec.ts +++ b/app/spec/models/query-spec.ts @@ -1,6 +1,6 @@ /* eslint quote-props: 0 */ import ModelQuery from '../../src/flux/models/query'; -import Attributes from '../../src/flux/attributes'; +import * as Attributes from '../../src/flux/attributes'; import { Message } from '../../src/flux/models/message'; import { Thread } from '../../src/flux/models/thread'; import { Account } from '../../src/flux/models/account'; diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/both-email-and-url-in.html b/app/spec/services/autolinker-fixtures/both-email-and-url-in.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/both-email-and-url-in.html rename to app/spec/services/autolinker-fixtures/both-email-and-url-in.html diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/both-email-and-url-out.html b/app/spec/services/autolinker-fixtures/both-email-and-url-out.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/both-email-and-url-out.html rename to app/spec/services/autolinker-fixtures/both-email-and-url-out.html diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/gmail-in.html b/app/spec/services/autolinker-fixtures/gmail-in.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/gmail-in.html rename to app/spec/services/autolinker-fixtures/gmail-in.html diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/gmail-out.html b/app/spec/services/autolinker-fixtures/gmail-out.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/gmail-out.html rename to app/spec/services/autolinker-fixtures/gmail-out.html diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/linkedin-in.html b/app/spec/services/autolinker-fixtures/linkedin-in.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/linkedin-in.html rename to app/spec/services/autolinker-fixtures/linkedin-in.html diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/linkedin-out.html b/app/spec/services/autolinker-fixtures/linkedin-out.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/linkedin-out.html rename to app/spec/services/autolinker-fixtures/linkedin-out.html diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/medium-post-in.html b/app/spec/services/autolinker-fixtures/medium-post-in.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/medium-post-in.html rename to app/spec/services/autolinker-fixtures/medium-post-in.html diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/medium-post-out.html b/app/spec/services/autolinker-fixtures/medium-post-out.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/medium-post-out.html rename to app/spec/services/autolinker-fixtures/medium-post-out.html diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/nylas-url-in.html b/app/spec/services/autolinker-fixtures/nylas-url-in.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/nylas-url-in.html rename to app/spec/services/autolinker-fixtures/nylas-url-in.html diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/nylas-url-out.html b/app/spec/services/autolinker-fixtures/nylas-url-out.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/nylas-url-out.html rename to app/spec/services/autolinker-fixtures/nylas-url-out.html diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/plaintext-in.html b/app/spec/services/autolinker-fixtures/plaintext-in.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/plaintext-in.html rename to app/spec/services/autolinker-fixtures/plaintext-in.html diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/plaintext-out.html b/app/spec/services/autolinker-fixtures/plaintext-out.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/plaintext-out.html rename to app/spec/services/autolinker-fixtures/plaintext-out.html diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/readme-in.html b/app/spec/services/autolinker-fixtures/readme-in.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/readme-in.html rename to app/spec/services/autolinker-fixtures/readme-in.html diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/readme-out.html b/app/spec/services/autolinker-fixtures/readme-out.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/readme-out.html rename to app/spec/services/autolinker-fixtures/readme-out.html diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/strangeemails-in.html b/app/spec/services/autolinker-fixtures/strangeemails-in.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/strangeemails-in.html rename to app/spec/services/autolinker-fixtures/strangeemails-in.html diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/strangeemails-out.html b/app/spec/services/autolinker-fixtures/strangeemails-out.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/strangeemails-out.html rename to app/spec/services/autolinker-fixtures/strangeemails-out.html diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/strangephones-in.html b/app/spec/services/autolinker-fixtures/strangephones-in.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/strangephones-in.html rename to app/spec/services/autolinker-fixtures/strangephones-in.html diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/strangephones-out.html b/app/spec/services/autolinker-fixtures/strangephones-out.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/strangephones-out.html rename to app/spec/services/autolinker-fixtures/strangephones-out.html diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/twitter-in.html b/app/spec/services/autolinker-fixtures/twitter-in.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/twitter-in.html rename to app/spec/services/autolinker-fixtures/twitter-in.html diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/twitter-out.html b/app/spec/services/autolinker-fixtures/twitter-out.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/twitter-out.html rename to app/spec/services/autolinker-fixtures/twitter-out.html diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/url-with-port-in.html b/app/spec/services/autolinker-fixtures/url-with-port-in.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/url-with-port-in.html rename to app/spec/services/autolinker-fixtures/url-with-port-in.html diff --git a/app/internal_packages/message-list/specs/autolinker-fixtures/url-with-port-out.html b/app/spec/services/autolinker-fixtures/url-with-port-out.html similarity index 100% rename from app/internal_packages/message-list/specs/autolinker-fixtures/url-with-port-out.html rename to app/spec/services/autolinker-fixtures/url-with-port-out.html diff --git a/app/internal_packages/message-list/specs/autolinker-spec.ts b/app/spec/services/autolinker-spec.ts similarity index 86% rename from app/internal_packages/message-list/specs/autolinker-spec.ts rename to app/spec/services/autolinker-spec.ts index 2c4d2d714..0afb72366 100644 --- a/app/internal_packages/message-list/specs/autolinker-spec.ts +++ b/app/spec/services/autolinker-spec.ts @@ -1,11 +1,10 @@ import fs from 'fs'; import path from 'path'; -import { autolink } from '../lib/autolinker'; +import { Autolink } from '../../src/services/autolinker'; describe('autolink', function autolinkSpec() { const fixturesDir = path.join(__dirname, 'autolinker-fixtures'); - fs - .readdirSync(fixturesDir) + fs.readdirSync(fixturesDir) .filter(filename => filename.indexOf('-in.html') !== -1) .forEach(filename => { it(`should properly autolink a variety of email bodies ${filename}`, () => { @@ -17,7 +16,7 @@ describe('autolink', function autolinkSpec() { const expected = fs.readFileSync(expectedPath).toString(); div.innerHTML = input; - autolink({ body: div }); + Autolink(div); expect(div.innerHTML).toEqual(expected); }); diff --git a/app/src/app-env.ts b/app/src/app-env.ts index f553052f8..0b387e60b 100644 --- a/app/src/app-env.ts +++ b/app/src/app-env.ts @@ -39,7 +39,7 @@ export default class AppEnvConstructor { windowEventHandler: import('./window-event-handler').default; actionBridge: import('./flux/action-bridge').default; mailsyncBridge: import('./flux/mailsync-bridge').default; - errorLogger: import('./error-logger'); + errorLogger: any; savedState: any; isReloading: boolean; @@ -670,8 +670,8 @@ export default class AppEnvConstructor { ); const browserWindow = this.getCurrentWindow(); - if (browserWindow.isResizable() !== loadSettings.resizable) { - browserWindow.setResizable(loadSettings.resizable); + if (browserWindow.resizable !== loadSettings.resizable) { + browserWindow.resizable = loadSettings.resizable; } if (!loadSettings.hidden) { @@ -772,18 +772,20 @@ export default class AppEnvConstructor { remote.process.exit(status); } - showOpenDialog(options, callback) { - return callback(remote.dialog.showOpenDialog(this.getCurrentWindow(), options)); + async showOpenDialog(options: Electron.OpenDialogOptions, callback: (paths: string[]) => void) { + const result = await remote.dialog.showOpenDialog(this.getCurrentWindow(), options); + callback(result.filePaths); } - showSaveDialog(options, callback) { + async showSaveDialog(options: Electron.SaveDialogOptions, callback: (path: string) => void) { if (options.title == null) { options.title = 'Save File'; } - return callback(remote.dialog.showSaveDialog(this.getCurrentWindow(), options)); + const result = await remote.dialog.showSaveDialog(this.getCurrentWindow(), options); + callback(result.filePath); } - showErrorDialog( + async showErrorDialog( messageData, { showInMainWindow, detail }: { showInMainWindow?: boolean; detail?: string } = {} ) { @@ -805,33 +807,29 @@ export default class AppEnvConstructor { } if (!detail) { - return remote.dialog.showMessageBox(winToShow, { + return remote.dialog.showMessageBoxSync(winToShow, { type: 'warning', buttons: [localized('Okay')], message: title, detail: message, }); } - return remote.dialog.showMessageBox( - winToShow, - { - type: 'warning', - buttons: [localized('Okay'), localized('Show Detail')], - message: title, - detail: message, - }, - buttonIndex => { - if (buttonIndex === 1) { - const { Actions } = require('mailspring-exports'); - const { CodeSnippet } = require('mailspring-component-kit'); - Actions.openModal({ - component: CodeSnippet({ intro: message, code: detail, className: 'error-details' }), - width: 500, - height: 300, - }); - } - } - ); + + const result = remote.dialog.showMessageBoxSync(winToShow, { + type: 'warning', + buttons: [localized('Okay'), localized('Show Detail')], + message: title, + detail: message, + }); + if (result === 1) { + const { Actions } = require('mailspring-exports'); + const { CodeSnippet } = require('mailspring-component-kit'); + Actions.openModal({ + component: CodeSnippet({ intro: message, code: detail, className: 'error-details' }), + width: 500, + height: 300, + }); + } } // Delegate to the browser's process fileListCache diff --git a/app/src/browser/application-touch-bar.ts b/app/src/browser/application-touch-bar.ts index 8357c8ff0..17885e6f2 100644 --- a/app/src/browser/application-touch-bar.ts +++ b/app/src/browser/application-touch-bar.ts @@ -118,7 +118,9 @@ export default class ApplicationTouchBar { // Take a single linear pass through the touch bar items and place // consecutive items with the same `group` key into touchbar groups. // This results in /very slightly/ tighter spacing in the touchbar. - const final: Electron.TouchBarGroup[] = []; + const final: Array< + Electron.TouchBarButton | Electron.TouchBarSpacer | Electron.TouchBarGroup + > = []; let group: Array = []; let groupName = null; diff --git a/app/src/browser/application.ts b/app/src/browser/application.ts index a89cf0da4..0fff2ffa3 100644 --- a/app/src/browser/application.ts +++ b/app/src/browser/application.ts @@ -90,7 +90,7 @@ export default class Application extends EventEmitter { buttons = [localized('Quit'), localized('Rebuild')]; } - const buttonIndex = dialog.showMessageBox({ type: 'warning', buttons, message }); + const buttonIndex = dialog.showMessageBoxSync({ type: 'warning', buttons, message }); if (buttonIndex === 0) { app.quit(); @@ -285,7 +285,7 @@ export default class Application extends EventEmitter { this._resettingAndRelaunching = true; if (errorMessage) { - dialog.showMessageBox({ + dialog.showMessageBoxSync({ type: 'warning', buttons: [localized('Okay')], message: localized( @@ -324,26 +324,23 @@ export default class Application extends EventEmitter { }); }); - this.on('application:run-package-specs', () => { - dialog.showOpenDialog( - { - title: localized('Choose Directory'), - defaultPath: this.configDirPath, - buttonLabel: localized('Choose'), - properties: ['openDirectory'], - }, - filenames => { - if (!filenames || filenames.length === 0) { - return; - } - this.runSpecs({ - exitWhenDone: false, - showSpecsInWindow: true, - resourcePath: this.resourcePath, - specDirectory: filenames[0], - }); - } - ); + this.on('application:run-package-specs', async () => { + const { filePaths } = await dialog.showOpenDialog({ + title: localized('Choose Directory'), + defaultPath: this.configDirPath, + buttonLabel: localized('Choose'), + properties: ['openDirectory'], + }); + + if (!filePaths || filePaths.length === 0) { + return; + } + this.runSpecs({ + exitWhenDone: false, + showSpecsInWindow: true, + resourcePath: this.resourcePath, + specDirectory: filePaths[0], + }); }); this.on('application:reset-database', this._resetDatabaseAndRelaunch); @@ -550,7 +547,7 @@ export default class Application extends EventEmitter { ipcMain.on('encountered-theme-error', (event, { message, detail }) => { if (userResetTheme) return; - const buttonIndex = dialog.showMessageBox({ + const buttonIndex = dialog.showMessageBoxSync({ type: 'warning', buttons: [localized('Reset Theme'), localized('Continue')], defaultId: 0, diff --git a/app/src/browser/autoupdate-manager.ts b/app/src/browser/autoupdate-manager.ts index ad8ae6a94..acd6ed233 100644 --- a/app/src/browser/autoupdate-manager.ts +++ b/app/src/browser/autoupdate-manager.ts @@ -54,9 +54,7 @@ export default class AutoUpdateManager extends EventEmitter { host = `updates-staging.getmailspring.com`; } - this.feedURL = `https://${host}/check/${params.platform}/${params.arch}/${params.version}/${ - params.id - }/${params.channel}`; + this.feedURL = `https://${host}/check/${params.platform}/${params.arch}/${params.version}/${params.id}/${params.channel}`; if (autoUpdater) { autoUpdater.setFeedURL(this.feedURL); } @@ -148,7 +146,7 @@ export default class AutoUpdateManager extends EventEmitter { }; } - check({ hidePopups }: {hidePopups?: boolean} = {}) { + check({ hidePopups }: { hidePopups?: boolean } = {}) { this.updateFeedURL(); if (!hidePopups) { autoUpdater.once('update-not-available', this.onUpdateNotAvailable); @@ -162,7 +160,12 @@ export default class AutoUpdateManager extends EventEmitter { } dialogIcon() { - const iconPath = path.join(global.application.resourcePath, 'static', 'images', 'mailspring.png'); + const iconPath = path.join( + global.application.resourcePath, + 'static', + 'images', + 'mailspring.png' + ); if (!fs.existsSync(iconPath)) return undefined; return nativeImage.createFromPath(iconPath); } diff --git a/app/src/browser/config-persistence-manager.ts b/app/src/browser/config-persistence-manager.ts index ef8f660e9..72a870e1a 100644 --- a/app/src/browser/config-persistence-manager.ts +++ b/app/src/browser/config-persistence-manager.ts @@ -61,16 +61,12 @@ export default class ConfigPersistenceManager { let detail = error.message; if (error instanceof SyntaxError) { - detail += `\n\nThe file ${ - this.configFilePath - } has incorrect JSON formatting or is empty. Fix the formatting to resolve this error, or reset your settings to continue using N1.`; + detail += `\n\nThe file ${this.configFilePath} has incorrect JSON formatting or is empty. Fix the formatting to resolve this error, or reset your settings to continue using N1.`; } else { - detail += `\n\nWe were unable to read the file ${ - this.configFilePath - }. Make sure you have permissions to access this file, and check that the file is not open or being edited and try again.`; + detail += `\n\nWe were unable to read the file ${this.configFilePath}. Make sure you have permissions to access this file, and check that the file is not open or being edited and try again.`; } - const clickedIndex = dialog.showMessageBox({ + const clickedIndex = dialog.showMessageBoxSync({ type: 'error', message, detail, @@ -127,12 +123,10 @@ export default class ConfigPersistenceManager { } _showSaveErrorDialog() { - const clickedIndex = dialog.showMessageBox({ + const clickedIndex = dialog.showMessageBoxSync({ type: 'error', message: localized(`Failed to save "%@"`, path.basename(this.configFilePath)), - detail: `\n\nWe were unable to save the file ${ - this.configFilePath - }. Make sure you have permissions to access this file, and check that the file is not open or being edited and try again.`, + detail: `\n\nWe were unable to save the file ${this.configFilePath}. Make sure you have permissions to access this file, and check that the file is not open or being edited and try again.`, buttons: [localized('Okay'), localized('Try Again')], }); return ['ignore', 'retry'][clickedIndex]; diff --git a/app/src/browser/mailspring-window.ts b/app/src/browser/mailspring-window.ts index 044148a4c..79bbbf906 100644 --- a/app/src/browser/mailspring-window.ts +++ b/app/src/browser/mailspring-window.ts @@ -99,6 +99,7 @@ export default class MailspringWindow extends EventEmitter { webPreferences: { nodeIntegration: true, contextIsolation: false, + webviewTag: true, }, autoHideMenuBar, }; @@ -286,7 +287,7 @@ export default class MailspringWindow extends EventEmitter { return; } - const chosen = dialog.showMessageBox(this.browserWindow, { + const chosen = dialog.showMessageBoxSync(this.browserWindow, { type: 'warning', buttons: ['Close', 'Keep Waiting'], message: 'Mailspring is not responding', @@ -312,7 +313,7 @@ export default class MailspringWindow extends EventEmitter { if (this.neverClose) { this.browserWindow.reload(); } else { - const chosen = dialog.showMessageBox({ + const chosen = dialog.showMessageBoxSync({ type: 'warning', buttons: ['Close Window', 'Reload', 'Keep It Open'], message: 'Mailspring has crashed', diff --git a/app/src/browser/main.js b/app/src/browser/main.js index 3847a459b..ca2b1f611 100644 --- a/app/src/browser/main.js +++ b/app/src/browser/main.js @@ -278,6 +278,23 @@ const start = () => { app.removeListener('open-file', onOpenFileBeforeReady); app.removeListener('open-url', onOpenUrlBeforeReady); + // Block remote JS execution in a second way in case our tag approach + // is compromised somehow https://www.electronjs.org/docs/tutorial/security + // This CSP string should match the one in app/static/index.html + require('electron').session.defaultSession.webRequest.onHeadersReceived((details, callback) => { + if (details.url.startsWith('devtools://')) { + return callback(details); + } + callback({ + responseHeaders: { + ...details.responseHeaders, + 'Content-Security-Policy': [ + "default-src * mailspring:; script-src 'self' 'unsafe-inline' chrome-extension://react-developer-tools; style-src * 'unsafe-inline' mailspring:; img-src * data: mailspring: file:;", + ], + }, + }); + }); + // eslint-disable-next-line const Application = require(path.join(options.resourcePath, 'src', 'browser', 'application')) .default; diff --git a/app/src/browser/move-to-applications.ts b/app/src/browser/move-to-applications.ts index 2a14d4ff9..0246f67de 100644 --- a/app/src/browser/move-to-applications.ts +++ b/app/src/browser/move-to-applications.ts @@ -7,7 +7,7 @@ export default function moveToApplications() { } // prompt the user and ask if they'd like to move the application - const idx = dialog.showMessageBox({ + const idx = dialog.showMessageBoxSync({ type: 'question', buttons: [localized('Move to Applications'), localized('Not Now')], defaultId: 0, diff --git a/app/src/browser/system-tray-manager.ts b/app/src/browser/system-tray-manager.ts index 683f8770c..827fccad1 100644 --- a/app/src/browser/system-tray-manager.ts +++ b/app/src/browser/system-tray-manager.ts @@ -41,7 +41,7 @@ function _getIcon(iconPath, isTemplateImg = false) { } const icon = nativeImage.createFromPath(iconPath); if (isTemplateImg) { - icon.setTemplateImage(true); + icon.isMacTemplateImage = true; } return icon; } diff --git a/app/src/browser/types/global-ext.d.ts b/app/src/browser/types/global-ext.d.ts index f1b96a79c..506b58329 100644 --- a/app/src/browser/types/global-ext.d.ts +++ b/app/src/browser/types/global-ext.d.ts @@ -1,11 +1,10 @@ - export {}; declare global { module NodeJS { interface Global { jasmine: any; shellStartTime: number; - errorLogger: import('../../error-logger'); + errorLogger: any; application: import('../application').default; } } @@ -25,4 +24,4 @@ declare global { group: string; } } -} \ No newline at end of file +} diff --git a/app/src/components/composer-editor/base-block-plugins.tsx b/app/src/components/composer-editor/base-block-plugins.tsx index 19c0bac23..c59b5a581 100644 --- a/app/src/components/composer-editor/base-block-plugins.tsx +++ b/app/src/components/composer-editor/base-block-plugins.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Editor, Value, Node } from 'slate'; +import { Editor, Value, Node, Block } from 'slate'; import SoftBreak from 'slate-soft-break'; import EditList from '@bengotow/slate-edit-list'; import AutoReplace from 'slate-auto-replace'; @@ -370,7 +370,7 @@ const MailspringBaseBlockPlugin: ComposerEditorPlugin = { .filter(config => config.button) .map(BuildToggleButton), renderNode, - commands: { + appCommands: { 'core:select-all': (event, editor: Editor) => { // If the document contains void blocks the browser's natural solution is to set // the selection to a DOM fragment range not to a contenteditable text range @@ -420,8 +420,8 @@ const plugins: ComposerEditorPlugin[] = [ // Pressing backspace when you're at the top of the document should not delete down { - onKeyDown: function onKeyDown(event, editor: Editor, next: () => void) { - if (event.key !== 'Backspace' || event.shiftKey || event.metaKey || event.optionKey) { + onKeyDown: function onKeyDown(event: React.KeyboardEvent, editor: Editor, next: () => void) { + if (event.key !== 'Backspace' || event.shiftKey || event.metaKey || event.altKey) { return next(); } const { selection, focusText, document } = editor.value; @@ -444,7 +444,7 @@ const plugins: ComposerEditorPlugin[] = [ // Return breaks you out of blockquotes completely { - onKeyDown: function onKeyDown(event, editor: Editor, next: () => void) { + onKeyDown: function onKeyDown(event: React.KeyboardEvent, editor: Editor, next: () => void) { if (event.shiftKey) { return next(); } diff --git a/app/src/components/composer-editor/base-mark-plugins.tsx b/app/src/components/composer-editor/base-mark-plugins.tsx index bba02612f..5a8483b2c 100644 --- a/app/src/components/composer-editor/base-mark-plugins.tsx +++ b/app/src/components/composer-editor/base-mark-plugins.tsx @@ -298,7 +298,7 @@ const BaseMarkPlugin: ComposerEditorPlugin = { let size = 2; if (provided.endsWith('px')) { // 16px = 12pt - size = PT_TO_SIZE[Math.round(Number(provided.replace('px', '')) / 1 * 0.75)]; + size = PT_TO_SIZE[Math.round((Number(provided.replace('px', '')) / 1) * 0.75)]; } if (provided.endsWith('em')) { // 1em = 12pt @@ -315,7 +315,7 @@ const BaseMarkPlugin: ComposerEditorPlugin = { }), ]), renderMark, - commands: { + appCommands: { 'contenteditable:bold': (event, editor) => editor.toggleMark(MARK_CONFIG.bold.type), 'contenteditable:underline': (event, editor) => editor.toggleMark(MARK_CONFIG.underline.type), 'contenteditable:italic': (event, editor) => editor.toggleMark(MARK_CONFIG.italic.type), diff --git a/app/src/components/composer-editor/composer-editor.tsx b/app/src/components/composer-editor/composer-editor.tsx index 7ca11311f..af923a80d 100644 --- a/app/src/components/composer-editor/composer-editor.tsx +++ b/app/src/components/composer-editor/composer-editor.tsx @@ -2,7 +2,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import * as Immutable from 'immutable'; import { Editor, Value, Operation, Range } from 'slate'; -import { Editor as SlateEditorComponent, EditorProps } from 'slate-react'; +import { Editor as SlateEditorComponent, EditorProps, Plugin } from 'slate-react'; import { clipboard as ElectronClipboard } from 'electron'; import { InlineStyleTransformer } from 'mailspring-exports'; import path from 'path'; @@ -44,8 +44,8 @@ export class ComposerEditor extends React.Component { // every render. this._pluginKeyHandlers = {}; plugins.forEach(plugin => { - if (!plugin.commands) return; - Object.entries(plugin.commands).forEach( + if (!plugin.appCommands) return; + Object.entries(plugin.appCommands).forEach( ([command, handler]: [string, (event: any, val: any) => any]) => { this._pluginKeyHandlers[command] = event => { if (!this._mounted) return; @@ -262,7 +262,7 @@ export class ComposerEditor extends React.Component { onCopy={this.onCopy} onPaste={this.onPaste} spellCheck={false} - plugins={plugins} + plugins={(plugins as any) as Plugin[]} propsForPlugins={propsForPlugins} />
diff --git a/app/src/components/composer-editor/conversion.tsx b/app/src/components/composer-editor/conversion.tsx index 4256b40a3..b6deffb2e 100644 --- a/app/src/components/composer-editor/conversion.tsx +++ b/app/src/components/composer-editor/conversion.tsx @@ -253,13 +253,14 @@ on the result. */ }); } else { if (node.nodes && node.nodes instanceof Array) { + // @ts-ignore node.nodes = node.nodes.map(applyMark); } } return node; }; - + // @ts-ignore return mark.nodes.reduce(function(nodes, node) { const ret = applyMark(node); if (Array.isArray(ret)) return nodes.concat(ret); @@ -439,11 +440,13 @@ export function convertToPlainText(value: Value) { text = text.replace(/\n\n\n+/g, '\n\n').trim(); return text; } - if (isQuoteNode(node)) { + if (isQuoteNode(node) && node.object === 'block') { const content = node.nodes.map(serializeNode).join('\n'); return deepenPlaintextQuote(content); } - if (node.object === 'document' || (node.object === 'block' && Block.isBlockList(node.nodes))) { + if (node.object === 'document') { + return node.nodes.map(serializeNode).join('\n'); + } else if (node.object === 'block' && Block.isBlockList(node.nodes)) { return node.nodes.map(serializeNode).join('\n'); } else { return node.text; diff --git a/app/src/components/composer-editor/emoji-plugins.tsx b/app/src/components/composer-editor/emoji-plugins.tsx index da9eb70b8..09d11ed4f 100644 --- a/app/src/components/composer-editor/emoji-plugins.tsx +++ b/app/src/components/composer-editor/emoji-plugins.tsx @@ -213,7 +213,7 @@ export function updateEmojiMark(editor: Editor, existing, { typed, suggestions, editor.moveToFocus(); } -function onKeyDown(event, editor: Editor, next: () => void) { +function onKeyDown(event: React.KeyboardEvent, editor: Editor, next: () => void) { if ([' ', 'Return', 'Enter'].includes(event.key)) { const emoji = editor.value.marks.find(i => i.type === EMOJI_TYPING_TYPE); if (!emoji) return next(); @@ -239,7 +239,7 @@ function onKeyDown(event, editor: Editor, next: () => void) { return next(); } -function onKeyUp(event, editor: Editor, next: () => void) { +function onKeyUp(event: React.KeyboardEvent, editor: Editor, next: () => void) { const emoji = editor.value.marks.find(i => i.type === EMOJI_TYPING_TYPE); if (!emoji) { const { offset, key } = editor.value.selection.focus; diff --git a/app/src/components/composer-editor/link-plugins.tsx b/app/src/components/composer-editor/link-plugins.tsx index efe8aaa19..ed29ef9cf 100644 --- a/app/src/components/composer-editor/link-plugins.tsx +++ b/app/src/components/composer-editor/link-plugins.tsx @@ -106,7 +106,7 @@ const BaseLinkPlugin: ComposerEditorPlugin = { }), ], onPaste, - onKeyDown: function onKeyDown(event, editor: Editor, next: () => void) { + onKeyDown: function onKeyDown(event: React.KeyboardEvent, editor: Editor, next: () => void) { // ensure space and enter always terminate links if (!['Space', 'Enter', ' ', 'Return'].includes(event.key)) { return next(); @@ -117,14 +117,16 @@ const BaseLinkPlugin: ComposerEditorPlugin = { } return next(); }, - renderMark, + renderMark: ({ mark, children, targetIsHTML }, editor, next) => { + return renderMark({ mark, children, targetIsHTML }, editor, next); + }, rules, - commands: { + appCommands: { 'contenteditable:insert-link': (event, editor) => { // want to see a hack? here you go! // 1: find our container and then find the link toolbar icon - this approach // ensures we get the link button in the /current/ composer. - const linkButton = event.target + const linkButton = (event.target as HTMLElement) .closest('.RichEditor-root') .querySelector('.fa.fa-link') .closest('button'); diff --git a/app/src/components/composer-editor/template-plugins.tsx b/app/src/components/composer-editor/template-plugins.tsx index d716cb91d..466ffbf9d 100644 --- a/app/src/components/composer-editor/template-plugins.tsx +++ b/app/src/components/composer-editor/template-plugins.tsx @@ -64,7 +64,7 @@ const rules = [ }, ]; -function onKeyDown(event, editor: Editor, next: () => void) { +function onKeyDown(event: React.KeyboardEvent, editor: Editor, next: () => void) { // If the user has a template variable selected and types a character, // delete the template variable. By default you just can't type. if ( diff --git a/app/src/components/composer-editor/types.ts b/app/src/components/composer-editor/types.ts index 0062f8478..4249bc3a4 100644 --- a/app/src/components/composer-editor/types.ts +++ b/app/src/components/composer-editor/types.ts @@ -1,4 +1,5 @@ import { Node, Editor, Value, Mark, Block, Inline } from 'slate'; +import { Plugin, RenderMarkProps } from 'slate-react'; export interface Rule { deserialize?: ( @@ -8,11 +9,11 @@ export interface Rule { serialize?: (obj: any, children: string) => React.ReactNode | void; } -export interface ComposerEditorPlugin { +export interface ComposerEditorPlugin extends Omit { renderMark?: ( - { mark, children, targetIsHTML }: { mark: Mark; children: any; targetIsHTML?: boolean }, - editor: Editor | null, - next: () => void + { mark, children, targetIsHTML }: RenderMarkProps & { targetIsHTML?: boolean }, + editor?: Editor, + next?: () => void ) => void | string | JSX.Element; renderNode?: ( @@ -27,15 +28,11 @@ export interface ComposerEditorPlugin { toolbarComponents?: React.ComponentType[]; toolbarSectionClass?: string; - commands?: { [command: string]: (event: CustomEvent, editor: Editor) => Editor }; + appCommands?: { [command: string]: (event: CustomEvent, editor: Editor) => Editor }; onChange?(editor: Editor, next: () => void); - onPaste?: (event, editor: Editor, next: () => void) => void; - onKeyDown?: (event, editor: Editor, next: () => void) => void; - onKeyUp?: (event, editor: Editor, next: () => void) => void; - onClick?: (event, editor: Editor, next: () => void) => void; - onCompositionStart?: (event, editor: Editor, next: () => void) => void; - onCompositionEnd?: (event, editor: Editor, next: () => void) => void; + onKeyDown?: (event: React.KeyboardEvent, editor: Editor, next: () => void) => void; + onKeyUp?: (event: React.KeyboardEvent, editor: Editor, next: () => void) => void; } export interface ComposerEditorPluginToolbarComponentProps { diff --git a/app/src/components/date-picker.tsx b/app/src/components/date-picker.tsx index 3915dbe83..6874a2796 100644 --- a/app/src/components/date-picker.tsx +++ b/app/src/components/date-picker.tsx @@ -13,7 +13,7 @@ type DatePickerState = { focused: boolean; }; -export default class DatePicker extends React.Component { +export class DatePicker extends React.Component { static displayName = 'DatePicker'; static contextTypes = { diff --git a/app/src/components/decorators/listens-to-observable.tsx b/app/src/components/decorators/listens-to-observable.tsx index cecce84d8..b6e28f041 100644 --- a/app/src/components/decorators/listens-to-observable.tsx +++ b/app/src/components/decorators/listens-to-observable.tsx @@ -1,11 +1,28 @@ import React from 'react'; -function ListensToObservable(ComposedComponent, { getObservable, getStateFromObservable }) { +function ListensToObservable( + ComposedComponent: typeof React.Component & { + displayName?: string; + containerRequired?: boolean; + containerStyles?: object; + }, + { + getObservable, + getStateFromObservable, + }: { + getObservable: (props: T) => Rx.Observable; + getStateFromObservable: (data: U, { props: T }) => V; + } +) { return class extends ComposedComponent { static displayName = ComposedComponent.displayName; static containerRequired = ComposedComponent.containerRequired; static containerStyles = ComposedComponent.containerStyles; + disposable: any; + unmounted: boolean; + observable: Rx.Observable; + constructor(props) { super(props); this.state = getStateFromObservable(null, { props }); diff --git a/app/src/components/drop-zone.tsx b/app/src/components/drop-zone.tsx index cd1b8fa33..88232d8fd 100644 --- a/app/src/components/drop-zone.tsx +++ b/app/src/components/drop-zone.tsx @@ -5,7 +5,7 @@ import _ from 'underscore'; interface DropZoneProps { id?: string; className?: string; - style?: any; + style?: React.CSSProperties; onClick?: (e: React.MouseEvent) => void; onDoubleClick?: (e: React.MouseEvent) => void; shouldAcceptDrop: (e: React.DragEvent) => boolean; diff --git a/app/src/components/empty-list-state.tsx b/app/src/components/empty-list-state.tsx index 814d0ac02..b15e4deb5 100644 --- a/app/src/components/empty-list-state.tsx +++ b/app/src/components/empty-list-state.tsx @@ -69,7 +69,7 @@ class EmptyInboxState extends React.Component<{}, { width: number; height: numbe window.requestAnimationFrame(() => this.setState({ width: entries[0].contentRect.width, - height: entries[0].contentRect.width, + height: entries[0].contentRect.height, }) ) ); diff --git a/app/src/components/flexbox.tsx b/app/src/components/flexbox.tsx index d9c9e49ef..fca595aab 100644 --- a/app/src/components/flexbox.tsx +++ b/app/src/components/flexbox.tsx @@ -4,7 +4,7 @@ import React from 'react'; type FlexboxProps = { direction?: string; inline?: boolean; - style?: object; + style?: React.CSSProperties; height?: string; }; /* diff --git a/app/src/components/injected-component.tsx b/app/src/components/injected-component.tsx index 93678cd26..2ee2cb45d 100644 --- a/app/src/components/injected-component.tsx +++ b/app/src/components/injected-component.tsx @@ -10,7 +10,7 @@ type InjectedComponentProps = { className?: string; exposedProps?: object; fallback?: (...args: any[]) => any; - style?: object; + style?: React.CSSProperties; requiredMethods?: string[]; onComponentDidChange?: (...args: any[]) => any; }; diff --git a/app/src/components/list-data-source.ts b/app/src/components/list-data-source.ts index 9b1c3e284..4d3147ff9 100644 --- a/app/src/components/list-data-source.ts +++ b/app/src/components/list-data-source.ts @@ -1,9 +1,9 @@ /* eslint no-unused-vars: 0 */ import { EventEmitter } from 'events'; -import ListSelection from './list-selection'; +import { ListSelection } from './list-selection'; import { Model } from '../flux/models/model'; -export default class ListDataSource { +export class ListDataSource { // getters prevent use before decl static get Empty() { diff --git a/app/src/components/list-selection.ts b/app/src/components/list-selection.ts index ef8ec6636..829cb65c1 100644 --- a/app/src/components/list-selection.ts +++ b/app/src/components/list-selection.ts @@ -2,9 +2,9 @@ import _ from 'underscore'; import { Model } from '../flux/models/model'; import DatabaseStore from '../flux/stores/database-store'; -import ListDataSource from './list-data-source'; +import { ListDataSource } from './list-data-source'; -export default class ListSelection { +export class ListSelection { _caches: { ids?: string[]; } = {}; diff --git a/app/src/components/list-tabular-item.tsx b/app/src/components/list-tabular-item.tsx index 3fe61330c..50de38076 100644 --- a/app/src/components/list-tabular-item.tsx +++ b/app/src/components/list-tabular-item.tsx @@ -18,7 +18,7 @@ type ListTabularItemProps = { onDoubleClick?: (...args: any[]) => any; }; -export default class ListTabularItem extends React.Component { +export class ListTabularItem extends React.Component { static displayName = 'ListTabularItem'; static propTypes = { metrics: PropTypes.object, @@ -86,8 +86,7 @@ export default class ListTabularItem extends React.Component { if (names[column.name]) { console.warn( - `ListTabular: Columns do not have distinct names, will cause React error! \`${column.name - }\` twice.` + `ListTabular: Columns do not have distinct names, will cause React error! \`${column.name}\` twice.` ); } names[column.name] = true; diff --git a/app/src/components/list-tabular.tsx b/app/src/components/list-tabular.tsx index ccce981d3..f06d3785a 100644 --- a/app/src/components/list-tabular.tsx +++ b/app/src/components/list-tabular.tsx @@ -7,9 +7,11 @@ import PropTypes from 'prop-types'; import { ScrollRegion, ScrollRegionProps } from './scroll-region'; import { Spinner } from './spinner'; -import ListDataSource from './list-data-source'; -import ListSelection from './list-selection'; -import ListTabularItem from './list-tabular-item'; +export * from './list-data-source'; + +import { ListDataSource } from './list-data-source'; +import { ListSelection } from './list-selection'; +import { ListTabularItem } from './list-tabular-item'; export class ListTabularColumn { name: string; @@ -135,8 +137,6 @@ interface ListTabularState { empty: any; } -export type ListDataSource = ListDataSource; - export class ListTabular extends Component { static displayName = 'ListTabular'; diff --git a/app/src/components/menu.tsx b/app/src/components/menu.tsx index be664367a..6ee9d8b24 100644 --- a/app/src/components/menu.tsx +++ b/app/src/components/menu.tsx @@ -17,7 +17,7 @@ export interface MenuNameEmailContentProps { email?: string; } -export interface MenuProps extends HTMLProps { +export interface MenuProps extends HTMLProps { className?: string; footerComponents?: React.ReactNode; headerComponents?: React.ReactNode; diff --git a/app/src/components/mini-month-view.tsx b/app/src/components/mini-month-view.tsx new file mode 100644 index 000000000..1ff2bf941 --- /dev/null +++ b/app/src/components/mini-month-view.tsx @@ -0,0 +1,120 @@ +import _ from 'underscore'; +import React from 'react'; +import moment, { Moment } from 'moment'; +import classnames from 'classnames'; + +interface MiniMonthViewProps { + value: Moment; + onChange: (moment: Moment) => void; +} + +interface MiniMonthViewState { + shownYear: number; + shownMonth: number; +} + +export class MiniMonthView extends React.Component { + static displayName = 'MiniMonthView'; + + today = moment(); + + constructor(props) { + super(props); + this.state = this._stateFromProps(props); + } + + componentWillReceiveProps(newProps) { + this.setState(this._stateFromProps(newProps)); + } + + _stateFromProps(props: MiniMonthViewProps) { + return { + shownYear: props.value.year(), + shownMonth: props.value.month(), + }; + } + + _isSameDay(m1: Moment, m2: Moment) { + return m1.dayOfYear() === m2.dayOfYear() && m1.year() === m2.year(); + } + + _renderDays(month: Moment) { + const curMonthNumber = month.month(); + + const dayIter = month.clone().date(1); + const startWeek = dayIter.week(); + const endWeek = moment(dayIter) + .date(dayIter.daysInMonth()) + .week(); + + const weekEls = []; + for (let week = startWeek; week <= endWeek; week++) { + dayIter.week(week); // Locale aware! + const dayEls = []; + for (let weekday = 0; weekday < 7; weekday++) { + dayIter.weekday(weekday); // Locale aware! + const dayStr = dayIter.format('D'); + const className = classnames({ + day: true, + today: this._isSameDay(dayIter, this.today), + 'cur-day': this._isSameDay(dayIter, this.props.value), + 'cur-month': dayIter.month() === curMonthNumber, + }); + dayEls.push( +
+ {dayStr} +
+ ); + } + weekEls.push( +
+ {dayEls} +
+ ); + } + return ( +
{ + if (event.target instanceof HTMLElement && event.target.dataset.unix) { + this.props.onChange(moment(Number(event.target.dataset.unix))); + } + }} + > + {weekEls} +
+ ); + } + + render() { + const weekdayGen = moment(this.state.shownYear); + const month = moment([this.state.shownYear, this.state.shownMonth]); + + const onChangeMonth = (delta: number) => { + const next = month.clone().add(delta, 'months'); + this.setState({ shownYear: next.year(), shownMonth: next.month() }); + }; + + return ( +
+
+
onChangeMonth(-1)}> + ‹ +
+ {month.format('MMMM YYYY')} +
onChangeMonth(1)}> + › +
+
+
+ {[0, 1, 2, 3, 4, 5, 6].map(i => ( + + {weekdayGen.weekday(i).format('dd')} + + ))} +
+ {this._renderDays(month)} +
+ ); + } +} diff --git a/app/src/components/multiselect-list.tsx b/app/src/components/multiselect-list.tsx index 94143133c..b14e6f1bf 100644 --- a/app/src/components/multiselect-list.tsx +++ b/app/src/components/multiselect-list.tsx @@ -9,7 +9,7 @@ import { KeyCommandsRegion } from 'mailspring-component-kit'; import MultiselectListInteractionHandler from './multiselect-list-interaction-handler'; import MultiselectSplitInteractionHandler from './multiselect-split-interaction-handler'; -import ListDataSource from './list-data-source'; +import { ListDataSource } from './list-data-source'; export interface MultiselectListProps extends ListTabularProps { focusedId?: string; diff --git a/app/src/components/resizable-region.tsx b/app/src/components/resizable-region.tsx index 23bce6cd9..db9ab8ee0 100644 --- a/app/src/components/resizable-region.tsx +++ b/app/src/components/resizable-region.tsx @@ -73,7 +73,7 @@ type ResizableRegionProps = { initialHeight?: number; minHeight?: number; maxHeight?: number; - style?: object; + style?: React.CSSProperties; }; type ResizableRegionState = { diff --git a/app/src/components/retina-img.tsx b/app/src/components/retina-img.tsx index 91239db0c..79ebf247d 100644 --- a/app/src/components/retina-img.tsx +++ b/app/src/components/retina-img.tsx @@ -84,7 +84,7 @@ type RetinaImgProps = { name?: string; url?: string; className?: string; - style?: object; + style?: React.CSSProperties; fallback?: string; selected?: boolean; active?: boolean; diff --git a/app/src/components/scroll-region.tsx b/app/src/components/scroll-region.tsx index ed6bd4549..e7f7b75d4 100644 --- a/app/src/components/scroll-region.tsx +++ b/app/src/components/scroll-region.tsx @@ -17,22 +17,33 @@ interface TicksProvider { listen: (callback: (Ticks) => void) => () => void; } -type ScrollbarProps = { +interface ScrollbarProps { scrollTooltipComponent?: React.ComponentType; scrollbarTickProvider?: TicksProvider; - getScrollRegion?: (...args: any[]) => any; -}; + getScrollRegion?: () => ScrollRegion; +} -type ScrollbarState = { +interface ScrollSharedState { totalHeight: number; - trackHeight: number; viewportHeight: number; viewportScrollTop: number; dragging: boolean; scrolling: boolean; - scrollbarTicks?: Ticks; +} + +const InitialSharedState: ScrollSharedState = { + totalHeight: 0, + viewportHeight: 0, + viewportScrollTop: 0, + dragging: false, + scrolling: false, }; +interface ScrollbarState extends ScrollSharedState { + trackHeight: number; + scrollbarTicks?: Ticks; +} + class Scrollbar extends React.Component { static displayName = 'Scrollbar'; static propTypes = { @@ -49,24 +60,16 @@ class Scrollbar extends React.Component { getScrollRegion: PropTypes.func, }; - _heightObserver: ResizeObserver; + _heightObserver: ResizeObserver = null; _tickUnsub?: () => void; _trackOffset: number; _mouseOffsetWithinHandle: number; - constructor(props) { - super(props); - this._heightObserver = null; - this.state = { - totalHeight: 0, - trackHeight: 0, - viewportHeight: 0, - viewportScrollTop: 0, - dragging: false, - scrolling: false, - scrollbarTicks: [], - }; - } + state = { + ...InitialSharedState, + trackHeight: 0, + scrollbarTicks: [], + }; componentDidMount() { const trackEl = ReactDOM.findDOMNode(this.refs.track) as HTMLElement; @@ -220,21 +223,18 @@ class Scrollbar extends React.Component { } export interface ScrollRegionProps { + ref?: React.Ref | string; onScroll?: (...args: any[]) => any; onScrollEnd?: (...args: any[]) => any; + onViewportResize?: (size: { width: number; height: number }) => void; + onContentResize?: (size: { width: number; height: number }) => void; className?: string; scrollTooltipComponent?: React.ComponentType; scrollbarTickProvider?: TicksProvider; - getScrollbar?: (...args: any[]) => any; + scrollbarRef?: React.RefObject; } -type ScrollRegionState = { - totalHeight: number; - viewportHeight: number; - viewportScrollTop: number; - scrolling: boolean; - dragging: boolean; -}; +interface ScrollRegionState extends ScrollSharedState {} interface ScrollToOptions { position?: string; @@ -261,7 +261,7 @@ export enum ScrollPosition { The ScrollRegion component attaches a custom scrollbar. */ export class ScrollRegion extends React.Component< - ScrollRegionProps & React.HTMLProps, + ScrollRegionProps & React.HTMLProps, ScrollRegionState > { static displayName = 'ScrollRegion'; @@ -269,17 +269,23 @@ export class ScrollRegion extends React.Component< static propTypes = { onScroll: PropTypes.func, onScrollEnd: PropTypes.func, + onContentResize: PropTypes.func, + onViewportResize: PropTypes.func, className: PropTypes.string, scrollTooltipComponent: PropTypes.func, scrollbarTickProvider: PropTypes.object, children: PropTypes.oneOfType([PropTypes.element, PropTypes.array]), - getScrollbar: PropTypes.func, + scrollbarRef: PropTypes.object, }; static ScrollPosition = ScrollPosition; // Concept from https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/c/tdef/UITableViewScrollPosition static Scrollbar = Scrollbar; + _viewportRef = React.createRef(); + _innerRef = React.createRef(); + _ownScrollbarRef = React.createRef(); + _mounted = false; _scrollToTaskId = 0; _scrollbarComponent = null; @@ -288,30 +294,35 @@ export class ScrollRegion extends React.Component< _onScrollEnd: () => void; state = { - totalHeight: 0, - viewportHeight: 0, - viewportScrollTop: 0, - dragging: false, - scrolling: false, + ...InitialSharedState, }; - get scrollTop() { - return (ReactDOM.findDOMNode(this.refs.content) as HTMLElement).scrollTop; + public get viewportEl() { + return this._viewportRef.current; } - set scrollTop(val) { - (ReactDOM.findDOMNode(this.refs.content) as HTMLElement).scrollTop = val; + public get contentEl() { + return this._innerRef.current; + } + + public get scrollTop() { + return this._viewportRef.current ? this._viewportRef.current.scrollTop : 0; + } + + public set scrollTop(val) { + this._viewportRef.current.scrollTop = val; } componentDidMount() { this._mounted = true; - const viewportEl = ReactDOM.findDOMNode(this.refs.content) as HTMLElement; - const innerWrapperEl = ReactDOM.findDOMNode(this.refs.inner) as HTMLElement; + const viewportEl = this._viewportRef.current; + const innerWrapperEl = this._innerRef.current; this._viewportHeightObserver = new window.ResizeObserver(entries => { if (entries[0] && entries[0].contentRect.height !== this.state.viewportHeight) { this._setSharedState({ viewportHeight: entries[0].contentRect.height }); + this.props.onViewportResize && this.props.onViewportResize(entries[0].contentRect); } }); this._totalHeightObserver = new window.ResizeObserver(entries => { @@ -319,22 +330,27 @@ export class ScrollRegion extends React.Component< // and the contentRect.height is the inner padded size, not the bounding box size. if (entries[0] && entries[0].contentRect.height !== this.state.totalHeight) { this._setSharedState({ totalHeight: entries[0].target.clientHeight }); + this.props.onContentResize && this.props.onContentResize(entries[0].contentRect); } }); this._totalHeightObserver.observe(innerWrapperEl); this._viewportHeightObserver.observe(viewportEl); - this._setSharedState({ + const dims = { viewportScrollTop: 0, viewportHeight: viewportEl.clientHeight, totalHeight: innerWrapperEl.clientHeight, - }); - } + }; + this._setSharedState(dims); - componentWillReceiveProps(props) { - if (this.shouldInvalidateScrollbarComponent(props)) { - this._scrollbarComponent = null; + // If we are using a scrollbar attached elsewhere and handed to us through the ref, there's + // a good chance it was mounted at the same time we were and the `ref` is not valid yet. + // Wait a tick and send it the sizing so it sizes the handle correctly. + if (this.props.scrollbarRef && !this.props.scrollbarRef.current) { + window.requestAnimationFrame(() => { + this._mounted && this._setSharedState(dims); + }); } } @@ -348,7 +364,7 @@ export class ScrollRegion extends React.Component< if (newProps.scrollTooltipComponent !== this.props.scrollTooltipComponent) { return true; } - if (newProps.getScrollbar !== this.props.getScrollbar) { + if (newProps.scrollbarRef !== this.props.scrollbarRef) { return true; } return false; @@ -363,11 +379,11 @@ export class ScrollRegion extends React.Component< scrolling: this.state.scrolling, }); - if (!this.props.getScrollbar) { + if (!this.props.scrollbarRef) { if (this._scrollbarComponent == null) { this._scrollbarComponent = ( {this._scrollbarComponent} -
-
+
+
{this.props.children}
@@ -428,7 +444,7 @@ export class ScrollRegion extends React.Component< _scroll({ position, settle, done }, clientRectProviderCallback) { let settleFn; - const contentNode = ReactDOM.findDOMNode(this.refs.content) as HTMLElement; + const viewportNode = this._viewportRef.current; if (position == null) { position = ScrollRegion.ScrollPosition.Visible; } @@ -444,24 +460,24 @@ export class ScrollRegion extends React.Component< settleFn(() => { // If another scroll call has been made since ours, don't do anything. - if (this._scrollToTaskId !== taskId || !contentNode) { + if (this._scrollToTaskId !== taskId || !viewportNode) { return typeof done === 'function' ? done(false) : undefined; } - const contentClientRect = contentNode.getBoundingClientRect(); + const contentClientRect = viewportNode.getBoundingClientRect(); const rect = _.clone(clientRectProviderCallback()); if (!rect || !contentClientRect) return; // For sanity's sake, convert the client rectangle we get into a rect // relative to the contentRect of our scroll region. - rect.top = rect.top - contentClientRect.top + contentNode.scrollTop; - rect.bottom = rect.bottom - contentClientRect.top + contentNode.scrollTop; + rect.top = rect.top - contentClientRect.top + viewportNode.scrollTop; + rect.bottom = rect.bottom - contentClientRect.top + viewportNode.scrollTop; // Also give ourselves a representation of the visible region, in the same // coordinate space as `rect` const contentVisibleRect = _.clone(contentClientRect) as any; - contentVisibleRect.top += contentNode.scrollTop; - contentVisibleRect.bottom += contentNode.scrollTop; + contentVisibleRect.top += viewportNode.scrollTop; + contentVisibleRect.bottom += viewportNode.scrollTop; if (position === ScrollRegion.ScrollPosition.Top) { this.scrollTop = rect.top; @@ -475,7 +491,7 @@ export class ScrollRegion extends React.Component< } } else if (position === ScrollRegion.ScrollPosition.Visible) { const distanceBelowBottom = - rect.top + rect.height - (contentClientRect.height + contentNode.scrollTop); + rect.top + rect.height - (contentClientRect.height + viewportNode.scrollTop); const distanceAboveTop = this.scrollTop - rect.top; if (distanceBelowBottom >= 0) { this.scrollTop += distanceBelowBottom; @@ -491,14 +507,13 @@ export class ScrollRegion extends React.Component< } _settleHeight = callback => { - const contentNode = ReactDOM.findDOMNode(this.refs.content) as HTMLElement; + const viewportNode = this._viewportRef.current; let lastContentHeight = -1; - // eslint-disable-next-line no-var - var scrollIfSettled = () => { + const scrollIfSettled = () => { if (!this._mounted) { return; } - const contentRect = contentNode.getBoundingClientRect(); + const contentRect = viewportNode.getBoundingClientRect(); if (contentRect.height !== lastContentHeight) { lastContentHeight = contentRect.height; } else { @@ -509,17 +524,17 @@ export class ScrollRegion extends React.Component< scrollIfSettled(); }; - _setSharedState(state) { - const scrollbar = this.props.getScrollbar ? this.props.getScrollbar() : this.refs.scrollbar; - if (scrollbar) scrollbar.setState(state); - this.setState(state); + _setSharedState(state: Partial) { + const scrollbar = (this.props.scrollbarRef || this._ownScrollbarRef).current; + if (scrollbar) scrollbar.setState(state as any); + this.setState(state as any); } _onScroll = event => { // onScroll events propogate, which is a bit strange. We could actually be // receiving a scroll event for a textarea inside the scroll region. // See Preferences > Signatures > textarea - if (event.target !== ReactDOM.findDOMNode(this.refs.content)) { + if (event.target !== this._viewportRef.current) { return; } diff --git a/app/src/components/spinner.tsx b/app/src/components/spinner.tsx index c34a0cd5d..a66a6eef1 100644 --- a/app/src/components/spinner.tsx +++ b/app/src/components/spinner.tsx @@ -6,7 +6,7 @@ import classNames from 'classnames'; type SpinnerProps = { visible?: boolean; withCover?: boolean; - style?: object; + style?: React.CSSProperties; }; type SpinnerState = { hidden: boolean; diff --git a/app/src/components/tab-group-region.tsx b/app/src/components/tab-group-region.tsx index ca7dde47d..4fe7048d7 100644 --- a/app/src/components/tab-group-region.tsx +++ b/app/src/components/tab-group-region.tsx @@ -50,7 +50,7 @@ export class TabGroupRegion extends React.Component= 0) { array.splice(index, 1); } return array; } + export function deepClone(object) { if (_.isArray(object)) { return object.map(function(value) { - return plus.deepClone(value); + return deepClone(value); }); } else if (_.isObject(object) && !_.isFunction(object)) { return _.mapObject(object, function(value) { - return plus.deepClone(value); + return deepClone(value); }); } else { return object; } } -export function deepExtend(target) { - var i, key, object, result, _i, _len, _ref; - result = target; - i = 0; - while (++i < arguments.length) { - object = arguments[i]; + +export function deepExtend(...args) { + let result = args[0]; + let i = 0; + while (++i < args.length) { + const object = args[i]; if (isPlainObject(result) && isPlainObject(object)) { - _ref = Object.keys(object); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - key = _ref[_i]; - result[key] = plus.deepExtend(result[key], object[key]); + const _ref = Object.keys(object); + for (let _i = 0; _i < _ref.length; _i++) { + const key = _ref[_i]; + result[key] = deepExtend(result[key], object[key]); } } else { - result = plus.deepClone(object); + result = deepClone(object); } } return result; } + export function valueForKeyPath(object, keyPath) { - var key, keys, _i, _len; - keys = splitKeyPath(keyPath); - for (_i = 0, _len = keys.length; _i < _len; _i++) { - key = keys[_i]; + const keys = splitKeyPath(keyPath); + for (let _i = 0; _i < keys.length; _i++) { + const key = keys[_i]; object = object[key]; if (object == null) { return; @@ -74,11 +75,11 @@ export function valueForKeyPath(object, keyPath) { } return object; } + export function setValueForKeyPath(object, keyPath, value) { - var key, keys; - keys = splitKeyPath(keyPath); + const keys = splitKeyPath(keyPath); while (keys.length > 1) { - key = keys.shift(); + const key = keys.shift(); if (object[key] == null) { object[key] = {}; } diff --git a/app/src/config.ts b/app/src/config.ts index 0900d87b8..960329318 100644 --- a/app/src/config.ts +++ b/app/src/config.ts @@ -925,10 +925,11 @@ Config.addSchemaEnforcers({ }, }); -var isPlainObject = value => - _.isObject(value) && !_.isArray(value) && !_.isFunction(value) && !_.isString(value); +function isPlainObject(value) { + return _.isObject(value) && !_.isArray(value) && !_.isFunction(value) && !_.isString(value); +} -var splitKeyPath = function(keyPath) { +function splitKeyPath(keyPath) { if (keyPath == null) { return []; } @@ -943,9 +944,9 @@ var splitKeyPath = function(keyPath) { } keyPathArray.push(keyPath.substr(startIndex, keyPath.length)); return keyPathArray; -}; +} -var withoutEmptyObjects = function(object) { +function withoutEmptyObjects(object) { let resultObject = undefined; if (isPlainObject(object)) { for (const key in object) { @@ -962,4 +963,4 @@ var withoutEmptyObjects = function(object) { resultObject = object; } return resultObject; -}; +} diff --git a/app/src/date-utils.ts b/app/src/date-utils.ts index adb3e6872..04ddb03af 100644 --- a/app/src/date-utils.ts +++ b/app/src/date-utils.ts @@ -1,4 +1,4 @@ -import moment from 'moment-timezone'; +import moment, { Moment } from 'moment-timezone'; // Init locale for moment moment.locale(navigator.language); @@ -268,7 +268,13 @@ const DateUtils = { getChronoPast, - parseDateString(dateLikeString) { + parseDateString( + dateLikeString: string + ): { + leftoverText: string; + start: Moment; + end: Moment; + } { const parsed = getChrono().parse(dateLikeString); const gotTime = { start: false, end: false }; const gotDay = { start: false, end: false }; diff --git a/app/src/default-client-helper.ts b/app/src/default-client-helper.ts index a36b8c024..8fb3d456e 100644 --- a/app/src/default-client-helper.ts +++ b/app/src/default-client-helper.ts @@ -1,16 +1,23 @@ -import { exec } from 'child_process'; import fs from 'fs'; +import { exec } from 'child_process'; import { remote, shell } from 'electron'; import { localized } from './intl'; const bundleIdentifier = 'com.mailspring.mailspring'; -export class DefaultClientHelperWindows { +interface DCH { + available(): boolean; + isRegisteredForURLScheme(scheme: string, callback: (registered: boolean | Error) => void): void; + resetURLScheme(scheme, callback: (error?: Error) => {}): void; + registerForURLScheme(scheme: string, callback: (error?: Error) => {}): void; +} + +export class DefaultClientHelperWindows implements DCH { available() { return true; } - isRegisteredForURLScheme(scheme, callback) { + isRegisteredForURLScheme(scheme: string, callback: (registered: boolean | Error) => void) { if (!callback) { throw new Error('isRegisteredForURLScheme is async, provide a callback'); } @@ -34,25 +41,24 @@ export class DefaultClientHelperWindows { ); } - resetURLScheme() { - remote.dialog.showMessageBox( - { - type: 'info', - buttons: [localized('Learn More')], - message: localized('Visit Windows Settings to change your default mail client'), - detail: localized( - "You'll find Mailspring, along with other options, listed in Default Apps > Mail." - ), - }, - () => { - shell.openExternal( - 'http://support.getmailspring.com/hc/en-us/articles/115001881412-Choose-Mailspring-as-the-default-mail-client-on-Windows' - ); - } - ); + async resetURLScheme() { + const { response } = await remote.dialog.showMessageBox({ + type: 'info', + buttons: [localized('Learn More')], + message: localized('Visit Windows Settings to change your default mail client'), + detail: localized( + "You'll find Mailspring, along with other options, listed in Default Apps > Mail." + ), + }); + + if (response === 0) { + shell.openExternal( + 'http://support.getmailspring.com/hc/en-us/articles/115001881412-Choose-Mailspring-as-the-default-mail-client-on-Windows' + ); + } } - registerForURLScheme(scheme, callback = (error?: Error, result?: null) => {}) { + registerForURLScheme(scheme: string, callback = (error?: Error) => {}) { // Ensure that our registry entires are present const WindowsUpdater = remote.require('./windows-updater'); WindowsUpdater.createRegistryEntries( @@ -60,45 +66,45 @@ export class DefaultClientHelperWindows { allowEscalation: true, registerDefaultIfPossible: true, }, - (err, didMakeDefault) => { + async (err, didMakeDefault) => { if (err) { - remote.dialog.showMessageBox({ + await remote.dialog.showMessageBox({ type: 'error', buttons: [localized('OK')], message: localized('An error has occurred'), detail: err.message, }); + return; } + if (!didMakeDefault) { - remote.dialog.showMessageBox( - { - type: 'info', - buttons: [localized('Learn More')], - defaultId: 1, - message: localized( - 'Visit Windows Settings to finish making Mailspring your mail client' - ), - detail: localized("Click 'Learn More' to view instructions in our knowledge base."), - }, - () => { - shell.openExternal( - 'http://support.getmailspring.com/hc/en-us/articles/115001881412-Choose-Mailspring-as-the-default-mail-client-on-Windows' - ); - } - ); + const { response } = await remote.dialog.showMessageBox({ + type: 'info', + buttons: [localized('Learn More')], + defaultId: 1, + message: localized( + 'Visit Windows Settings to finish making Mailspring your mail client' + ), + detail: localized("Click 'Learn More' to view instructions in our knowledge base."), + }); + if (response === 0) { + shell.openExternal( + 'http://support.getmailspring.com/hc/en-us/articles/115001881412-Choose-Mailspring-as-the-default-mail-client-on-Windows' + ); + } } - callback(null, null); + callback(null); } ); } } -export class DefaultClientHelperLinux { +export class DefaultClientHelperLinux implements DCH { available() { return !process.env.SNAP; } - isRegisteredForURLScheme(scheme, callback) { + isRegisteredForURLScheme(scheme: string, callback: (registered: boolean | Error) => void) { if (!callback) { throw new Error('isRegisteredForURLScheme is async, provide a callback'); } @@ -107,26 +113,26 @@ export class DefaultClientHelperLinux { ); } - resetURLScheme(scheme, callback = (error?: Error, result?: null) => {}) { + resetURLScheme(scheme: string, callback = (error?: Error) => {}) { exec(`xdg-mime default thunderbird.desktop x-scheme-handler/${scheme}`, err => - err ? callback(err) : callback(null, null) + err ? callback(err) : callback(null) ); } - registerForURLScheme(scheme, callback = (error?: Error, result?: null) => {}) { + registerForURLScheme(scheme: string, callback = (error?: Error) => {}) { exec(`xdg-mime default Mailspring.desktop x-scheme-handler/${scheme}`, err => - err ? callback(err) : callback(null, null) + err ? callback(err) : callback(null) ); } } -export class DefaultClientHelperMac { +export class DefaultClientHelperMac implements DCH { secure = false; available() { return true; } - getLaunchServicesPlistPath(callback) { + getLaunchServicesPlistPath(callback: (plist: string) => void) { const secure = `${process.env.HOME}/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist`; const insecure = `${process.env.HOME}/Library/Preferences/com.apple.LaunchServices.plist`; @@ -189,7 +195,7 @@ export class DefaultClientHelperMac { }); } - isRegisteredForURLScheme(scheme, callback) { + isRegisteredForURLScheme(scheme: string, callback: (registered: boolean) => void) { if (!callback) { throw new Error('isRegisteredForURLScheme is async, provide a callback'); } @@ -204,7 +210,7 @@ export class DefaultClientHelperMac { }); } - resetURLScheme(scheme, callback) { + resetURLScheme(scheme: string, callback = (error?: Error) => {}) { this.readDefaults(defaults => { // Remove anything already registered for the scheme for (let ii = defaults.length - 1; ii >= 0; ii--) { @@ -216,7 +222,7 @@ export class DefaultClientHelperMac { }); } - registerForURLScheme(scheme, callback) { + registerForURLScheme(scheme: string, callback = (error?: Error) => {}) { this.readDefaults(defaults => { // Remove anything already registered for the scheme for (let ii = defaults.length - 1; ii >= 0; ii--) { @@ -236,14 +242,16 @@ export class DefaultClientHelperMac { } } -let Default: any = null; +let Default: + | typeof DefaultClientHelperMac + | typeof DefaultClientHelperLinux + | typeof DefaultClientHelperWindows = null; + if (process.platform === 'darwin') { Default = DefaultClientHelperMac; -} else if (process.platform === 'linux') { - Default = DefaultClientHelperLinux; } else if (process.platform === 'win32') { Default = DefaultClientHelperWindows; } else { - Default = {}; + Default = DefaultClientHelperLinux; } export const DefaultClientHelper = Default; diff --git a/app/src/error-logger.js b/app/src/error-logger.js index cc0de43a9..24ff5b4a4 100644 --- a/app/src/error-logger.js +++ b/app/src/error-logger.js @@ -109,9 +109,7 @@ module.exports = ErrorLogger = (function() { crashReporter.start({ productName: 'Mailspring', companyName: 'Mailspring', - submitURL: `https://id.getmailspring.com/report-crash?ver=${appVersion}&platform=${ - process.platform - }`, + submitURL: `https://id.getmailspring.com/report-crash?ver=${appVersion}&platform=${process.platform}`, uploadToServer: true, autoSubmit: true, extra: { diff --git a/app/src/flux/actions.ts b/app/src/flux/actions.ts index 61933e5e9..3d43cee42 100644 --- a/app/src/flux/actions.ts +++ b/app/src/flux/actions.ts @@ -1,4 +1,5 @@ import Reflux from 'reflux'; +import { Listenable } from 'mailspring-store'; const ActionScopeWindow = 'window'; const ActionScopeGlobal = 'global'; @@ -57,7 +58,7 @@ Section: General */ type ActionFn = (...args: any[]) => void; -interface Action extends ActionFn { +interface Action extends ActionFn, Listenable { actionName: string; scope: 'window' | 'global' | 'main'; sync: boolean; diff --git a/app/src/flux/attributes.ts b/app/src/flux/attributes.ts index f6567415e..2af6618a2 100644 --- a/app/src/flux/attributes.ts +++ b/app/src/flux/attributes.ts @@ -1,30 +1,39 @@ -import { Matcher } from './attributes/matcher'; -import SortOrder from './attributes/sort-order'; -import AttributeNumber from './attributes/attribute-number'; -import AttributeString from './attributes/attribute-string'; -import AttributeObject from './attributes/attribute-object'; -import AttributeBoolean from './attributes/attribute-boolean'; -import AttributeDateTime from './attributes/attribute-datetime'; -import AttributeCollection from './attributes/attribute-collection'; -import AttributeJoinedData from './attributes/attribute-joined-data'; +import { AttributeNumber } from './attributes/attribute-number'; +import { AttributeString } from './attributes/attribute-string'; +import { AttributeObject } from './attributes/attribute-object'; +import { AttributeBoolean } from './attributes/attribute-boolean'; +import { AttributeDateTime } from './attributes/attribute-datetime'; +import { AttributeCollection } from './attributes/attribute-collection'; +import { AttributeJoinedData } from './attributes/attribute-joined-data'; -export default { - Matcher: Matcher, - SortOrder: SortOrder, +export * from './attributes/matcher'; +export * from './attributes/sort-order'; +export * from './attributes/attribute-number'; +export * from './attributes/attribute-string'; +export * from './attributes/attribute-object'; +export * from './attributes/attribute-boolean'; +export * from './attributes/attribute-datetime'; +export * from './attributes/attribute-collection'; +export * from './attributes/attribute-joined-data'; - Number: options => new AttributeNumber(options), - String: options => new AttributeString(options), - Object: options => new AttributeObject(options), - Boolean: options => new AttributeBoolean(options), - DateTime: options => new AttributeDateTime(options), - Collection: options => new AttributeCollection(options), - JoinedData: options => new AttributeJoinedData(options), - - AttributeNumber: AttributeNumber, - AttributeString: AttributeString, - AttributeObject: AttributeObject, - AttributeBoolean: AttributeBoolean, - AttributeDateTime: AttributeDateTime, - AttributeCollection: AttributeCollection, - AttributeJoinedData: AttributeJoinedData, -}; +export function Number(options: ConstructorParameters[0]) { + return new AttributeNumber(options); +} +export function String(options: ConstructorParameters[0]) { + return new AttributeString(options); +} +export function Boolean(options: ConstructorParameters[0]) { + return new AttributeBoolean(options); +} +export function DateTime(options: ConstructorParameters[0]) { + return new AttributeDateTime(options); +} +export function Collection(options: ConstructorParameters[0]) { + return new AttributeCollection(options); +} +export function JoinedData(options: ConstructorParameters[0]) { + return new AttributeJoinedData(options); +} +export function Obj(options: ConstructorParameters[0]) { + return new AttributeObject(options); +} diff --git a/app/src/flux/attributes/attribute-boolean.ts b/app/src/flux/attributes/attribute-boolean.ts index 8a3ef2cc3..fb97bf612 100644 --- a/app/src/flux/attributes/attribute-boolean.ts +++ b/app/src/flux/attributes/attribute-boolean.ts @@ -1,4 +1,4 @@ -import Attribute from './attribute'; +import { Attribute } from './attribute'; import { Matcher } from './matcher'; /* Public: The value of this attribute is always a boolean. Null values are coerced to false. @@ -8,7 +8,7 @@ String attributes can be queries using `equal` and `not`. Matching on Section: Database */ -export default class AttributeBoolean extends Attribute { +export class AttributeBoolean extends Attribute { toJSON(val) { return val; } diff --git a/app/src/flux/attributes/attribute-collection.ts b/app/src/flux/attributes/attribute-collection.ts index fd5d74b2b..b42133215 100644 --- a/app/src/flux/attributes/attribute-collection.ts +++ b/app/src/flux/attributes/attribute-collection.ts @@ -1,4 +1,4 @@ -import Attribute from './attribute'; +import { Attribute } from './attribute'; import { Matcher } from './matcher'; import { Model } from '../models/model'; @@ -30,7 +30,7 @@ The value of this attribute is always an array of other model objects. Section: Database */ -export default class AttributeCollection extends Attribute { +export class AttributeCollection extends Attribute { itemClass: typeof Model; joinOnField: string; joinTableName: string; @@ -39,19 +39,16 @@ export default class AttributeCollection extends Attribute { constructor({ modelKey, jsonKey, + queryable, itemClass, joinOnField, joinQueryableBy, joinTableName, - queryable, - }: { - modelKey: string; - jsonKey: string; - queryable: boolean; - itemClass: typeof Model; - joinOnField: string; - joinTableName: string; - joinQueryableBy: string[]; + }: ConstructorParameters[0] & { + itemClass?: typeof Model; + joinOnField?: string; + joinTableName?: string; + joinQueryableBy?: string[]; }) { super({ modelKey, jsonKey, queryable }); this.itemClass = itemClass; @@ -60,7 +57,7 @@ export default class AttributeCollection extends Attribute { this.joinQueryableBy = joinQueryableBy || []; } - toJSON(vals) { + toJSON(vals: (object | Model)[]) { if (!vals) { return []; } @@ -72,16 +69,14 @@ export default class AttributeCollection extends Attribute { return vals.map(val => { if (this.itemClass && !(val instanceof this.itemClass)) { throw new Error( - `AttributeCollection::toJSON: Value \`${val}\` in ${this.modelKey} is not an ${ - this.itemClass.name - }` + `AttributeCollection::toJSON: Value \`${val}\` in ${this.modelKey} is not an ${this.itemClass.name}` ); } - return val.toJSON !== undefined ? val.toJSON() : val; + return val && typeof val === 'object' && 'toJSON' in val ? val.toJSON() : val; }); } - fromJSON(json) { + fromJSON(json: object[]) { const Klass = this.itemClass; if (!json || !(json instanceof Array)) { diff --git a/app/src/flux/attributes/attribute-datetime.ts b/app/src/flux/attributes/attribute-datetime.ts index 8b74a65a3..d416e5244 100644 --- a/app/src/flux/attributes/attribute-datetime.ts +++ b/app/src/flux/attributes/attribute-datetime.ts @@ -1,4 +1,4 @@ -import Attribute from './attribute'; +import { Attribute } from './attribute'; import { Matcher } from './matcher'; /* @@ -6,7 +6,7 @@ Public: The value of this attribute is always a Javascript `Date`, or `null`. Section: Database */ -export default class AttributeDateTime extends Attribute { +export class AttributeDateTime extends Attribute { toJSON(val) { if (!val) { return null; diff --git a/app/src/flux/attributes/attribute-joined-data.ts b/app/src/flux/attributes/attribute-joined-data.ts index 7fe380909..48abe0f55 100644 --- a/app/src/flux/attributes/attribute-joined-data.ts +++ b/app/src/flux/attributes/attribute-joined-data.ts @@ -1,4 +1,4 @@ -import Attribute from './attribute'; +import { Attribute } from './attribute'; const NullPlaceholder = '!NULLVALUE!'; @@ -32,12 +32,17 @@ JoinedData attributes cannot be `queryable`. Section: Database */ -export default class AttributeJoinedData extends Attribute { +export class AttributeJoinedData extends Attribute { static NullPlaceholder = NullPlaceholder; modelTable: string; - constructor({ modelKey, jsonKey, modelTable, queryable }) { + constructor({ + modelKey, + jsonKey, + modelTable, + queryable, + }: ConstructorParameters[0] & { modelTable?: string }) { super({ modelKey, jsonKey, queryable }); this.modelTable = modelTable; } @@ -65,8 +70,6 @@ export default class AttributeJoinedData extends Attribute { } includeSQL(klass) { - return `LEFT OUTER JOIN \`${this.modelTable}\` ON \`${this.modelTable}\`.\`id\` = \`${ - klass.name - }\`.\`id\``; + return `LEFT OUTER JOIN \`${this.modelTable}\` ON \`${this.modelTable}\`.\`id\` = \`${klass.name}\`.\`id\``; } } diff --git a/app/src/flux/attributes/attribute-number.ts b/app/src/flux/attributes/attribute-number.ts index ee3a6b661..48b336d70 100644 --- a/app/src/flux/attributes/attribute-number.ts +++ b/app/src/flux/attributes/attribute-number.ts @@ -1,4 +1,4 @@ -import Attribute from './attribute'; +import { Attribute } from './attribute'; import { Matcher } from './matcher'; /* @@ -6,7 +6,7 @@ Public: The value of this attribute is always a number, or null. Section: Database */ -export default class AttributeNumber extends Attribute { +export class AttributeNumber extends Attribute { toJSON(val) { return val; } diff --git a/app/src/flux/attributes/attribute-object.ts b/app/src/flux/attributes/attribute-object.ts index 0528bca5c..33f4f09bb 100644 --- a/app/src/flux/attributes/attribute-object.ts +++ b/app/src/flux/attributes/attribute-object.ts @@ -1,14 +1,19 @@ -import Attribute from './attribute'; +import { Attribute } from './attribute'; import * as Utils from '../models/utils'; import { Model } from '../models/model'; /* Public: An object that can be cast to `itemClass` Section: Database */ -export default class AttributeObject extends Attribute { +export class AttributeObject extends Attribute { private itemClass: typeof Model; - constructor({ modelKey, jsonKey, itemClass, queryable }) { + constructor({ + modelKey, + jsonKey, + itemClass, + queryable, + }: ConstructorParameters[0] & { itemClass?: typeof Model }) { super({ modelKey, jsonKey, queryable }); this.itemClass = itemClass; } diff --git a/app/src/flux/attributes/attribute-string.ts b/app/src/flux/attributes/attribute-string.ts index 545635f01..cf1b6e746 100644 --- a/app/src/flux/attributes/attribute-string.ts +++ b/app/src/flux/attributes/attribute-string.ts @@ -1,4 +1,4 @@ -import Attribute from './attribute'; +import { Attribute } from './attribute'; import { Matcher } from './matcher'; /* @@ -9,7 +9,7 @@ String attributes can be queries using `equal`, `not`, and `startsWith`. Matchin Section: Database */ -export default class AttributeString extends Attribute { +export class AttributeString extends Attribute { toJSON(val) { return val; } diff --git a/app/src/flux/attributes/attribute.ts b/app/src/flux/attributes/attribute.ts index f50c546ab..7af09417c 100644 --- a/app/src/flux/attributes/attribute.ts +++ b/app/src/flux/attributes/attribute.ts @@ -1,5 +1,5 @@ import { Matcher } from './matcher'; -import SortOrder from './sort-order'; +import { SortOrder } from './sort-order'; /* Public: The Attribute class represents a single model attribute, like 'account_id'. @@ -9,7 +9,7 @@ The Attribute class also exposes convenience methods for generating {Matcher} ob Section: Database */ -export default class Attribute { +export class Attribute { public modelKey: string; public tableColumn: string; public jsonKey: string; @@ -23,14 +23,14 @@ export default class Attribute { loadFromColumn, }: { modelKey: string; - queryable: boolean; - jsonKey: string; + queryable?: boolean; + jsonKey?: string; loadFromColumn?: boolean; }) { this.modelKey = modelKey; this.tableColumn = modelKey; this.jsonKey = jsonKey || modelKey; - this.queryable = queryable; + this.queryable = queryable || false; if (loadFromColumn && !queryable) { throw new Error('loadFromColumn requires queryable'); } @@ -49,7 +49,7 @@ export default class Attribute { } // Public: Returns a {Matcher} for objects `=` to the provided value. - equal(val) { + equal(val: string | number | boolean) { this._assertPresentAndQueryable('equal', val); return new Matcher(this, '=', val); } @@ -63,9 +63,7 @@ export default class Attribute { } if (val.length === 0) { console.warn( - `Attribute::in (${ - this.modelKey - }) called with an empty set. You should avoid this useless query!` + `Attribute::in (${this.modelKey}) called with an empty set. You should avoid this useless query!` ); } if (val.length === 1) { diff --git a/app/src/flux/attributes/matcher.ts b/app/src/flux/attributes/matcher.ts index 9d2bc9dce..28cdbc6ac 100644 --- a/app/src/flux/attributes/matcher.ts +++ b/app/src/flux/attributes/matcher.ts @@ -1,6 +1,8 @@ import LocalSearchQueryBackend from '../../services/search/search-query-backend-local'; -import Attribute from './attribute'; -import AttributeCollection from './attribute-collection'; +import { QueryExpression } from '../../services/search/search-query-ast'; +import { Attribute } from './attribute'; +import { AttributeCollection } from './attribute-collection'; +import { Model } from '../models/model'; // https://www.sqlite.org/faq.html#q14 // That's right. Two single quotes in a row… @@ -82,7 +84,7 @@ export class Matcher { return this.val; } - evaluate(model) { + evaluate(model: typeof Model) { let modelValue = model[this.attr.modelKey]; if (modelValue instanceof Function) { modelValue = modelValue(); @@ -207,7 +209,7 @@ export class Matcher { class OrCompositeMatcher extends Matcher { children: Matcher[]; - constructor(children) { + constructor(children: Matcher[]) { super(); this.children = children; } @@ -224,7 +226,7 @@ class OrCompositeMatcher extends Matcher { return this.children.some(matcher => matcher.evaluate(model)); } - joinSQL(klass) { + joinSQL(klass: typeof Model) { const joins = []; for (const matcher of this.children) { const join = matcher.joinSQL(klass); @@ -235,7 +237,7 @@ class OrCompositeMatcher extends Matcher { return joins.length ? joins.join(' ') : false; } - whereSQL(klass) { + whereSQL(klass: typeof Model) { const wheres = this.children.map(matcher => matcher.whereSQL(klass)); return `(${wheres.join(' OR ')})`; } @@ -244,7 +246,7 @@ class OrCompositeMatcher extends Matcher { class AndCompositeMatcher extends Matcher { children: Matcher[]; - constructor(children) { + constructor(children: Matcher[]) { super(); this.children = children; } @@ -261,7 +263,7 @@ class AndCompositeMatcher extends Matcher { return this.children.every(m => m.evaluate(model)); } - joinSQL(klass) { + joinSQL(klass: typeof Model) { const joins = []; for (const matcher of this.children) { const join = matcher.joinSQL(klass); @@ -272,22 +274,22 @@ class AndCompositeMatcher extends Matcher { return joins.join(' '); } - whereSQL(klass) { + whereSQL(klass: typeof Model) { const wheres = this.children.map(m => m.whereSQL(klass)); return `(${wheres.join(' AND ')})`; } } class NotCompositeMatcher extends AndCompositeMatcher { - whereSQL(klass) { + whereSQL(klass: typeof Model) { return `NOT (${super.whereSQL(klass)})`; } } class StructuredSearchMatcher extends Matcher { - _searchQuery: string; + _searchQuery: QueryExpression; - constructor(searchQuery) { + constructor(searchQuery: QueryExpression) { super(null, null, null); this._searchQuery = searchQuery; } @@ -308,7 +310,7 @@ class StructuredSearchMatcher extends Matcher { return true; } - whereSQL(klass) { + whereSQL(klass: typeof Model) { return new LocalSearchQueryBackend(klass.name).compile(this._searchQuery); } } @@ -316,7 +318,7 @@ class StructuredSearchMatcher extends Matcher { class SearchMatcher extends Matcher { searchQuery: string; - constructor(searchQuery) { + constructor(searchQuery: string) { if (typeof searchQuery !== 'string' || searchQuery.length === 0) { throw new Error('You must pass a string with non-zero length to search.'); } @@ -345,7 +347,7 @@ class SearchMatcher extends Matcher { return true; } - whereSQL(klass) { + whereSQL(klass: typeof Model) { const searchTable = `${klass.name}Search`; return `\`${klass.name}\`.\`id\` IN (SELECT \`content_id\` FROM \`${searchTable}\` WHERE \`${searchTable}\` MATCH '"${this.searchQuery}"*' LIMIT 1000)`; } diff --git a/app/src/flux/attributes/sort-order.ts b/app/src/flux/attributes/sort-order.ts index ca1ef8ada..042d3b2d6 100644 --- a/app/src/flux/attributes/sort-order.ts +++ b/app/src/flux/attributes/sort-order.ts @@ -1,4 +1,5 @@ -import Attribute from './attribute'; +import { Attribute } from './attribute'; +import { Model } from '../models/model'; /* Public: Represents a particular sort direction on a particular column. You should not @@ -14,16 +15,16 @@ DatabaseStore.findBy(Message) Section: Database */ -export default class SortOrder { +export class SortOrder { public attr: Attribute; public direction: 'ASC' | 'DESC'; - constructor(attr, direction: 'ASC' | 'DESC' = 'DESC') { + constructor(attr: Attribute, direction: 'ASC' | 'DESC' = 'DESC') { this.attr = attr; this.direction = direction; } - orderBySQL(klass) { + orderBySQL(klass: typeof Model) { return `\`${klass.name}\`.\`${this.attr.tableColumn}\` ${this.direction}`; } diff --git a/app/src/flux/mailsync-bridge.ts b/app/src/flux/mailsync-bridge.ts index 953bb06a5..20e1335ce 100644 --- a/app/src/flux/mailsync-bridge.ts +++ b/app/src/flux/mailsync-bridge.ts @@ -14,7 +14,7 @@ import DatabaseStore from './stores/database-store'; import OnlineStatusStore from './stores/online-status-store'; import DatabaseChangeRecord from './stores/database-change-record'; import DatabaseObjectRegistry from '../registries/database-object-registry'; -import { MailsyncProcess } from '../mailsync-process'; +import { MailsyncProcess, MailsyncProcessExit } from '../mailsync-process'; import KeyManager from '../key-manager'; import * as Actions from './actions'; import * as Utils from './models/utils'; @@ -43,7 +43,9 @@ class CrashTracker { delete this._tooManyFailures[key]; } - recordClientCrash(fullAccountJSON, { code, error, signal }) { + recordClientCrash(fullAccountJSON, crash: MailsyncProcessExit) { + console.log(`Sync worker exited.`, crash); + this._appendCrashToHistory(fullAccountJSON); // We now let crashpad do this, because Sentry was losing it's mind. @@ -298,7 +300,7 @@ export default class MailsyncBridge { client.identity = IdentityStore.identity(); client.sync(); client.on('deltas', this._onIncomingMessages); - client.on('close', ({ code, error, signal }) => { + client.on('close', ({ code, error, signal }: MailsyncProcessExit) => { if (this._clients[account.id] !== client) { return; } diff --git a/app/src/flux/models/account.ts b/app/src/flux/models/account.ts index f22e9a93e..1c341f1d0 100644 --- a/app/src/flux/models/account.ts +++ b/app/src/flux/models/account.ts @@ -1,6 +1,7 @@ /* eslint global-require:0 */ -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { ModelWithMetadata } from './model-with-metadata'; +import { MailsyncProcessExit } from 'mailspring-exports'; let CategoryStore = null; let Contact = null; @@ -50,7 +51,7 @@ export class Account extends ModelWithMetadata { modelKey: 'emailAddress', }), - settings: Attributes.Object({ + settings: Attributes.Obj({ modelKey: 'settings', }), @@ -58,15 +59,15 @@ export class Account extends ModelWithMetadata { modelKey: 'label', }), - autoaddress: Attributes.Object({ + autoaddress: Attributes.Obj({ modelKey: 'autoaddress', }), - aliases: Attributes.Object({ + aliases: Attributes.Obj({ modelKey: 'aliases', }), - defaultAlias: Attributes.Object({ + defaultAlias: Attributes.Obj({ modelKey: 'defaultAlias', }), @@ -74,7 +75,7 @@ export class Account extends ModelWithMetadata { modelKey: 'syncState', }), - syncError: Attributes.Object({ + syncError: Attributes.Obj({ modelKey: 'syncError', }), @@ -111,7 +112,7 @@ export class Account extends ModelWithMetadata { public aliases: string[]; public defaultAlias: string; public syncState: string; - public syncError: string; + public syncError: MailsyncProcessExit | null; public color: string; constructor(args) { diff --git a/app/src/flux/models/calendar.ts b/app/src/flux/models/calendar.ts index b99cb01d7..ebaaf9339 100644 --- a/app/src/flux/models/calendar.ts +++ b/app/src/flux/models/calendar.ts @@ -1,5 +1,5 @@ import { Model, AttributeValues } from './model'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; /** Public: The Calendar model represents a Calendar object. diff --git a/app/src/flux/models/category.ts b/app/src/flux/models/category.ts index 443028810..2afb4af27 100644 --- a/app/src/flux/models/category.ts +++ b/app/src/flux/models/category.ts @@ -1,7 +1,7 @@ /* eslint global-require: 0 */ import utf7 from 'utf7'; import { Model, AttributeValues } from './model'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { localized } from '../../intl'; // We look for a few standard categories and display them in the Mailboxes @@ -107,7 +107,7 @@ export class Category extends Model { queryable: true, modelKey: 'path', }), - localStatus: Attributes.Object({ + localStatus: Attributes.Obj({ modelKey: 'localStatus', }), }; diff --git a/app/src/flux/models/contact-book.ts b/app/src/flux/models/contact-book.ts index 94464f13d..440955b2d 100644 --- a/app/src/flux/models/contact-book.ts +++ b/app/src/flux/models/contact-book.ts @@ -1,6 +1,6 @@ /* eslint global-require: 0 */ import { Model, AttributeValues } from './model'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; export class ContactBook extends Model { static attributes = { diff --git a/app/src/flux/models/contact-group.ts b/app/src/flux/models/contact-group.ts index 69041c7c8..685839606 100644 --- a/app/src/flux/models/contact-group.ts +++ b/app/src/flux/models/contact-group.ts @@ -1,6 +1,6 @@ /* eslint global-require: 0 */ import { Model, AttributeValues } from './model'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; export class ContactGroup extends Model { static attributes = { diff --git a/app/src/flux/models/contact.ts b/app/src/flux/models/contact.ts index 19d9014ff..32737e622 100644 --- a/app/src/flux/models/contact.ts +++ b/app/src/flux/models/contact.ts @@ -1,7 +1,7 @@ /* eslint global-require: 0 */ import _str from 'underscore.string'; import { Model, AttributeValues } from './model'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import * as Utils from './utils'; import RegExpUtils from '../../regexp-utils'; import { AccountStore } from '../stores/account-store'; @@ -391,7 +391,7 @@ export class Contact extends Model { modelKey: 'refs', }), - info: Attributes.Object({ + info: Attributes.Obj({ modelKey: 'info', }), }; diff --git a/app/src/flux/models/event.ts b/app/src/flux/models/event.ts index dba1af5d2..afa5055f1 100644 --- a/app/src/flux/models/event.ts +++ b/app/src/flux/models/event.ts @@ -1,5 +1,5 @@ import { Model, AttributeValues } from './model'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { Contact } from './contact'; // the Chrono node module is huge diff --git a/app/src/flux/models/file.ts b/app/src/flux/models/file.ts index eb50e225f..0a4a6ac5e 100644 --- a/app/src/flux/models/file.ts +++ b/app/src/flux/models/file.ts @@ -1,7 +1,7 @@ /* eslint global-require: 0 */ import path from 'path'; import { Model, AttributeValues } from './model'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { localized } from '../../intl'; import RegExpUtils from '../../regexp-utils'; diff --git a/app/src/flux/models/message.ts b/app/src/flux/models/message.ts index 48969cfd7..c5a5000c0 100644 --- a/app/src/flux/models/message.ts +++ b/app/src/flux/models/message.ts @@ -6,7 +6,7 @@ import * as Utils from './utils'; import { Event } from './event'; import { Contact } from './contact'; import { Folder } from './folder'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { ModelWithMetadata } from './model-with-metadata'; import { AttributeValues } from './model'; @@ -172,7 +172,7 @@ export class Message extends ModelWithMetadata { modelKey: 'forwardedHeaderMessageId', }), - folder: Attributes.Object({ + folder: Attributes.Obj({ queryable: false, modelKey: 'folder', itemClass: Folder, diff --git a/app/src/flux/models/model-with-metadata.ts b/app/src/flux/models/model-with-metadata.ts index cafcdf7d7..73cf540f7 100644 --- a/app/src/flux/models/model-with-metadata.ts +++ b/app/src/flux/models/model-with-metadata.ts @@ -1,12 +1,12 @@ import { Model, AttributeValues } from './model'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; /** Cloud-persisted data that is associated with a single Mailspring API object (like a `Thread`, `Message`, or `Account`). */ export class PluginMetadata extends Model { - static attributes = { + static attributes = ({ pluginId: Attributes.String({ modelKey: 'pluginId', }), @@ -14,10 +14,10 @@ export class PluginMetadata extends Model { jsonKey: 'v', modelKey: 'version', }), - value: Attributes.Object({ + value: Attributes.Obj({ modelKey: 'value', }), - }; + } as unknown) as typeof Model['attributes']; public pluginId: string; public version: number; diff --git a/app/src/flux/models/model.ts b/app/src/flux/models/model.ts index 83a9917d8..435b20f92 100644 --- a/app/src/flux/models/model.ts +++ b/app/src/flux/models/model.ts @@ -1,5 +1,5 @@ -import Attributes from '../attributes'; -import Attribute from '../attributes/attribute'; +import * as Attributes from '../attributes'; +import { Attribute } from '../attributes/attribute'; /** Public: A base class for API objects that provides abstract support for @@ -36,7 +36,12 @@ type ModelAttributes = { [attribute: string]: Attribute; }; +export interface ModelClass { + new (): Model; +} + export class Model implements HasStaticAttributes { + // @ts-ignore 'constructor': typeof Model; // prettier-ignore static attributes: ModelAttributes = { diff --git a/app/src/flux/models/mutable-query-result-set.ts b/app/src/flux/models/mutable-query-result-set.ts index 2ef44ad90..f1f23da26 100644 --- a/app/src/flux/models/mutable-query-result-set.ts +++ b/app/src/flux/models/mutable-query-result-set.ts @@ -1,5 +1,5 @@ import { QueryResultSet } from './query-result-set'; -import AttributeJoinedData from '../attributes/attribute-joined-data'; +import { AttributeJoinedData } from '../attributes/attribute-joined-data'; import { Model } from './model'; import ModelQuery from './query'; diff --git a/app/src/flux/models/provider-syncback-request.ts b/app/src/flux/models/provider-syncback-request.ts index 84944d2bf..92442d321 100644 --- a/app/src/flux/models/provider-syncback-request.ts +++ b/app/src/flux/models/provider-syncback-request.ts @@ -1,5 +1,5 @@ import { Model } from './model'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; export default class ProviderSyncbackRequest extends Model { static attributes = { @@ -10,15 +10,15 @@ export default class ProviderSyncbackRequest extends Model { modelKey: 'type', }), - error: Attributes.Object({ + error: Attributes.Obj({ modelKey: 'error', }), - props: Attributes.Object({ + props: Attributes.Obj({ modelKey: 'props', }), - responseJSON: Attributes.Object({ + responseJSON: Attributes.Obj({ modelKey: 'responseJSON', jsonKey: 'response_json', }), diff --git a/app/src/flux/models/query-subscription-pool.ts b/app/src/flux/models/query-subscription-pool.ts index ec6adac52..b753bbebf 100644 --- a/app/src/flux/models/query-subscription-pool.ts +++ b/app/src/flux/models/query-subscription-pool.ts @@ -2,6 +2,7 @@ import { QuerySubscription } from './query-subscription'; import DatabaseChangeRecord from '../stores/database-change-record'; import ModelQuery from './query'; +import { Model } from './model'; let DatabaseStore = null; /* @@ -97,7 +98,7 @@ class QuerySubscriptionPool { return stack.slice(ii, ii + 4).join('\n'); } - _keyForQuery(query: ModelQuery) { + _keyForQuery(query: ModelQuery) { return query.sql(); } diff --git a/app/src/flux/models/query-subscription.ts b/app/src/flux/models/query-subscription.ts index b892e91b5..e1064179d 100644 --- a/app/src/flux/models/query-subscription.ts +++ b/app/src/flux/models/query-subscription.ts @@ -4,11 +4,15 @@ import { MutableQueryResultSet } from './mutable-query-result-set'; import ModelQuery from './query'; import { Model } from './model'; import DatabaseChangeRecord from '../stores/database-change-record'; +import { QueryResultSet } from './query-result-set'; + +type QuerySubscriptionResult = QueryResultSet | number | T | T[]; +type QuerySubscriptionCallback = (result: QuerySubscriptionResult) => void; export class QuerySubscription { _set: MutableQueryResultSet = null; - _callbacks = []; - _lastResult = undefined; // null is a valid result! + _callbacks: QuerySubscriptionCallback[] = []; + _lastResult: QuerySubscriptionResult | undefined = undefined; // null is a valid result! _updateInFlight = false; _queuedChangeRecords = []; _queryVersion = 1; @@ -16,7 +20,7 @@ export class QuerySubscription { _options: any; constructor( - query, + query: ModelQuery | ModelQuery, options: { initialModels?: T[]; emitResultSet?: boolean; @@ -53,7 +57,7 @@ export class QuerySubscription { return this._query; }; - addCallback = callback => { + addCallback = (callback: QuerySubscriptionCallback) => { if (!(callback instanceof Function)) { throw new Error(`QuerySubscription:addCallback - expects a function, received ${callback}`); } @@ -63,11 +67,11 @@ export class QuerySubscription { } }; - hasCallback = callback => { + hasCallback = (callback: QuerySubscriptionCallback) => { return this._callbacks.indexOf(callback) !== -1; }; - removeCallback(callback) { + removeCallback(callback: QuerySubscriptionCallback) { if (!(callback instanceof Function)) { throw new Error( `QuerySubscription:removeCallback - expects a function, received ${callback}` @@ -170,7 +174,7 @@ export class QuerySubscription { } }; - _itemSortOrderHasChanged(old, updated) { + _itemSortOrderHasChanged(old: T, updated: T) { if (!old || !updated) return true; for (const descriptor of this._query.orderSortDescriptors()) { @@ -211,7 +215,7 @@ export class QuerySubscription { } } - _getMissingRange = (desiredRange, currentRange) => { + _getMissingRange = (desiredRange: QueryRange, currentRange: QueryRange) => { if (currentRange && !currentRange.isInfinite() && !desiredRange.isInfinite()) { const ranges = QueryRange.rangesBySubtracting(desiredRange, currentRange); return ranges.length === 1 ? ranges[0] : desiredRange; @@ -219,7 +223,7 @@ export class QuerySubscription { return desiredRange; }; - _getQueryForRange = (range, fetchEntireModels: boolean) => { + _getQueryForRange = (range: QueryRange, fetchEntireModels: boolean) => { let rangeQuery = null; if (!range.isInfinite()) { rangeQuery = rangeQuery || this._query.clone(); @@ -233,7 +237,10 @@ export class QuerySubscription { return rangeQuery; }; - _fetchRange(range, { version, fetchEntireModels }) { + _fetchRange( + range: QueryRange, + { version, fetchEntireModels }: { version: number; fetchEntireModels: boolean } + ) { const rangeQuery = this._getQueryForRange(range, fetchEntireModels); const haveModels = this._set && this._set.modelCacheCount() > 0; @@ -308,7 +315,8 @@ export class QuerySubscription { this._set.setQuery(this._query); this._lastResult = this._set.immutableClone(); } else { - this._lastResult = this._query.formatResult(this._set.models()); + const models = this._set.models(); + this._lastResult = this._query.formatResult(models); } this._callbacks.forEach(callback => callback(this._lastResult)); diff --git a/app/src/flux/models/query.ts b/app/src/flux/models/query.ts index 218082ae4..1aa48aaa8 100644 --- a/app/src/flux/models/query.ts +++ b/app/src/flux/models/query.ts @@ -1,10 +1,11 @@ /* eslint global-require: 0 */ -import Attributes from '../attributes'; +import { Matcher, AttributeJoinedData, AttributeCollection, SortOrder } from '../attributes'; import { QueryRange } from './query-range'; +import { QueryExpression } from '../../services/search/search-query-ast'; import * as Utils from './utils'; import { Model } from './model'; -const { Matcher, AttributeJoinedData, AttributeCollection } = Attributes; +type DB = typeof import('mailspring-exports').DatabaseStore; /* Public: ModelQuery exposes an ActiveRecord-style syntax for building database queries @@ -37,16 +38,16 @@ query.where([Thread.attributes.categories.contains('label-id')]) Section: Database */ -export default class ModelQuery { - private _database: typeof import('mailspring-exports').DatabaseStore; - private _matchers = []; - private _orders = []; +export default class ModelQuery { + private _database: DB; + private _matchers: Matcher[] = []; + private _orders: SortOrder[] = []; private _backgroundable = true; private _distinct = false; private _range = QueryRange.infinite(); private _returnOne = false; private _returnIds = false; - private _includeJoinedData = []; + private _includeJoinedData: AttributeJoinedData[] = []; _background = false; _count = false; @@ -58,13 +59,15 @@ export default class ModelQuery { // - `database` (optional) An optional reference to a {DatabaseStore} the // query will be executed on. // - constructor(klass, database) { + constructor(klass, database: DB) { this._klass = klass.SubclassesUseModelTable || klass; this._database = database || require('./database-store').default; } clone(): ModelQuery { - const q = new ModelQuery(this._klass, this._database).where(this._matchers).order(this._orders); + const q = new ModelQuery(this._klass, this._database) + .where(this._matchers) + .order(this._orders); q._orders = [...this._orders]; q._includeJoinedData = [...this._includeJoinedData]; q._range = this._range.clone(); @@ -101,7 +104,12 @@ export default class ModelQuery { // // This method is chainable. // - where(matchers: any[] | any) { + where( + matchers: + | Matcher[] + | Matcher + | { [key: string]: string | string[] | number | number[] | boolean } + ) { this._assertNotFinalized(); if (matchers instanceof Matcher) { @@ -119,9 +127,7 @@ export default class ModelQuery { const value = matchers[key]; const attr = this._klass.attributes[key]; if (!attr) { - const msg = `Cannot create where clause \`${key}:${value}\`. ${key} is not an attribute of ${ - this._klass.name - }`; + const msg = `Cannot create where clause \`${key}:${value}\`. ${key} is not an attribute of ${this._klass.name}`; throw new Error(msg); } @@ -135,19 +141,19 @@ export default class ModelQuery { return this; } - whereAny(matchers) { + whereAny(matchers: Matcher[]) { this._assertNotFinalized(); this._matchers.push(new Matcher.Or(matchers)); return this; } - search(query) { + search(query: string) { this._assertNotFinalized(); this._matchers.push(new Matcher.Search(query)); return this; } - structuredSearch(query) { + structuredSearch(query: QueryExpression) { this._assertNotFinalized(); this._matchers.push(new Matcher.StructuredSearch(query)); return this; @@ -160,7 +166,7 @@ export default class ModelQuery { // // This method is chainable. // - include(attr) { + include(attr: AttributeJoinedData) { this._assertNotFinalized(); if (!(attr instanceof AttributeJoinedData)) { throw new Error('query.include() must be called with a joined data attribute'); @@ -190,7 +196,7 @@ export default class ModelQuery { // // This method is chainable. // - order(ordersOrOrder) { + order(ordersOrOrder: SortOrder | SortOrder[]) { this._assertNotFinalized(); const orders = ordersOrOrder instanceof Array ? ordersOrOrder : [ordersOrOrder]; this._orders = this._orders.concat(orders); @@ -214,7 +220,7 @@ export default class ModelQuery { // // This method is chainable. // - limit(limit) { + limit(limit: number) { this._assertNotFinalized(); if (this._returnOne && limit > 1) { throw new Error('Cannot use limit > 1 with one()'); @@ -230,7 +236,7 @@ export default class ModelQuery { // // This method is chainable. // - offset(offset) { + offset(offset: number) { this._assertNotFinalized(); this._range = this._range.clone(); this._range.offset = offset; @@ -241,7 +247,7 @@ export default class ModelQuery { // // A convenience method for setting both limit and offset given a desired page size. // - page(start, end, pageSize = 50, pagePadding = 100) { + page(start: number, end: number, pageSize = 50, pagePadding = 100) { const roundToPage = n => Math.max(0, Math.floor(n / pageSize) * pageSize); this.offset(roundToPage(start - pagePadding)); this.limit(roundToPage(end - start + pagePadding * 2)); @@ -272,7 +278,7 @@ export default class ModelQuery { // Returns a {Promise} that resolves with the Models returned by the // query, or rejects with an error from the Database layer. // - then(next: (arg0: T) => U ): Promise { + then(next: (arg0: T) => U): Promise { return this.run().then(next); } @@ -283,7 +289,7 @@ export default class ModelQuery { return this._database.run(this); } - inflateResult(result) { + inflateResult(result: { [key: string]: any }[]) { if (!result) { return null; } @@ -292,12 +298,12 @@ export default class ModelQuery { return result[0].count / 1; } if (this._returnIds) { - return result.map(row => row.id); + return result.map(row => row.id as string); } try { return result.map(row => { - const object = Utils.convertToModel(JSON.parse(row.data)); + const object: T = Utils.convertToModel(JSON.parse(row.data)); for (const attrName of Object.keys(this._klass.attributes)) { const attr = this._klass.attributes[attrName]; if (!attr.needsColumn() || !attr.loadFromColumn) { @@ -321,14 +327,22 @@ export default class ModelQuery { } } - formatResult(inflated) { + formatResult(inflated: U) { + if (this._count) { + if (typeof inflated != 'number') { + throw new Error(`Expected query result to be a number.`); + } + return inflated; + } + + if (!(inflated instanceof Array)) { + throw new Error(`Expected query result to be an array.`); + } + if (this._returnOne) { // be careful not to return "undefined" if no items returned return inflated.length > 0 ? inflated[0] : null; } - if (this._count) { - return inflated; - } return [...inflated]; } @@ -355,7 +369,7 @@ export default class ModelQuery { result += `, ${attr.tableColumn} `; } this._includeJoinedData.forEach(attr => { - result += `, ${attr.selectSQL(this._klass)} `; + result += `, ${attr.selectSQL()} `; }); } @@ -378,9 +392,7 @@ export default class ModelQuery { if (joins.length === 1 && this._canSubselectForJoin(joins[0], allMatchers)) { const subSql = this._subselectSQL(joins[0], this._matchers, order, limit); - return `SELECT${distinct} ${result} FROM \`${ - this._klass.name - }\` WHERE \`id\` IN (${subSql}) ${order}`; + return `SELECT${distinct} ${result} FROM \`${this._klass.name}\` WHERE \`id\` IN (${subSql}) ${order}`; } return `SELECT${distinct} ${result} FROM \`${ @@ -396,8 +408,8 @@ export default class ModelQuery { // // Note: This is currently only intended for use in the thread list // - _canSubselectForJoin(matcher, allMatchers) { - const joinAttribute = matcher.attribute(); + _canSubselectForJoin(matcher: Matcher, allMatchers: Matcher[]) { + const joinAttribute = matcher.attribute() as AttributeCollection; if (!Number.isInteger(this._range.limit)) { return false; @@ -416,8 +428,13 @@ export default class ModelQuery { return allMatchersOnJoinTable && allOrdersOnJoinTable; } - _subselectSQL(returningMatcher, subselectMatchers, order, limit) { - const returningAttribute = returningMatcher.attribute(); + _subselectSQL( + returningMatcher: Matcher, + subselectMatchers: Matcher[], + order: string, + limit: string + ) { + const returningAttribute = returningMatcher.attribute() as AttributeCollection; const table = returningAttribute.tableNameForJoinAgainst(this._klass); const wheres = subselectMatchers.map(c => c.whereSQL(this._klass)).filter(c => !!c); @@ -530,7 +547,7 @@ export default class ModelQuery { return all; } - matcherValueForModelKey(key) { + matcherValueForModelKey(key: string) { const matcher = this._matchers.find(m => m.attr.modelKey === key); return matcher ? matcher.val : null; } diff --git a/app/src/flux/models/thread.ts b/app/src/flux/models/thread.ts index ed09fe85b..cda628b20 100644 --- a/app/src/flux/models/thread.ts +++ b/app/src/flux/models/thread.ts @@ -3,7 +3,7 @@ import { Contact } from './contact'; import { Folder } from './folder'; import { Label } from './label'; import { Category } from './category'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import DatabaseStore from '../stores/database-store'; import { ModelWithMetadata } from './model-with-metadata'; diff --git a/app/src/flux/models/utils.ts b/app/src/flux/models/utils.ts index 82c163fb3..e027f3994 100644 --- a/app/src/flux/models/utils.ts +++ b/app/src/flux/models/utils.ts @@ -14,8 +14,8 @@ import DatabaseObjectRegistry from '../../registries/database-object-registry'; export function waitFor(latch, options: { timeout?: number } = {}) { const timeout = options.timeout || 400; const expire = Date.now() + timeout; - return new Promise(function(resolve, reject) { - var attempt = function() { + return new Promise(function(resolve, reject) { + const attempt = () => { if (Date.now() > expire) { return reject(new Error(`Utils.waitFor hit timeout (${timeout}ms) without firing.`)); } @@ -116,7 +116,7 @@ export function wordSearchRegExp(str = '') { // Takes an optional customizer. The customizer is passed the key and the // new cloned value for that key. The customizer is expected to either // modify the value and return it or simply be the identity function. -export function deepClone(object, customizer?, stackSeen = [], stackRefs = []) { +export function deepClone(object: T, customizer?, stackSeen = [], stackRefs = []): T { let newObject; if (!_.isObject(object)) { return object; diff --git a/app/src/flux/stores/account-store.ts b/app/src/flux/stores/account-store.ts index 5b41769f0..00656ced4 100644 --- a/app/src/flux/stores/account-store.ts +++ b/app/src/flux/stores/account-store.ts @@ -14,6 +14,7 @@ const configAccountsKey = 'accounts'; const configVersionKey = 'accountsVersion'; export type IAliasSet = Array; + /* Public: The AccountStore listens to changes to the available accounts in the database and exposes the currently active Account via {::current} @@ -160,7 +161,7 @@ class _AccountStore extends MailspringStore { * Actions.updateAccount is called directly from the local-sync worker. * This will update the account with its updated sync state */ - _onUpdateAccount = (id, updated) => { + _onUpdateAccount = (id: string, updated: Partial) => { const idx = this._accounts.findIndex(a => a.id === id); let account = this._accounts[idx]; if (!account) return; @@ -176,7 +177,7 @@ class _AccountStore extends MailspringStore { * the AccountStore and runs `ensureK2Consistency`. This will actually * delete the Account on the local sync side. */ - _onRemoveAccount = id => { + _onRemoveAccount = (id: string) => { const account = this._accounts.find(a => a.id === id); if (!account) return; @@ -209,7 +210,7 @@ class _AccountStore extends MailspringStore { } }; - _onReorderAccount = (id, newIdx) => { + _onReorderAccount = (id: string, newIdx: number) => { const existingIdx = this._accounts.findIndex(a => a.id === id); if (existingIdx === -1) return; const account = this._accounts[existingIdx]; @@ -219,7 +220,7 @@ class _AccountStore extends MailspringStore { this._save(); }; - addAccount = async account => { + addAccount = async (account: Account) => { if (!account.emailAddress || !account.provider || !(account instanceof Account)) { throw new Error(`Returned account data is invalid: ${JSON.stringify(account)}`); } diff --git a/app/src/flux/stores/attachment-store.ts b/app/src/flux/stores/attachment-store.ts index cb16e34cf..19f0eb6fb 100644 --- a/app/src/flux/stores/attachment-store.ts +++ b/app/src/flux/stores/attachment-store.ts @@ -160,7 +160,7 @@ class AttachmentStore extends MailspringStore { .then(filePath => shell.openItem(filePath)) .catch(this._catchFSErrors) .catch(error => { - return this._presentError({ file, error }); + this._presentError({ file, error }); }); }; @@ -214,54 +214,56 @@ class AttachmentStore extends MailspringStore { _fetchAndSaveAll = files => { const defaultPath = this._defaultSaveDir(); - const options = { - defaultPath, - title: localized('Save Into...'), - buttonLabel: localized('Download All'), - properties: ['openDirectory', 'createDirectory'], - }; return new Promise(resolve => { - AppEnv.showOpenDialog(options, selected => { - if (!selected) { - return; - } - const dirPath = selected[0]; - if (!dirPath) { - return; - } - this._lastDownloadDirectory = dirPath; - AppEnv.savedState.lastDownloadDirectory = dirPath; - - const seenPaths = new Set(); - const lastSavePaths = []; - const savePromises = files.map(file => { - let externalPath = path.join(dirPath, file.safeDisplayName()); - while (seenPaths.has(externalPath) || fs.existsSync(externalPath)) { - externalPath = this._incrementPathToAvoidCollision(externalPath); + AppEnv.showOpenDialog( + { + defaultPath, + title: localized('Save Into...'), + buttonLabel: localized('Download All'), + properties: ['openDirectory', 'createDirectory'], + }, + selected => { + if (!selected) { + return; } - seenPaths.add(externalPath); + const dirPath = selected[0]; + if (!dirPath) { + return; + } + this._lastDownloadDirectory = dirPath; + AppEnv.savedState.lastDownloadDirectory = dirPath; - return this._prepareAndResolveFilePath(file) - .then(filePath => this._writeToExternalPath(filePath, externalPath)) - .then(() => lastSavePaths.push(externalPath)); - }); - - Promise.all(savePromises) - .then(() => { - if ( - lastSavePaths.length > 0 && - AppEnv.config.get('core.attachments.openFolderAfterDownload') - ) { - shell.showItemInFolder(lastSavePaths[0]); + const seenPaths = new Set(); + const lastSavePaths = []; + const savePromises = files.map(file => { + let externalPath = path.join(dirPath, file.safeDisplayName()); + while (seenPaths.has(externalPath) || fs.existsSync(externalPath)) { + externalPath = this._incrementPathToAvoidCollision(externalPath); } - return resolve(lastSavePaths); - }) - .catch(this._catchFSErrors) - .catch(error => { - return this._presentError({ error }); + seenPaths.add(externalPath); + + return this._prepareAndResolveFilePath(file) + .then(filePath => this._writeToExternalPath(filePath, externalPath)) + .then(() => lastSavePaths.push(externalPath)); }); - }); + + Promise.all(savePromises) + .then(() => { + if ( + lastSavePaths.length > 0 && + AppEnv.config.get('core.attachments.openFolderAfterDownload') + ) { + shell.showItemInFolder(lastSavePaths[0]); + } + return resolve(lastSavePaths); + }) + .catch(this._catchFSErrors) + .catch(error => { + this._presentError({ error }); + }); + } + ); }); }; @@ -309,7 +311,7 @@ class AttachmentStore extends MailspringStore { const name = file ? file.displayName() : localized('one or more files'); const errorString = error ? error.toString() : ''; - return remote.dialog.showMessageBox({ + remote.dialog.showMessageBoxSync({ type: 'warning', message: localized('Download Failed'), detail: localized( @@ -335,7 +337,7 @@ class AttachmentStore extends MailspringStore { } if (message) { - remote.dialog.showMessageBox({ + remote.dialog.showMessageBoxSync({ type: 'warning', message: localized('Download Failed'), detail: `${message}\n\n${error.message}`, diff --git a/app/src/flux/stores/database-agent.js b/app/src/flux/stores/database-agent.js index 9a5737cad..f809a9e79 100644 --- a/app/src/flux/stores/database-agent.js +++ b/app/src/flux/stores/database-agent.js @@ -4,40 +4,32 @@ const dbs = {}; const deathDelay = 5000; let deathTimer = setTimeout(() => process.exit(0), deathDelay); -const getDatabase = (dbpath) => { +function getDatabase(dbpath) { if (dbs[dbpath]) { - return dbs[dbpath].openPromise; + return Promise.resolve(dbs[dbpath]); } - let openResolve = null; - - dbs[dbpath] = new Sqlite3(dbpath, {readonly: true}); - dbs[dbpath].on('close', (err) => { + try { + dbs[dbpath] = new Sqlite3(dbpath, { readonly: true, timeout: 10000 }); + } catch (err) { console.error(err); process.exit(1); - }); - dbs[dbpath].on('open', () => { - openResolve(dbs[dbpath]); - }); + } - dbs[dbpath].openPromise = new Promise((resolve) => { - openResolve = resolve; - }); - - return dbs[dbpath].openPromise; + return Promise.resolve(dbs[dbpath]); } -process.on('message', (m) => { +process.on('message', m => { clearTimeout(deathTimer); - const {query, values, id, dbpath} = m; + const { query, values, id, dbpath } = m; const start = Date.now(); - getDatabase(dbpath).then((db) => { + getDatabase(dbpath).then(db => { clearTimeout(deathTimer); const fn = query.startsWith('SELECT') ? 'all' : 'run'; const stmt = db.prepare(query); const results = stmt[fn](values); - process.send({type: 'results', results, id, agentTime: Date.now() - start}); + process.send({ type: 'results', results, id, agentTime: Date.now() - start }); clearTimeout(deathTimer); deathTimer = setTimeout(() => process.exit(0), deathDelay); diff --git a/app/src/flux/stores/database-store.ts b/app/src/flux/stores/database-store.ts index 26610c641..5c297d7c8 100644 --- a/app/src/flux/stores/database-store.ts +++ b/app/src/flux/stores/database-store.ts @@ -4,7 +4,7 @@ import createDebug from 'debug'; import childProcess, { ChildProcess } from 'child_process'; import LRU from 'lru-cache'; import Sqlite3 from 'better-sqlite3'; -import { remote, EventEmitter } from 'electron'; +import { remote } from 'electron'; import { ExponentialBackoffScheduler } from '../../backoff-schedulers'; import { Model } from '../models/model'; import MailspringStore from '../../global/mailspring-store'; @@ -51,28 +51,22 @@ function handleUnrecoverableDatabaseError( async function openDatabase(dbPath) { try { - const database = await new Promise((resolve, reject) => { - const db = new Sqlite3(dbPath, { readonly: true }) as Sqlite3.Database & EventEmitter; - db.on('close', reject); - db.on('open', () => { - // https://www.sqlite.org/wal.html - // WAL provides more concurrency as readers do not block writers and a writer - // does not block readers. Reading and writing can proceed concurrently. - db.pragma(`journal_mode = WAL`); + const db = new Sqlite3(dbPath, { readonly: true, timeout: 10000 }) as Sqlite3.Database; - // Note: These are properties of the connection, so they must be set regardless - // of whether the database setup queries are run. + // https://www.sqlite.org/wal.html + // WAL provides more concurrency as readers do not block writers and a writer + // does not block readers. Reading and writing can proceed concurrently. + db.pragma(`journal_mode = WAL`); - // https://www.sqlite.org/intern-v-extern-blob.html - // A database page size of 8192 or 16384 gives the best performance for large BLOB I/O. - db.pragma(`main.page_size = 8192`); - db.pragma(`main.cache_size = 20000`); - db.pragma(`main.synchronous = NORMAL`); + // Note: These are properties of the connection, so they must be set regardless + // of whether the database setup queries are run. - resolve(db); - }); - }); - return database; + // https://www.sqlite.org/intern-v-extern-blob.html + // A database page size of 8192 or 16384 gives the best performance for large BLOB I/O. + db.pragma(`main.page_size = 8192`); + db.pragma(`main.cache_size = 20000`); + db.pragma(`main.synchronous = NORMAL`); + return db; } catch (err) { handleUnrecoverableDatabaseError(err); return null; @@ -218,7 +212,7 @@ class DatabaseStore extends MailspringStore { // If a query is made before the database has been opened, the query will be // held in a queue and run / resolved when the database is ready. _query(query: SQLString, values: SQLValue[] = [], background = false) { - return new Promise(async (resolve, reject) => { + return new Promise<{ [key: string]: any }[]>(async (resolve, reject) => { if (!this._open) { this._waiting.push(() => this._query(query, values).then(resolve, reject)); return; @@ -403,7 +397,7 @@ class DatabaseStore extends MailspringStore { // // Returns a {Query} // - find(klass, id) { + find(klass: typeof Model, id) { if (!klass) { throw new Error(`DatabaseStore::find - You must provide a class`); } @@ -512,9 +506,12 @@ class DatabaseStore extends MailspringStore { // Returns a {Promise} that // - resolves with the result of the database query. // - run(modelQuery: Query, options = { format: true }): Promise { + run(modelQuery: Query, options?: { format: boolean }): Promise; + run(modelQuery: Query, options: { format: false }): Promise; + + run(modelQuery: Query, options = { format: true }): Promise { return this._query(modelQuery.sql(), [], modelQuery._background).then(result => { - let transformed = modelQuery.inflateResult(result); + let transformed: any = modelQuery.inflateResult(result); if (options.format !== false) { transformed = modelQuery.formatResult(transformed); } diff --git a/app/src/flux/stores/draft-store.ts b/app/src/flux/stores/draft-store.ts index 4fc84666e..724f6c031 100644 --- a/app/src/flux/stores/draft-store.ts +++ b/app/src/flux/stores/draft-store.ts @@ -179,7 +179,7 @@ class DraftStore extends MailspringStore { // this is a fake status! for (const draft of drafts) { if (this._draftsSending[draft.headerMessageId]) { - const m = draft.metadataForPluginId('send-later'); + const m = draft.metadataForPluginId('send-later') as any; if (m && m.isUndoSend && !m.expiration) { delete this._draftsSending[draft.headerMessageId]; } diff --git a/app/src/flux/stores/feature-usage-store.tsx b/app/src/flux/stores/feature-usage-store.tsx index 72726d4d8..0ce579f40 100644 --- a/app/src/flux/stores/feature-usage-store.tsx +++ b/app/src/flux/stores/feature-usage-store.tsx @@ -3,7 +3,7 @@ import React from 'react'; import MailspringStore from 'mailspring-store'; import { FeatureUsedUpModal } from 'mailspring-component-kit'; import * as Actions from '../actions'; -import { IdentityStore } from './identity-store'; +import { IdentityStore, EMPTY_FEATURE_USAGE, IIdentity } from './identity-store'; import { SendFeatureUsageEventTask } from '../tasks/send-feature-usage-event-task'; import { localized } from '../../intl'; @@ -11,6 +11,12 @@ class NoProAccessError extends Error {} const UsageRecordedServerSide = ['contact-profiles', 'translation']; +export interface FeatureLexicon { + headerText: string; + rechargeText: string; + iconUrl: string; +} + /** * FeatureUsageStore is backed by the IdentityStore * @@ -48,7 +54,7 @@ const UsageRecordedServerSide = ['contact-profiles', 'translation']; * Valid periods are: * 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'unlimited' */ -class FeatureUsageStore extends MailspringStore { +class _FeatureUsageStore extends MailspringStore { _waitForModalClose = []; NoProAccessError = NoProAccessError; _disp: Rx.Disposable; @@ -72,10 +78,8 @@ class FeatureUsageStore extends MailspringStore { this._usub(); } - displayUpgradeModal(feature, lexicon) { - // + displayUpgradeModal(feature: string, lexicon: FeatureLexicon) { const featureData = this._dataForFeature(feature); - const { iconUrl } = lexicon; let { headerText, rechargeText } = lexicon; if (!featureData.quota) { @@ -84,7 +88,7 @@ class FeatureUsageStore extends MailspringStore { headerText = headerText || localized("You've reached your quota"); const time = featureData.period === 'monthly' ? localized('month') : localized('week'); - rechargeText = rechargeText.replace('%1$@', featureData.quota).replace('%2$@', time); + rechargeText = rechargeText.replace('%1$@', `${featureData.quota}`).replace('%2$@', time); } Actions.openModal({ @@ -94,7 +98,7 @@ class FeatureUsageStore extends MailspringStore { ), @@ -105,7 +109,7 @@ class FeatureUsageStore extends MailspringStore { }); } - isUsable(feature) { + isUsable(feature: string) { const { usedInPeriod, quota } = this._dataForFeature(feature); if (!quota) { return true; @@ -113,7 +117,7 @@ class FeatureUsageStore extends MailspringStore { return usedInPeriod < quota; } - async markUsedOrUpgrade(feature, lexicon = {}) { + async markUsedOrUpgrade(feature: string, lexicon: FeatureLexicon) { if (!this.isUsable(feature)) { // throws if the user declines await this.displayUpgradeModal(feature, lexicon); @@ -121,8 +125,8 @@ class FeatureUsageStore extends MailspringStore { this.markUsed(feature); } - markUsed(feature) { - const next = JSON.parse(JSON.stringify(IdentityStore.identity())); + markUsed(feature: string) { + const next: IIdentity = JSON.parse(JSON.stringify(IdentityStore.identity())); if (!next || !next.featureUsage) return; if (next.featureUsage[feature]) { @@ -145,18 +149,19 @@ class FeatureUsageStore extends MailspringStore { this._waitForModalClose = []; }; - _dataForFeature(feature) { + _dataForFeature(feature: string) { const identity = IdentityStore.identity(); if (!identity) { - return {}; + return EMPTY_FEATURE_USAGE; } + const usage = identity.featureUsage || {}; if (!usage[feature]) { AppEnv.reportError(new Error(`Warning: No usage information available for ${feature}`)); - return {}; + return EMPTY_FEATURE_USAGE; } return usage[feature]; } } -export default new FeatureUsageStore(); +export const FeatureUsageStore = new _FeatureUsageStore(); diff --git a/app/src/flux/stores/identity-store.ts b/app/src/flux/stores/identity-store.ts index 395fceefe..95d915729 100644 --- a/app/src/flux/stores/identity-store.ts +++ b/app/src/flux/stores/identity-store.ts @@ -19,8 +19,34 @@ export interface IIdentity { emailAddress: string; stripePlan: string; stripePlanEffective: string; + featureUsage: { + [featureKey: string]: { + featureLimitName: 'pro'; + usedInPeriod: number; + quota: number; + period: 'weekly' | 'monthly'; + }; + }; } +export const EMPTY_IDENTITY: IIdentity = { + id: '', + token: '', + firstName: '', + lastName: '', + emailAddress: '', + stripePlan: 'basic', + stripePlanEffective: '', + featureUsage: {}, +}; + +export const EMPTY_FEATURE_USAGE = { + featureLimitName: 'pro', + period: 'monthly', + usedInPeriod: 0, + quota: 0, +}; + class _IdentityStore extends MailspringStore { _identity: IIdentity = null; _disp: Disposable; @@ -133,7 +159,7 @@ class _IdentityStore extends MailspringStore { * for the full list of utm_ labels. */ async fetchSingleSignOnURL( - path, + path: string, { source, campaign, content }: { source?: string; campaign?: string; content?: string } = {} ) { if (!this._identity) { diff --git a/app/src/flux/stores/task-queue.ts b/app/src/flux/stores/task-queue.ts index 99c0c9514..36ddfeaf2 100644 --- a/app/src/flux/stores/task-queue.ts +++ b/app/src/flux/stores/task-queue.ts @@ -1,6 +1,6 @@ import _ from 'underscore'; import MailspringStore from 'mailspring-store'; -import { Rx, SyncbackDraftTask } from 'mailspring-exports'; +import { Rx } from 'mailspring-exports'; import { Task } from '../tasks/task'; import DatabaseStore from './database-store'; @@ -46,8 +46,8 @@ class TaskQueue extends MailspringStore { _completed: Task[] = []; _currentSequentialId = Date.now(); - _waitingForLocal: Array<{ task: Task; resolve: (arg: Task) => void }> = []; - _waitingForRemote: Array<{ task: Task; resolve: (arg: Task) => void }> = []; + _waitingForLocal: Array<{ task: Task; resolve: (arg: any) => void }> = []; + _waitingForRemote: Array<{ task: Task; resolve: (arg: any) => void }> = []; constructor() { super(); @@ -95,8 +95,12 @@ class TaskQueue extends MailspringStore { return [...this._queue, ...this._completed]; } - findTasks(typeOrClass, matching = {}, { includeCompleted }: { includeCompleted?: boolean } = {}) { - const type = typeOrClass instanceof String ? typeOrClass : typeOrClass.name; + findTasks( + typeOrClass: string | typeof Task, + matching = {}, + { includeCompleted }: { includeCompleted?: boolean } = {} + ) { + const type = typeof typeOrClass === 'string' ? typeOrClass : typeOrClass.name; const tasks = includeCompleted ? [...this._queue, ...this._completed] : this._queue; const matches = tasks.filter(task => { @@ -112,24 +116,24 @@ class TaskQueue extends MailspringStore { return matches; } - waitForPerformLocal = task => { + waitForPerformLocal = (task: T) => { const upToDateTask = [...this._queue, ...this._completed].find(t => t.id === task.id); if (upToDateTask && upToDateTask.hasRunLocally()) { - return Promise.resolve(upToDateTask); + return Promise.resolve(upToDateTask as T); } - return new Promise(resolve => { + return new Promise(resolve => { this._waitingForLocal.push({ task, resolve }); }); }; - waitForPerformRemote = task => { + waitForPerformRemote = (task: T) => { const upToDateTask = [...this._queue, ...this._completed].find(t => t.id === task.id); if (upToDateTask && upToDateTask.status === Task.Status.Complete) { - return Promise.resolve(upToDateTask); + return Promise.resolve(upToDateTask as T); } - return new Promise(resolve => { + return new Promise(resolve => { this._waitingForRemote.push({ task, resolve }); }); }; diff --git a/app/src/flux/stores/workspace-store.ts b/app/src/flux/stores/workspace-store.ts index cde680c1b..86b48b4eb 100644 --- a/app/src/flux/stores/workspace-store.ts +++ b/app/src/flux/stores/workspace-store.ts @@ -41,7 +41,7 @@ interface SheetToolbarDeclaration { export interface SheetDeclaration { id: string; - columns: string[]; + columns: { [mode: string]: { id: string; Toolbar: { id: string } }[] }; supportedModes: string[]; icon: string; @@ -52,7 +52,7 @@ export interface SheetDeclaration { Toolbar: SheetToolbarDeclaration; Header: { id: string }; Footer: { id: string }; - Center: { id: string }; + // Center: { id: string }; } /* @@ -293,23 +293,14 @@ class WorkspaceStore extends MailspringStore { // *`columns` An {Object} with keys for each layout mode the Sheet // supports. For each key, provide an array of column names. // - defineSheet(id, options: Partial = {}, columns = {}) { - // Make sure all the locations have definitions so that packages - // can register things into these locations and their toolbars. - for (const layout in columns) { - const cols = columns[layout]; - for (let idx = 0; idx < cols.length; idx++) { - const col = cols[idx]; - if (Location[col] == null) { - Location[col] = { id: `${col}`, Toolbar: { id: `${col}:Toolbar` } }; - } - cols[idx] = Location[col]; - } - } - + defineSheet( + id, + options: Partial = {}, + columns: { [mode: string]: string[] } = {} + ) { Sheet[id] = { id, - columns, + columns: {}, supportedModes: Object.keys(columns), icon: options.icon, @@ -325,6 +316,18 @@ class WorkspaceStore extends MailspringStore { Footer: { id: `Sheet:${id}:Footer` }, }; + // Make sure all the locations have definitions so that packages + // can register things into these locations and their toolbars. + for (const [mode, cols] of Object.entries(columns)) { + Sheet[id].columns[mode] = []; + for (const col of cols) { + if (Location[col] == null) { + Location[col] = { id: `${col}`, Toolbar: { id: `${col}:Toolbar` } }; + } + Sheet[id].columns[mode].push(Location[col]); + } + } + if (options.root && !this.rootSheet() && !(options as any).silent) { this._onSelectRootSheet(Sheet[id]); } diff --git a/app/src/flux/tasks/change-contactgroup-membership-task.ts b/app/src/flux/tasks/change-contactgroup-membership-task.ts index d7cb2d86f..64a22f4d8 100644 --- a/app/src/flux/tasks/change-contactgroup-membership-task.ts +++ b/app/src/flux/tasks/change-contactgroup-membership-task.ts @@ -1,5 +1,5 @@ import { Task } from './task'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { Contact } from '../models/contact'; import { ContactGroup } from '../models/contact-group'; import { AttributeValues } from '../models/model'; @@ -9,7 +9,7 @@ export class ChangeContactGroupMembershipTask extends Task { static attributes = { ...Task.attributes, - group: Attributes.Object({ + group: Attributes.Obj({ modelKey: 'group', itemClass: ContactGroup, }), diff --git a/app/src/flux/tasks/change-folder-task.ts b/app/src/flux/tasks/change-folder-task.ts index 5a6c205ab..328203488 100644 --- a/app/src/flux/tasks/change-folder-task.ts +++ b/app/src/flux/tasks/change-folder-task.ts @@ -1,5 +1,5 @@ import { ChangeMailTask } from './change-mail-task'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { Folder } from '../models/folder'; import { localized } from '../../intl'; import { Message } from '../models/Message'; @@ -21,11 +21,11 @@ export class ChangeFolderTask extends ChangeMailTask { static attributes = { ...ChangeMailTask.attributes, - previousFolder: Attributes.Object({ + previousFolder: Attributes.Obj({ modelKey: 'previousFolder', itemClass: Folder, }), - folder: Attributes.Object({ + folder: Attributes.Obj({ modelKey: 'folder', itemClass: Folder, }), diff --git a/app/src/flux/tasks/change-labels-task.ts b/app/src/flux/tasks/change-labels-task.ts index 5903c3e3a..67d3258c0 100644 --- a/app/src/flux/tasks/change-labels-task.ts +++ b/app/src/flux/tasks/change-labels-task.ts @@ -1,6 +1,6 @@ import { Label } from '../models/label'; import { ChangeMailTask } from './change-mail-task'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { localized } from '../../intl'; import { AttributeValues } from '../models/model'; import { Thread } from '../models/thread'; diff --git a/app/src/flux/tasks/change-mail-task.ts b/app/src/flux/tasks/change-mail-task.ts index 8abeb36e0..488da8a58 100644 --- a/app/src/flux/tasks/change-mail-task.ts +++ b/app/src/flux/tasks/change-mail-task.ts @@ -1,5 +1,5 @@ import { Task } from './task'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { Thread } from '../models/thread'; import { Message } from '../models/message'; import { AttributeValues } from '../models/model'; diff --git a/app/src/flux/tasks/change-role-mapping-task.ts b/app/src/flux/tasks/change-role-mapping-task.ts index c0da6559f..b9c7babac 100644 --- a/app/src/flux/tasks/change-role-mapping-task.ts +++ b/app/src/flux/tasks/change-role-mapping-task.ts @@ -1,5 +1,5 @@ import { Task } from './task'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { localized } from '../../intl'; import { AttributeValues } from '../models/model'; diff --git a/app/src/flux/tasks/change-starred-task.ts b/app/src/flux/tasks/change-starred-task.ts index 7b4599620..3ca829e7e 100644 --- a/app/src/flux/tasks/change-starred-task.ts +++ b/app/src/flux/tasks/change-starred-task.ts @@ -1,6 +1,6 @@ /* eslint no-unused-vars: 0*/ import _ from 'underscore'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { ChangeMailTask } from './change-mail-task'; import { localized } from '../../intl'; import { AttributeValues } from '../models/model'; diff --git a/app/src/flux/tasks/change-unread-task.ts b/app/src/flux/tasks/change-unread-task.ts index 514475a5e..567c46485 100644 --- a/app/src/flux/tasks/change-unread-task.ts +++ b/app/src/flux/tasks/change-unread-task.ts @@ -1,6 +1,6 @@ /* eslint no-unused-vars: 0*/ import _ from 'underscore'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { ChangeMailTask } from './change-mail-task'; import { localized } from '../../intl'; import { AttributeValues } from '../models/model'; diff --git a/app/src/flux/tasks/destroy-category-task.ts b/app/src/flux/tasks/destroy-category-task.ts index e45d4bd14..cdec56637 100644 --- a/app/src/flux/tasks/destroy-category-task.ts +++ b/app/src/flux/tasks/destroy-category-task.ts @@ -1,6 +1,6 @@ import utf7 from 'utf7'; import { Task } from './task'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { localized } from '../../intl'; import { AttributeValues } from '../models/model'; diff --git a/app/src/flux/tasks/destroy-contact-task.ts b/app/src/flux/tasks/destroy-contact-task.ts index 2472831e4..4e4fd9d7c 100644 --- a/app/src/flux/tasks/destroy-contact-task.ts +++ b/app/src/flux/tasks/destroy-contact-task.ts @@ -1,5 +1,5 @@ import { Task } from './task'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { Contact } from '../models/contact'; import { AttributeValues } from '../models/model'; import { localized } from 'mailspring-exports'; diff --git a/app/src/flux/tasks/destroy-contactgroup-task.ts b/app/src/flux/tasks/destroy-contactgroup-task.ts index 51fd5db67..8c613edc9 100644 --- a/app/src/flux/tasks/destroy-contactgroup-task.ts +++ b/app/src/flux/tasks/destroy-contactgroup-task.ts @@ -1,5 +1,5 @@ import { Task } from './task'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { ContactGroup } from '../models/contact-group'; import { AttributeValues } from '../models/model'; import { SyncbackContactGroupTask } from './syncback-contactgroup-task'; @@ -8,7 +8,7 @@ export class DestroyContactGroupTask extends Task { static attributes = { ...Task.attributes, - group: Attributes.Object({ + group: Attributes.Obj({ modelKey: 'group', itemClass: ContactGroup, }), diff --git a/app/src/flux/tasks/destroy-draft-task.ts b/app/src/flux/tasks/destroy-draft-task.ts index b31977466..ec57e15ae 100644 --- a/app/src/flux/tasks/destroy-draft-task.ts +++ b/app/src/flux/tasks/destroy-draft-task.ts @@ -1,5 +1,5 @@ import { Task } from './task'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { localized } from '../../intl'; import { AttributeValues } from '../models/model'; diff --git a/app/src/flux/tasks/event-rsvp-task.ts b/app/src/flux/tasks/event-rsvp-task.ts index 6d24fb649..05edd7beb 100644 --- a/app/src/flux/tasks/event-rsvp-task.ts +++ b/app/src/flux/tasks/event-rsvp-task.ts @@ -1,6 +1,6 @@ import { Task } from './task'; import { AttributeValues } from '../models/model'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { localized, ICSParticipantStatus, diff --git a/app/src/flux/tasks/expunge-all-in-folder-task.ts b/app/src/flux/tasks/expunge-all-in-folder-task.ts index d9ee0ba49..7c39622a6 100644 --- a/app/src/flux/tasks/expunge-all-in-folder-task.ts +++ b/app/src/flux/tasks/expunge-all-in-folder-task.ts @@ -1,6 +1,6 @@ import { Task } from './task'; import { Folder } from '../models/folder'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { localized } from '../../intl'; import { AttributeValues } from '../models/model'; @@ -8,7 +8,7 @@ export class ExpungeAllInFolderTask extends Task { static attributes = { ...Task.attributes, - folder: Attributes.Object({ + folder: Attributes.Obj({ modelKey: 'folder', itemClass: Folder, }), diff --git a/app/src/flux/tasks/get-message-rfc2822-task.ts b/app/src/flux/tasks/get-message-rfc2822-task.ts index bb3f10ab8..ba6ee485b 100644 --- a/app/src/flux/tasks/get-message-rfc2822-task.ts +++ b/app/src/flux/tasks/get-message-rfc2822-task.ts @@ -1,5 +1,5 @@ import { Task } from './task'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { AttributeValues } from '../models/model'; export class GetMessageRFC2822Task extends Task { diff --git a/app/src/flux/tasks/send-draft-task.ts b/app/src/flux/tasks/send-draft-task.ts index 2d6ff5347..9922a1530 100644 --- a/app/src/flux/tasks/send-draft-task.ts +++ b/app/src/flux/tasks/send-draft-task.ts @@ -2,7 +2,7 @@ import { AccountStore } from '../stores/account-store'; import { Task } from './task'; import * as Actions from '../actions'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { Message } from '../models/message'; import SoundRegistry from '../../registries/sound-registry'; import { Composer as ComposerExtensionRegistry } from '../../registries/extension-registry'; @@ -82,14 +82,14 @@ export class SendDraftTask extends Task { static attributes = { ...Task.attributes, - draft: Attributes.Object({ + draft: Attributes.Obj({ modelKey: 'draft', itemClass: Message, }), headerMessageId: Attributes.String({ modelKey: 'headerMessageId', }), - perRecipientBodies: Attributes.Object({ + perRecipientBodies: Attributes.Obj({ modelKey: 'perRecipientBodies', }), diff --git a/app/src/flux/tasks/send-feature-usage-event-task.ts b/app/src/flux/tasks/send-feature-usage-event-task.ts index 78cb45fc9..d3ac5041e 100644 --- a/app/src/flux/tasks/send-feature-usage-event-task.ts +++ b/app/src/flux/tasks/send-feature-usage-event-task.ts @@ -1,5 +1,5 @@ import { Task } from './task'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { AccountStore } from '../stores/account-store'; import { AttributeValues } from '../models/model'; diff --git a/app/src/flux/tasks/syncback-category-task.ts b/app/src/flux/tasks/syncback-category-task.ts index 9cd2a9664..fc8676622 100644 --- a/app/src/flux/tasks/syncback-category-task.ts +++ b/app/src/flux/tasks/syncback-category-task.ts @@ -1,6 +1,6 @@ import utf7 from 'utf7'; import { Task } from './task'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { localized } from '../../intl'; import { AttributeValues } from '../models/model'; @@ -14,7 +14,7 @@ export class SyncbackCategoryTask extends Task { existingPath: Attributes.String({ modelKey: 'existingPath', }), - created: Attributes.Object({ + created: Attributes.Obj({ modelKey: 'created', }), }; diff --git a/app/src/flux/tasks/syncback-contact-task.ts b/app/src/flux/tasks/syncback-contact-task.ts index 42003ab0f..e0f3e81e9 100644 --- a/app/src/flux/tasks/syncback-contact-task.ts +++ b/app/src/flux/tasks/syncback-contact-task.ts @@ -1,5 +1,5 @@ import { Task } from './task'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { Contact } from '../models/contact'; import { AttributeValues } from '../models/model'; @@ -7,7 +7,7 @@ export class SyncbackContactTask extends Task { static attributes = { ...Task.attributes, - contact: Attributes.Object({ + contact: Attributes.Obj({ modelKey: 'contact', itemClass: Contact, }), diff --git a/app/src/flux/tasks/syncback-contactgroup-task.ts b/app/src/flux/tasks/syncback-contactgroup-task.ts index a98a748cc..841a3b334 100644 --- a/app/src/flux/tasks/syncback-contactgroup-task.ts +++ b/app/src/flux/tasks/syncback-contactgroup-task.ts @@ -1,5 +1,5 @@ import { Task } from './task'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { ContactGroup } from '../models/contact-group'; import { AttributeValues } from '../models/model'; @@ -7,7 +7,7 @@ export class SyncbackContactGroupTask extends Task { static attributes = { ...Task.attributes, - group: Attributes.Object({ + group: Attributes.Obj({ modelKey: 'group', itemClass: ContactGroup, }), diff --git a/app/src/flux/tasks/syncback-draft-task.ts b/app/src/flux/tasks/syncback-draft-task.ts index 87684357b..43d1dee71 100644 --- a/app/src/flux/tasks/syncback-draft-task.ts +++ b/app/src/flux/tasks/syncback-draft-task.ts @@ -1,5 +1,5 @@ import { Task } from './task'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { Message } from '../models/message'; import { localized } from '../../intl'; import { AttributeValues } from '../models/model'; @@ -11,7 +11,7 @@ export class SyncbackDraftTask extends Task { headerMessageId: Attributes.String({ modelKey: 'headerMessageId', }), - draft: Attributes.Object({ + draft: Attributes.Obj({ modelKey: 'draft', itemClass: Message, }), diff --git a/app/src/flux/tasks/syncback-metadata-task.ts b/app/src/flux/tasks/syncback-metadata-task.ts index dab59b35b..6aa7da928 100644 --- a/app/src/flux/tasks/syncback-metadata-task.ts +++ b/app/src/flux/tasks/syncback-metadata-task.ts @@ -1,5 +1,5 @@ import { Task } from './task'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { Model, AttributeValues } from '../models/model'; export class SyncbackMetadataTask extends Task { @@ -54,10 +54,10 @@ export class SyncbackMetadataTask extends Task { modelHeaderMessageId: Attributes.String({ modelKey: 'modelHeaderMessageId', }), - value: Attributes.Object({ + value: Attributes.Obj({ modelKey: 'value', }), - undoValue: Attributes.Object({ + undoValue: Attributes.Obj({ modelKey: 'undoValue', }), }; diff --git a/app/src/flux/tasks/task.ts b/app/src/flux/tasks/task.ts index c4a1fb901..efd9f47d1 100644 --- a/app/src/flux/tasks/task.ts +++ b/app/src/flux/tasks/task.ts @@ -1,9 +1,8 @@ /* eslint no-unused-vars: 0*/ import _ from 'underscore'; import { Model } from '../models/model'; -import Attributes from '../attributes'; +import * as Attributes from '../attributes'; import { generateTempId } from '../models/utils'; -import { PermanentErrorCodes } from '../mailspring-api-request'; const Status = { Local: 'local', // means the task has NOT run the local phase yet @@ -14,7 +13,7 @@ const Status = { export class Task extends Model { static Status = Status; - static SubclassesUseModelTable = Task; + static SubclassesUseModelTable: typeof Model = Task; static attributes = { ...Model.attributes, @@ -26,7 +25,7 @@ export class Task extends Model { source: Attributes.String({ modelKey: 'source', }), - error: Attributes.Object({ + error: Attributes.Obj({ modelKey: 'error', }), }; diff --git a/app/src/global/mailspring-component-kit.d.ts b/app/src/global/mailspring-component-kit.d.ts index 73fb9b7a7..3d6981c9a 100644 --- a/app/src/global/mailspring-component-kit.d.ts +++ b/app/src/global/mailspring-component-kit.d.ts @@ -42,7 +42,8 @@ export const DropdownMenu: typeof import('../components/dropdown-menu').default; export const OutlineViewItem: typeof import('../components/outline-view-item').default; export * from '../components/outline-view'; export const DateInput: typeof import('../components/date-input').default; -export const DatePicker: typeof import('../components/date-picker').default; +export * from '../components/mini-month-view'; +export * from '../components/date-picker'; export const TimePicker: typeof import('../components/time-picker').default; export const Table: typeof import('../components/table/table').default; export const TableRow: typeof import('../components/table/table').TableRow; diff --git a/app/src/global/mailspring-component-kit.js b/app/src/global/mailspring-component-kit.js index 7ed9067e7..0c7a76f3c 100644 --- a/app/src/global/mailspring-component-kit.js +++ b/app/src/global/mailspring-component-kit.js @@ -101,6 +101,7 @@ lazyLoad('DropdownMenu', 'dropdown-menu'); lazyLoad('OutlineViewItem', 'outline-view-item'); lazyLoad('OutlineView', 'outline-view'); lazyLoad('DateInput', 'date-input'); +lazyLoad('MiniMonthView', 'mini-month-view'); lazyLoad('DatePicker', 'date-picker'); lazyLoad('TimePicker', 'time-picker'); lazyLoad('Table', 'table/table'); diff --git a/app/src/global/mailspring-exports.d.ts b/app/src/global/mailspring-exports.d.ts index 3aba002b3..f78bdc072 100644 --- a/app/src/global/mailspring-exports.d.ts +++ b/app/src/global/mailspring-exports.d.ts @@ -119,8 +119,7 @@ export const WorkspaceStore: WorkspaceStore; export type MailRulesStore = typeof import('../flux/stores/mail-rules-store').default; export const MailRulesStore: MailRulesStore; export * from '../flux/stores/send-actions-store'; -export type FeatureUsageStore = typeof import('../flux/stores/feature-usage-store').default; -export const FeatureUsageStore: FeatureUsageStore; +export * from '../flux/stores/feature-usage-store'; export type ThreadCountsStore = typeof import('../flux/stores/thread-counts-store').default; export const ThreadCountsStore: ThreadCountsStore; export type AttachmentStore = typeof import('../flux/stores/attachment-store').default; @@ -200,6 +199,7 @@ export type MessageUtils = typeof import('../flux/models/message-utils').default export const MessageUtils: MessageUtils; // Services +export * from '../services/autolinker'; export type KeyManager = typeof import('../key-manager').default; export const KeyManager: KeyManager; export type SoundRegistry = typeof import('../registries/sound-registry').default; diff --git a/app/src/global/mailspring-exports.js b/app/src/global/mailspring-exports.js index f3d6c888c..89639f10b 100644 --- a/app/src/global/mailspring-exports.js +++ b/app/src/global/mailspring-exports.js @@ -194,6 +194,7 @@ lazyLoad(`MailRulesTemplates`, 'mail-rules-templates'); lazyLoad(`MailRulesProcessor`, 'mail-rules-processor'); lazyLoad(`MailboxPerspective`, 'mailbox-perspective'); lazyLoad(`NativeNotifications`, 'native-notifications'); +lazyLoad(`Autolink`, 'services/autolinker'); lazyLoad(`SanitizeTransformer`, 'services/sanitize-transformer'); lazyLoad(`QuotedHTMLTransformer`, 'services/quoted-html-transformer'); lazyLoad(`InlineStyleTransformer`, 'services/inline-style-transformer'); diff --git a/app/src/global/mailspring-observables.ts b/app/src/global/mailspring-observables.ts index 44dafbbdb..ee088c9d2 100644 --- a/app/src/global/mailspring-observables.ts +++ b/app/src/global/mailspring-observables.ts @@ -8,6 +8,7 @@ import { Model } from '../flux/models/model'; import ModelQuery from '../flux/models/query'; import MailspringStore from 'mailspring-store'; import { Category } from '../flux/models/category'; +import { QueryResultSet } from '../flux/models/query-result-set'; interface ICategoryOperators { sort(): Observable & ICategoryOperators; @@ -44,7 +45,7 @@ const CategoryOperators = { const CategoryObservables = { forAllAccounts() { const folders = Rx.Observable.fromQuery(DatabaseStore.findAll(Folder)); - const labels = Rx.Observable.fromQuery(DatabaseStore.findAll