Merge branch 'release/3.5.15'
This commit is contained in:
13
.eslintrc
13
.eslintrc
@@ -1,11 +1,16 @@
|
||||
{
|
||||
"extends": ["eslint:recommended", "semistandard", "prettier"],
|
||||
"parser": "babel-eslint",
|
||||
"extends": ["eslint:recommended", "prettier"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "./app/assets/javascripts/tsconfig.json"
|
||||
},
|
||||
"rules": {
|
||||
"standard/no-callback-literal": 0, // Disable this as we have too many callbacks relying on literals
|
||||
"no-throw-literal": 0,
|
||||
// "no-console": "error",
|
||||
"semi": 1
|
||||
"no-console": "off",
|
||||
"semi": 1,
|
||||
"camelcase": "warn",
|
||||
"sort-imports": "off"
|
||||
},
|
||||
"env": {
|
||||
"browser": true
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -68,7 +68,6 @@ type AccountMenuState = {
|
||||
errorReportingEnabled: boolean;
|
||||
syncInProgress: boolean;
|
||||
syncError: string;
|
||||
syncPercentage: string;
|
||||
showSessions: boolean;
|
||||
}
|
||||
|
||||
@@ -101,7 +100,7 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
||||
},
|
||||
mutable: {},
|
||||
showBetaWarning: false,
|
||||
errorReportingEnabled: !storage.get(StorageKey.DisableErrorReporting),
|
||||
errorReportingEnabled: storage.get(StorageKey.DisableErrorReporting) === false,
|
||||
showSessions: false,
|
||||
} as AccountMenuState;
|
||||
}
|
||||
@@ -143,7 +142,6 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
||||
this.setState({
|
||||
syncInProgress: sync.inProgress,
|
||||
syncError: sync.errorMessage,
|
||||
syncPercentage: sync.humanReadablePercentage,
|
||||
});
|
||||
})
|
||||
this.removeBetaWarningListener = autorun(() => {
|
||||
@@ -636,7 +634,7 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
|
||||
} else {
|
||||
storage.set(StorageKey.DisableErrorReporting, false);
|
||||
}
|
||||
if (!this.application.getSyncStatus().syncInProgress) {
|
||||
if (!this.state.syncInProgress) {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { AppState } from '@/ui_models/app_state';
|
||||
import { PureViewCtrl } from '@/views';
|
||||
import { SNApplication, RemoteSession, UuidString } from '@standardnotes/snjs';
|
||||
import {
|
||||
SNApplication,
|
||||
RemoteSession,
|
||||
SessionStrings,
|
||||
UuidString,
|
||||
} from '@standardnotes/snjs';
|
||||
import { autorun, IAutorunOptions, IReactionPublic } from 'mobx';
|
||||
import { render, FunctionComponent } from 'preact';
|
||||
import { useState, useEffect, useRef } from 'preact/hooks';
|
||||
@@ -16,16 +21,20 @@ function useAutorun(view: (r: IReactionPublic) => any, opts?: IAutorunOptions) {
|
||||
useEffect(() => autorun(view, opts), []);
|
||||
}
|
||||
|
||||
type Session = RemoteSession & {
|
||||
revoking?: true;
|
||||
};
|
||||
|
||||
function useSessions(
|
||||
application: SNApplication
|
||||
): [
|
||||
RemoteSession[],
|
||||
Session[],
|
||||
() => void,
|
||||
boolean,
|
||||
(uuid: UuidString) => Promise<void>,
|
||||
string
|
||||
] {
|
||||
const [sessions, setSessions] = useState<RemoteSession[]>([]);
|
||||
const [sessions, setSessions] = useState<Session[]>([]);
|
||||
const [lastRefreshDate, setLastRefreshDate] = useState(Date.now());
|
||||
const [refreshing, setRefreshing] = useState(true);
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
@@ -41,7 +50,7 @@ function useSessions(
|
||||
setErrorMessage('An unknown error occured while loading sessions.');
|
||||
}
|
||||
} else {
|
||||
const sessions = response as RemoteSession[];
|
||||
const sessions = response as Session[];
|
||||
setSessions(sessions);
|
||||
setErrorMessage('');
|
||||
}
|
||||
@@ -54,9 +63,21 @@ function useSessions(
|
||||
}
|
||||
|
||||
async function revokeSession(uuid: UuidString) {
|
||||
const responsePromise = application.revokeSession(uuid);
|
||||
|
||||
let sessionsBeforeRevoke = sessions;
|
||||
setSessions(sessions.filter((session) => session.uuid !== uuid));
|
||||
const response = await application.revokeSession(uuid);
|
||||
|
||||
const sessionsDuringRevoke = sessions.slice();
|
||||
const toRemoveIndex = sessions.findIndex(
|
||||
(session) => session.uuid === uuid
|
||||
);
|
||||
sessionsDuringRevoke[toRemoveIndex] = {
|
||||
...sessionsDuringRevoke[toRemoveIndex],
|
||||
revoking: true,
|
||||
};
|
||||
setSessions(sessionsDuringRevoke);
|
||||
|
||||
const response = await responsePromise;
|
||||
if ('error' in response) {
|
||||
if (response.error?.message) {
|
||||
setErrorMessage(response.error?.message);
|
||||
@@ -64,6 +85,8 @@ function useSessions(
|
||||
setErrorMessage('An unknown error occured while revoking the session.');
|
||||
}
|
||||
setSessions(sessionsBeforeRevoke);
|
||||
} else {
|
||||
setSessions(sessions.filter((session) => session.uuid !== uuid));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +107,7 @@ const SessionsModal: FunctionComponent<{
|
||||
errorMessage,
|
||||
] = useSessions(application);
|
||||
|
||||
const [revokingSessionUuid, setRevokingSessionUuid] = useState('');
|
||||
const [confirmRevokingSessionUuid, setRevokingSessionUuid] = useState('');
|
||||
const closeRevokeSessionAlert = () => setRevokingSessionUuid('');
|
||||
const cancelRevokeRef = useRef<HTMLButtonElement>();
|
||||
|
||||
@@ -146,6 +169,7 @@ const SessionsModal: FunctionComponent<{
|
||||
</p>
|
||||
<button
|
||||
className="sk-button danger sk-label"
|
||||
disabled={session.revoking}
|
||||
onClick={() =>
|
||||
setRevokingSessionUuid(session.uuid)
|
||||
}
|
||||
@@ -165,7 +189,7 @@ const SessionsModal: FunctionComponent<{
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
{revokingSessionUuid && (
|
||||
{confirmRevokingSessionUuid && (
|
||||
<AlertDialog leastDestructiveRef={cancelRevokeRef}>
|
||||
<div className="sk-modal-content">
|
||||
<div className="sn-component">
|
||||
@@ -173,14 +197,10 @@ const SessionsModal: FunctionComponent<{
|
||||
<div className="sk-panel-content">
|
||||
<div className="sk-panel-section">
|
||||
<AlertDialogLabel className="sk-h3 sk-panel-section-title">
|
||||
Revoke this session?
|
||||
{SessionStrings.RevokeTitle}
|
||||
</AlertDialogLabel>
|
||||
<AlertDialogDescription className="sk-panel-row">
|
||||
<p>
|
||||
The associated app will be signed out and all data
|
||||
removed from the device when it is next launched. You
|
||||
can sign back in on that device at any time.
|
||||
</p>
|
||||
<p>{SessionStrings.RevokeText}</p>
|
||||
</AlertDialogDescription>
|
||||
<div className="sk-panel-row">
|
||||
<div className="sk-button-group">
|
||||
@@ -189,16 +209,16 @@ const SessionsModal: FunctionComponent<{
|
||||
ref={cancelRevokeRef}
|
||||
onClick={closeRevokeSessionAlert}
|
||||
>
|
||||
<span>Cancel</span>
|
||||
<span>{SessionStrings.RevokeCancelButton}</span>
|
||||
</button>
|
||||
<button
|
||||
className="sk-button danger sk-label"
|
||||
onClick={() => {
|
||||
closeRevokeSessionAlert();
|
||||
revokeSession(revokingSessionUuid);
|
||||
revokeSession(confirmRevokingSessionUuid);
|
||||
}}
|
||||
>
|
||||
<span>Revoke</span>
|
||||
<span>{SessionStrings.RevokeConfirmButton}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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'
|
||||
);
|
||||
|
||||
@@ -9,8 +9,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 {
|
||||
@@ -19,8 +19,9 @@ export class DesktopManager extends ApplicationService {
|
||||
$timeout: ng.ITimeoutService
|
||||
componentActivationObservers: ComponentActivationObserver[] = []
|
||||
updateObservers: {
|
||||
callback: UpdateObserverCallback
|
||||
}[] = []
|
||||
callback: UpdateObserverCallback;
|
||||
}[] = [];
|
||||
|
||||
isDesktop = isDesktopApplication();
|
||||
|
||||
dataLoaded = false
|
||||
@@ -156,7 +157,7 @@ export class DesktopManager extends ApplicationService {
|
||||
undefined
|
||||
);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.$timeout(() => {
|
||||
for (const observer of this.updateObservers) {
|
||||
@@ -187,12 +188,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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -15,9 +15,9 @@ import {
|
||||
} from '@/strings';
|
||||
|
||||
type InputValue = {
|
||||
prompt: ChallengePrompt
|
||||
value: string
|
||||
invalid: boolean
|
||||
prompt: ChallengePrompt;
|
||||
value: string;
|
||||
invalid: boolean;
|
||||
}
|
||||
|
||||
type Values = Record<number, InputValue>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
position: relative;
|
||||
overflow: unset;
|
||||
flex-basis: 0;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
[data-reach-dialog-content] .sk-modal-content,
|
||||
|
||||
@@ -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()"
|
||||
|
||||
@@ -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",
|
||||
@@ -71,7 +71,7 @@
|
||||
"@reach/alert-dialog": "^0.12.1",
|
||||
"@reach/dialog": "^0.12.1",
|
||||
"@standardnotes/sncrypto-web": "^1.2.9",
|
||||
"@standardnotes/snjs": "^2.0.32",
|
||||
"@standardnotes/snjs": "^2.0.38",
|
||||
"babel-loader": "^8.2.2",
|
||||
"mobx": "^6.0.4",
|
||||
"preact": "^10.5.7"
|
||||
|
||||
@@ -1045,10 +1045,10 @@
|
||||
"@standardnotes/sncrypto-common" "^1.2.7"
|
||||
libsodium-wrappers "^0.7.8"
|
||||
|
||||
"@standardnotes/snjs@^2.0.32":
|
||||
version "2.0.32"
|
||||
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.0.32.tgz#7742e3cb08ee560b5b7cf06e526ebc19ff2a81ba"
|
||||
integrity sha512-SnbOqGwx5H59HjFSzBKT/1Wh2zAAcx+QSQeYDyKjZKNYN0g8JvirzL0FpoLTxa15BRTl2mG073eIyFsEtx8LFA==
|
||||
"@standardnotes/snjs@^2.0.38":
|
||||
version "2.0.38"
|
||||
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.0.38.tgz#3edbdc6c04926f2f4940aea8ebb6c7007f6d4235"
|
||||
integrity sha512-cr+jdtDOyrwKlad/SRCy/D/cIsEQdShEsuazSIajpvXhwo9ixkj/wv/o0r3GrHug6CgXLOQRgzuZ9eZvi0PM9g==
|
||||
dependencies:
|
||||
"@standardnotes/sncrypto-common" "^1.2.9"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user