More TypeScript

This commit is contained in:
Mo Bitar
2020-04-10 09:21:45 -05:00
parent b5b53fdc43
commit 16bde8d1d4
11 changed files with 106 additions and 144 deletions

View File

@@ -1,5 +1,7 @@
'use strict'; 'use strict';
declare const __VERSION__: string
import angular from 'angular'; import angular from 'angular';
import { configRoutes } from './routes'; import { configRoutes } from './routes';

View File

@@ -40,7 +40,7 @@ export class WebApplication extends SNApplication {
private $compile?: ng.ICompileService private $compile?: ng.ICompileService
private scope?: ng.IScope private scope?: ng.IScope
private onDeinit?: (app: SNApplication) => void private onDeinit?: (app: WebApplication) => void
private webServices!: WebServices private webServices!: WebServices
private currentAuthenticationElement?: JQLite private currentAuthenticationElement?: JQLite
@@ -49,7 +49,7 @@ export class WebApplication extends SNApplication {
$compile: ng.ICompileService, $compile: ng.ICompileService,
$timeout: ng.ITimeoutService, $timeout: ng.ITimeoutService,
scope: ng.IScope, scope: ng.IScope,
onDeinit: () => void onDeinit: (app: WebApplication) => void
) { ) {
const namespace = ''; const namespace = '';
const deviceInterface = new WebDeviceInterface(namespace, $timeout); const deviceInterface = new WebDeviceInterface(namespace, $timeout);
@@ -148,7 +148,9 @@ export class WebApplication extends SNApplication {
const scope = this.scope!.$new(true) as PasswordWizardScope; const scope = this.scope!.$new(true) as PasswordWizardScope;
scope.type = type; scope.type = type;
scope.application = this; scope.application = this;
const el = this.$compile!("<password-wizard application='application' type='type'></password-wizard>")(scope); const el = this.$compile!(
"<password-wizard application='application' type='type'></password-wizard>"
)(scope as any);
angular.element(document.body).append(el); angular.element(document.body).append(el);
} }

View File

@@ -12,30 +12,41 @@ import {
AppState AppState
} from './services'; } from './services';
type AppManagerChangeCallback = () => void
export class ApplicationManager { export class ApplicationManager {
$compile: ng.ICompileService
$rootScope: ng.IRootScopeService
$timeout: ng.ITimeoutService
applications: WebApplication[] = []
changeObservers: AppManagerChangeCallback[] = []
activeApplication?: WebApplication
/* @ngInject */ /* @ngInject */
constructor($compile, $rootScope, $timeout) { constructor(
$compile: ng.ICompileService,
$rootScope: ng.IRootScopeService,
$timeout: ng.ITimeoutService
) {
this.$compile = $compile; this.$compile = $compile;
this.$timeout = $timeout; this.$timeout = $timeout;
this.$rootScope = $rootScope; this.$rootScope = $rootScope;
this.applications = [];
this.changeObservers = [];
this.onApplicationDeinit = this.onApplicationDeinit.bind(this); this.onApplicationDeinit = this.onApplicationDeinit.bind(this);
this.createDefaultApplication(); this.createDefaultApplication();
} }
/** @access private */ private createDefaultApplication() {
createDefaultApplication() {
this.activeApplication = this.createNewApplication(); this.activeApplication = this.createNewApplication();
this.applications.push(this.activeApplication); this.applications.push(this.activeApplication!);
this.notifyObserversOfAppChange(); this.notifyObserversOfAppChange();
} }
/** @callback */ /** @callback */
onApplicationDeinit(application) { onApplicationDeinit(application: WebApplication) {
removeFromArray(this.applications, application); removeFromArray(this.applications, application);
if(this.activeApplication === application) { if (this.activeApplication === application) {
this.activeApplication = null; this.activeApplication = undefined;
} }
if (this.applications.length === 0) { if (this.applications.length === 0) {
this.createDefaultApplication(); this.createDefaultApplication();
@@ -43,8 +54,7 @@ export class ApplicationManager {
this.notifyObserversOfAppChange(); this.notifyObserversOfAppChange();
} }
/** @access private */ private createNewApplication() {
createNewApplication() {
const scope = this.$rootScope.$new(true); const scope = this.$rootScope.$new(true);
const application = new WebApplication( const application = new WebApplication(
this.$compile, this.$compile,
@@ -97,8 +107,7 @@ export class ApplicationManager {
return this.activeApplication; return this.activeApplication;
} }
/** @access public */ public getApplications() {
getApplications() {
return this.applications.slice(); return this.applications.slice();
} }
@@ -106,20 +115,17 @@ export class ApplicationManager {
* Notifies observer when the active application has changed. * Notifies observer when the active application has changed.
* Any application which is no longer active is destroyed, and * Any application which is no longer active is destroyed, and
* must be removed from the interface. * must be removed from the interface.
* @access public
* @param {function} callback
*/ */
addApplicationChangeObserver(callback) { public addApplicationChangeObserver(callback: AppManagerChangeCallback) {
this.changeObservers.push(callback); this.changeObservers.push(callback);
if (this.application) { if (this.application) {
callback(); callback();
} }
} }
notifyObserversOfAppChange() { private notifyObserversOfAppChange() {
for (const observer of this.changeObservers) { for (const observer of this.changeObservers) {
observer(); observer();
} }
} }
} }

View File

@@ -545,10 +545,10 @@ class EditorCtrl extends PureCtrl {
const title = this.state.note.safeTitle().length const title = this.state.note.safeTitle().length
? `'${this.state.note.title}'` ? `'${this.state.note.title}'`
: "this note"; : "this note";
const text = StringDeleteNote({ const text = StringDeleteNote(
title: title, title,
permanently: permanently permanently
}); );
this.application.alertService.confirm({ this.application.alertService.confirm({
text: text, text: text,
destructive: true, destructive: true,
@@ -616,7 +616,7 @@ class EditorCtrl extends PureCtrl {
emptyTrash() { emptyTrash() {
const count = this.getTrashCount(); const count = this.getTrashCount();
this.application.alertService.confirm({ this.application.alertService.confirm({
text: StringEmptyTrash({ count }), text: StringEmptyTrash(count),
destructive: true, destructive: true,
onConfirm: () => { onConfirm: () => {
this.application.emptyTrash(); this.application.emptyTrash();

View File

@@ -1,3 +1,6 @@
import { WebApplication } from './application';
import { SNAlertService } from "../../../../snjs/dist/@types";
const DB_NAME = 'standardnotes'; const DB_NAME = 'standardnotes';
const STORE_NAME = 'items'; const STORE_NAME = 'items';
const READ_WRITE = 'readwrite'; const READ_WRITE = 'readwrite';
@@ -15,37 +18,34 @@ const DB_DELETION_BLOCKED =
const QUOTE_EXCEEDED_ERROR = 'QuotaExceededError'; const QUOTE_EXCEEDED_ERROR = 'QuotaExceededError';
export class Database { export class Database {
constructor() {
this.locked = true; private locked = true
private alertService?: SNAlertService
private db?: IDBDatabase
public deinit() {
this.alertService = undefined;
this.db = undefined;
} }
/** @access public */ public setAlertService(alertService: SNAlertService) {
deinit() { this.alertService = alertService;
this.alertService = null;
this.db = null;
}
/** @access public */
setApplication(application) {
this.alertService = application.alertService;
} }
/** /**
* Relinquishes the lock and allows db operations to proceed * Relinquishes the lock and allows db operations to proceed
* @access public
*/ */
unlock() { public unlock() {
this.locked = false; this.locked = false;
} }
/** /**
* Opens the database natively, or returns the existing database object if already opened. * Opens the database natively, or returns the existing database object if already opened.
* @access public * @param onNewDatabase - Callback to invoke when a database has been created
* @param {function} onNewDatabase - Callback to invoke when a database has been created
* as part of the open process. This can happen on new application sessions, or if the * as part of the open process. This can happen on new application sessions, or if the
* browser deleted the database without the user being aware. * browser deleted the database without the user being aware.
*/ */
async openDatabase(onNewDatabase) { public async openDatabase(onNewDatabase?: () => void): Promise<IDBDatabase | undefined> {
if (this.locked) { if (this.locked) {
throw Error('Attempting to open locked database'); throw Error('Attempting to open locked database');
} }
@@ -55,8 +55,9 @@ export class Database {
const request = window.indexedDB.open(DB_NAME, 1); const request = window.indexedDB.open(DB_NAME, 1);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
request.onerror = (event) => { request.onerror = (event) => {
if (event.target.errorCode) { const target = event!.target! as any;
this.showAlert('Offline database issue: ' + event.target.errorCode); if (target.errorCode) {
this.showAlert('Offline database issue: ' + target.errorCode);
} else { } else {
this.displayOfflineAlert(); this.displayOfflineAlert();
} }
@@ -66,18 +67,21 @@ export class Database {
reject(Error('IndexedDB open request blocked')); reject(Error('IndexedDB open request blocked'));
}; };
request.onsuccess = (event) => { request.onsuccess = (event) => {
const db = event.target.result; const target = event!.target! as IDBOpenDBRequest;
const db = target.result;
db.onversionchange = () => { db.onversionchange = () => {
db.close(); db.close();
}; };
db.onerror = (errorEvent) => { db.onerror = (errorEvent) => {
throw Error('Database error: ' + errorEvent.target.errorCode); const target = errorEvent?.target as any;
throw Error('Database error: ' + target.errorCode);
}; };
this.db = db; this.db = db;
resolve(db); resolve(db);
}; };
request.onupgradeneeded = (event) => { request.onupgradeneeded = (event) => {
const db = event.target.result; const target = event!.target! as IDBOpenDBRequest;
const db = target.result;
db.onversionchange = () => { db.onversionchange = () => {
db.close(); db.close();
}; };
@@ -94,24 +98,24 @@ export class Database {
objectStore.transaction.oncomplete = () => { objectStore.transaction.oncomplete = () => {
/* Ready to store values in the newly created objectStore. */ /* Ready to store values in the newly created objectStore. */
if (db.version === 1 && onNewDatabase) { if (db.version === 1 && onNewDatabase) {
onNewDatabase(); onNewDatabase && onNewDatabase();
} }
}; };
}; };
}); });
} }
/** @access public */ public async getAllPayloads() {
async getAllPayloads() { const db = (await this.openDatabase())!;
const db = await this.openDatabase(); return new Promise((resolve) => {
return new Promise((resolve, reject) => {
const objectStore = const objectStore =
db.transaction(STORE_NAME). db.transaction(STORE_NAME).
objectStore(STORE_NAME); objectStore(STORE_NAME);
const payloads = []; const payloads: any = [];
const cursorRequest = objectStore.openCursor(); const cursorRequest = objectStore.openCursor();
cursorRequest.onsuccess = (event) => { cursorRequest.onsuccess = (event) => {
const cursor = event.target.result; const target = event!.target! as any;
const cursor = target.result;
if (cursor) { if (cursor) {
payloads.push(cursor.value); payloads.push(cursor.value);
cursor.continue(); cursor.continue();
@@ -122,28 +126,25 @@ export class Database {
}); });
} }
/** @access public */ public async savePayload(payload: any) {
async savePayload(payload) {
return this.savePayloads([payload]); return this.savePayloads([payload]);
} }
/** @access public */ public async savePayloads(payloads: any[]) {
async savePayloads(payloads) {
if (payloads.length === 0) { if (payloads.length === 0) {
return; return;
} }
const db = await this.openDatabase(); const db = (await this.openDatabase())!;
const transaction = db.transaction(STORE_NAME, READ_WRITE); const transaction = db.transaction(STORE_NAME, READ_WRITE);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
transaction.oncomplete = () => { }; transaction.oncomplete = () => { };
transaction.onerror = (event) => { transaction.onerror = (event) => {
this.showGenericError(event.target.error); const target = event!.target! as any;
}; this.showGenericError(target.error);
transaction.onblocked = (event) => {
this.showGenericError(event.target.error);
}; };
transaction.onabort = (event) => { transaction.onabort = (event) => {
const error = event.target.error; const target = event!.target! as any;
const error = target.error;
if (error.name === QUOTE_EXCEEDED_ERROR) { if (error.name === QUOTE_EXCEEDED_ERROR) {
this.showAlert(OUT_OF_SPACE); this.showAlert(OUT_OF_SPACE);
} else { } else {
@@ -156,10 +157,9 @@ export class Database {
}); });
} }
/** @access private */ private putItems(objectStore: IDBObjectStore, items: any[]) {
putItems(objectStore, items) {
return Promise.all(items.map((item) => { return Promise.all(items.map((item) => {
return new Promise((resolve, reject) => { return new Promise((resolve) => {
const request = objectStore.put(item); const request = objectStore.put(item);
request.onerror = resolve; request.onerror = resolve;
request.onsuccess = resolve; request.onsuccess = resolve;
@@ -167,9 +167,8 @@ export class Database {
})); }));
} }
/** @access public */ public async deletePayload(uuid: string) {
async deletePayload(uuid) { const db = (await this.openDatabase())!;
const db = await this.openDatabase();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const request = const request =
db.transaction(STORE_NAME, READ_WRITE) db.transaction(STORE_NAME, READ_WRITE)
@@ -180,15 +179,14 @@ export class Database {
}); });
} }
/** @access public */ public async clearAllPayloads() {
async clearAllPayloads() {
const deleteRequest = window.indexedDB.deleteDatabase(DB_NAME); const deleteRequest = window.indexedDB.deleteDatabase(DB_NAME);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
deleteRequest.onerror = () => { deleteRequest.onerror = () => {
reject(Error('Error deleting database.')); reject(Error('Error deleting database.'));
}; };
deleteRequest.onsuccess = () => { deleteRequest.onsuccess = () => {
this.db = null; this.db = undefined;
resolve(); resolve();
}; };
deleteRequest.onblocked = (event) => { deleteRequest.onblocked = (event) => {
@@ -198,30 +196,24 @@ export class Database {
}); });
} }
/** @access private */ private showAlert(message: string) {
showAlert(message) { this.alertService!.alert(message);
this.alertService.alert({ text: message });
} }
/** private showGenericError(error: {code: number, name: string}) {
* @access private
* @param {object} error - {code, name}
*/
showGenericError(error) {
const message = const message =
`Unable to save changes locally due to an unknown system issue. ` + `Unable to save changes locally due to an unknown system issue. ` +
`Issue Code: ${error.code} Issue Name: ${error.name}.`; `Issue Code: ${error.code} Issue Name: ${error.name}.`;
this.showAlert(message); this.showAlert(message);
} }
/** @access private */ private displayOfflineAlert() {
displayOfflineAlert() {
const message = const message =
"There was an issue loading your offline database. This could happen for two reasons:" + "There was an issue loading your offline database. This could happen for two reasons:" +
"\n\n1. You're in a private window in your browser. We can't save your data without " + "\n\n1. You're in a private window in your browser. We can't save your data without " +
"access to the local database. Please use a non-private window." + "access to the local database. Please use a non-private window." +
"\n\n2. You have two windows of the app open at the same time. " + "\n\n2. You have two windows of the app open at the same time. " +
"Please close any other app instances and reload the page."; "Please close any other app instances and reload the page.";
this.alertService.alert({ text: message }); this.alertService!.alert(message);
} }
} }

View File

@@ -379,7 +379,7 @@ class AccountMenuCtrl extends PureCtrl {
importData: null importData: null
}); });
if (errorCount > 0) { if (errorCount > 0) {
const message = StringImportError({ errorCount: errorCount }); const message = StringImportError(errorCount);
this.application.alertService.alert({ this.application.alertService.alert({
text: message text: message
}); });

View File

@@ -117,7 +117,7 @@ class PanelResizerCtrl {
} }
handleResize() { handleResize() {
debounce(() => { debounce(this, () => {
this.reloadDefaultValues(); this.reloadDefaultValues();
this.handleWidthEvent(); this.handleWidthEvent();
this.$timeout(() => { this.$timeout(() => {

View File

@@ -2,7 +2,7 @@
export const STRING_SESSION_EXPIRED = "Your session has expired. New changes will not be pulled in. Please sign out and sign back in to refresh your session."; export const STRING_SESSION_EXPIRED = "Your session has expired. New changes will not be pulled in. Please sign out and sign back in to refresh your session.";
export const STRING_DEFAULT_FILE_ERROR = "Please use FileSafe or the Bold Editor to attach images and files. Learn more at standardnotes.org/filesafe."; export const STRING_DEFAULT_FILE_ERROR = "Please use FileSafe or the Bold Editor to attach images and files. Learn more at standardnotes.org/filesafe.";
export const STRING_GENERIC_SYNC_ERROR = "There was an error syncing. Please try again. If all else fails, try signing out and signing back in."; export const STRING_GENERIC_SYNC_ERROR = "There was an error syncing. Please try again. If all else fails, try signing out and signing back in.";
export function StringSyncException(data) { export function StringSyncException(data: any) {
return `There was an error while trying to save your items. Please contact support and share this message: ${data}.`; return `There was an error while trying to save your items. Please contact support and share this message: ${data}.`;
} }
@@ -19,12 +19,12 @@ export const STRING_ELLIPSES = "...";
export const STRING_GENERIC_SAVE_ERROR = "There was an error saving your note. Please try again."; export const STRING_GENERIC_SAVE_ERROR = "There was an error saving your note. Please try again.";
export const STRING_DELETE_PLACEHOLDER_ATTEMPT = "This note is a placeholder and cannot be deleted. To remove from your list, simply navigate to a different note."; export const STRING_DELETE_PLACEHOLDER_ATTEMPT = "This note is a placeholder and cannot be deleted. To remove from your list, simply navigate to a different note.";
export const STRING_DELETE_LOCKED_ATTEMPT = "This note is locked. If you'd like to delete it, unlock it, and try again."; export const STRING_DELETE_LOCKED_ATTEMPT = "This note is locked. If you'd like to delete it, unlock it, and try again.";
export function StringDeleteNote({ title, permanently }) { export function StringDeleteNote(title: string, permanently: boolean) {
return permanently return permanently
? `Are you sure you want to permanently delete ${title}?` ? `Are you sure you want to permanently delete ${title}?`
: `Are you sure you want to move ${title} to the trash?`; : `Are you sure you want to move ${title} to the trash?`;
} }
export function StringEmptyTrash({ count }) { export function StringEmptyTrash(count: number) {
return `Are you sure you want to permanently delete ${count} note(s)?`; return `Are you sure you want to permanently delete ${count} note(s)?`;
} }
@@ -43,7 +43,7 @@ export const STRING_NON_MATCHING_PASSWORDS = "The two passwords you entered do n
export const STRING_GENERATING_LOGIN_KEYS = "Generating Login Keys..."; export const STRING_GENERATING_LOGIN_KEYS = "Generating Login Keys...";
export const STRING_GENERATING_REGISTER_KEYS = "Generating Account Keys..."; export const STRING_GENERATING_REGISTER_KEYS = "Generating Account Keys...";
export const STRING_INVALID_IMPORT_FILE = "Unable to open file. Ensure it is a proper JSON file and try again."; export const STRING_INVALID_IMPORT_FILE = "Unable to open file. Ensure it is a proper JSON file and try again.";
export function StringImportError({ errorCount }) { 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.`;
} }

View File

@@ -1,4 +1,4 @@
export function getParameterByName(name, url) { export function getParameterByName(name: string, url: string) {
name = name.replace(/[[\]]/g, '\\$&'); name = name.replace(/[[\]]/g, '\\$&');
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'); var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
var results = regex.exec(url); var results = regex.exec(url);
@@ -7,48 +7,14 @@ export function getParameterByName(name, url) {
return decodeURIComponent(results[2].replace(/\+/g, ' ')); return decodeURIComponent(results[2].replace(/\+/g, ' '));
} }
export function parametersFromURL(url) { export function isNullOrUndefined(value: any) {
url = url.split('?').slice(-1)[0];
var obj = {};
url.replace(/([^=&]+)=([^&]*)/g, function(m, key, value) {
obj[decodeURIComponent(key)] = decodeURIComponent(value);
});
return obj;
}
export function isNullOrUndefined(value) {
return value === null || value === undefined; return value === null || value === undefined;
} }
export function dictToArray(dict) { export function dictToArray(dict: any) {
return Object.keys(dict).map((key) => dict[key]); return Object.keys(dict).map((key) => dict[key]);
} }
export function humanReadableList(array) {
const addSeparator = (index, length) => {
if (index > 0) {
if (index === length - 1) {
if (length === 2) {
return ' and ';
} else {
return ', and ';
}
} else {
return ', ';
}
}
return '';
};
let result = '';
for (let i = 0; i < array.length; i++) {
const value = array[i];
result += addSeparator(i, array.length);
result += value;
}
return result;
}
export function getPlatformString() { export function getPlatformString() {
try { try {
const platform = navigator.platform.toLowerCase(); const platform = navigator.platform.toLowerCase();
@@ -67,8 +33,8 @@ export function getPlatformString() {
} }
} }
let sharedDateFormatter; let sharedDateFormatter: Intl.DateTimeFormat;
export function dateToLocalizedString(date) { export function dateToLocalizedString(date: Date) {
if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) { if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
if (!sharedDateFormatter) { if (!sharedDateFormatter) {
const locale = ( const locale = (
@@ -94,9 +60,9 @@ export function dateToLocalizedString(date) {
} }
/** Via https://davidwalsh.name/javascript-debounce-function */ /** Via https://davidwalsh.name/javascript-debounce-function */
export function debounce(func, wait, immediate) { export function debounce(this: any, func: any, wait: number, immediate: boolean) {
let timeout; let timeout: any;
return function () { return () => {
const context = this; const context = this;
const args = arguments; const args = arguments;
const later = function () { const later = function () {
@@ -111,20 +77,14 @@ export function debounce(func, wait, immediate) {
}; };
export function isDesktopApplication() { export function isDesktopApplication() {
return window.isElectron; return (window as any).isElectron;
} }
/* Use with numbers and strings, not objects */
// eslint-disable-next-line no-extend-native
Array.prototype.containsPrimitiveSubset = function(array) {
return !array.some(val => this.indexOf(val) === -1);
};
// https://tc39.github.io/ecma262/#sec-array.prototype.includes // https://tc39.github.io/ecma262/#sec-array.prototype.includes
if (!Array.prototype.includes) { if (!Array.prototype.includes) {
// eslint-disable-next-line no-extend-native // eslint-disable-next-line no-extend-native
Object.defineProperty(Array.prototype, 'includes', { Object.defineProperty(Array.prototype, 'includes', {
value: function(searchElement, fromIndex) { value: function(searchElement: any, fromIndex: number) {
if (this == null) { if (this == null) {
throw new TypeError('"this" is null or not defined'); throw new TypeError('"this" is null or not defined');
} }
@@ -151,7 +111,7 @@ if (!Array.prototype.includes) {
// b. If k < 0, let k be 0. // b. If k < 0, let k be 0.
var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
function sameValueZero(x, y) { function sameValueZero(x: number, y: number) {
return ( return (
x === y || x === y ||
(typeof x === 'number' && (typeof x === 'number' &&

View File

@@ -17,7 +17,7 @@ export class WebDeviceInterface extends DeviceInterface {
} }
setApplication(application: SNApplication) { setApplication(application: SNApplication) {
this.database.setApplication(application); this.database.setAlertService(application.alertService!);
} }
deinit() { deinit() {