diff --git a/.eslintrc b/.eslintrc index 0f893399a..c88737d79 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,10 +1,16 @@ { "extends": ["eslint:recommended", "prettier"], "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "./app/assets/javascripts/tsconfig.json" + }, "rules": { - "standard/no-callback-literal": "off", // Disable this as we have too many callbacks relying on literals - "no-throw-literal": "off", - "camelcase": "off" + "standard/no-callback-literal": 0, // Disable this as we have too many callbacks relying on literals + "no-throw-literal": 0, + "no-console": "off", + "semi": 1, + "camelcase": "warn", + "sort-imports": "off" }, "env": { "browser": true diff --git a/app/assets/javascripts/app.ts b/app/assets/javascripts/app.ts index 69cb4fcab..4051a7883 100644 --- a/app/assets/javascripts/app.ts +++ b/app/assets/javascripts/app.ts @@ -59,10 +59,35 @@ import { StartApplication } from './startApplication'; import { Bridge } from './services/bridge'; import { SessionsModalDirective } from './directives/views/sessionsModal'; + +function reloadHiddenFirefoxTab(): boolean { + /** + * For Firefox pinned tab issue: + * When a new browser session is started, and SN is in a pinned tab, + * SN exhibits strange behavior until the tab is reloaded. + */ + if ( + document.hidden && + navigator.userAgent.toLowerCase().includes('firefox') && + !localStorage.getItem('reloading') + ) { + localStorage.setItem('reloading', 'true'); + location.reload(); + return true; + } else { + localStorage.removeItem('reloading'); + return false; + } +} + const startApplication: StartApplication = async function startApplication( defaultSyncServerHost: string, bridge: Bridge ) { + if (reloadHiddenFirefoxTab()) { + return; + } + SNLog.onLog = console.log; startErrorReporting(); diff --git a/app/assets/javascripts/directives/views/accountMenu.ts b/app/assets/javascripts/directives/views/accountMenu.ts index 17ddd2cf0..737799ce9 100644 --- a/app/assets/javascripts/directives/views/accountMenu.ts +++ b/app/assets/javascripts/directives/views/accountMenu.ts @@ -65,7 +65,6 @@ type AccountMenuState = { errorReportingEnabled: boolean; syncInProgress: boolean; syncError: string; - syncPercentage: string; showSessions: boolean; } @@ -98,7 +97,7 @@ class AccountMenuCtrl extends PureViewCtrl { }, mutable: {}, showBetaWarning: false, - errorReportingEnabled: !storage.get(StorageKey.DisableErrorReporting), + errorReportingEnabled: storage.get(StorageKey.DisableErrorReporting) === false, showSessions: false, } as AccountMenuState; } @@ -140,7 +139,6 @@ class AccountMenuCtrl extends PureViewCtrl { this.setState({ syncInProgress: sync.inProgress, syncError: sync.errorMessage, - syncPercentage: sync.humanReadablePercentage, }); }) this.removeBetaWarningListener = autorun(() => { @@ -556,7 +554,7 @@ class AccountMenuCtrl extends PureViewCtrl { } else { storage.set(StorageKey.DisableErrorReporting, false); } - if (!this.application.getSyncStatus().syncInProgress) { + if (!this.state.syncInProgress) { window.location.reload(); } } diff --git a/app/assets/javascripts/services/archiveManager.ts b/app/assets/javascripts/services/archiveManager.ts index c1b183dbd..eaa800f3e 100644 --- a/app/assets/javascripts/services/archiveManager.ts +++ b/app/assets/javascripts/services/archiveManager.ts @@ -1,5 +1,5 @@ import { WebApplication } from '@/ui_models/application'; -import { EncryptionIntent, ProtectedAction, SNItem, ContentType, SNNote } from '@standardnotes/snjs'; +import { EncryptionIntent, ProtectedAction, SNItem, ContentType, SNNote, BackupFile } from '@standardnotes/snjs'; function zippableTxtName(name: string, suffix = ""): string { const sanitizedName = name @@ -22,22 +22,27 @@ export class ArchiveManager { } public async downloadBackup(encrypted: boolean) { - const items = this.application.allItems(); - const run = async () => { - // download in Standard Notes format const intent = encrypted ? EncryptionIntent.FileEncrypted : EncryptionIntent.FileDecrypted; + + const data = await this.application.createBackupFile(intent); + if (!data) { + return; + } + const blobData = new Blob( + [JSON.stringify(data, null, 2)], + { type: 'text/json' } + ); if (encrypted) { - const data = await this.itemsData(items, intent); this.downloadData( - data!, + blobData, `Standard Notes Encrypted Backup and Import File - ${this.formattedDate()}.txt` ); } else { /** download as zipped plain text files */ - this.downloadZippedItems(items); + this.downloadZippedDecryptedItems(data); } }; @@ -65,15 +70,6 @@ export class ArchiveManager { return string; } - private async itemsData(items: SNItem[], intent: EncryptionIntent) { - const data = await this.application.createBackupFile(items, intent); - if (!data) { - return undefined; - } - const blobData = new Blob([data], { type: 'text/json' }); - return blobData; - } - private get zip() { return (window as any).zip; } @@ -95,17 +91,19 @@ export class ArchiveManager { }); } - private async downloadZippedItems( - items: SNItem[] + private async downloadZippedDecryptedItems( + data: BackupFile ) { await this.loadZip(); + const items = data.items; this.zip.createWriter( new this.zip.BlobWriter('application/zip'), async (zipWriter: any) => { - - const data = await this.application.createBackupFile(items, EncryptionIntent.FileDecrypted); await new Promise((resolve) => { - const blob = new Blob([data!], { type: 'text/plain' }); + const blob = new Blob( + [JSON.stringify(data, null, 2)], + { type: 'text/plain' } + ); const fileName = zippableTxtName( 'Standard Notes Backup and Import File.txt' ); diff --git a/app/assets/javascripts/services/desktopManager.ts b/app/assets/javascripts/services/desktopManager.ts index 057c20c3c..3fb873bfb 100644 --- a/app/assets/javascripts/services/desktopManager.ts +++ b/app/assets/javascripts/services/desktopManager.ts @@ -17,8 +17,8 @@ import { Bridge } from './bridge'; type UpdateObserverCallback = (component: SNComponent) => void type ComponentActivationCallback = (payload: PurePayload) => void type ComponentActivationObserver = { - id: string, - callback: ComponentActivationCallback + id: string; + callback: ComponentActivationCallback; } export class DesktopManager extends ApplicationService { @@ -27,7 +27,7 @@ export class DesktopManager extends ApplicationService { $timeout: ng.ITimeoutService componentActivationObservers: ComponentActivationObserver[] = [] updateObservers: { - callback: UpdateObserverCallback + callback: UpdateObserverCallback; }[] = []; isDesktop = isDesktopApplication(); @@ -165,7 +165,7 @@ export class DesktopManager extends ApplicationService { undefined ); } - }) + }); this.$timeout(() => { for (const observer of this.updateObservers) { @@ -196,12 +196,11 @@ export class DesktopManager extends ApplicationService { }); } - desktop_requestBackupFile() { - return this.application!.createBackupFile( - undefined, - undefined, - true - ); + async desktop_requestBackupFile() { + const data = this.application!.createBackupFile(EncryptionIntent.FileEncrypted); + if (data) { + return JSON.stringify(data, null, 2); + } } desktop_didBeginBackup() { diff --git a/app/assets/javascripts/services/errorReporting.ts b/app/assets/javascripts/services/errorReporting.ts index 50c439db5..0a309e3ab 100644 --- a/app/assets/javascripts/services/errorReporting.ts +++ b/app/assets/javascripts/services/errorReporting.ts @@ -1,4 +1,4 @@ -import { SNLog } from '@standardnotes/snjs'; +import { isNullOrUndefined, SNLog } from '@standardnotes/snjs'; import { isDesktopApplication, isDev } from '@/utils'; import { storage, StorageKey } from './localStorage'; import Bugsnag from '@bugsnag/js'; @@ -6,6 +6,7 @@ import Bugsnag from '@bugsnag/js'; declare const __VERSION__: string; declare global { interface Window { + // eslint-disable-next-line camelcase _bugsnag_api_key?: string; } } @@ -21,8 +22,15 @@ function redactFilePath(line: string): string { } export function startErrorReporting() { + const disableErrorReporting = storage.get(StorageKey.DisableErrorReporting); if ( - storage.get(StorageKey.DisableErrorReporting) || + /** + * Error reporting used to be opt-out, but is now opt-in, so + * treat the absence of an error reporting preference as an indication + * to disable error reporting. + */ + isNullOrUndefined(disableErrorReporting) || + disableErrorReporting || !window._bugsnag_api_key ) { SNLog.onError = console.error; diff --git a/app/assets/stylesheets/_reach-sub.scss b/app/assets/stylesheets/_reach-sub.scss index b6a9446cf..af80a7ccf 100644 --- a/app/assets/stylesheets/_reach-sub.scss +++ b/app/assets/stylesheets/_reach-sub.scss @@ -25,6 +25,7 @@ position: relative; overflow: unset; flex-basis: 0; + max-width: 600px; } [data-reach-dialog-content] .sk-modal-content, diff --git a/app/assets/templates/directives/account-menu.pug b/app/assets/templates/directives/account-menu.pug index 1cc5892d0..585f1c6fb 100644 --- a/app/assets/templates/directives/account-menu.pug +++ b/app/assets/templates/directives/account-menu.pug @@ -150,16 +150,6 @@ .sk-panel-column .sk-h1.sk-bold.wrap {{self.state.user.email}} .sk-subtitle.subtle.normal {{self.state.server}} - .sk-horizontal-group( - delay='1000', - delay-hide='true', - show='self.state.syncInProgress' - ) - .sk-spinner.small.info - .sk-sublabel - | Syncing - span(ng-if='self.state.syncPercentage') - | ({{self.state.syncPercentage}}) .sk-panel-row a.sk-a.info.sk-panel-row.condensed( ng-click="self.openPasswordWizard()" diff --git a/package.json b/package.json index 805665532..7fd2c0e2c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "standard-notes-web", - "version": "3.5.14", + "version": "3.5.15", "license": "AGPL-3.0-or-later", "repository": { "type": "git",