diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000000..212fef118c0 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,38 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# Howto with your editor: +# Sublime: https://github.com/sindresorhus/editorconfig-sublime + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[**] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 + +# Standard at: https://github.com/felixge/node-style-guide +[**.js, **.json] +trim_trailing_whitespace = true +quote_type = single +curly_bracket_next_line = false +spaces_around_operators = true +space_after_control_statements = true +space_after_anonymous_functions = false +spaces_in_brackets = false + +# No Standard. Please document a standard if different from .js +[**.yml, **.html, **.css] +trim_trailing_whitespace = true + +# No standard. Please document a standard if different from .js +[**.md] + +# Standard at: +[Makefile] + +[package*] +indent_style = space +indent_size = 2 diff --git a/index.js b/index.js index 3242b0ba4e5..48e7a839f9e 100644 --- a/index.js +++ b/index.js @@ -2,8 +2,8 @@ * @format */ -import {AppRegistry} from 'react-native'; +import { AppRegistry } from 'react-native'; import App from './js/App'; -import {name as appName} from './app.json'; +import { name as appName } from './app.json'; AppRegistry.registerComponent(appName, () => App); diff --git a/js/Expensify.js b/js/Expensify.js index d53e8e39102..66886d19ef8 100644 --- a/js/Expensify.js +++ b/js/Expensify.js @@ -1,64 +1,64 @@ -import {init as StoreInit} from './store/Store.js'; +import { init as StoreInit } from './store/Store.js'; import SignInPage from './page/SignInPage.js'; import HomePage from './page/HomePage/HomePage.js'; import * as Store from './store/Store.js'; import * as ActiveClientManager from './lib/ActiveClientManager.js'; -import {verifyAuthToken} from './store/actions/SessionActions.js'; +import { verifyAuthToken } from './store/actions/SessionActions.js'; import STOREKEYS from './store/STOREKEYS.js'; -import React, {Component} from 'react'; -import {Route, Router, Redirect, Switch} from './lib/Router'; +import React, { Component } from 'react'; +import { Route, Router, Redirect, Switch } from './lib/Router'; // Initialize the store when the app loads for the first time StoreInit(); export default class Expensify extends Component { - constructor(props) { - super(props); + constructor(props) { + super(props); - this.sessionChanged = this.sessionChanged.bind(this); + this.sessionChanged = this.sessionChanged.bind(this); - this.state = { - redirectTo: null, - }; - } + this.state = { + redirectTo: null, + }; + } - async componentDidMount() { - // Listen for when the app wants to redirect to a specific URL - Store.subscribe(STOREKEYS.APP_REDIRECT_TO, (redirectTo) => { - this.setState({redirectTo}); - }); + async componentDidMount() { + // Listen for when the app wants to redirect to a specific URL + Store.subscribe(STOREKEYS.APP_REDIRECT_TO, (redirectTo) => { + this.setState({ redirectTo }); + }); - // Verify that our authToken is OK to use - verifyAuthToken(); + // Verify that our authToken is OK to use + verifyAuthToken(); - // Initialize this client as being an active client - await ActiveClientManager.init(); - //TODO: Refactor window events - // window.addEventListener('beforeunload', () => { - // ActiveClientManager.removeClient(); - // }); - } + // Initialize this client as being an active client + await ActiveClientManager.init(); + //TODO: Refactor window events + // window.addEventListener('beforeunload', () => { + // ActiveClientManager.removeClient(); + // }); + } - /** - * When the session changes, change which page the user sees - * - * @param {object} newSession - */ - sessionChanged(newSession) { - this.setState({isAuthTokenValid: newSession && newSession.authToken}); - } + /** + * When the session changes, change which page the user sees + * + * @param {object} newSession + */ + sessionChanged(newSession) { + this.setState({ isAuthTokenValid: newSession && newSession.authToken }); + } - render() { - return ( - - {/* If there is ever a property for redirecting, we do the redirect here */} - {this.state.redirectTo && } + render() { + return ( + + {/* If there is ever a property for redirecting, we do the redirect here */} + {this.state.redirectTo && } - - - - - - ); - } + + + + + + ); + } } diff --git a/js/ROUTES.js b/js/ROUTES.js index ca2c73306db..26da0e9d0fa 100644 --- a/js/ROUTES.js +++ b/js/ROUTES.js @@ -2,6 +2,6 @@ * This is a file containing constants for all of the routes we want to be able to go to */ export default { - SIGNIN: '/signin', - HOME: '/', + SIGNIN: '/signin', + HOME: '/', }; diff --git a/js/config.js b/js/config.js index 666fae80efe..5eddf86f8b5 100644 --- a/js/config.js +++ b/js/config.js @@ -1,6 +1,6 @@ const CONFIG = { - PUSHER: { - APP_KEY: '829fd8fd2a6036568469', - CLUSTER: 'us3', - }, + PUSHER: { + APP_KEY: '829fd8fd2a6036568469', + CLUSTER: 'us3', + }, }; diff --git a/js/lib/ActiveClientManager.js b/js/lib/ActiveClientManager.js index 1ba3a478aed..3dfff83993d 100644 --- a/js/lib/ActiveClientManager.js +++ b/js/lib/ActiveClientManager.js @@ -8,20 +8,20 @@ const clientID = Guid(); * Add our client ID to the list of active IDs */ const init = async () => { - const activeClientIDs = (await Store.get(STOREKEYS.ACTIVE_CLIENT_IDS)) || []; - activeClientIDs.push(clientID); - Store.set(STOREKEYS.ACTIVE_CLIENT_IDS, activeClientIDs); + const activeClientIDs = (await Store.get(STOREKEYS.ACTIVE_CLIENT_IDS)) || []; + activeClientIDs.push(clientID); + Store.set(STOREKEYS.ACTIVE_CLIENT_IDS, activeClientIDs); }; /** * Remove this client ID from the array of active client IDs when this client is exited */ function removeClient() { - const activeClientIDs = Store.get(STOREKEYS.ACTIVE_CLIENT_IDS) || []; - const newActiveClientIDs = activeClientIDs.filter( - (activeClientID) => activeClientID !== clientID, - ); - Store.set(STOREKEYS.ACTIVE_CLIENT_IDS, newActiveClientIDs); + const activeClientIDs = Store.get(STOREKEYS.ACTIVE_CLIENT_IDS) || []; + const newActiveClientIDs = activeClientIDs.filter( + (activeClientID) => activeClientID !== clientID, + ); + Store.set(STOREKEYS.ACTIVE_CLIENT_IDS, newActiveClientIDs); } /** @@ -30,11 +30,11 @@ function removeClient() { * @returns {boolean} */ function isClientTheLeader() { - const activeClientIDs = Store.get(STOREKEYS.ACTIVE_CLIENT_IDS) || []; - if (!activeClientIDs.length) { - return false; - } - return activeClientIDs[0] === clientID; + const activeClientIDs = Store.get(STOREKEYS.ACTIVE_CLIENT_IDS) || []; + if (!activeClientIDs.length) { + return false; + } + return activeClientIDs[0] === clientID; } -export {init, removeClient, isClientTheLeader}; +export { init, removeClient, isClientTheLeader }; diff --git a/js/lib/DateUtils.js b/js/lib/DateUtils.js index f9f9b57c840..b291d96975d 100644 --- a/js/lib/DateUtils.js +++ b/js/lib/DateUtils.js @@ -11,7 +11,7 @@ import Str from './Str.js'; * @private */ function getTimezone() { - return 'America/Los_Angeles'; + return 'America/Los_Angeles'; } /** @@ -25,9 +25,9 @@ function getTimezone() { * @private */ function getLocalMomentFromTimestamp(timestamp) { - // We need a default here for flows where we may not have initialized the TIME_ZONE NVP like generatng PDFs in printablereport.php - const timezone = getTimezone(); - return moment.unix(timestamp).tz(timezone); + // We need a default here for flows where we may not have initialized the TIME_ZONE NVP like generatng PDFs in printablereport.php + const timezone = getTimezone(); + return moment.unix(timestamp).tz(timezone); } /** @@ -44,15 +44,15 @@ function getLocalMomentFromTimestamp(timestamp) { * @returns {String} */ function timestampToDateTime(timestamp, includeTimeZone = false) { - const date = getLocalMomentFromTimestamp(timestamp); - let format = - moment().year() !== date.get('year') - ? 'MMM D, YYYY [at] LT' - : 'MMM D [at] LT'; - if (includeTimeZone) { - format = `${format} [UTC]Z`; - } - return date.format(format); + const date = getLocalMomentFromTimestamp(timestamp); + let format = + moment().year() !== date.get('year') + ? 'MMM D, YYYY [at] LT' + : 'MMM D [at] LT'; + if (includeTimeZone) { + format = `${format} [UTC]Z`; + } + return date.format(format); } /** @@ -73,44 +73,44 @@ function timestampToDateTime(timestamp, includeTimeZone = false) { * @returns {String} */ function timestampToRelative(timestamp) { - const date = getLocalMomentFromTimestamp(timestamp); - const durationFromLocalNow = moment.duration( - date.diff(getLocalMomentFromTimestamp(moment().unix())), - ); - const round = (num) => Math.floor(Math.abs(num)); + const date = getLocalMomentFromTimestamp(timestamp); + const durationFromLocalNow = moment.duration( + date.diff(getLocalMomentFromTimestamp(moment().unix())), + ); + const round = (num) => Math.floor(Math.abs(num)); - if (date.isAfter(moment().subtract(60, 'seconds'))) { - return '< 1 minute ago'; - } + if (date.isAfter(moment().subtract(60, 'seconds'))) { + return '< 1 minute ago'; + } - if (date.isAfter(moment().subtract(60, 'minutes'))) { - const minutes = round(durationFromLocalNow.asMinutes()); - return `${minutes} ${Str.pluralize('minute', 'minutes', minutes)} ago`; - } + if (date.isAfter(moment().subtract(60, 'minutes'))) { + const minutes = round(durationFromLocalNow.asMinutes()); + return `${minutes} ${Str.pluralize('minute', 'minutes', minutes)} ago`; + } - if (date.isAfter(moment().subtract(24, 'hours'))) { - const hours = round(durationFromLocalNow.asHours()); - return `${hours} ${Str.pluralize('hour', 'hours', hours)} ago`; - } + if (date.isAfter(moment().subtract(24, 'hours'))) { + const hours = round(durationFromLocalNow.asHours()); + return `${hours} ${Str.pluralize('hour', 'hours', hours)} ago`; + } - if (date.isAfter(moment().subtract(30, 'days'))) { - const days = round(durationFromLocalNow.asDays()); - return `${days} ${Str.pluralize('day', 'days', days)} ago`; - } + if (date.isAfter(moment().subtract(30, 'days'))) { + const days = round(durationFromLocalNow.asDays()); + return `${days} ${Str.pluralize('day', 'days', days)} ago`; + } - if (date.isAfter(moment().subtract(1, 'year'))) { - return date.format('MMM D'); - } + if (date.isAfter(moment().subtract(1, 'year'))) { + return date.format('MMM D'); + } - return date.format('MMM D, YYYY'); + return date.format('MMM D, YYYY'); } /** * @namespace DateUtils */ const DateUtils = { - timestampToRelative, - timestampToDateTime, + timestampToRelative, + timestampToDateTime, }; export default DateUtils; diff --git a/js/lib/ExpensiMark.js b/js/lib/ExpensiMark.js index 6492f40b874..f2b9a6a11a7 100644 --- a/js/lib/ExpensiMark.js +++ b/js/lib/ExpensiMark.js @@ -1,63 +1,63 @@ import Str from './Str.js'; export default class ExpensiMark { - constructor() { - /** - * The list of regex replacements to do on a comment. Check the link regex is first so links are processed - * before other delimiters - * - * @type {Object[]} - */ - this.rules = [ - { - name: 'link', - regex: - '([_*~]*?)(((?:https?):\\/\\/|www\\.)[^\\s<>*~_"\'´.-][^\\s<>"\'´]*?\\.[a-z\\d]+[^\\s<>*~"\']*)\\1', - replacement: '$1$2$1', - }, - { + constructor() { /** - * Use \b in this case because it will match on words, letters, and _: https://www.rexegg.com/regex-boundaries.html#wordboundary - * The !_blank is to prevent the `target="_blank">` section of the link replacement from being captured - * Additionally, something like `\b\_([^<>]*?)\_\b` doesn't work because it won't replace `_https://www.test.com_` + * The list of regex replacements to do on a comment. Check the link regex is first so links are processed + * before other delimiters + * + * @type {Object[]} */ - name: 'italic', - regex: '(?!_blank">)\\b\\_(.*?)\\_\\b', - replacement: '$1', - }, - { - // Use \B in this case because \b doesn't match * or ~. \B will match everything that \b doesn't, so it works for * and ~: https://www.rexegg.com/regex-boundaries.html#notb - name: 'bold', - regex: '\\B\\*(.*?)\\*\\B', - replacement: '$1', - }, - { - name: 'strikethrough', - regex: '\\B\\~(.*?)\\~\\B', - replacement: '$1', - }, - { - name: 'newline', - regex: '\\n', - replacement: '
', - }, - ]; - } + this.rules = [ + { + name: 'link', + regex: + '([_*~]*?)(((?:https?):\\/\\/|www\\.)[^\\s<>*~_"\'´.-][^\\s<>"\'´]*?\\.[a-z\\d]+[^\\s<>*~"\']*)\\1', + replacement: '$1$2$1', + }, + { + /** + * Use \b in this case because it will match on words, letters, and _: https://www.rexegg.com/regex-boundaries.html#wordboundary + * The !_blank is to prevent the `target="_blank">` section of the link replacement from being captured + * Additionally, something like `\b\_([^<>]*?)\_\b` doesn't work because it won't replace `_https://www.test.com_` + */ + name: 'italic', + regex: '(?!_blank">)\\b\\_(.*?)\\_\\b', + replacement: '$1', + }, + { + // Use \B in this case because \b doesn't match * or ~. \B will match everything that \b doesn't, so it works for * and ~: https://www.rexegg.com/regex-boundaries.html#notb + name: 'bold', + regex: '\\B\\*(.*?)\\*\\B', + replacement: '$1', + }, + { + name: 'strikethrough', + regex: '\\B\\~(.*?)\\~\\B', + replacement: '$1', + }, + { + name: 'newline', + regex: '\\n', + replacement: '
', + }, + ]; + } - /** - * Replaces markdown with html elements - * - * @param {String} text - * @returns {String} - */ - replace(text) { - // This ensures that any html the user puts into the comment field shows as raw html - text = Str.safeEscape(text); + /** + * Replaces markdown with html elements + * + * @param {String} text + * @returns {String} + */ + replace(text) { + // This ensures that any html the user puts into the comment field shows as raw html + text = Str.safeEscape(text); - this.rules.forEach((rule) => { - text = text.replace(new RegExp(rule.regex, 'g'), rule.replacement); - }); + this.rules.forEach((rule) => { + text = text.replace(new RegExp(rule.regex, 'g'), rule.replacement); + }); - return text; - } + return text; + } } diff --git a/js/lib/Guid.js b/js/lib/Guid.js index 334e13b8e00..27dd71654e5 100644 --- a/js/lib/Guid.js +++ b/js/lib/Guid.js @@ -3,10 +3,10 @@ * @returns {String} */ export default function guid() { - function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); - } - return `${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`; + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + return `${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`; } diff --git a/js/lib/Network.js b/js/lib/Network.js index fc3697765e1..0ff15d7e693 100644 --- a/js/lib/Network.js +++ b/js/lib/Network.js @@ -12,24 +12,24 @@ let isAppOffline = false; * @returns {$.Deferred} */ async function request(command, data, type = 'post') { - console.debug(`Making "${command}" ${type} request`); - const formData = new FormData(); - formData.append('authToken', await Store.get('session', 'authToken')); - for (const property in data) { - formData.append(property, data[property]); - } - try { - let response = await fetch( - `https://expensify.com.dev/api?command=${command}`, - { - method: type, - body: formData, - }, - ); - return await response.json(); - } catch (error) { - isAppOffline = true; - } + console.debug(`Making "${command}" ${type} request`); + const formData = new FormData(); + formData.append('authToken', await Store.get('session', 'authToken')); + for (const property in data) { + formData.append(property, data[property]); + } + try { + let response = await fetch( + `https://www.expensify.com.dev/api?command=${command}`, + { + method: type, + body: formData, + }, + ); + return await response.json(); + } catch (error) { + isAppOffline = true; + } } // Holds a queue of all the write requests that need to happen @@ -43,49 +43,49 @@ const delayedWriteQueue = []; * @returns {$.Deferred} */ function delayedWrite(command, data, cb) { - const promise = $.Deferred(); + const promise = $.Deferred(); - // Add the write request to a queue of actions to perform - delayedWriteQueue.push({ - command, - data, - promise, - }); + // Add the write request to a queue of actions to perform + delayedWriteQueue.push({ + command, + data, + promise, + }); - return promise; + return promise; } /** * Process the write queue by looping through the queue and attempting to make the requests */ function processWriteQueue() { - if (isAppOffline) { - // Make a simple request to see if we're online again - request('Get', null, 'get').done(() => { - isAppOffline = false; - }); - return; - } + if (isAppOffline) { + // Make a simple request to see if we're online again + request('Get', null, 'get').done(() => { + isAppOffline = false; + }); + return; + } - if (delayedWriteQueue.length === 0) { - return; - } + if (delayedWriteQueue.length === 0) { + return; + } - for (let i = 0; i < delayedWriteQueue.length; i++) { - // Take the request object out of the queue and make the request - const delayedWriteRequest = delayedWriteQueue.shift(); + for (let i = 0; i < delayedWriteQueue.length; i++) { + // Take the request object out of the queue and make the request + const delayedWriteRequest = delayedWriteQueue.shift(); - request(delayedWriteRequest.command, delayedWriteRequest.data) - .done(delayedWriteRequest.promise.resolve) - .fail(() => { - // If the request failed, we need to put the request object back into the queue - delayedWriteQueue.push(delayedWriteRequest); - }); - } + request(delayedWriteRequest.command, delayedWriteRequest.data) + .done(delayedWriteRequest.promise.resolve) + .fail(() => { + // If the request failed, we need to put the request object back into the queue + delayedWriteQueue.push(delayedWriteRequest); + }); + } } // TODO: Figure out setInterval // Process our write queue very often // setInterval(processWriteQueue, 1000); -export {request, delayedWrite}; +export { request, delayedWrite }; diff --git a/js/lib/PersistentStorage.js b/js/lib/PersistentStorage.js index 26c8f305ebd..f5f8b346adc 100644 --- a/js/lib/PersistentStorage.js +++ b/js/lib/PersistentStorage.js @@ -11,12 +11,12 @@ import AsyncStorage from '@react-native-community/async-storage'; * @param {string} key */ const get = async (key) => { - try { - const jsonValue = await AsyncStorage.getItem(key); - return jsonValue != null ? JSON.parse(jsonValue) : null; - } catch (e) { - console.error(`Could not parse value from local storage. Key: ${key}`); - } + try { + const jsonValue = await AsyncStorage.getItem(key); + return jsonValue != null ? JSON.parse(jsonValue) : null; + } catch (e) { + console.error(`Could not parse value from local storage. Key: ${key}`); + } }; /** @@ -26,14 +26,14 @@ const get = async (key) => { * @param {mixed} val */ const set = async (key, val) => { - await AsyncStorage.setItem(key, JSON.stringify(val)); + await AsyncStorage.setItem(key, JSON.stringify(val)); }; /** * Empty out the storage (like when the user signs out) */ const clear = async () => { - await AsyncStorage.clear(); + await AsyncStorage.clear(); }; -export {get, set, clear}; +export { get, set, clear }; diff --git a/js/lib/Router/index.js b/js/lib/Router/index.js index e5a9aba6866..abe10c4aa5c 100644 --- a/js/lib/Router/index.js +++ b/js/lib/Router/index.js @@ -1,11 +1,11 @@ import { - BrowserRouter as Router, - Link, - Route, - Redirect, - Switch, - withRouter, + BrowserRouter as Router, + Link, + Route, + Redirect, + Switch, + withRouter, } from 'react-router-dom'; -export default {Link, Route, Redirect, Router, Switch, withRouter}; -export {Link, Route, Redirect, Router, Switch, withRouter}; +export default { Link, Route, Redirect, Router, Switch, withRouter }; +export { Link, Route, Redirect, Router, Switch, withRouter }; diff --git a/js/lib/Router/index.native.js b/js/lib/Router/index.native.js index 9fa3bfbe78a..9b8bd03726c 100644 --- a/js/lib/Router/index.native.js +++ b/js/lib/Router/index.native.js @@ -1,11 +1,11 @@ import { - NativeRouter as Router, - Link, - Route, - Redirect, - Switch, - withRouter, + NativeRouter as Router, + Link, + Route, + Redirect, + Switch, + withRouter, } from 'react-router-native'; -export default {Link, Route, Redirect, Router, Switch, withRouter}; -export {Link, Route, Redirect, Router, Switch, withRouter}; +export default { Link, Route, Redirect, Router, Switch, withRouter }; +export { Link, Route, Redirect, Router, Switch, withRouter }; diff --git a/js/lib/Str.js b/js/lib/Str.js index a70b15a6cdd..d8660585d8c 100644 --- a/js/lib/Str.js +++ b/js/lib/Str.js @@ -1,54 +1,54 @@ /* globals $, _ */ const Str = { - /** - * Returns the proper phrase depending on the count that is passed. - * Example: - * console.log(Str.pluralize('puppy', 'puppies', 1)); // puppy - * console.log(Str.pluralize('puppy', 'puppies', 3)); // puppies - * - * @param {String} singular form of the phrase - * @param {String} plural form of the phrase - * @param {Number} n the count which determines the plurality - * - * @return {String} - */ - pluralize(singular, plural, n) { - if (!n || n > 1) { - return plural; - } - return singular; - }, + /** + * Returns the proper phrase depending on the count that is passed. + * Example: + * console.log(Str.pluralize('puppy', 'puppies', 1)); // puppy + * console.log(Str.pluralize('puppy', 'puppies', 3)); // puppies + * + * @param {String} singular form of the phrase + * @param {String} plural form of the phrase + * @param {Number} n the count which determines the plurality + * + * @return {String} + */ + pluralize(singular, plural, n) { + if (!n || n > 1) { + return plural; + } + return singular; + }, - /** - * Escape text while preventing any sort of double escape, so 'X & Y' -> 'X & Y' and 'X & Y' -> 'X & Y' - * - * @param {String} s the string to escape - * @return {String} the escaped string - */ - safeEscape(s) { - return _.escape(_.unescape(s)); - }, + /** + * Escape text while preventing any sort of double escape, so 'X & Y' -> 'X & Y' and 'X & Y' -> 'X & Y' + * + * @param {String} s the string to escape + * @return {String} the escaped string + */ + safeEscape(s) { + return _.escape(_.unescape(s)); + }, - /** - * Decodes the given HTML encoded string. - * - * @param {String} s The string to decode. - * @return {String} The decoded string. - */ - htmlDecode(s) { - return $('