diff --git a/app/assets/javascripts/app.js b/app/assets/javascripts/app.ts similarity index 98% rename from app/assets/javascripts/app.js rename to app/assets/javascripts/app.ts index 797752f9d..a648f718b 100644 --- a/app/assets/javascripts/app.js +++ b/app/assets/javascripts/app.ts @@ -1,5 +1,7 @@ 'use strict'; +declare const __VERSION__: string + import angular from 'angular'; import { configRoutes } from './routes'; diff --git a/app/assets/javascripts/application.ts b/app/assets/javascripts/application.ts index 48c3557cc..f6b846289 100644 --- a/app/assets/javascripts/application.ts +++ b/app/assets/javascripts/application.ts @@ -40,7 +40,7 @@ export class WebApplication extends SNApplication { private $compile?: ng.ICompileService private scope?: ng.IScope - private onDeinit?: (app: SNApplication) => void + private onDeinit?: (app: WebApplication) => void private webServices!: WebServices private currentAuthenticationElement?: JQLite @@ -49,7 +49,7 @@ export class WebApplication extends SNApplication { $compile: ng.ICompileService, $timeout: ng.ITimeoutService, scope: ng.IScope, - onDeinit: () => void + onDeinit: (app: WebApplication) => void ) { const namespace = ''; const deviceInterface = new WebDeviceInterface(namespace, $timeout); @@ -148,7 +148,9 @@ export class WebApplication extends SNApplication { const scope = this.scope!.$new(true) as PasswordWizardScope; scope.type = type; scope.application = this; - const el = this.$compile!("")(scope); + const el = this.$compile!( + "" + )(scope as any); angular.element(document.body).append(el); } diff --git a/app/assets/javascripts/applicationManager.js b/app/assets/javascripts/applicationManager.ts similarity index 74% rename from app/assets/javascripts/applicationManager.js rename to app/assets/javascripts/applicationManager.ts index dc3ca3bdd..540b91363 100644 --- a/app/assets/javascripts/applicationManager.js +++ b/app/assets/javascripts/applicationManager.ts @@ -12,30 +12,41 @@ import { AppState } from './services'; +type AppManagerChangeCallback = () => void + export class ApplicationManager { + + $compile: ng.ICompileService + $rootScope: ng.IRootScopeService + $timeout: ng.ITimeoutService + applications: WebApplication[] = [] + changeObservers: AppManagerChangeCallback[] = [] + activeApplication?: WebApplication + /* @ngInject */ - constructor($compile, $rootScope, $timeout) { + constructor( + $compile: ng.ICompileService, + $rootScope: ng.IRootScopeService, + $timeout: ng.ITimeoutService + ) { this.$compile = $compile; this.$timeout = $timeout; this.$rootScope = $rootScope; - this.applications = []; - this.changeObservers = []; this.onApplicationDeinit = this.onApplicationDeinit.bind(this); this.createDefaultApplication(); } - - /** @access private */ - createDefaultApplication() { + + private createDefaultApplication() { this.activeApplication = this.createNewApplication(); - this.applications.push(this.activeApplication); + this.applications.push(this.activeApplication!); this.notifyObserversOfAppChange(); } /** @callback */ - onApplicationDeinit(application) { + onApplicationDeinit(application: WebApplication) { removeFromArray(this.applications, application); - if(this.activeApplication === application) { - this.activeApplication = null; + if (this.activeApplication === application) { + this.activeApplication = undefined; } if (this.applications.length === 0) { this.createDefaultApplication(); @@ -43,8 +54,7 @@ export class ApplicationManager { this.notifyObserversOfAppChange(); } - /** @access private */ - createNewApplication() { + private createNewApplication() { const scope = this.$rootScope.$new(true); const application = new WebApplication( this.$compile, @@ -97,8 +107,7 @@ export class ApplicationManager { return this.activeApplication; } - /** @access public */ - getApplications() { + public getApplications() { return this.applications.slice(); } @@ -106,20 +115,17 @@ export class ApplicationManager { * Notifies observer when the active application has changed. * Any application which is no longer active is destroyed, and * must be removed from the interface. - * @access public - * @param {function} callback */ - addApplicationChangeObserver(callback) { + public addApplicationChangeObserver(callback: AppManagerChangeCallback) { this.changeObservers.push(callback); if (this.application) { callback(); } } - notifyObserversOfAppChange() { + private notifyObserversOfAppChange() { for (const observer of this.changeObservers) { observer(); } } - } \ No newline at end of file diff --git a/app/assets/javascripts/controllers/editor.js b/app/assets/javascripts/controllers/editor.js index bcabbc170..3330caac1 100644 --- a/app/assets/javascripts/controllers/editor.js +++ b/app/assets/javascripts/controllers/editor.js @@ -545,10 +545,10 @@ class EditorCtrl extends PureCtrl { const title = this.state.note.safeTitle().length ? `'${this.state.note.title}'` : "this note"; - const text = StringDeleteNote({ - title: title, - permanently: permanently - }); + const text = StringDeleteNote( + title, + permanently + ); this.application.alertService.confirm({ text: text, destructive: true, @@ -616,7 +616,7 @@ class EditorCtrl extends PureCtrl { emptyTrash() { const count = this.getTrashCount(); this.application.alertService.confirm({ - text: StringEmptyTrash({ count }), + text: StringEmptyTrash(count), destructive: true, onConfirm: () => { this.application.emptyTrash(); diff --git a/app/assets/javascripts/database.js b/app/assets/javascripts/database.ts similarity index 70% rename from app/assets/javascripts/database.js rename to app/assets/javascripts/database.ts index 6401f873f..42f05e4d7 100644 --- a/app/assets/javascripts/database.js +++ b/app/assets/javascripts/database.ts @@ -1,3 +1,6 @@ +import { WebApplication } from './application'; +import { SNAlertService } from "../../../../snjs/dist/@types"; + const DB_NAME = 'standardnotes'; const STORE_NAME = 'items'; const READ_WRITE = 'readwrite'; @@ -15,37 +18,34 @@ const DB_DELETION_BLOCKED = const QUOTE_EXCEEDED_ERROR = 'QuotaExceededError'; 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 */ - deinit() { - this.alertService = null; - this.db = null; - } - - /** @access public */ - setApplication(application) { - this.alertService = application.alertService; + public setAlertService(alertService: SNAlertService) { + this.alertService = alertService; } /** * Relinquishes the lock and allows db operations to proceed - * @access public */ - unlock() { + public unlock() { this.locked = false; } /** * Opens the database natively, or returns the existing database object if already opened. - * @access public - * @param {function} onNewDatabase - Callback to invoke when a database has been created + * @param 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 * browser deleted the database without the user being aware. */ - async openDatabase(onNewDatabase) { + public async openDatabase(onNewDatabase?: () => void): Promise { if (this.locked) { throw Error('Attempting to open locked database'); } @@ -55,8 +55,9 @@ export class Database { const request = window.indexedDB.open(DB_NAME, 1); return new Promise((resolve, reject) => { request.onerror = (event) => { - if (event.target.errorCode) { - this.showAlert('Offline database issue: ' + event.target.errorCode); + const target = event!.target! as any; + if (target.errorCode) { + this.showAlert('Offline database issue: ' + target.errorCode); } else { this.displayOfflineAlert(); } @@ -66,18 +67,21 @@ export class Database { reject(Error('IndexedDB open request blocked')); }; request.onsuccess = (event) => { - const db = event.target.result; + const target = event!.target! as IDBOpenDBRequest; + const db = target.result; db.onversionchange = () => { db.close(); }; 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; resolve(db); }; request.onupgradeneeded = (event) => { - const db = event.target.result; + const target = event!.target! as IDBOpenDBRequest; + const db = target.result; db.onversionchange = () => { db.close(); }; @@ -94,24 +98,24 @@ export class Database { objectStore.transaction.oncomplete = () => { /* Ready to store values in the newly created objectStore. */ if (db.version === 1 && onNewDatabase) { - onNewDatabase(); + onNewDatabase && onNewDatabase(); } }; }; }); } - /** @access public */ - async getAllPayloads() { - const db = await this.openDatabase(); - return new Promise((resolve, reject) => { + public async getAllPayloads() { + const db = (await this.openDatabase())!; + return new Promise((resolve) => { const objectStore = db.transaction(STORE_NAME). objectStore(STORE_NAME); - const payloads = []; + const payloads: any = []; const cursorRequest = objectStore.openCursor(); cursorRequest.onsuccess = (event) => { - const cursor = event.target.result; + const target = event!.target! as any; + const cursor = target.result; if (cursor) { payloads.push(cursor.value); cursor.continue(); @@ -122,28 +126,25 @@ export class Database { }); } - /** @access public */ - async savePayload(payload) { + public async savePayload(payload: any) { return this.savePayloads([payload]); } - /** @access public */ - async savePayloads(payloads) { + public async savePayloads(payloads: any[]) { if (payloads.length === 0) { return; } - const db = await this.openDatabase(); + const db = (await this.openDatabase())!; const transaction = db.transaction(STORE_NAME, READ_WRITE); return new Promise((resolve, reject) => { transaction.oncomplete = () => { }; transaction.onerror = (event) => { - this.showGenericError(event.target.error); - }; - transaction.onblocked = (event) => { - this.showGenericError(event.target.error); + const target = event!.target! as any; + this.showGenericError(target.error); }; transaction.onabort = (event) => { - const error = event.target.error; + const target = event!.target! as any; + const error = target.error; if (error.name === QUOTE_EXCEEDED_ERROR) { this.showAlert(OUT_OF_SPACE); } else { @@ -156,10 +157,9 @@ export class Database { }); } - /** @access private */ - putItems(objectStore, items) { + private putItems(objectStore: IDBObjectStore, items: any[]) { return Promise.all(items.map((item) => { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { const request = objectStore.put(item); request.onerror = resolve; request.onsuccess = resolve; @@ -167,9 +167,8 @@ export class Database { })); } - /** @access public */ - async deletePayload(uuid) { - const db = await this.openDatabase(); + public async deletePayload(uuid: string) { + const db = (await this.openDatabase())!; return new Promise((resolve, reject) => { const request = db.transaction(STORE_NAME, READ_WRITE) @@ -180,15 +179,14 @@ export class Database { }); } - /** @access public */ - async clearAllPayloads() { + public async clearAllPayloads() { const deleteRequest = window.indexedDB.deleteDatabase(DB_NAME); return new Promise((resolve, reject) => { deleteRequest.onerror = () => { reject(Error('Error deleting database.')); }; deleteRequest.onsuccess = () => { - this.db = null; + this.db = undefined; resolve(); }; deleteRequest.onblocked = (event) => { @@ -198,30 +196,24 @@ export class Database { }); } - /** @access private */ - showAlert(message) { - this.alertService.alert({ text: message }); + private showAlert(message: string) { + this.alertService!.alert(message); } - /** - * @access private - * @param {object} error - {code, name} - */ - showGenericError(error) { + private showGenericError(error: {code: number, name: string}) { const message = `Unable to save changes locally due to an unknown system issue. ` + `Issue Code: ${error.code} Issue Name: ${error.name}.`; this.showAlert(message); } - /** @access private */ - displayOfflineAlert() { + private displayOfflineAlert() { const message = "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 " + "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. " + "Please close any other app instances and reload the page."; - this.alertService.alert({ text: message }); + this.alertService!.alert(message); } -} +} \ No newline at end of file diff --git a/app/assets/javascripts/directives/views/accountMenu.js b/app/assets/javascripts/directives/views/accountMenu.js index 7f87960e6..daf9581cc 100644 --- a/app/assets/javascripts/directives/views/accountMenu.js +++ b/app/assets/javascripts/directives/views/accountMenu.js @@ -379,7 +379,7 @@ class AccountMenuCtrl extends PureCtrl { importData: null }); if (errorCount > 0) { - const message = StringImportError({ errorCount: errorCount }); + const message = StringImportError(errorCount); this.application.alertService.alert({ text: message }); diff --git a/app/assets/javascripts/directives/views/panelResizer.js b/app/assets/javascripts/directives/views/panelResizer.js index a98614f90..10d360205 100644 --- a/app/assets/javascripts/directives/views/panelResizer.js +++ b/app/assets/javascripts/directives/views/panelResizer.js @@ -117,7 +117,7 @@ class PanelResizerCtrl { } handleResize() { - debounce(() => { + debounce(this, () => { this.reloadDefaultValues(); this.handleWidthEvent(); this.$timeout(() => { diff --git a/app/assets/javascripts/index.js b/app/assets/javascripts/index.ts similarity index 100% rename from app/assets/javascripts/index.js rename to app/assets/javascripts/index.ts diff --git a/app/assets/javascripts/strings.js b/app/assets/javascripts/strings.ts similarity index 94% rename from app/assets/javascripts/strings.js rename to app/assets/javascripts/strings.ts index 6130a8cab..c8a027953 100644 --- a/app/assets/javascripts/strings.js +++ b/app/assets/javascripts/strings.ts @@ -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_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 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}.`; } @@ -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_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 function StringDeleteNote({ title, permanently }) { +export function StringDeleteNote(title: string, permanently: boolean) { return permanently ? `Are you sure you want to permanently delete ${title}?` : `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)?`; } @@ -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_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 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.`; } diff --git a/app/assets/javascripts/utils.js b/app/assets/javascripts/utils.ts similarity index 71% rename from app/assets/javascripts/utils.js rename to app/assets/javascripts/utils.ts index 008c58d53..1344dffdf 100644 --- a/app/assets/javascripts/utils.js +++ b/app/assets/javascripts/utils.ts @@ -1,4 +1,4 @@ -export function getParameterByName(name, url) { +export function getParameterByName(name: string, url: string) { name = name.replace(/[[\]]/g, '\\$&'); var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'); var results = regex.exec(url); @@ -7,48 +7,14 @@ export function getParameterByName(name, url) { return decodeURIComponent(results[2].replace(/\+/g, ' ')); } -export function parametersFromURL(url) { - 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) { +export function isNullOrUndefined(value: any) { return value === null || value === undefined; } -export function dictToArray(dict) { +export function dictToArray(dict: any) { 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() { try { const platform = navigator.platform.toLowerCase(); @@ -67,8 +33,8 @@ export function getPlatformString() { } } -let sharedDateFormatter; -export function dateToLocalizedString(date) { +let sharedDateFormatter: Intl.DateTimeFormat; +export function dateToLocalizedString(date: Date) { if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) { if (!sharedDateFormatter) { const locale = ( @@ -94,9 +60,9 @@ export function dateToLocalizedString(date) { } /** Via https://davidwalsh.name/javascript-debounce-function */ -export function debounce(func, wait, immediate) { - let timeout; - return function () { +export function debounce(this: any, func: any, wait: number, immediate: boolean) { + let timeout: any; + return () => { const context = this; const args = arguments; const later = function () { @@ -111,20 +77,14 @@ export function debounce(func, wait, immediate) { }; 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 if (!Array.prototype.includes) { // eslint-disable-next-line no-extend-native Object.defineProperty(Array.prototype, 'includes', { - value: function(searchElement, fromIndex) { + value: function(searchElement: any, fromIndex: number) { if (this == null) { 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. var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); - function sameValueZero(x, y) { + function sameValueZero(x: number, y: number) { return ( x === y || (typeof x === 'number' && diff --git a/app/assets/javascripts/web_device_interface.ts b/app/assets/javascripts/web_device_interface.ts index 7cc29e4dc..2e01f34a9 100644 --- a/app/assets/javascripts/web_device_interface.ts +++ b/app/assets/javascripts/web_device_interface.ts @@ -17,7 +17,7 @@ export class WebDeviceInterface extends DeviceInterface { } setApplication(application: SNApplication) { - this.database.setApplication(application); + this.database.setAlertService(application.alertService!); } deinit() {