Skip to content

Commit

Permalink
client: Use prettier for formatting
Browse files Browse the repository at this point in the history
I do not like the formatting very much but at least it is consistent and supports TypeScript.
https://typescript-eslint.io/troubleshooting/formatting/

Also replace `lint:js` npm script in the `client/` directory with `check:js:lint`
and add `check:js` which runs that in addition to newly introduced `check:js:prettify`.
This matches how the stylesheet scripts are structured.

Adding `eslint-config-prettier` disables stylistic rules that would fight with Prettier.
Additionally, I had to remove some clashing from our ESLint config.

We are using `eslint-config-prettier` instead of `eslint-plugin-prettier`,
the latter would run prettier as part of ESLint but we are already running it separately.
  • Loading branch information
jtojnar committed Jul 11, 2024
1 parent ad588fd commit a79d10b
Show file tree
Hide file tree
Showing 44 changed files with 2,799 additions and 2,003 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
- Source filters are stricter, they need to start and end with a `/`. ([#1423](https://github.com/fossar/selfoss/pull/1423))
- OPML importer has been merged into the React client. ([#1442](https://github.com/fossar/selfoss/pull/1442))
- Web requests will send `Accept-Encoding` header. ([#1482](https://github.com/fossar/selfoss/pull/1482))
- [Prettier](https://prettier.io/) is now used for code formatting. ([#1493](https://github.com/fossar/selfoss/pull/1493))

## 2.19 – 2022-10-12
**This version requires PHP ~~5.6~~ 7.2 (see known regressions section) or newer. It is also the last version to support PHP 7.**
Expand Down
29 changes: 2 additions & 27 deletions client/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,21 @@
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended"
"plugin:react-hooks/recommended",
"prettier"
],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"rules": {
"no-eval": "error",
"array-bracket-spacing": "error",
"no-array-constructor": "error",
"block-spacing": "error",
"brace-style": "error",
"camelcase": "error",
"comma-dangle": ["error", "only-multiline"],
"comma-spacing": "error",
"comma-style": "error",
"curly": "error",
"eol-last": "error",
"func-call-spacing": "error",
"indent": ["error", 4],
"key-spacing": "error",
"keyword-spacing": "error",
"linebreak-style": ["error", "unix"],
"max-statements-per-line": ["error", {"max": 1}],
"no-multiple-empty-lines": "error",
"no-trailing-spaces": "error",
"no-use-before-define": "error",
"no-whitespace-before-property": "error",
"object-curly-newline": ["error", {"multiline": true, "consistent": true}],
"one-var-declaration-per-line": "error",
"quotes": ["error", "single", {"avoidEscape": true}],
"react-hooks/exhaustive-deps": ["warn", {
"additionalHooks": "(useStateWithDeps)"
}],
"semi": "error",
"semi-spacing": "error",
"space-before-blocks": "error",
"space-before-function-paren": ["error", "never"],
"space-in-parens": "error",
"space-infix-ops": "error",
"unicode-bom": "error"
},
"env": {
Expand Down
2 changes: 1 addition & 1 deletion client/images/wallabag.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion client/js/Filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
export const FilterType = {
NEWEST: 'newest',
UNREAD: 'unread',
STARRED: 'starred'
STARRED: 'starred',
};
186 changes: 100 additions & 86 deletions client/js/helpers/ajax.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,22 @@ export const rejectUnless = (pred) => (response) => {
}
};


/**
* fetch API considers a HTTP error a successful state.
* Passing this function as a Promise handler will make the promise fail when HTTP error occurs.
*/
export const rejectIfNotOkay = (response) => {
return rejectUnless(response => response.ok)(response);
return rejectUnless((response) => response.ok)(response);
};

/**
* Override fetch options.
*/
export const options = (newOpts) => (fetch) => (url, opts = {}) => fetch(url, mergeDeepLeft(opts, newOpts));
export const options =
(newOpts) =>
(fetch) =>
(url, opts = {}) =>
fetch(url, mergeDeepLeft(opts, newOpts));

/**
* Override just a single fetch option.
Expand All @@ -45,7 +48,6 @@ export const headers = (value) => option('headers', value);
*/
export const header = (name, value) => headers({ [name]: value });


/**
* Lift a wrapper function so that it can wrap a function returning more than just a Promise.
*
Expand All @@ -56,102 +58,115 @@ export const header = (name, value) => headers({ [name]: value });
*
* @sig ((...params → Promise) → (...params → Promise)) → (...params → {promise: Promise, ...}) → (...params → {promise: Promise, ...})
*/
export const liftToPromiseField = (wrapper) => (f) => (...params) => {
let rest;
const promise = wrapper((...innerParams) => {
const {promise, ...innerRest} = f(...innerParams);
rest = innerRest;
return promise;
})(...params);

return {promise, ...rest};
};

export const liftToPromiseField =
(wrapper) =>
(f) =>
(...params) => {
let rest;
const promise = wrapper((...innerParams) => {
const { promise, ...innerRest } = f(...innerParams);
rest = innerRest;
return promise;
})(...params);

return { promise, ...rest };
};

/**
* Wrapper for fetch that makes it cancellable using AbortController.
* @return {controller: AbortController, promise: Promise}
*/
export const makeAbortableFetch = (fetch) => (url, opts = {}) => {
const controller = opts.abortController || new AbortController();
const promise = fetch(url, {
signal: controller.signal,
...opts
});

return {controller, promise};
};
export const makeAbortableFetch =
(fetch) =>
(url, opts = {}) => {
const controller = opts.abortController || new AbortController();
const promise = fetch(url, {
signal: controller.signal,
...opts,
});

return { controller, promise };
};

/**
* Wrapper for abortable fetch that adds timeout support.
* @return {controller: AbortController, promise: Promise}
*/
export const makeFetchWithTimeout = (abortableFetch) => (url, opts = {}) => {
// offline db consistency requires ajax calls to fail reliably,
// so we enforce a default timeout on ajax calls
const { timeout = 60000, ...rest } = opts;
const {controller, promise} = abortableFetch(url, rest);

if (timeout !== 0) {
const newPromise = promise.catch((error) => {
// Change error name in case of time out so that we can
// distinguish it from explicit abort.
if (error.name === 'AbortError' && promise.timedOut) {
error = new TimeoutError(`Request timed out after ${timeout / 1000} seconds`);
}

throw error;
});

setTimeout(() => {
promise.timedOut = true;
controller.abort();
}, timeout);

return {controller, promise: newPromise};
}

return {controller, promise};
};

export const makeFetchWithTimeout =
(abortableFetch) =>
(url, opts = {}) => {
// offline db consistency requires ajax calls to fail reliably,
// so we enforce a default timeout on ajax calls
const { timeout = 60000, ...rest } = opts;
const { controller, promise } = abortableFetch(url, rest);

if (timeout !== 0) {
const newPromise = promise.catch((error) => {
// Change error name in case of time out so that we can
// distinguish it from explicit abort.
if (error.name === 'AbortError' && promise.timedOut) {
error = new TimeoutError(
`Request timed out after ${timeout / 1000} seconds`,
);
}

throw error;
});

setTimeout(() => {
promise.timedOut = true;
controller.abort();
}, timeout);

return { controller, promise: newPromise };
}

return { controller, promise };
};

/**
* Wrapper for fetch that makes it fail on HTTP errors.
* @return Promise
*/
export const makeFetchFailOnHttpErrors = (fetch) => (url, opts = {}) => {
const { failOnHttpErrors = true, ...rest } = opts;
const promise = fetch(url, rest);

if (failOnHttpErrors) {
return promise.then(rejectIfNotOkay);
}
export const makeFetchFailOnHttpErrors =
(fetch) =>
(url, opts = {}) => {
const { failOnHttpErrors = true, ...rest } = opts;
const promise = fetch(url, rest);

return promise;
};
if (failOnHttpErrors) {
return promise.then(rejectIfNotOkay);
}

return promise;
};

/**
* Wrapper for fetch that converts URLSearchParams body of GET requests to query string.
*/
export const makeFetchSupportGetBody = (fetch) => (url, opts = {}) => {
const { body, method, ...rest } = opts;

let newUrl = url;
let newOpts = opts;
if (Object.keys(opts).includes('method') && Object.keys(opts).includes('body') && method.toUpperCase() === 'GET' && body instanceof URLSearchParams) {
const [main, ...fragments] = newUrl.split('#');
const separator = main.includes('?') ? '&' : '?';
// append the body to the query string
newUrl = `${main}${separator}${body.toString()}#${fragments.join('#')}`;
// remove the body since it has been moved to URL
newOpts = { method, rest };
}

return fetch(newUrl, newOpts);
};

export const makeFetchSupportGetBody =
(fetch) =>
(url, opts = {}) => {
const { body, method, ...rest } = opts;

let newUrl = url;
let newOpts = opts;
if (
Object.keys(opts).includes('method') &&
Object.keys(opts).includes('body') &&
method.toUpperCase() === 'GET' &&
body instanceof URLSearchParams
) {
const [main, ...fragments] = newUrl.split('#');
const separator = main.includes('?') ? '&' : '?';
// append the body to the query string
newUrl = `${main}${separator}${body.toString()}#${fragments.join('#')}`;
// remove the body since it has been moved to URL
newOpts = { method, rest };
}

return fetch(newUrl, newOpts);
};

/**
* Cancellable fetch with timeout support that rejects on HTTP errors.
Expand All @@ -166,23 +181,22 @@ export const fetch = pipe(
makeFetchFailOnHttpErrors,
makeFetchSupportGetBody,
makeAbortableFetch,
makeFetchWithTimeout
makeFetchWithTimeout,
)(window.fetch);


export const get = liftToPromiseField(option('method', 'GET'))(fetch);


export const post = liftToPromiseField(option('method', 'POST'))(fetch);


export const delete_ = liftToPromiseField(option('method', 'DELETE'))(fetch);


/**
* Using URLSearchParams directly handles dictionaries inconveniently.
* For example, it joins arrays with commas or includes undefined keys.
*/
export const makeSearchParams = (data) => new URLSearchParams(formurlencoded(data, {
ignorenull: true
}));
export const makeSearchParams = (data) =>
new URLSearchParams(
formurlencoded(data, {
ignorenull: true,
}),
);
16 changes: 3 additions & 13 deletions client/js/helpers/authorizations.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,24 @@
import { useListenableValue } from './hooks';
import { useMemo } from 'react';


export function useLoggedIn() {
return useListenableValue(selfoss.loggedin);
}

export function useAllowedToRead() {
const loggedIn = useLoggedIn();

return useMemo(
() => selfoss.isAllowedToRead(),
[loggedIn],
);
return useMemo(() => selfoss.isAllowedToRead(), [loggedIn]);
}

export function useAllowedToUpdate() {
const loggedIn = useLoggedIn();

return useMemo(
() => selfoss.isAllowedToUpdate(),
[loggedIn],
);
return useMemo(() => selfoss.isAllowedToUpdate(), [loggedIn]);
}

export function useAllowedToWrite() {
const loggedIn = useLoggedIn();

return useMemo(
() => selfoss.isAllowedToWrite(),
[loggedIn],
);
return useMemo(() => selfoss.isAllowedToWrite(), [loggedIn]);
}
8 changes: 6 additions & 2 deletions client/js/helpers/color.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@
*
* @see https://24ways.org/2010/calculating-color-contrast/
*/
export function colorByBrightness(hexColor, darkColor = '#555', brightColor = '#EEE') {
export function colorByBrightness(
hexColor,
darkColor = '#555',
brightColor = '#EEE',
) {
// Strip hash sign.
const color = hexColor.substr(1);
const r = parseInt(color.substr(0, 2), 16);
const g = parseInt(color.substr(2, 2), 16);
const b = parseInt(color.substr(4, 2), 16);
const yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
const yiq = (r * 299 + g * 587 + b * 114) / 1000;
return yiq >= 128 ? darkColor : brightColor;
}
Loading

0 comments on commit a79d10b

Please sign in to comment.