feat: snjs app groups (#468)

* feat: snjs app groups

* fix: update snjs version to point to wip commit

* wip: account switcher

* feat: rename lock manager to auto lock service

* fix: more relevant sign out copy

* chore(deps): update snjs

* fix: use setTimeout instead of setImmediate

* feat: make account switcher expiremental feature

* chore(deps): upgrade snjs
This commit is contained in:
Mo Bitar
2020-09-15 10:55:32 -05:00
committed by GitHub
parent ae6ef50f88
commit 2b6abeebfc
46 changed files with 590 additions and 375 deletions

View File

@@ -7,6 +7,7 @@ import angular from 'angular';
import { configRoutes } from './routes'; import { configRoutes } from './routes';
import { ApplicationGroup } from './ui_models/application_group'; import { ApplicationGroup } from './ui_models/application_group';
import { AccountSwitcher } from './views/account_switcher/account_switcher';
import { import {
ApplicationGroupView, ApplicationGroupView,
@@ -104,6 +105,7 @@ function startApplication(
angular angular
.module('app') .module('app')
.directive('accountMenu', () => new AccountMenu()) .directive('accountMenu', () => new AccountMenu())
.directive('accountSwitcher', () => new AccountSwitcher())
.directive('actionsMenu', () => new ActionsMenu()) .directive('actionsMenu', () => new ActionsMenu())
.directive('challengeModal', () => new ChallengeModal()) .directive('challengeModal', () => new ChallengeModal())
.directive('componentModal', () => new ComponentModal()) .directive('componentModal', () => new ComponentModal())

View File

@@ -1,6 +1,5 @@
import { SNAlertService } from "@node_modules/snjs/dist/@types"; import { SNAlertService } from "snjs/dist/@types";
const DB_NAME = 'standardnotes';
const STORE_NAME = 'items'; const STORE_NAME = 'items';
const READ_WRITE = 'readwrite'; const READ_WRITE = 'readwrite';
@@ -17,18 +16,18 @@ const DB_DELETION_BLOCKED =
const QUOTE_EXCEEDED_ERROR = 'QuotaExceededError'; const QUOTE_EXCEEDED_ERROR = 'QuotaExceededError';
export class Database { export class Database {
private locked = true private locked = true
private alertService?: SNAlertService
private db?: IDBDatabase private db?: IDBDatabase
public deinit() { constructor(
this.alertService = undefined; public databaseName: string,
this.db = undefined; private alertService: SNAlertService) {
} }
public setAlertService(alertService: SNAlertService) { public deinit() {
this.alertService = alertService; (this.alertService as any) = undefined;
this.db = undefined;
} }
/** /**
@@ -51,7 +50,7 @@ export class Database {
if (this.db) { if (this.db) {
return this.db; return this.db;
} }
const request = window.indexedDB.open(DB_NAME, 1); const request = window.indexedDB.open(this.databaseName, 1);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
request.onerror = (event) => { request.onerror = (event) => {
const target = event!.target! as any; const target = event!.target! as any;
@@ -181,7 +180,7 @@ export class Database {
} }
public async clearAllPayloads(): Promise<void> { public async clearAllPayloads(): Promise<void> {
const deleteRequest = window.indexedDB.deleteDatabase(DB_NAME); const deleteRequest = window.indexedDB.deleteDatabase(this.databaseName);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
deleteRequest.onerror = () => { deleteRequest.onerror = () => {
reject(Error('Error deleting database.')); reject(Error('Error deleting database.'));

View File

@@ -7,9 +7,9 @@ export function selectOnFocus($window: ng.IWindowService) {
if (!$window.getSelection()!.toString()) { if (!$window.getSelection()!.toString()) {
const input = element[0] as HTMLInputElement; const input = element[0] as HTMLInputElement;
/** Allow text to populate */ /** Allow text to populate */
setImmediate(() => { setTimeout(() => {
input.setSelectionRange(0, input.value.length); input.setSelectionRange(0, input.value.length);
}) }, 0);
} }
}); });
} }

View File

@@ -70,7 +70,8 @@ type AccountMenuState = {
class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> { class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
public appVersion: string public appVersion: string
private syncStatus?: SyncOpStatus /** @template */
syncStatus?: SyncOpStatus
private closeFunction?: () => void private closeFunction?: () => void
/* @ngInject */ /* @ngInject */
@@ -86,7 +87,7 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
getInitialState() { getInitialState() {
return { return {
appVersion: 'v' + ((window as any).electronAppVersion || this.appVersion), appVersion: 'v' + ((window as any).electronAppVersion || this.appVersion),
passcodeAutoLockOptions: this.application!.getLockService().getAutoLockIntervalOptions(), passcodeAutoLockOptions: this.application!.getAutolockService().getAutoLockIntervalOptions(),
user: this.application!.getUser(), user: this.application!.getUser(),
formData: { formData: {
mergeLocal: true, mergeLocal: true,
@@ -463,7 +464,7 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
} }
async reloadAutoLockInterval() { async reloadAutoLockInterval() {
const interval = await this.application!.getLockService().getAutoLockInterval(); const interval = await this.application!.getAutolockService().getAutoLockInterval();
this.setState({ this.setState({
selectedAutoLockInterval: interval selectedAutoLockInterval: interval
}); });
@@ -471,7 +472,7 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
async selectAutoLockInterval(interval: number) { async selectAutoLockInterval(interval: number) {
const run = async () => { const run = async () => {
await this.application!.getLockService().setAutoLockInterval(interval); await this.application!.getAutolockService().setAutoLockInterval(interval);
this.reloadAutoLockInterval(); this.reloadAutoLockInterval();
}; };
const needsPrivilege = await this.application!.privilegesService!.actionRequiresPrivilege( const needsPrivilege = await this.application!.privilegesService!.actionRequiresPrivilege(

View File

@@ -4,7 +4,7 @@ import { SNComponent, SNItem, ComponentArea } from 'snjs';
import { isDesktopApplication } from '@/utils'; import { isDesktopApplication } from '@/utils';
import template from '%/directives/editor-menu.pug'; import template from '%/directives/editor-menu.pug';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { ComponentMutator } from '@node_modules/snjs/dist/@types/models'; import { ComponentMutator } from 'snjs/dist/@types/models';
interface EditorMenuScope { interface EditorMenuScope {
callback: (component: SNComponent) => void callback: (component: SNComponent) => void

View File

@@ -1,7 +1,7 @@
import { WebDirective } from '../../types'; import { WebDirective } from '../../types';
import { WebApplication } from '@/ui_models/application'; import { WebApplication } from '@/ui_models/application';
import template from '%/directives/history-menu.pug'; import template from '%/directives/history-menu.pug';
import { SNItem, ItemHistoryEntry } from '@node_modules/snjs/dist/@types'; import { SNItem, ItemHistoryEntry } from 'snjs/dist/@types';
import { PureViewCtrl } from '@/views'; import { PureViewCtrl } from '@/views';
import { ItemSessionHistory } from 'snjs/dist/@types/services/history/session/item_session_history'; import { ItemSessionHistory } from 'snjs/dist/@types/services/history/session/item_session_history';
import { RemoteHistoryList, RemoteHistoryListEntry } from 'snjs/dist/@types/services/history/history_manager'; import { RemoteHistoryList, RemoteHistoryListEntry } from 'snjs/dist/@types/services/history/history_manager';

View File

@@ -3,7 +3,7 @@ import { WebApplication } from '@/ui_models/application';
import template from '%/directives/privileges-management-modal.pug'; import template from '%/directives/privileges-management-modal.pug';
import { PrivilegeCredential, ProtectedAction, SNPrivileges, PrivilegeSessionLength } from 'snjs'; import { PrivilegeCredential, ProtectedAction, SNPrivileges, PrivilegeSessionLength } from 'snjs';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { PrivilegeMutator } from '@node_modules/snjs/dist/@types/models'; import { PrivilegeMutator } from 'snjs/dist/@types/models';
type DisplayInfo = { type DisplayInfo = {
label: string label: string

View File

@@ -8,7 +8,7 @@ import {
ComponentArea ComponentArea
} from 'snjs'; } from 'snjs';
import template from '%/directives/revision-preview-modal.pug'; import template from '%/directives/revision-preview-modal.pug';
import { PayloadContent } from '@node_modules/snjs/dist/@types/protocol/payloads/generator'; import { PayloadContent } from 'snjs/dist/@types/protocol/payloads/generator';
import { confirmDialog } from '@/services/alertService'; import { confirmDialog } from '@/services/alertService';
interface RevisionPreviewScope { interface RevisionPreviewScope {

View File

@@ -1,3 +1,4 @@
import { ApplicationGroup } from './../ui_models/application_group';
import { WebApplication } from '@/ui_models/application'; import { WebApplication } from '@/ui_models/application';
import { isDesktopApplication } from '@/utils'; import { isDesktopApplication } from '@/utils';
import { AppStateEvent } from '@/ui_models/app_state'; import { AppStateEvent } from '@/ui_models/app_state';
@@ -12,7 +13,7 @@ const LOCK_INTERVAL_ONE_HOUR = 3600 * MILLISECONDS_PER_SECOND;
const STORAGE_KEY_AUTOLOCK_INTERVAL = "AutoLockIntervalKey"; const STORAGE_KEY_AUTOLOCK_INTERVAL = "AutoLockIntervalKey";
export class LockManager { export class AutolockService {
private application: WebApplication private application: WebApplication
private unsubState: any private unsubState: any
@@ -21,11 +22,13 @@ export class LockManager {
private lockAfterDate?: Date private lockAfterDate?: Date
private lockTimeout?: any private lockTimeout?: any
constructor(application: WebApplication) { constructor(
application: WebApplication
) {
this.application = application; this.application = application;
setImmediate(() => { setTimeout(() => {
this.observeVisibility(); this.observeVisibility();
}); }, 0);
} }
observeVisibility() { observeVisibility() {
@@ -50,6 +53,10 @@ export class LockManager {
} }
} }
private lockApplication() {
this.application.lock();
}
async setAutoLockInterval(interval: number) { async setAutoLockInterval(interval: number) {
return this.application!.setValue( return this.application!.setValue(
STORAGE_KEY_AUTOLOCK_INTERVAL, STORAGE_KEY_AUTOLOCK_INTERVAL,
@@ -118,7 +125,7 @@ export class LockManager {
this.lockAfterDate && this.lockAfterDate &&
new Date() > this.lockAfterDate new Date() > this.lockAfterDate
) { ) {
this.application.lock(); this.lockApplication();
} }
this.cancelAutoLockTimer(); this.cancelAutoLockTimer();
} else { } else {
@@ -145,7 +152,7 @@ export class LockManager {
this.lockAfterDate = addToNow(interval / MILLISECONDS_PER_SECOND); this.lockAfterDate = addToNow(interval / MILLISECONDS_PER_SECOND);
this.lockTimeout = setTimeout(() => { this.lockTimeout = setTimeout(() => {
this.cancelAutoLockTimer(); this.cancelAutoLockTimer();
this.application.lock(); this.lockApplication();
this.lockAfterDate = undefined; this.lockAfterDate = undefined;
}, interval); }, interval);
} }

View File

@@ -2,7 +2,7 @@ export { AlertService } from './alertService';
export { ArchiveManager } from './archiveManager'; export { ArchiveManager } from './archiveManager';
export { DesktopManager } from './desktopManager'; export { DesktopManager } from './desktopManager';
export { KeyboardManager } from './keyboardManager'; export { KeyboardManager } from './keyboardManager';
export { LockManager } from './lockManager'; export { AutolockService } from './autolock_service';
export { NativeExtManager } from './nativeExtManager'; export { NativeExtManager } from './nativeExtManager';
export { PreferencesManager } from './preferencesManager'; export { PreferencesManager } from './preferencesManager';
export { StatusManager } from './statusManager'; export { StatusManager } from './statusManager';

View File

@@ -10,8 +10,8 @@ import {
Copy, Copy,
dictToArray dictToArray
} from 'snjs'; } from 'snjs';
import { PayloadContent } from '@node_modules/snjs/dist/@types/protocol/payloads/generator'; import { PayloadContent } from 'snjs/dist/@types/protocol/payloads/generator';
import { ComponentPermission } from '@node_modules/snjs/dist/@types/models/app/component'; import { ComponentPermission } from 'snjs/dist/@types/models/app/component';
/** A class for handling installation of system extensions */ /** A class for handling installation of system extensions */
export class NativeExtManager extends ApplicationService { export class NativeExtManager extends ApplicationService {

View File

@@ -46,7 +46,7 @@ export const STRING_INVALID_IMPORT_FILE = "Unable to open file. Ensure it is a p
export function StringImportError(errorCount: number) { export function StringImportError(errorCount: number) {
return `Import complete. ${errorCount} items were not imported because there was an error decrypting them. Make sure the password is correct and try again.`; return `Import complete. ${errorCount} items were not imported because there was an error decrypting them. Make sure the password is correct and try again.`;
} }
export const STRING_ENTER_ACCOUNT_PASSCODE = 'Enter your application passcode to decrypt your data and unlock the application'; export const STRING_ENTER_ACCOUNT_PASSCODE = 'Enter your application passcode to unlock the application';
export const STRING_ENTER_ACCOUNT_PASSWORD = 'Enter your account password'; export const STRING_ENTER_ACCOUNT_PASSWORD = 'Enter your account password';
export const STRING_ENTER_PASSCODE_FOR_MIGRATION = 'Your application passcode is required to perform an upgrade of your local data storage structure.'; export const STRING_ENTER_PASSCODE_FOR_MIGRATION = 'Your application passcode is required to perform an upgrade of your local data storage structure.';
export const STRING_ENTER_PASSCODE_FOR_LOGIN_REGISTER = 'Enter your application passcode before signing in or registering'; export const STRING_ENTER_PASSCODE_FOR_LOGIN_REGISTER = 'Enter your application passcode before signing in or registering';

View File

@@ -27,6 +27,10 @@ export interface PermissionsModalScope extends Partial<ng.IScope> {
callback: (approved: boolean) => void callback: (approved: boolean) => void
} }
export interface AccountSwitcherScope extends Partial<ng.IScope> {
application: any
}
export type PanelPuppet = { export type PanelPuppet = {
onReady?: () => void onReady?: () => void
ready?: boolean ready?: boolean

View File

@@ -1,3 +1,4 @@
import { AccountSwitcherScope } from './../types';
import { ComponentGroup } from './component_group'; import { ComponentGroup } from './component_group';
import { EditorGroup } from '@/ui_models/editor_group'; import { EditorGroup } from '@/ui_models/editor_group';
import { InputModalScope } from '@/directives/views/inputModal'; import { InputModalScope } from '@/directives/views/inputModal';
@@ -16,7 +17,7 @@ import { AlertService } from '@/services/alertService';
import { WebDeviceInterface } from '@/web_device_interface'; import { WebDeviceInterface } from '@/web_device_interface';
import { import {
DesktopManager, DesktopManager,
LockManager, AutolockService,
ArchiveManager, ArchiveManager,
NativeExtManager, NativeExtManager,
StatusManager, StatusManager,
@@ -27,11 +28,12 @@ import {
import { AppState } from '@/ui_models/app_state'; import { AppState } from '@/ui_models/app_state';
import { SNWebCrypto } from 'sncrypto/dist/sncrypto-web'; import { SNWebCrypto } from 'sncrypto/dist/sncrypto-web';
import { Bridge } from '@/services/bridge'; import { Bridge } from '@/services/bridge';
import { DeinitSource } from 'snjs/dist/@types/types';
type WebServices = { type WebServices = {
appState: AppState appState: AppState
desktopService: DesktopManager desktopService: DesktopManager
lockService: LockManager autolockService: AutolockService
archiveService: ArchiveManager archiveService: ArchiveManager
nativeExtService: NativeExtManager nativeExtService: NativeExtManager
statusService: StatusManager statusService: StatusManager
@@ -44,7 +46,6 @@ export class WebApplication extends SNApplication {
private $compile?: ng.ICompileService private $compile?: ng.ICompileService
private scope?: ng.IScope private scope?: ng.IScope
private onDeinit?: (app: WebApplication) => void
private webServices!: WebServices private webServices!: WebServices
private currentAuthenticationElement?: JQLite private currentAuthenticationElement?: JQLite
public editorGroup: EditorGroup public editorGroup: EditorGroup
@@ -52,38 +53,33 @@ export class WebApplication extends SNApplication {
/* @ngInject */ /* @ngInject */
constructor( constructor(
deviceInterface: WebDeviceInterface,
identifier: string,
$compile: ng.ICompileService, $compile: ng.ICompileService,
$timeout: ng.ITimeoutService,
scope: ng.IScope, scope: ng.IScope,
onDeinit: (app: WebApplication) => void,
defaultSyncServerHost: string, defaultSyncServerHost: string,
bridge: Bridge, bridge: Bridge,
) { ) {
const deviceInterface = new WebDeviceInterface(
$timeout,
bridge
);
super( super(
bridge.environment, bridge.environment,
platformFromString(getPlatformString()), platformFromString(getPlatformString()),
deviceInterface, deviceInterface,
new SNWebCrypto(), new SNWebCrypto(),
new AlertService(), new AlertService(),
undefined, identifier,
undefined, undefined,
undefined, undefined,
defaultSyncServerHost defaultSyncServerHost
); );
this.$compile = $compile; this.$compile = $compile;
this.scope = scope; this.scope = scope;
this.onDeinit = onDeinit;
deviceInterface.setApplication(this); deviceInterface.setApplication(this);
this.editorGroup = new EditorGroup(this); this.editorGroup = new EditorGroup(this);
this.componentGroup = new ComponentGroup(this); this.componentGroup = new ComponentGroup(this);
} }
/** @override */ /** @override */
deinit() { deinit(source: DeinitSource) {
for (const key of Object.keys(this.webServices)) { for (const key of Object.keys(this.webServices)) {
const service = (this.webServices as any)[key]; const service = (this.webServices as any)[key];
if (service.deinit) { if (service.deinit) {
@@ -92,8 +88,6 @@ export class WebApplication extends SNApplication {
service.application = undefined; service.application = undefined;
} }
this.webServices = {} as WebServices; this.webServices = {} as WebServices;
this.onDeinit!(this);
this.onDeinit = undefined;
this.$compile = undefined; this.$compile = undefined;
this.editorGroup.deinit(); this.editorGroup.deinit();
this.componentGroup.deinit(); this.componentGroup.deinit();
@@ -102,9 +96,9 @@ export class WebApplication extends SNApplication {
this.scope = undefined; this.scope = undefined;
/** Allow our Angular directives to be destroyed and any pending digest cycles /** Allow our Angular directives to be destroyed and any pending digest cycles
* to complete before destroying the global application instance and all its services */ * to complete before destroying the global application instance and all its services */
setImmediate(() => { setTimeout(() => {
super.deinit(); super.deinit(source);
}) }, 0)
} }
setWebServices(services: WebServices) { setWebServices(services: WebServices) {
@@ -119,8 +113,8 @@ export class WebApplication extends SNApplication {
return this.webServices.desktopService; return this.webServices.desktopService;
} }
public getLockService() { public getAutolockService() {
return this.webServices.lockService; return this.webServices.autolockService;
} }
public getArchiveService() { public getArchiveService() {
@@ -257,4 +251,15 @@ export class WebApplication extends SNApplication {
)(scope); )(scope);
angular.element(document.body).append(el); angular.element(document.body).append(el);
} }
public openAccountSwitcher() {
const scope = this.scope!.$new(true) as Partial<AccountSwitcherScope>;
scope.application = this;
const el = this.$compile!(
"<account-switcher application='application' "
+ "class='sk-modal'></account-switcher>"
)(scope as any);
angular.element(document.body).append(el);
}
} }

View File

@@ -1,10 +1,11 @@
import { WebDeviceInterface } from '@/web_device_interface';
import { WebApplication } from './application'; import { WebApplication } from './application';
import { removeFromArray } from 'snjs'; import { ApplicationDescriptor, SNApplicationGroup, DeviceInterface } from 'snjs';
import { import {
ArchiveManager, ArchiveManager,
DesktopManager, DesktopManager,
KeyboardManager, KeyboardManager,
LockManager, AutolockService,
NativeExtManager, NativeExtManager,
PreferencesManager, PreferencesManager,
StatusManager, StatusManager,
@@ -13,16 +14,11 @@ import {
import { AppState } from '@/ui_models/app_state'; import { AppState } from '@/ui_models/app_state';
import { Bridge } from '@/services/bridge'; import { Bridge } from '@/services/bridge';
type AppManagerChangeCallback = () => void export class ApplicationGroup extends SNApplicationGroup {
export class ApplicationGroup {
$compile: ng.ICompileService $compile: ng.ICompileService
$rootScope: ng.IRootScopeService $rootScope: ng.IRootScopeService
$timeout: ng.ITimeoutService $timeout: ng.ITimeoutService
applications: WebApplication[] = []
changeObservers: AppManagerChangeCallback[] = []
activeApplication?: WebApplication
/* @ngInject */ /* @ngInject */
constructor( constructor(
@@ -32,46 +28,35 @@ export class ApplicationGroup {
private defaultSyncServerHost: string, private defaultSyncServerHost: string,
private bridge: Bridge, private bridge: Bridge,
) { ) {
super(new WebDeviceInterface(
$timeout,
bridge
));
this.$compile = $compile; this.$compile = $compile;
this.$timeout = $timeout; this.$timeout = $timeout;
this.$rootScope = $rootScope; this.$rootScope = $rootScope;
this.onApplicationDeinit = this.onApplicationDeinit.bind(this); }
this.createDefaultApplication();
async initialize(callback?: any) {
await super.initialize({
applicationCreator: this.createApplication
});
/** FIXME(baptiste): rely on a less fragile method to detect Electron */ /** FIXME(baptiste): rely on a less fragile method to detect Electron */
if ((window as any).isElectron) { if ((window as any).isElectron) {
Object.defineProperty(window, 'desktopManager', { Object.defineProperty(window, 'desktopManager', {
get: () => this.activeApplication?.getDesktopService() get: () => (this.primaryApplication as WebApplication).getDesktopService()
}); });
} }
} }
private createDefaultApplication() { private createApplication = (descriptor: ApplicationDescriptor, deviceInterface: DeviceInterface) => {
this.activeApplication = this.createNewApplication();
this.applications.push(this.activeApplication!);
this.notifyObserversOfAppChange();
}
/** @callback */
onApplicationDeinit(application: WebApplication) {
removeFromArray(this.applications, application);
if (this.activeApplication === application) {
this.activeApplication = undefined;
}
if (this.applications.length === 0) {
this.createDefaultApplication();
} else {
this.notifyObserversOfAppChange();
}
}
private createNewApplication() {
const scope = this.$rootScope.$new(true); const scope = this.$rootScope.$new(true);
const application = new WebApplication( const application = new WebApplication(
deviceInterface as WebDeviceInterface,
descriptor.identifier,
this.$compile, this.$compile,
this.$timeout,
scope, scope,
this.onApplicationDeinit,
this.defaultSyncServerHost, this.defaultSyncServerHost,
this.bridge, this.bridge,
); );
@@ -90,7 +75,7 @@ export class ApplicationGroup {
this.bridge, this.bridge,
); );
const keyboardService = new KeyboardManager(); const keyboardService = new KeyboardManager();
const lockService = new LockManager( const autolockService = new AutolockService(
application application
); );
const nativeExtService = new NativeExtManager( const nativeExtService = new NativeExtManager(
@@ -108,7 +93,7 @@ export class ApplicationGroup {
archiveService, archiveService,
desktopService, desktopService,
keyboardService, keyboardService,
lockService, autolockService,
nativeExtService, nativeExtService,
prefsService, prefsService,
statusService, statusService,
@@ -116,34 +101,4 @@ export class ApplicationGroup {
}); });
return application; return application;
} }
get application() {
return this.activeApplication;
}
public getApplications() {
return this.applications.slice();
}
/**
* Notifies observer when the active application has changed.
* Any application which is no longer active is destroyed, and
* must be removed from the interface.
*/
public addApplicationChangeObserver(callback: AppManagerChangeCallback) {
this.changeObservers.push(callback);
if (this.application) {
callback();
}
return () => {
removeFromArray(this.changeObservers, callback);
}
}
private notifyObserversOfAppChange() {
for (const observer of this.changeObservers) {
observer();
}
}
} }

View File

@@ -1,6 +1,6 @@
import { SNComponent, ComponentArea, removeFromArray, addIfUnique } from 'snjs'; import { SNComponent, ComponentArea, removeFromArray, addIfUnique } from 'snjs';
import { WebApplication } from './application'; import { WebApplication } from './application';
import { UuidString } from '@node_modules/snjs/dist/@types/types'; import { UuidString } from 'snjs/dist/@types/types';
/** Areas that only allow a single component to be active */ /** Areas that only allow a single component to be active */
const SingleComponentAreas = [ const SingleComponentAreas = [

View File

@@ -63,9 +63,15 @@ export class PureViewCtrl<P = CtrlProps, S = CtrlState> {
if (!this.$timeout) { if (!this.$timeout) {
return; return;
} }
this.state = Object.freeze(Object.assign({}, this.state, state));
return new Promise((resolve) => { return new Promise((resolve) => {
this.stateTimeout = this.$timeout(resolve); this.stateTimeout = this.$timeout(() => {
/**
* State changes must be *inside* the timeout block for them to be affected in the UI
* Otherwise UI controllers will need to use $timeout everywhere
*/
this.state = Object.freeze(Object.assign({}, this.state, state));
resolve();
});
}); });
} }

View File

@@ -0,0 +1,32 @@
.sk-modal-background(ng-click="ctrl.dismiss()")
#account-switcher.sk-modal-content
.sn-component
.sk-menu-panel#menu-panel
.sk-menu-panel-header
.sk-menu-panel-column
.sk-menu-panel-header-title Account Switcher
.sk-menu-panel-column
a.sk-label.info(ng-click='ctrl.addNewApplication()') Add Account
.sk-menu-panel-row(
ng-repeat='descriptor in ctrl.state.descriptors track by descriptor.identifier'
ng-click='ctrl.selectDescriptor(descriptor)'
)
.sk-menu-panel-column.stretch
.left
.sk-menu-panel-column(ng-if='descriptor.identifier == ctrl.activeApplication.identifier')
.sk-circle.small.success
.sk-menu-panel-column.stretch
input.sk-label.clickable(
ng-model='descriptor.label'
ng-disabled='descriptor != ctrl.state.editingDescriptor'
ng-keyup='$event.keyCode == 13 && ctrl.submitRename($event)',
ng-attr-id='input-{{descriptor.identifier}}'
spellcheck="false"
)
.sk-sublabel(ng-if='descriptor.identifier == ctrl.activeApplication.identifier')
| Current Application
.sk-menu-panel-column(ng-if='descriptor.identifier == ctrl.activeApplication.identifier')
.sk-button.success(
ng-click='ctrl.renameDescriptor($event, descriptor)'
)
.sk-label Rename

View File

@@ -0,0 +1,105 @@
import { ApplicationGroup } from '@/ui_models/application_group';
import { WebApplication } from '@/ui_models/application';
import template from './account-switcher.pug';
import {
ApplicationDescriptor,
} from 'snjs';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { WebDirective } from '@/types';
class AccountSwitcherCtrl extends PureViewCtrl<{}, {
descriptors: ApplicationDescriptor[];
editingDescriptor?: ApplicationDescriptor
}> {
private $element: JQLite
application!: WebApplication
private removeAppGroupObserver: any;
/** @template */
activeApplication!: WebApplication
/* @ngInject */
constructor(
$element: JQLite,
$timeout: ng.ITimeoutService,
private mainApplicationGroup: ApplicationGroup
) {
super($timeout);
this.$element = $element;
this.removeAppGroupObserver = mainApplicationGroup.addApplicationChangeObserver(() => {
this.activeApplication = mainApplicationGroup.primaryApplication as WebApplication;
this.reloadApplications();
});
}
$onInit() {
super.$onInit();
}
reloadApplications() {
this.setState({
descriptors: this.mainApplicationGroup.getDescriptors()
})
}
/** @template */
addNewApplication() {
this.dismiss();
this.mainApplicationGroup.addNewApplication();
}
/** @template */
selectDescriptor(descriptor: ApplicationDescriptor) {
this.dismiss();
this.mainApplicationGroup.loadApplicationForDescriptor(descriptor);
}
inputForDescriptor(descriptor: ApplicationDescriptor) {
return document.getElementById(`input-${descriptor.identifier}`);
}
/** @template */
renameDescriptor($event: Event, descriptor: ApplicationDescriptor) {
$event.stopPropagation();
this.setState({ editingDescriptor: descriptor }).then(() => {
const input = this.inputForDescriptor(descriptor);
input?.focus();
})
}
/** @template */
submitRename() {
this.mainApplicationGroup.renameDescriptor(
this.state.editingDescriptor!,
this.state.editingDescriptor!.label
)
this.setState({ editingDescriptor: undefined });
}
deinit() {
(this.application as any) = undefined;
super.deinit();
this.removeAppGroupObserver();
this.removeAppGroupObserver = undefined;
}
dismiss() {
const elem = this.$element;
const scope = elem.scope();
scope.$destroy();
elem.remove();
}
}
export class AccountSwitcher extends WebDirective {
constructor() {
super();
this.restrict = 'E';
this.template = template;
this.controller = AccountSwitcherCtrl;
this.controllerAs = 'ctrl';
this.bindToController = true;
this.scope = {
application: '='
};
}
}

View File

@@ -15,3 +15,12 @@
ng-if='!self.state.needsUnlock && self.state.ready' ng-if='!self.state.needsUnlock && self.state.ready'
application='self.application' application='self.application'
) )
svg(data-ionicons="5.1.2", style="display: none")
symbol#people-circle-outline.ionicon(viewbox="0 0 512 512")
path(d="M256 464c-114.69 0-208-93.31-208-208S141.31 48 256 48s208 93.31 208 208-93.31 208-208 208zm0-384c-97 0-176 79-176 176s79 176 176 176 176-78.95 176-176S353.05 80 256 80z")
path(d="M323.67 292c-17.4 0-34.21-7.72-47.34-21.73a83.76 83.76 0 01-22-51.32c-1.47-20.7 4.88-39.75 17.88-53.62S303.38 144 323.67 144c20.14 0 38.37 7.62 51.33 21.46s19.47 33 18 53.51a84 84 0 01-22 51.3C357.86 284.28 341.06 292 323.67 292zm55.81-74zM163.82 295.36c-29.76 0-55.93-27.51-58.33-61.33-1.23-17.32 4.15-33.33 15.17-45.08s26.22-18 43.15-18 32.12 6.44 43.07 18.14 16.5 27.82 15.25 45c-2.44 33.77-28.6 61.27-58.31 61.27zM420.37 355.28c-1.59-4.7-5.46-9.71-13.22-14.46-23.46-14.33-52.32-21.91-83.48-21.91-30.57 0-60.23 7.9-83.53 22.25-26.25 16.17-43.89 39.75-51 68.18-1.68 6.69-4.13 19.14-1.51 26.11a192.18 192.18 0 00232.75-80.17zM163.63 401.37c7.07-28.21 22.12-51.73 45.47-70.75a8 8 0 00-2.59-13.77c-12-3.83-25.7-5.88-42.69-5.88-23.82 0-49.11 6.45-68.14 18.17-5.4 3.33-10.7 4.61-14.78 5.75a192.84 192.84 0 0077.78 86.64l1.79-.14a102.82 102.82 0 013.16-20.02z")
symbol#layers-sharp.ionicon(viewbox="0 0 512 512")
path(d="M480 150L256 48 32 150l224 104 224-104zM255.71 392.95l-144.81-66.2L32 362l224 102 224-102-78.69-35.3-145.6 66.25z")
path(d="M480 256l-75.53-33.53L256.1 290.6l-148.77-68.17L32 256l224 102 224-102z")

View File

@@ -14,7 +14,7 @@ import {
STRING_DEFAULT_FILE_ERROR STRING_DEFAULT_FILE_ERROR
} from '@/strings'; } from '@/strings';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { PermissionDialog } from '@node_modules/snjs/dist/@types/services/component_manager'; import { PermissionDialog } from 'snjs/dist/@types/services/component_manager';
import { alertDialog } from '@/services/alertService'; import { alertDialog } from '@/services/alertService';
class ApplicationViewCtrl extends PureViewCtrl { class ApplicationViewCtrl extends PureViewCtrl {

View File

@@ -1,4 +1,5 @@
application-view( application-view(
ng-repeat='application in self.applications', ng-repeat='application in self.applications',
ng-if='application == self.activeApplication'
application='application' application='application'
) )

View File

@@ -7,7 +7,8 @@ class ApplicationGroupViewCtrl {
private $timeout: ng.ITimeoutService private $timeout: ng.ITimeoutService
private applicationGroup: ApplicationGroup private applicationGroup: ApplicationGroup
public applications: WebApplication[] = [] applications!: WebApplication[]
activeApplication!: WebApplication
/* @ngInject */ /* @ngInject */
constructor( constructor(
@@ -19,11 +20,13 @@ class ApplicationGroupViewCtrl {
this.applicationGroup.addApplicationChangeObserver(() => { this.applicationGroup.addApplicationChangeObserver(() => {
this.reload(); this.reload();
}); });
this.applicationGroup.initialize();
} }
reload() { reload() {
this.$timeout(() => { this.$timeout(() => {
this.applications = this.applicationGroup.getApplications(); this.activeApplication = this.applicationGroup.primaryApplication as WebApplication;
this.applications = this.applicationGroup.getApplications() as WebApplication[];
}); });
} }
} }
@@ -33,7 +36,7 @@ export class ApplicationGroupView extends WebDirective {
super(); super();
this.template = template; this.template = template;
this.controller = ApplicationGroupViewCtrl; this.controller = ApplicationGroupViewCtrl;
this.replace = true; this.replace = false;
this.controllerAs = 'self'; this.controllerAs = 'self';
this.bindToController = true; this.bindToController = true;
} }

View File

@@ -70,7 +70,7 @@
.sk-spinner.small .sk-spinner.small
.sk-app-bar-item(ng-if='ctrl.offline') .sk-app-bar-item(ng-if='ctrl.offline')
.sk-label Offline .sk-label Offline
.sk-app-bar-item(ng-click='ctrl.refreshData()', ng-if='!ctrl.offline') .sk-app-bar-item(ng-click='ctrl.refreshData()' ng-if='!ctrl.offline')
.sk-label Refresh .sk-label Refresh
.sk-app-bar-item.border(ng-if='ctrl.state.dockShortcuts.length > 0') .sk-app-bar-item.border(ng-if='ctrl.state.dockShortcuts.length > 0')
.sk-app-bar-item.dock-shortcut(ng-repeat='shortcut in ctrl.state.dockShortcuts') .sk-app-bar-item.dock-shortcut(ng-repeat='shortcut in ctrl.state.dockShortcuts')
@@ -78,15 +78,23 @@
ng-class="{'underline': shortcut.component.active}", ng-class="{'underline': shortcut.component.active}",
ng-click='ctrl.selectShortcut(shortcut)' ng-click='ctrl.selectShortcut(shortcut)'
) )
.div(ng-if="shortcut.icon.type == 'circle'", title='{{shortcut.name}}') .div(ng-if="shortcut.icon.type == 'circle'" title='{{shortcut.name}}')
.sk-circle.small( .sk-circle.small(
ng-style="{'background-color': shortcut.icon.background_color, 'border-color': shortcut.icon.border_color}" ng-style="{'background-color': shortcut.icon.background_color, 'border-color': shortcut.icon.border_color}"
) )
.div(ng-if="shortcut.icon.type == 'svg'", title='{{shortcut.name}}') .div(ng-if="shortcut.icon.type == 'svg'" title='{{shortcut.name}}')
.svg-item( .svg-item(
elem-ready='ctrl.initSvgForShortcut(shortcut)', elem-ready='ctrl.initSvgForShortcut(shortcut)',
ng-attr-id='dock-svg-{{shortcut.component.uuid}}' ng-attr-id='dock-svg-{{shortcut.component.uuid}}'
) )
.sk-app-bar-item.border(ng-if='ctrl.state.hasAccountSwitcher')
.sk-app-bar-item(
ng-if='ctrl.state.hasAccountSwitcher'
ng-click='ctrl.openAccountSwitcher()',
)
#account-switcher-icon(ng-class='{"alone": !ctrl.state.hasPasscode}')
svg.info.ionicon
use(href="#layers-sharp")
.sk-app-bar-item.border(ng-if='ctrl.state.hasPasscode') .sk-app-bar-item.border(ng-if='ctrl.state.hasPasscode')
#lock-item.sk-app-bar-item( #lock-item.sk-app-bar-item(
ng-click='ctrl.lockApp()', ng-click='ctrl.lockApp()',

View File

@@ -1,3 +1,4 @@
import { ApplicationGroup } from '@/ui_models/application_group';
import { FooterStatus, WebDirective } from '@/types'; import { FooterStatus, WebDirective } from '@/types';
import { dateToLocalizedString, preventRefreshing } from '@/utils'; import { dateToLocalizedString, preventRefreshing } from '@/utils';
import { import {
@@ -11,7 +12,7 @@ import {
ComponentAction, ComponentAction,
topLevelCompare, topLevelCompare,
CollectionSort, CollectionSort,
ComponentMutator, ComponentMutator
} from 'snjs'; } from 'snjs';
import template from './footer-view.pug'; import template from './footer-view.pug';
import { AppStateEvent, EventSource } from '@/ui_models/app_state'; import { AppStateEvent, EventSource } from '@/ui_models/app_state';
@@ -22,6 +23,14 @@ import {
} from '@/strings'; } from '@/strings';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
/**
* Disable before production release.
* Anyone who used the beta will still have access to
* the account switcher in production via local storage flag
*/
const ACCOUNT_SWITCHER_ENABLED = true;
const ACCOUNT_SWITCHER_FEATURE_KEY = 'account_switcher';
type DockShortcut = { type DockShortcut = {
name: string, name: string,
component: SNComponent, component: SNComponent,
@@ -37,8 +46,8 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
hasPasscode: boolean; hasPasscode: boolean;
dataUpgradeAvailable: boolean; dataUpgradeAvailable: boolean;
dockShortcuts: DockShortcut[]; dockShortcuts: DockShortcut[];
hasAccountSwitcher: boolean
}> { }> {
private $rootScope: ng.IRootScopeService private $rootScope: ng.IRootScopeService
private rooms: SNComponent[] = [] private rooms: SNComponent[] = []
private themesWithIcons: SNTheme[] = [] private themesWithIcons: SNTheme[] = []
@@ -66,6 +75,7 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
constructor( constructor(
$rootScope: ng.IRootScopeService, $rootScope: ng.IRootScopeService,
$timeout: ng.ITimeoutService, $timeout: ng.ITimeoutService,
private mainApplicationGroup: ApplicationGroup
) { ) {
super($timeout); super($timeout);
this.$rootScope = $rootScope; this.$rootScope = $rootScope;
@@ -92,11 +102,22 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
$onInit() { $onInit() {
super.$onInit(); super.$onInit();
this.application!.getStatusService().addStatusObserver((string: string) => { this.application.getStatusService().addStatusObserver((string: string) => {
this.$timeout(() => { this.$timeout(() => {
this.arbitraryStatusMessage = string; this.arbitraryStatusMessage = string;
}); });
}); });
this.loadAccountSwitcherState();
}
loadAccountSwitcherState() {
const stringValue = localStorage.getItem(ACCOUNT_SWITCHER_FEATURE_KEY);
if (!stringValue && ACCOUNT_SWITCHER_ENABLED) {
/** Enable permanently for this user so they don't lose the feature after its disabled */
localStorage.setItem(ACCOUNT_SWITCHER_FEATURE_KEY, JSON.stringify(true));
}
const hasAccountSwitcher = stringValue ? JSON.parse(stringValue) : ACCOUNT_SWITCHER_ENABLED;
this.setState({ hasAccountSwitcher });
} }
getInitialState() { getInitialState() {
@@ -105,17 +126,24 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
dataUpgradeAvailable: false, dataUpgradeAvailable: false,
hasPasscode: false, hasPasscode: false,
dockShortcuts: [], dockShortcuts: [],
descriptors: this.mainApplicationGroup.getDescriptors(),
hasAccountSwitcher: false
}; };
} }
reloadUpgradeStatus() { reloadUpgradeStatus() {
this.application!.checkForSecurityUpdate().then((available) => { this.application.checkForSecurityUpdate().then((available) => {
this.setState({ this.setState({
dataUpgradeAvailable: available dataUpgradeAvailable: available
}); });
}); });
} }
/** @template */
openAccountSwitcher() {
this.application.openAccountSwitcher();
}
async onAppLaunch() { async onAppLaunch() {
super.onAppLaunch(); super.onAppLaunch();
this.reloadPasscodeStatus(); this.reloadPasscodeStatus();
@@ -128,11 +156,11 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
} }
reloadUser() { reloadUser() {
this.user = this.application!.getUser(); this.user = this.application.getUser();
} }
async reloadPasscodeStatus() { async reloadPasscodeStatus() {
const hasPasscode = this.application!.hasPasscode(); const hasPasscode = this.application.hasPasscode();
this.setState({ this.setState({
hasPasscode: hasPasscode hasPasscode: hasPasscode
}); });
@@ -157,23 +185,23 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
this.closeAccountMenu(); this.closeAccountMenu();
} }
} else if (eventName === AppStateEvent.BeganBackupDownload) { } else if (eventName === AppStateEvent.BeganBackupDownload) {
this.backupStatus = this.application!.getStatusService().addStatusFromString( this.backupStatus = this.application.getStatusService().addStatusFromString(
"Saving local backup..." "Saving local backup..."
); );
} else if (eventName === AppStateEvent.EndedBackupDownload) { } else if (eventName === AppStateEvent.EndedBackupDownload) {
if (data.success) { if (data.success) {
this.backupStatus = this.application!.getStatusService().replaceStatusWithString( this.backupStatus = this.application.getStatusService().replaceStatusWithString(
this.backupStatus!, this.backupStatus!,
"Successfully saved backup." "Successfully saved backup."
); );
} else { } else {
this.backupStatus = this.application!.getStatusService().replaceStatusWithString( this.backupStatus = this.application.getStatusService().replaceStatusWithString(
this.backupStatus!, this.backupStatus!,
"Unable to save local backup." "Unable to save local backup."
); );
} }
this.$timeout(() => { this.$timeout(() => {
this.backupStatus = this.application!.getStatusService().removeStatus(this.backupStatus!); this.backupStatus = this.application.getStatusService().removeStatus(this.backupStatus!);
}, 2000); }, 2000);
} }
} }
@@ -199,7 +227,7 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
} else if (eventName === ApplicationEvent.CompletedFullSync) { } else if (eventName === ApplicationEvent.CompletedFullSync) {
if (!this.didCheckForOffline) { if (!this.didCheckForOffline) {
this.didCheckForOffline = true; this.didCheckForOffline = true;
if (this.offline && this.application!.getNoteCount() === 0) { if (this.offline && this.application.getNoteCount() === 0) {
this.showAccountMenu = true; this.showAccountMenu = true;
} }
} }
@@ -230,10 +258,10 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
} }
) )
this.observerRemovers.push(this.application!.streamItems( this.observerRemovers.push(this.application.streamItems(
ContentType.Component, ContentType.Component,
async () => { async () => {
const components = this.application!.getItems(ContentType.Component) as SNComponent[]; const components = this.application.getItems(ContentType.Component) as SNComponent[];
this.rooms = components.filter((candidate) => { this.rooms = components.filter((candidate) => {
return candidate.area === ComponentArea.Rooms && !candidate.deleted; return candidate.area === ComponentArea.Rooms && !candidate.deleted;
}); });
@@ -244,10 +272,10 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
} }
)); ));
this.observerRemovers.push(this.application!.streamItems( this.observerRemovers.push(this.application.streamItems(
ContentType.Theme, ContentType.Theme,
async () => { async () => {
const themes = this.application!.getDisplayableItems(ContentType.Theme) as SNTheme[]; const themes = this.application.getDisplayableItems(ContentType.Theme) as SNTheme[];
this.themesWithIcons = themes; this.themesWithIcons = themes;
this.reloadDockShortcuts(); this.reloadDockShortcuts();
} }
@@ -255,14 +283,14 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
} }
registerComponentHandler() { registerComponentHandler() {
this.unregisterComponent = this.application!.componentManager!.registerHandler({ this.unregisterComponent = this.application.componentManager!.registerHandler({
identifier: 'room-bar', identifier: 'room-bar',
areas: [ComponentArea.Rooms, ComponentArea.Modal], areas: [ComponentArea.Rooms, ComponentArea.Modal],
actionHandler: (component, action, data) => { actionHandler: (component, action, data) => {
if (action === ComponentAction.SetSize) { if (action === ComponentAction.SetSize) {
/** Do comparison to avoid repetitive calls by arbitrary component */ /** Do comparison to avoid repetitive calls by arbitrary component */
if (!topLevelCompare(component.getLastSize(), data)) { if (!topLevelCompare(component.getLastSize(), data)) {
this.application!.changeItem<ComponentMutator>(component.uuid, (mutator) => { this.application.changeItem<ComponentMutator>(component.uuid, (mutator) => {
mutator.setLastSize(data); mutator.setLastSize(data);
}) })
} }
@@ -288,7 +316,7 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
* then closing it after a short delay. * then closing it after a short delay.
*/ */
const extWindow = this.rooms.find((room) => { const extWindow = this.rooms.find((room) => {
return room.package_info.identifier === this.application! return room.package_info.identifier === this.application
.getNativeExtService().extManagerId; .getNativeExtService().extManagerId;
}); });
if (!extWindow) { if (!extWindow) {
@@ -305,17 +333,17 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
} }
updateOfflineStatus() { updateOfflineStatus() {
this.offline = this.application!.noAccount(); this.offline = this.application.noAccount();
} }
async openSecurityUpdate() { async openSecurityUpdate() {
preventRefreshing(STRING_CONFIRM_APP_QUIT_DURING_UPGRADE, async () => { preventRefreshing(STRING_CONFIRM_APP_QUIT_DURING_UPGRADE, async () => {
await this.application!.performProtocolUpgrade(); await this.application.performProtocolUpgrade();
}); });
} }
findErrors() { findErrors() {
this.hasError = this.application!.getSyncStatus().hasError(); this.hasError = this.application.getSyncStatus().hasError();
} }
accountMenuPressed() { accountMenuPressed() {
@@ -332,12 +360,12 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
} }
lockApp() { lockApp() {
this.application!.lock(); this.application.lock();
} }
refreshData() { refreshData() {
this.isRefreshing = true; this.isRefreshing = true;
this.application!.sync({ this.application.sync({
queueStrategy: SyncQueueStrategy.ForceSpawnNew, queueStrategy: SyncQueueStrategy.ForceSpawnNew,
checkIntegrity: true checkIntegrity: true
}).then((response) => { }).then((response) => {
@@ -345,7 +373,7 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
this.isRefreshing = false; this.isRefreshing = false;
}, 200); }, 200);
if (response && response.error) { if (response && response.error) {
this.application!.alertService!.alert( this.application.alertService!.alert(
STRING_GENERIC_SYNC_ERROR STRING_GENERIC_SYNC_ERROR
); );
} else { } else {
@@ -355,7 +383,7 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
} }
syncUpdated() { syncUpdated() {
this.lastSyncDate = dateToLocalizedString(this.application!.getLastSyncDate()!); this.lastSyncDate = dateToLocalizedString(this.application.getLastSyncDate()!);
} }
onNewUpdateAvailable() { onNewUpdateAvailable() {
@@ -364,7 +392,7 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
clickedNewUpdateAnnouncement() { clickedNewUpdateAnnouncement() {
this.newUpdateAvailable = false; this.newUpdateAvailable = false;
this.application!.alertService!.alert( this.application.alertService!.alert(
STRING_NEW_UPDATE_READY STRING_NEW_UPDATE_READY
); );
} }
@@ -409,7 +437,7 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
} }
selectShortcut(shortcut: DockShortcut) { selectShortcut(shortcut: DockShortcut) {
this.application!.toggleComponent(shortcut.component); this.application.toggleComponent(shortcut.component);
} }
onRoomDismiss(room: SNComponent) { onRoomDismiss(room: SNComponent) {
@@ -430,12 +458,12 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
}; };
if (!this.roomShowState[room.uuid]) { if (!this.roomShowState[room.uuid]) {
const requiresPrivilege = await this.application!.privilegesService! const requiresPrivilege = await this.application.privilegesService!
.actionRequiresPrivilege( .actionRequiresPrivilege(
ProtectedAction.ManageExtensions ProtectedAction.ManageExtensions
); );
if (requiresPrivilege) { if (requiresPrivilege) {
this.application!.presentPrivilegesModal( this.application.presentPrivilegesModal(
ProtectedAction.ManageExtensions, ProtectedAction.ManageExtensions,
run run
); );
@@ -448,7 +476,7 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
} }
clickOutsideAccountMenu() { clickOutsideAccountMenu() {
if (this.application && this.application!.authenticationInProgress()) { if (this.application && this.application.authenticationInProgress()) {
return; return;
} }
this.showAccountMenu = false; this.showAccountMenu = false;

View File

@@ -20,7 +20,7 @@ import {
NoteSortKey, NoteSortKey,
notePassesFilter notePassesFilter
} from './note_utils'; } from './note_utils';
import { UuidString } from '@node_modules/snjs/dist/@types/types'; import { UuidString } from 'snjs/dist/@types/types';
type NotesState = { type NotesState = {
panelTitle: string panelTitle: string

View File

@@ -1,10 +1,10 @@
import { DeviceInterface, getGlobalScope, SNApplication } from 'snjs'; import { DeviceInterface, getGlobalScope, SNApplication, ApplicationIdentifier } from 'snjs';
import { Database } from '@/database'; import { Database } from '@/database';
import { Bridge } from './services/bridge'; import { Bridge } from './services/bridge';
export class WebDeviceInterface extends DeviceInterface { export class WebDeviceInterface extends DeviceInterface {
private database: Database private databases: Database[] = []
constructor( constructor(
timeout: any, timeout: any,
@@ -14,16 +14,23 @@ export class WebDeviceInterface extends DeviceInterface {
timeout || setTimeout.bind(getGlobalScope()), timeout || setTimeout.bind(getGlobalScope()),
setInterval.bind(getGlobalScope()) setInterval.bind(getGlobalScope())
); );
this.database = new Database();
} }
setApplication(application: SNApplication) { setApplication(application: SNApplication) {
this.database.setAlertService(application.alertService!); const database = new Database(application.identifier, application.alertService!);
this.databases.push(database);
}
private databaseForIdentifier(identifier: ApplicationIdentifier) {
return this.databases.find(database => database.databaseName === identifier)!;
} }
deinit() { deinit() {
super.deinit(); super.deinit();
this.database.deinit(); for(const database of this.databases) {
database.deinit();
}
this.databases = [];
} }
async getRawStorageValue(key: string) { async getRawStorageValue(key: string) {
@@ -53,10 +60,10 @@ export class WebDeviceInterface extends DeviceInterface {
localStorage.clear(); localStorage.clear();
} }
async openDatabase() { async openDatabase(identifier: ApplicationIdentifier) {
this.database.unlock(); this.databaseForIdentifier(identifier).unlock();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.database.openDatabase(() => { this.databaseForIdentifier(identifier).openDatabase(() => {
resolve({ isNewDatabase: true }); resolve({ isNewDatabase: true });
}).then(() => { }).then(() => {
resolve({ isNewDatabase: false }); resolve({ isNewDatabase: false });
@@ -66,63 +73,51 @@ export class WebDeviceInterface extends DeviceInterface {
}) as Promise<{ isNewDatabase?: boolean } | undefined>; }) as Promise<{ isNewDatabase?: boolean } | undefined>;
} }
private getDatabaseKeyPrefix() { async getAllRawDatabasePayloads(identifier: ApplicationIdentifier) {
if (this.namespace) { return this.databaseForIdentifier(identifier).getAllPayloads();
return `${this.namespace}-item-`;
} else {
return `item-`;
}
} }
private keyForPayloadId(id: string) { async saveRawDatabasePayload(payload: any, identifier: ApplicationIdentifier) {
return `${this.getDatabaseKeyPrefix()}${id}`; return this.databaseForIdentifier(identifier).savePayload(payload);
} }
async getAllRawDatabasePayloads() { async saveRawDatabasePayloads(payloads: any[], identifier: ApplicationIdentifier) {
return this.database.getAllPayloads(); return this.databaseForIdentifier(identifier).savePayloads(payloads);
} }
async saveRawDatabasePayload(payload: any) { async removeRawDatabasePayloadWithId(id: string, identifier: ApplicationIdentifier) {
return this.database.savePayload(payload); return this.databaseForIdentifier(identifier).deletePayload(id);
} }
async saveRawDatabasePayloads(payloads: any[]) { async removeAllRawDatabasePayloads(identifier: ApplicationIdentifier) {
return this.database.savePayloads(payloads); return this.databaseForIdentifier(identifier).clearAllPayloads();
} }
async removeRawDatabasePayloadWithId(id: string) { async getNamespacedKeychainValue(identifier: ApplicationIdentifier) {
return this.database.deletePayload(id);
}
async removeAllRawDatabasePayloads() {
return this.database.clearAllPayloads();
}
async getNamespacedKeychainValue() {
const keychain = await this.getRawKeychainValue(); const keychain = await this.getRawKeychainValue();
if (!keychain) { if (!keychain) {
return; return;
} }
return keychain[this.namespace!.identifier]; return keychain[identifier];
} }
async setNamespacedKeychainValue(value: any) { async setNamespacedKeychainValue(value: any, identifier: ApplicationIdentifier) {
let keychain = await this.getRawKeychainValue(); let keychain = await this.getRawKeychainValue();
if (!keychain) { if (!keychain) {
keychain = {}; keychain = {};
} }
this.bridge.setKeychainValue({ this.bridge.setKeychainValue({
...keychain, ...keychain,
[this.namespace!.identifier]: value [identifier]: value
}); });
} }
async clearNamespacedKeychainValue() { async clearNamespacedKeychainValue(identifier: ApplicationIdentifier) {
const keychain = await this.getRawKeychainValue(); const keychain = await this.getRawKeychainValue();
if (!keychain) { if (!keychain) {
return; return;
} }
delete keychain[this.namespace!.identifier]; delete keychain[identifier];
this.bridge.setKeychainValue(keychain); this.bridge.setKeychainValue(keychain);
} }

View File

@@ -31,18 +31,28 @@
border-bottom: 2px solid var(--sn-stylekit-info-color); border-bottom: 2px solid var(--sn-stylekit-info-color);
} }
svg { .ionicon {
width: 12px; width: 12px;
height: 12px; height: 12px;
fill: var(--sn-stylekit-foreground-color); fill: var(--sn-stylekit-secondary-foreground-color);
&:hover { &:hover {
fill: var(--sn-stylekit-info-color); fill: var(--sn-stylekit-info-color) !important;
color: var(--sn-stylekit-info-color) !important;
} }
} }
} }
#account-panel, #sync-resolution-menu { #account-switcher-icon {
// When this icon is alone in the footer bar, it is displayed with too little
// padding on the right, possibly due to the fact it is a raw SVG.
&.alone {
margin-right: 4px;
}
}
#account-panel,
#sync-resolution-menu {
width: 400px; width: 400px;
} }

View File

@@ -12,22 +12,30 @@
Modified icons to fit ionicons grid from original. Modified icons to fit ionicons grid from original.
*/ */
@font-face { @font-face {
font-family: "Ionicons"; font-family: 'Ionicons';
src: url("../fonts/ionicons.eot?v=2.0.0"); src: url('../fonts/ionicons.eot?v=2.0.0');
src: url("../fonts/ionicons.eot?v=2.0.1#iefix") format("embedded-opentype"), src: url('../fonts/ionicons.eot?v=2.0.1#iefix') format('embedded-opentype'),
url("../fonts/ionicons.ttf?v=2.0.1") format("truetype"), url('../fonts/ionicons.ttf?v=2.0.1') format('truetype'),
url("../fonts/ionicons.woff?v=2.0.1") format("woff"), url('../fonts/ionicons.woff?v=2.0.1') format('woff'),
url("../fonts/ionicons.svg?v=2.0.1#Ionicons") format("svg"); url('../fonts/ionicons.svg?v=2.0.1#Ionicons') format('svg');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
.ionicon-fill-none {
fill: none;
}
.ionicon-stroke-width {
stroke-width: 32px;
}
.ion, .ion,
.ionicons, .ionicons,
.ion-locked:before, .ion-locked:before,
.ion-plus:before { .ion-plus:before {
display: inline-block; display: inline-block;
font-family: "Ionicons"; font-family: 'Ionicons';
speak: none; speak: none;
font-style: normal; font-style: normal;
font-weight: normal; font-weight: normal;
@@ -41,11 +49,11 @@
} }
.ion-locked:before { .ion-locked:before {
content: "\f200"; content: '\f200';
} }
.ion-plus:before { .ion-plus:before {
content: "\f218"; content: '\f218';
} }
/*# sourceMappingURL=ionicons.css.map */ /*# sourceMappingURL=ionicons.css.map */

View File

@@ -25,6 +25,22 @@
} }
} }
#account-switcher {
min-width: 400px;
max-width: 580px;
input, input:disabled {
width: 100%;
border: none;
background-color: transparent !important;
color: inherit;
margin-left: -2px;
}
input.clickable:hover {
cursor: pointer;
}
}
#privileges-modal { #privileges-modal {
min-width: 400px; min-width: 400px;
max-width: 700px; max-width: 700px;
@@ -50,7 +66,8 @@
background-color: var(--sn-stylekit-background-color); background-color: var(--sn-stylekit-background-color);
color: var(--sn-stylekit-contrast-foreground-color); color: var(--sn-stylekit-contrast-foreground-color);
th, td { th,
td {
padding: 6px 13px; padding: 6px 13px;
border: 1px solid var(--sn-stylekit-contrast-border-color); border: 1px solid var(--sn-stylekit-contrast-border-color);
} }
@@ -157,9 +174,9 @@
padding-bottom: 0; padding-bottom: 0;
min-width: 300px; min-width: 300px;
-webkit-box-shadow: 0px 2px 35px 0px rgba(0,0,0,0.19); -webkit-box-shadow: 0px 2px 35px 0px rgba(0, 0, 0, 0.19);
-moz-box-shadow: 0px 2px 35px 0px rgba(0,0,0,0.19); -moz-box-shadow: 0px 2px 35px 0px rgba(0, 0, 0, 0.19);
box-shadow: 0px 2px 35px 0px rgba(0,0,0,0.19); box-shadow: 0px 2px 35px 0px rgba(0, 0, 0, 0.19);
} }
} }

View File

@@ -343,4 +343,4 @@
!self.state.formData.showLogin && !self.state.formData.showLogin &&
!self.state.formData.showRegister` !self.state.formData.showRegister`
) )
| {{ self.state.user ? "Sign out and clear local data" : "Clear all local data" }} | {{ self.state.user ? "Sign out" : "Clear session data" }}

View File

@@ -1,10 +1,11 @@
import { SNAlertService } from "@node_modules/snjs/dist/@types"; import { SNAlertService } from "snjs/dist/@types";
export declare class Database { export declare class Database {
databaseName: string;
private alertService;
private locked; private locked;
private alertService?;
private db?; private db?;
constructor(databaseName: string, alertService: SNAlertService);
deinit(): void; deinit(): void;
setAlertService(alertService: SNAlertService): void;
/** /**
* Relinquishes the lock and allows db operations to proceed * Relinquishes the lock and allows db operations to proceed
*/ */

View File

@@ -18,5 +18,5 @@ export declare class AlertService implements SNAlertService {
*/ */
alert(text: string, title?: string, closeButtonText?: string): Promise<void>; alert(text: string, title?: string, closeButtonText?: string): Promise<void>;
confirm(text: string, title?: string, confirmButtonText?: string, confirmButtonType?: ButtonType, cancelButtonText?: string): Promise<boolean>; confirm(text: string, title?: string, confirmButtonText?: string, confirmButtonType?: ButtonType, cancelButtonText?: string): Promise<boolean>;
blockingDialog(text: string): () => void; blockingDialog(text: string, title?: string): () => void;
} }

View File

@@ -0,0 +1,27 @@
import { WebApplication } from '@/ui_models/application';
export declare class AutolockService {
private application;
private unsubState;
private pollFocusInterval;
private lastFocusState?;
private lockAfterDate?;
private lockTimeout?;
constructor(application: WebApplication);
observeVisibility(): void;
deinit(): void;
private lockApplication;
setAutoLockInterval(interval: number): Promise<void>;
getAutoLockInterval(): Promise<any>;
/**
* Verify document is in focus every so often as visibilitychange event is
* not triggered on a typical window blur event but rather on tab changes.
*/
beginWebFocusPolling(): void;
getAutoLockIntervalOptions(): {
value: number;
label: string;
}[];
documentVisibilityChanged(visible: boolean): Promise<void>;
beginAutoLockTimer(): Promise<void>;
cancelAutoLockTimer(): void;
}

View File

@@ -2,7 +2,7 @@ export { AlertService } from './alertService';
export { ArchiveManager } from './archiveManager'; export { ArchiveManager } from './archiveManager';
export { DesktopManager } from './desktopManager'; export { DesktopManager } from './desktopManager';
export { KeyboardManager } from './keyboardManager'; export { KeyboardManager } from './keyboardManager';
export { LockManager } from './lockManager'; export { AutolockService } from './autolock_service';
export { NativeExtManager } from './nativeExtManager'; export { NativeExtManager } from './nativeExtManager';
export { PreferencesManager } from './preferencesManager'; export { PreferencesManager } from './preferencesManager';
export { StatusManager } from './statusManager'; export { StatusManager } from './statusManager';

View File

@@ -1,5 +1,5 @@
import { SNPredicate, ApplicationService } from 'snjs'; import { SNPredicate, ApplicationService } from 'snjs';
import { PayloadContent } from '@node_modules/snjs/dist/@types/protocol/payloads/generator'; import { PayloadContent } from 'snjs/dist/@types/protocol/payloads/generator';
/** A class for handling installation of system extensions */ /** A class for handling installation of system extensions */
export declare class NativeExtManager extends ApplicationService { export declare class NativeExtManager extends ApplicationService {
extManagerId: string; extManagerId: string;

View File

@@ -32,9 +32,10 @@ export declare const STRING_GENERATING_LOGIN_KEYS = "Generating Login Keys...";
export declare const STRING_GENERATING_REGISTER_KEYS = "Generating Account Keys..."; export declare const STRING_GENERATING_REGISTER_KEYS = "Generating Account Keys...";
export declare const STRING_INVALID_IMPORT_FILE = "Unable to open file. Ensure it is a proper JSON file and try again."; export declare const STRING_INVALID_IMPORT_FILE = "Unable to open file. Ensure it is a proper JSON file and try again.";
export declare function StringImportError(errorCount: number): string; export declare function StringImportError(errorCount: number): string;
export declare const STRING_ENTER_ACCOUNT_PASSCODE = "Enter your application passcode"; export declare const STRING_ENTER_ACCOUNT_PASSCODE = "Enter your application passcode to unlock the application";
export declare const STRING_ENTER_ACCOUNT_PASSWORD = "Enter your account password"; export declare const STRING_ENTER_ACCOUNT_PASSWORD = "Enter your account password";
export declare const STRING_ENTER_PASSCODE_FOR_MIGRATION = "Your application passcode is required to perform an upgrade of your local data storage structure."; export declare const STRING_ENTER_PASSCODE_FOR_MIGRATION = "Your application passcode is required to perform an upgrade of your local data storage structure.";
export declare const STRING_ENTER_PASSCODE_FOR_LOGIN_REGISTER = "Enter your application passcode before signing in or registering";
export declare const STRING_STORAGE_UPDATE = "Storage Update"; export declare const STRING_STORAGE_UPDATE = "Storage Update";
export declare const STRING_AUTHENTICATION_REQUIRED = "Authentication Required"; export declare const STRING_AUTHENTICATION_REQUIRED = "Authentication Required";
export declare const STRING_UNSUPPORTED_BACKUP_FILE_VERSION = "This backup file was created using an unsupported version of the application and cannot be imported here. Please update your application and try again."; export declare const STRING_UNSUPPORTED_BACKUP_FILE_VERSION = "This backup file was created using an unsupported version of the application and cannot be imported here. Please update your application and try again.";

View File

@@ -28,6 +28,9 @@ export interface PermissionsModalScope extends Partial<ng.IScope> {
permissionsString: string; permissionsString: string;
callback: (approved: boolean) => void; callback: (approved: boolean) => void;
} }
export interface AccountSwitcherScope extends Partial<ng.IScope> {
application: any;
}
export declare type PanelPuppet = { export declare type PanelPuppet = {
onReady?: () => void; onReady?: () => void;
ready?: boolean; ready?: boolean;

View File

@@ -3,13 +3,15 @@ import { ComponentGroup } from './component_group';
import { EditorGroup } from '@/ui_models/editor_group'; import { EditorGroup } from '@/ui_models/editor_group';
import { PasswordWizardType } from '@/types'; import { PasswordWizardType } from '@/types';
import { SNApplication, Challenge, ProtectedAction } from 'snjs'; import { SNApplication, Challenge, ProtectedAction } from 'snjs';
import { DesktopManager, LockManager, ArchiveManager, NativeExtManager, StatusManager, ThemeManager, PreferencesManager, KeyboardManager } from '@/services'; import { WebDeviceInterface } from '@/web_device_interface';
import { DesktopManager, AutolockService, ArchiveManager, NativeExtManager, StatusManager, ThemeManager, PreferencesManager, KeyboardManager } from '@/services';
import { AppState } from '@/ui_models/app_state'; import { AppState } from '@/ui_models/app_state';
import { Bridge } from '@/services/bridge'; import { Bridge } from '@/services/bridge';
import { DeinitSource } from 'snjs/dist/@types/types';
declare type WebServices = { declare type WebServices = {
appState: AppState; appState: AppState;
desktopService: DesktopManager; desktopService: DesktopManager;
lockService: LockManager; autolockService: AutolockService;
archiveService: ArchiveManager; archiveService: ArchiveManager;
nativeExtService: NativeExtManager; nativeExtService: NativeExtManager;
statusService: StatusManager; statusService: StatusManager;
@@ -20,18 +22,17 @@ declare type WebServices = {
export declare class WebApplication extends SNApplication { export declare class WebApplication extends SNApplication {
private $compile?; private $compile?;
private scope?; private scope?;
private onDeinit?;
private webServices; private webServices;
private currentAuthenticationElement?; private currentAuthenticationElement?;
editorGroup: EditorGroup; editorGroup: EditorGroup;
componentGroup: ComponentGroup; componentGroup: ComponentGroup;
constructor($compile: ng.ICompileService, $timeout: ng.ITimeoutService, scope: ng.IScope, onDeinit: (app: WebApplication) => void, defaultSyncServerHost: string, bridge: Bridge); constructor(deviceInterface: WebDeviceInterface, identifier: string, $compile: ng.ICompileService, scope: ng.IScope, defaultSyncServerHost: string, bridge: Bridge);
/** @override */ /** @override */
deinit(): void; deinit(source: DeinitSource): void;
setWebServices(services: WebServices): void; setWebServices(services: WebServices): void;
getAppState(): AppState; getAppState(): AppState;
getDesktopService(): DesktopManager; getDesktopService(): DesktopManager;
getLockService(): LockManager; getAutolockService(): AutolockService;
getArchiveService(): ArchiveManager; getArchiveService(): ArchiveManager;
getNativeExtService(): NativeExtManager; getNativeExtService(): NativeExtManager;
getStatusService(): StatusManager; getStatusService(): StatusManager;
@@ -47,5 +48,6 @@ export declare class WebApplication extends SNApplication {
authenticationInProgress(): boolean; authenticationInProgress(): boolean;
presentPasswordModal(callback: () => void): void; presentPasswordModal(callback: () => void): void;
presentRevisionPreviewModal(uuid: string, content: any): void; presentRevisionPreviewModal(uuid: string, content: any): void;
openAccountSwitcher(): void;
} }
export {}; export {};

View File

@@ -1,29 +1,13 @@
/// <reference types="angular" /> /// <reference types="angular" />
import { WebApplication } from './application'; import { SNApplicationGroup } from 'snjs';
import { Bridge } from '@/services/bridge'; import { Bridge } from '@/services/bridge';
declare type AppManagerChangeCallback = () => void; export declare class ApplicationGroup extends SNApplicationGroup {
export declare class ApplicationGroup {
private defaultSyncServerHost; private defaultSyncServerHost;
private bridge; private bridge;
$compile: ng.ICompileService; $compile: ng.ICompileService;
$rootScope: ng.IRootScopeService; $rootScope: ng.IRootScopeService;
$timeout: ng.ITimeoutService; $timeout: ng.ITimeoutService;
applications: WebApplication[];
changeObservers: AppManagerChangeCallback[];
activeApplication?: WebApplication;
constructor($compile: ng.ICompileService, $rootScope: ng.IRootScopeService, $timeout: ng.ITimeoutService, defaultSyncServerHost: string, bridge: Bridge); constructor($compile: ng.ICompileService, $rootScope: ng.IRootScopeService, $timeout: ng.ITimeoutService, defaultSyncServerHost: string, bridge: Bridge);
private createDefaultApplication; initialize(callback?: any): Promise<void>;
/** @callback */ private createApplication;
onApplicationDeinit(application: WebApplication): void;
private createNewApplication;
get application(): WebApplication | undefined;
getApplications(): WebApplication[];
/**
* Notifies observer when the active application has changed.
* Any application which is no longer active is destroyed, and
* must be removed from the interface.
*/
addApplicationChangeObserver(callback: AppManagerChangeCallback): () => void;
private notifyObserversOfAppChange;
} }
export {};

View File

@@ -1,6 +1,6 @@
import { SNComponent, ComponentArea } from 'snjs'; import { SNComponent, ComponentArea } from 'snjs';
import { WebApplication } from './application'; import { WebApplication } from './application';
import { UuidString } from '@node_modules/snjs/dist/@types/types'; import { UuidString } from 'snjs/dist/@types/types';
export declare class ComponentGroup { export declare class ComponentGroup {
private application; private application;
changeObservers: any[]; changeObservers: any[];

View File

@@ -0,0 +1,4 @@
import { WebDirective } from '@/types';
export declare class AccountSwitcher extends WebDirective {
constructor();
}

View File

@@ -1,4 +1,4 @@
import { SNNote, SNTag } from 'snjs'; import { SNNote } from 'snjs';
export declare enum NoteSortKey { export declare enum NoteSortKey {
CreatedAt = "created_at", CreatedAt = "created_at",
UserUpdatedAt = "userModifiedDate", UserUpdatedAt = "userModifiedDate",
@@ -8,4 +8,4 @@ export declare enum NoteSortKey {
/** @legacy Use UserUpdatedAt instead */ /** @legacy Use UserUpdatedAt instead */
ClientUpdatedAt = "client_updated_at" ClientUpdatedAt = "client_updated_at"
} }
export declare function notePassesFilter(note: SNNote, selectedTag: SNTag, showArchived: boolean, hidePinned: boolean, filterText: string): boolean; export declare function notePassesFilter(note: SNNote, showArchived: boolean, hidePinned: boolean, filterText: string): boolean;

View File

@@ -1,10 +1,11 @@
import { DeviceInterface, SNApplication } from 'snjs'; import { DeviceInterface, SNApplication, ApplicationIdentifier } from 'snjs';
import { Bridge } from './services/bridge'; import { Bridge } from './services/bridge';
export declare class WebDeviceInterface extends DeviceInterface { export declare class WebDeviceInterface extends DeviceInterface {
private bridge; private bridge;
private database; private databases;
constructor(timeout: any, bridge: Bridge); constructor(timeout: any, bridge: Bridge);
setApplication(application: SNApplication): void; setApplication(application: SNApplication): void;
private databaseForIdentifier;
deinit(): void; deinit(): void;
getRawStorageValue(key: string): Promise<string | null>; getRawStorageValue(key: string): Promise<string | null>;
getAllRawStorageKeyValues(): Promise<{ getAllRawStorageKeyValues(): Promise<{
@@ -14,19 +15,17 @@ export declare class WebDeviceInterface extends DeviceInterface {
setRawStorageValue(key: string, value: any): Promise<void>; setRawStorageValue(key: string, value: any): Promise<void>;
removeRawStorageValue(key: string): Promise<void>; removeRawStorageValue(key: string): Promise<void>;
removeAllRawStorageValues(): Promise<void>; removeAllRawStorageValues(): Promise<void>;
openDatabase(): Promise<{ openDatabase(identifier: ApplicationIdentifier): Promise<{
isNewDatabase?: boolean | undefined; isNewDatabase?: boolean | undefined;
} | undefined>; } | undefined>;
private getDatabaseKeyPrefix; getAllRawDatabasePayloads(identifier: ApplicationIdentifier): Promise<any[]>;
private keyForPayloadId; saveRawDatabasePayload(payload: any, identifier: ApplicationIdentifier): Promise<void>;
getAllRawDatabasePayloads(): Promise<any[]>; saveRawDatabasePayloads(payloads: any[], identifier: ApplicationIdentifier): Promise<void>;
saveRawDatabasePayload(payload: any): Promise<void>; removeRawDatabasePayloadWithId(id: string, identifier: ApplicationIdentifier): Promise<void>;
saveRawDatabasePayloads(payloads: any[]): Promise<void>; removeAllRawDatabasePayloads(identifier: ApplicationIdentifier): Promise<void>;
removeRawDatabasePayloadWithId(id: string): Promise<void>; getNamespacedKeychainValue(identifier: ApplicationIdentifier): Promise<any>;
removeAllRawDatabasePayloads(): Promise<void>; setNamespacedKeychainValue(value: any, identifier: ApplicationIdentifier): Promise<void>;
getNamespacedKeychainValue(): Promise<any>; clearNamespacedKeychainValue(identifier: ApplicationIdentifier): Promise<void>;
setNamespacedKeychainValue(value: any): Promise<void>;
clearNamespacedKeychainValue(): Promise<void>;
getRawKeychainValue(): Promise<any>; getRawKeychainValue(): Promise<any>;
clearRawKeychainValue(): Promise<void>; clearRawKeychainValue(): Promise<void>;
openUrl(url: string): void; openUrl(url: string): void;

6
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "standard-notes-web", "name": "standard-notes-web",
"version": "3.5.0-beta3", "version": "3.5.0-beta2",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@@ -10956,8 +10956,8 @@
"from": "github:standardnotes/sncrypto#8794c88daa967eaae493cd5fdec7506d52b257ad" "from": "github:standardnotes/sncrypto#8794c88daa967eaae493cd5fdec7506d52b257ad"
}, },
"snjs": { "snjs": {
"version": "github:standardnotes/snjs#a39193f71cdcf1c02a7f0fa0cb96850f07b1717f", "version": "github:standardnotes/snjs#b029e6f7da367fecc40acab6e378e40ce247505a",
"from": "github:standardnotes/snjs#a39193f71cdcf1c02a7f0fa0cb96850f07b1717f" "from": "github:standardnotes/snjs#b029e6f7da367fecc40acab6e378e40ce247505a"
}, },
"sockjs": { "sockjs": {
"version": "0.3.20", "version": "0.3.20",

View File

@@ -1,6 +1,6 @@
{ {
"name": "standard-notes-web", "name": "standard-notes-web",
"version": "3.5.0-beta3", "version": "3.5.0-beta2",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -11,7 +11,6 @@
"start": "webpack-dev-server --progress --config webpack.dev.js", "start": "webpack-dev-server --progress --config webpack.dev.js",
"watch": "webpack -w --config webpack.dev.js", "watch": "webpack -w --config webpack.dev.js",
"bundle": "webpack --config webpack.prod.js && npm run tsc", "bundle": "webpack --config webpack.prod.js && npm run tsc",
"bundle:desktop": "webpack --config webpack.prod.js --env.platform='desktop'",
"build": "bundle install && npm ci && bundle exec rails assets:precompile && npm run bundle", "build": "bundle install && npm ci && bundle exec rails assets:precompile && npm run bundle",
"submodules": "git submodule update --init --force", "submodules": "git submodule update --init --force",
"lint": "eslint --fix app/assets/javascripts/**/*.js", "lint": "eslint --fix app/assets/javascripts/**/*.js",
@@ -68,6 +67,6 @@
}, },
"dependencies": { "dependencies": {
"sncrypto": "github:standardnotes/sncrypto#8794c88daa967eaae493cd5fdec7506d52b257ad", "sncrypto": "github:standardnotes/sncrypto#8794c88daa967eaae493cd5fdec7506d52b257ad",
"snjs": "github:standardnotes/snjs#a39193f71cdcf1c02a7f0fa0cb96850f07b1717f" "snjs": "github:standardnotes/snjs#b029e6f7da367fecc40acab6e378e40ce247505a"
} }
} }