Lint JavaScript/TypeScript code (#346)

* Implement lint in all js projects

* Use yarn workspace

* Remove package-lock.json

* Fix

* Extract common dev dep

* Bump
This commit is contained in:
Sung Won Cho 2019-11-23 15:40:33 +08:00 committed by GitHub
commit e2e777db54
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
117 changed files with 12281 additions and 32567 deletions

View file

@ -17,7 +17,6 @@
*/
import React, { useState, useEffect } from 'react';
import classnames from 'classnames';
import initServices from '../utils/services';
import { logout } from '../store/auth/actions';
@ -69,13 +68,11 @@ const App: React.FunctionComponent<Props> = () => {
const [errMsg, setErrMsg] = useState('');
const dispatch = useDispatch();
const { path, auth, settings } = useSelector(state => {
return {
path: state.location.path,
auth: state.auth,
settings: state.settings
};
});
const { path, auth, settings } = useSelector(state => ({
path: state.location.path,
auth: state.auth,
settings: state.settings
}));
useCheckSessionValid(auth);

View file

@ -16,23 +16,19 @@
* along with Dnote. If not, see <https://www.gnu.org/licenses/>.
*/
import React, { useEffect } from 'react';
import React from 'react';
import CreatableSelect from 'react-select/creatable';
import cloneDeep from 'lodash/cloneDeep';
import { useSelector, useDispatch } from '../store/hooks';
import { updateBook, resetBook } from '../store/composer/actions';
import BookIcon from './BookIcon';
interface Props {
selectorRef: React.Dispatch<any>;
onAfterChange: () => void;
}
function useCurrentOptions(options) {
const currentValue = useSelector(state => {
return state.composer.bookUUID;
});
const currentValue = useSelector(state => state.composer.bookUUID);
for (let i = 0; i < options.length; i++) {
const option = options[i];
@ -46,19 +42,15 @@ function useCurrentOptions(options) {
}
function useOptions() {
const { books, composer } = useSelector(state => {
return {
books: state.books,
composer: state.composer
};
});
const { books, composer } = useSelector(state => ({
books: state.books,
composer: state.composer
}));
const opts = books.items.map(book => {
return {
label: book.label,
value: book.uuid
};
});
const opts = books.items.map(book => ({
label: book.label,
value: book.uuid
}));
if (composer.bookLabel !== '' && composer.bookUUID === '') {
opts.push({
@ -77,12 +69,9 @@ const BookSelector: React.FunctionComponent<Props> = ({
onAfterChange
}) => {
const dispatch = useDispatch();
const { books, composer } = useSelector(state => {
return {
books: state.books,
composer: state.composer
};
});
const { books } = useSelector(state => ({
books: state.books
}));
const options = useOptions();
const currentOption = useCurrentOptions(options);

View file

@ -16,7 +16,7 @@
* along with Dnote. If not, see <https://www.gnu.org/licenses/>.
*/
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect, useCallback } from 'react';
import classnames from 'classnames';
import { KEYCODE_ENTER } from 'jslib/helpers/keyboard';
@ -34,20 +34,20 @@ interface Props {}
// It needs to traverse the tree returned by the ref API of the 'react-select' library,
// and to guard against possible breaking changes, if the path does not exist, it noops.
function focusBookSelectorInput(bookSelectorRef) {
bookSelectorRef.select &&
return (
bookSelectorRef.select &&
bookSelectorRef.select.select &&
bookSelectorRef.select.select.inputRef &&
bookSelectorRef.select.select.inputRef.focus();
bookSelectorRef.select.select.inputRef.focus()
);
}
function useFetchData() {
const dispatch = useDispatch();
const { books } = useSelector(state => {
return {
books: state.books
};
});
const { books } = useSelector(state => ({
books: state.books
}));
useEffect(() => {
if (!books.isFetched) {
@ -57,12 +57,10 @@ function useFetchData() {
}
function useInitFocus(contentRef, bookSelectorRef) {
const { composer, books } = useSelector(state => {
return {
composer: state.composer,
books: state.books
};
});
const { composer, books } = useSelector(state => ({
composer: state.composer,
books: state.books
}));
useEffect(() => {
if (!books.isFetched) {
@ -76,7 +74,9 @@ function useInitFocus(contentRef, bookSelectorRef) {
contentRef.focus();
}
}
}, [contentRef, bookSelectorRef, books.isFetched]);
return () => null;
}, [contentRef, bookSelectorRef, books.isFetched, composer.bookLabel]);
}
const Composer: React.FunctionComponent<Props> = () => {
@ -88,27 +88,43 @@ const Composer: React.FunctionComponent<Props> = () => {
const [contentRef, setContentEl] = useState(null);
const [bookSelectorRef, setBookSelectorEl] = useState(null);
const { composer, settings, auth } = useSelector(state => {
return {
composer: state.composer,
settings: state.settings,
auth: state.auth
};
});
const { composer, settings, auth } = useSelector(state => ({
composer: state.composer,
settings: state.settings,
auth: state.auth
}));
const handleSubmit = async e => {
e.preventDefault();
const handleSubmit = useCallback(
async e => {
e.preventDefault();
const services = initServices(settings.apiUrl);
const services = initServices(settings.apiUrl);
setSubmitting(true);
setSubmitting(true);
try {
let bookUUID;
if (composer.bookUUID === '') {
const resp = await services.books.create(
try {
let bookUUID;
if (composer.bookUUID === '') {
const resp = await services.books.create(
{
name: composer.bookLabel
},
{
headers: {
Authorization: `Bearer ${auth.sessionKey}`
}
}
);
bookUUID = resp.book.uuid;
} else {
bookUUID = composer.bookUUID;
}
const resp = await services.notes.create(
{
name: composer.bookLabel
book_uuid: bookUUID,
content: composer.content
},
{
headers: {
@ -117,56 +133,48 @@ const Composer: React.FunctionComponent<Props> = () => {
}
);
bookUUID = resp.book.uuid;
} else {
bookUUID = composer.bookUUID;
// clear the composer state
setErrMsg('');
setSubmitting(false);
dispatch(resetComposer());
// navigate
dispatch(
navigate('/success', {
bookName: composer.bookLabel,
noteUUID: resp.result.uuid
})
);
} catch (err) {
setErrMsg(err.message);
setSubmitting(false);
}
const resp = await services.notes.create(
{
book_uuid: bookUUID,
content: composer.content
},
{
headers: {
Authorization: `Bearer ${auth.sessionKey}`
}
}
);
// clear the composer state
setErrMsg('');
setSubmitting(false);
dispatch(resetComposer());
// navigate
dispatch(
navigate('/success', {
bookName: composer.bookLabel,
noteUUID: resp.result.uuid
})
);
} catch (e) {
setErrMsg(e.message);
setSubmitting(false);
}
};
const handleSubmitShortcut = e => {
// Shift + Enter
if (e.shiftKey && e.keyCode === KEYCODE_ENTER) {
handleSubmit(e);
}
};
},
[
settings.apiUrl,
composer.bookUUID,
composer.content,
composer.bookLabel,
auth.sessionKey,
dispatch
]
);
useEffect(() => {
const handleSubmitShortcut = e => {
// Shift + Enter
if (e.shiftKey && e.keyCode === KEYCODE_ENTER) {
handleSubmit(e);
}
};
window.addEventListener('keydown', handleSubmitShortcut);
return () => {
window.removeEventListener('keydown', handleSubmitShortcut);
};
}, [composer]);
}, [composer, handleSubmit]);
let submitBtnText: string;
if (submitting) {

View file

@ -17,15 +17,9 @@
*/
import React, { useState } from 'react';
import { connect } from 'react-redux';
import { findDOMNode } from 'react-dom';
import Link from './Link';
import config from '../utils/config';
import { login } from '../store/auth/actions';
import { updateSettings } from '../store/settings/actions';
import { useDispatch } from '../store/hooks';
import services from '../utils/services';
import Flash from '../components/Flash';
interface Props {}
@ -45,8 +39,8 @@ const Home: React.FunctionComponent<Props> = () => {
try {
await dispatch(login({ email, password }));
} catch (e) {
console.log('error while logging in', e);
} catch (err) {
console.log('error while logging in', err);
setErrMsg(e.message);
setLoggingIn(false);
@ -97,10 +91,11 @@ const Home: React.FunctionComponent<Props> = () => {
</form>
<div className="actions">
Don't have an account?{' '}
Don&#39;t have an account?{' '}
<a
href="https://app.getdnote.com/join"
target="_blank"
rel="noopener noreferrer"
className="signup"
>
Sign Up

View file

@ -16,9 +16,9 @@
* along with Dnote. If not, see <https://www.gnu.org/licenses/>.
*/
/* eslint-disable react/jsx-props-no-spreading */
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { useDispatch } from '../store/hooks';
import { navigate } from '../store/location/actions';

View file

@ -53,6 +53,11 @@ export default ({ toggleMenu, loggedIn, onLogout }) => (
)}
</ul>
<div className="menu-overlay" onClick={toggleMenu} />
<div
className="menu-overlay"
onClick={toggleMenu}
onKeyDown={() => {}}
role="none"
/>
</Fragment>
);

View file

@ -17,23 +17,18 @@
*/
import React, { useState } from 'react';
import { connect } from 'react-redux';
import { findDOMNode } from 'react-dom';
import Link from './Link';
import Flash from './Flash';
import config from '../utils/config';
import { updateSettings, resetSettings } from '../store/settings/actions';
import { useDispatch, useSelector, useStore } from '../store/hooks';
import services from '../utils/services';
interface Props {}
// isValidURL checks if the given string is a valid URL
function isValidURL(url: string): boolean {
var a = document.createElement('a');
const a = document.createElement('a');
a.href = url;
return a.host && a.host != window.location.host;
return a.host && a.host !== window.location.host;
}
// validateFormState validates the given form state. If any input is
@ -49,11 +44,9 @@ function validateFormState({ apiUrl, webUrl }) {
}
const Settings: React.FunctionComponent<Props> = () => {
const { settings } = useSelector(state => {
return {
settings: state.settings
};
});
const { settings } = useSelector(state => ({
settings: state.settings
}));
const store = useStore();
const [apiUrl, setAPIUrl] = useState(settings.apiUrl);
@ -66,10 +59,10 @@ const Settings: React.FunctionComponent<Props> = () => {
dispatch(resetSettings());
setSuccessMsg('Restored the default settings');
const { settings } = store.getState();
const { settings: settingsState } = store.getState();
setAPIUrl(settings.apiUrl);
setWebUrl(settings.webUrl);
setAPIUrl(settingsState.apiUrl);
setWebUrl(settingsState.webUrl);
}
function handleSubmit(e) {

View file

@ -16,7 +16,7 @@
* along with Dnote. If not, see <https://www.gnu.org/licenses/>.
*/
import React, { Fragment, useEffect, useState } from 'react';
import React, { useEffect, useState, Fragment } from 'react';
import {
KEYCODE_ENTER,
@ -25,7 +25,6 @@ import {
} from 'jslib/helpers/keyboard';
import Flash from './Flash';
import ext from '../utils/ext';
import config from '../utils/config';
import BookIcon from './BookIcon';
import { navigate } from '../store/location/actions';
import { useSelector, useDispatch } from '../store/hooks';
@ -34,43 +33,41 @@ const Success: React.FunctionComponent = () => {
const [errorMsg, setErrorMsg] = useState('');
const dispatch = useDispatch();
const { location, settings } = useSelector(state => {
return {
location: state.location,
settings: state.settings
};
});
const { location, settings } = useSelector(state => ({
location: state.location,
settings: state.settings
}));
const { bookName, noteUUID } = location.state;
const handleKeydown = e => {
e.preventDefault();
if (e.keyCode === KEYCODE_ENTER) {
dispatch(navigate('/'));
} else if (e.keyCode === KEYCODE_ESC) {
window.close();
} else if (e.keyCode === KEYCODE_LOWERCASE_B) {
const url = `${settings.webUrl}/notes/${noteUUID}`;
ext.tabs
.create({ url })
.then(() => {
window.close();
})
.catch(err => {
setErrorMsg(err.message);
});
}
};
useEffect(() => {
const handleKeydown = e => {
e.preventDefault();
if (e.keyCode === KEYCODE_ENTER) {
dispatch(navigate('/'));
} else if (e.keyCode === KEYCODE_ESC) {
window.close();
} else if (e.keyCode === KEYCODE_LOWERCASE_B) {
const url = `${settings.webUrl}/notes/${noteUUID}`;
ext.tabs
.create({ url })
.then(() => {
window.close();
})
.catch(err => {
setErrorMsg(err.message);
});
}
};
window.addEventListener('keydown', handleKeydown);
return () => {
window.removeEventListener('keydown', handleKeydown);
};
}, []);
}, [dispatch, noteUUID, settings.webUrl]);
return (
<Fragment>
@ -80,7 +77,10 @@ const Success: React.FunctionComponent = () => {
<div>
<BookIcon width={20} height={20} className="book-icon" />
<h1 className="heading">Saved to {bookName}</h1>
<h1 className="heading">
Saved to
{bookName}
</h1>
</div>
<ul className="key-list">
@ -93,7 +93,8 @@ const Success: React.FunctionComponent = () => {
<div className="key-desc">Open in browser</div>
</li>
<li className="key-item">
<kbd className="key">ESC</kbd> <div className="key-desc">Close</div>
<kbd className="key">ESC</kbd>
<div className="key-desc">Close</div>
</li>
</ul>
</div>

View file

@ -19,8 +19,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { debounce } from 'jslib/helpers/perf';
import configureStore from './store';

View file

@ -16,7 +16,7 @@
* along with Dnote. If not, see <https://www.gnu.org/licenses/>.
*/
import { LOGIN, LOGOUT, LogoutAction, LoginAction } from './types';
import { LOGIN, LOGOUT, LogoutAction } from './types';
import { ThunkAction } from '../types';
import initServices from '../../utils/services';

View file

@ -17,7 +17,6 @@
*/
import { LOGIN, LOGOUT, AuthState, AuthActionType } from './types';
import config from '../../utils/config';
const initialState: AuthState = {
sessionKey: '',
@ -34,8 +33,8 @@ export default function(
return {
...state,
sessionKey: sessionKey,
sessionKeyExpiry: sessionKeyExpiry
sessionKey,
sessionKeyExpiry
};
}
case LOGOUT:

View file

@ -43,14 +43,14 @@ function initState(s: AppState | undefined): AppState {
return undefined;
}
const { settings } = s;
const { settings: settingsState } = s;
return {
...s,
settings: {
...settings,
apiUrl: settings.apiUrl || config.defaultApiEndpoint,
webUrl: settings.webUrl || config.defaultWebUrl
...settingsState,
apiUrl: settingsState.apiUrl || config.defaultApiEndpoint,
webUrl: settingsState.webUrl || config.defaultWebUrl
}
};
}

View file

@ -17,8 +17,6 @@
*/
import { UPDATE, RESET, UpdateAction, ResetAction } from './types';
import { ThunkAction } from '../types';
import initServices from '../../utils/services';
export function updateSettings(settings): UpdateAction {
return {

View file

@ -18,7 +18,7 @@
// module ext provides a cross-browser interface to access extension APIs
// by using WebExtensions API if available, and using Chrome as a fallback.
let ext: any = {};
const ext: any = {};
const apis = ['tabs', 'storage', 'runtime'];
@ -40,9 +40,9 @@ for (let i = 0; i < apis.length; i++) {
const fn = ext[api].create;
// Promisify chrome.tabs.create
ext[api].create = function(obj) {
ext[api].create = obj => {
return new Promise(resolve => {
fn(obj, function(tab) {
fn(obj, tab => {
resolve(tab);
});
});

View file

@ -22,7 +22,7 @@ function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
return response.text().then((body) => {
return response.text().then(body => {
const error = new Error(body);
error.response = response;
@ -48,7 +48,7 @@ export function post(url, data, options = {}) {
return request(url, {
method: 'POST',
body: JSON.stringify(data),
...options,
...options
});
}
@ -61,6 +61,6 @@ export function get(url, options = {}) {
return request(endpoint, {
method: 'GET',
...options,
...options
});
}

View file

@ -17,13 +17,11 @@
*/
import init from 'jslib/services';
import config from './config';
const initServices = (baseUrl: string) => {
return init({
baseUrl: baseUrl,
const initServices = (baseUrl: string) =>
init({
baseUrl,
pathPrefix: ''
});
};
export default initServices;