diff --git a/README.md b/README.md
index 1375da71..067ecd91 100644
--- a/README.md
+++ b/README.md
@@ -462,16 +462,8 @@ Add this snippet to the bottom of web page:
// if you set this param in `false` you will get notifications email notifications as admin
// but your users won't have interface for subscription
};
-
- (function(c) {
- for(var i = 0; i < c.length; i++){
- var d = document, s = d.createElement('script');
- s.src = remark_config.host + '/web/' +c[i] +'.js';
- s.defer = true;
- (d.head || d.body).appendChild(s);
- }
- })(remark_config.components || ['embed']);
+
```
And then add this node in the place where you want to see Remark42 widget:
diff --git a/docker-init.sh b/docker-init.sh
index cac2281e..a329c67f 100755
--- a/docker-init.sh
+++ b/docker-init.sh
@@ -1,8 +1,8 @@
#!/bin/sh
echo "prepare environment"
# replace BASE_URL constant by REMARK_URL
-sed -i "s|https://demo.remark42.com|${REMARK_URL}|g" /srv/web/*.html
-sed -i "s|https://demo.remark42.com|${REMARK_URL}|g" /srv/web/*.js
+sed -i "s|{% REMARK_URL %}|${REMARK_URL}|g" /srv/web/*.html
+sed -i "s|{% REMARK_URL %}|${REMARK_URL}|g" /srv/web/*.js
if [ -n "${SITE_ID}" ]; then
#replace "site_id: 'remark'" by SITE_ID
diff --git a/frontend/.babelrc.js b/frontend/.babelrc.js
index 76d6cd11..2104e039 100644
--- a/frontend/.babelrc.js
+++ b/frontend/.babelrc.js
@@ -1,18 +1,34 @@
+const getPresetEnv = options => ['@babel/preset-env', options];
+const preactPreset = [
+ '@babel/preset-react',
+ {
+ pragma: 'h',
+ pragmaFrag: 'Fragment',
+ },
+];
+
+const plugins = ['module:fast-async'];
+
module.exports = {
presets: [
- [
- '@babel/preset-env',
- {
- bugfixes: true,
- loose: true,
- },
- ],
- [
- '@babel/preset-react',
- {
- pragma: 'h',
- pragmaFrag: 'Fragment',
- },
- ],
+ getPresetEnv({
+ targets: 'defaults, not IE 11, not samsung 12',
+ useBuiltIns: 'usage',
+ corejs: 3,
+ bugfixes: true,
+ loose: true,
+ }),
+ preactPreset,
],
+ plugins,
+ env: {
+ modern: {
+ presets: [getPresetEnv({ targets: { esmodules: true }, loose: true, bugfixes: true }), preactPreset],
+ plugins,
+ },
+ test: {
+ presets: [getPresetEnv({ targets: { node: 'current' } }), preactPreset],
+ plugins,
+ },
+ },
};
diff --git a/frontend/.browserslistrc b/frontend/.browserslistrc
deleted file mode 100644
index e94f8140..00000000
--- a/frontend/.browserslistrc
+++ /dev/null
@@ -1 +0,0 @@
-defaults
diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js
index 89e37fa0..a3076c9b 100644
--- a/frontend/.eslintrc.js
+++ b/frontend/.eslintrc.js
@@ -1,127 +1,40 @@
-/* eslint-disable @typescript-eslint/camelcase */
-
module.exports = {
- parser: 'babel-eslint',
extends: [
- 'eslint:recommended',
+ 'react-app',
+ 'preact',
'plugin:jsx-a11y/recommended',
- 'plugin:@typescript-eslint/recommended',
- 'plugin:prettier/recommended',
+ 'prettier',
+ 'prettier/@typescript-eslint',
+ 'prettier/babel',
+ 'prettier/prettier',
+ 'prettier/react',
],
- plugins: ['react', 'jsx-a11y', 'prettier'],
+ plugins: ['jsx-a11y', 'prettier'],
+ rules: {
+ 'prettier/prettier': 'error',
+ },
overrides: [
{
- files: ['*.ts', '*.tsx'],
- plugins: ['@typescript-eslint'],
+ files: ['*.ts?(x)'],
parser: '@typescript-eslint/parser',
- parserOptions: {
- project: './tsconfig.json',
- tsconfigRootDir: __dirname,
- },
rules: {
- // needs for using optional chaining
- 'no-undef': 0,
- 'jsx-a11y/no-autofocus': 0,
- // disabling because typescipt uses it's own lint (see next rule)
- 'no-unused-vars': 0,
- // allow Rust-like var starting with _underscore
- '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
- // disabling because it's bad practice to mark accessibility in react classes
- '@typescript-eslint/explicit-member-accessibility': 0,
- // doesn't work in real world
- '@typescript-eslint/no-non-null-assertion': 0,
- // disabling because store actions use WATCH_ME_IM_SPECIAL case
- '@typescript-eslint/class-name-casing': 0,
- // disabling because server response contains snake case
- '@typescript-eslint/camelcase': 0,
- // disabling because it's standard behaviour that function is hoisted to top
- '@typescript-eslint/no-use-before-define': 0,
- // well
- '@typescript-eslint/ban-ts-ignore': 0,
- // better to be explicit here maybe?
- '@typescript-eslint/no-inferrable-types': 0,
+ 'no-undef': 'off',
+ 'no-redeclare': 'off',
+ 'no-unused-vars': 'off',
},
},
{
files: ['*.d.ts'],
rules: {
- 'no-var': 0,
+ '@typescript-eslint/no-unused-vars': 'off',
},
},
{
- files: ['*.test.ts', '*.test.tsx'],
+ files: ['*.(spec|test).ts?(x)'],
+ extends: ['react-app/jest'],
rules: {
- '@typescript-eslint/no-explicit-any': 0,
- '@typescript-eslint/no-object-literal-type-assertion': 0,
- },
- globals: {
- fail: true,
- },
- },
- {
- files: ['*.test.ts', '*.test.tsx', '*.test.js', '*.test.jsx'],
- rules: {
- 'max-nested-callbacks': ['warn', { max: 10 }],
+ 'import/first': 'off',
},
},
],
- env: {
- browser: true,
- node: true,
- es6: true,
- jest: true,
- },
- parserOptions: {
- ecmaVersion: 6,
- sourceType: 'module',
- ecmaFeatures: {
- modules: true,
- jsx: true,
- },
- },
- globals: {
- remark_config: true,
- __webpack_public_path__: true,
- },
- rules: {
- '@typescript-eslint/indent': 0,
- 'react/jsx-uses-react': 2,
- 'react/jsx-uses-vars': 2,
- 'no-cond-assign': 1,
- 'no-empty': ['error', { allowEmptyCatch: true }],
- 'no-console': 1,
- camelcase: 0,
- 'comma-style': 2,
- 'max-nested-callbacks': [2, 3],
- 'no-eval': 2,
- 'no-implied-eval': 2,
- 'no-new-func': 2,
- 'guard-for-in': 2,
- eqeqeq: 2,
- 'no-else-return': 2,
- 'no-redeclare': 2,
- 'no-dupe-keys': 2,
- radix: 2,
- strict: [2, 'never'],
- 'no-shadow': 0,
- 'callback-return': [1, ['callback', 'cb', 'next', 'done']],
- 'no-delete-var': 2,
- 'no-undef-init': 2,
- 'no-shadow-restricted-names': 2,
- 'handle-callback-err': 2,
- 'no-lonely-if': 2,
- 'constructor-super': 2,
- 'no-this-before-super': 2,
- 'no-dupe-class-members': 2,
- 'no-const-assign': 2,
- 'prefer-spread': 2,
- 'prefer-const': 2,
- 'no-useless-concat': 2,
- 'no-var': 2,
- 'object-shorthand': 2,
- 'prefer-arrow-callback': 2,
- 'prettier/prettier': 2,
- '@typescript-eslint/no-var-requires': 0,
- '@typescript-eslint/explicit-function-return-type': 0,
- },
};
diff --git a/frontend/.gitignore b/frontend/.gitignore
index 355e175c..55df0890 100644
--- a/frontend/.gitignore
+++ b/frontend/.gitignore
@@ -1,2 +1,6 @@
-.env
+node_modules
extracted-messages
+/public
+/*.log
+.env
+tsconfig.tsbuildinfo
\ No newline at end of file
diff --git a/frontend/.lintstagedrc.js b/frontend/.lintstagedrc.js
index 1622bd25..96730e4e 100644
--- a/frontend/.lintstagedrc.js
+++ b/frontend/.lintstagedrc.js
@@ -1,5 +1,5 @@
module.exports = {
'./**/*.{ts,tsx,js,jsx}': ['eslint --fix --max-warnings=0', 'prettier --write'],
- './**/*.{scss,pcss,css}': ['prettier --write', 'stylelint'],
- './iframe.html': ['prettier --write', 'stylelint'],
+ './**/*.css': ['prettier --write', 'stylelint'],
+ './templates/**.html': ['prettier --write', 'stylelint'],
};
diff --git a/frontend/.prettierignore b/frontend/.prettierignore
index 36170a7e..79ee3f7b 100644
--- a/frontend/.prettierignore
+++ b/frontend/.prettierignore
@@ -1,2 +1,4 @@
node_modules
public
+package.json
+package-lock.json
\ No newline at end of file
diff --git a/frontend/.prettierrc.js b/frontend/.prettierrc.js
index 7f94fc39..585d0f3f 100644
--- a/frontend/.prettierrc.js
+++ b/frontend/.prettierrc.js
@@ -1,6 +1,7 @@
module.exports = {
printWidth: 120,
useTabs: false,
+ tabWidth: 2,
semi: true,
singleQuote: true,
trailingComma: 'es5',
@@ -8,7 +9,7 @@ module.exports = {
arrowParens: 'avoid',
overrides: [
{
- files: ['*.ejs', '*.html'],
+ files: ['*.html'],
options: {
trailingComma: 'none',
},
diff --git a/frontend/.size-limit.js b/frontend/.size-limit.js
index b9339f9e..8a2c337a 100644
--- a/frontend/.size-limit.js
+++ b/frontend/.size-limit.js
@@ -1,22 +1,42 @@
module.exports = [
{
- path: 'public/embed.js',
- limit: '2.7 KB',
+ path: 'public/embed.mjs',
+ limit: '2.5 KB',
},
{
- limit: '86 KB',
+ path: 'public/embed.js',
+ limit: '2.5 KB',
+ },
+ {
+ limit: '70 KB',
+ path: 'public/remark.mjs',
+ },
+ {
+ limit: '73.5 KB',
path: 'public/remark.js',
},
{
- limit: '44 KB',
+ limit: '31 KB',
+ path: 'public/last-comments.mjs',
+ },
+ {
+ limit: '38 KB',
path: 'public/last-comments.js',
},
+ {
+ path: 'public/deleteme.mjs',
+ limit: '11 KB',
+ },
{
path: 'public/deleteme.js',
- limit: '35 KB',
+ limit: '18 KB',
+ },
+ {
+ path: 'public/counter.mjs',
+ limit: '0.7 KB',
},
{
path: 'public/counter.js',
- limit: '1.05 KB',
+ limit: '0.7 KB',
},
];
diff --git a/frontend/.stylelintignore b/frontend/.stylelintignore
index 36170a7e..a197d7e6 100644
--- a/frontend/.stylelintignore
+++ b/frontend/.stylelintignore
@@ -1,2 +1,3 @@
node_modules
public
+extracted-messages
\ No newline at end of file
diff --git a/frontend/.stylelintrc.js b/frontend/.stylelintrc.js
index 279d95e3..841565f7 100644
--- a/frontend/.stylelintrc.js
+++ b/frontend/.stylelintrc.js
@@ -1,4 +1,5 @@
-const path = require('path');
+const { CUSTOM_PROPERTIES_PATH } = require('./webpack.config');
+
module.exports = {
extends: ['stylelint-config-standard', 'stylelint-config-prettier'],
plugins: ['stylelint-value-no-unknown-custom-properties', '@mavrin/stylelint-declaration-use-css-custom-properties'],
@@ -19,7 +20,7 @@ module.exports = {
'csstools/value-no-unknown-custom-properties': [
true,
{
- importFrom: path.resolve(__dirname, './app/custom-properties.css'),
+ importFrom: CUSTOM_PROPERTIES_PATH,
},
],
},
diff --git a/frontend/Readme.md b/frontend/README.md
similarity index 93%
rename from frontend/Readme.md
rename to frontend/README.md
index dd236123..98e81b74 100644
--- a/frontend/Readme.md
+++ b/frontend/README.md
@@ -8,6 +8,7 @@
- if you want IDE integration, you need `eslint` and `stylelint` plugin to be installed.
### CSS Styles
+
- although styles have `scss` extension, it is actually pack of post-css plugins, so syntax differs, for example in `calc` function.
- now we are migrating to css-modules and this is recomended way to stylization. A file with styles should be named like `component.module.css`
- old component styles use BEM notation (at least it should): `block__element_modifier`. Also there are `mix` classes: `block_modifier`.
@@ -19,7 +20,7 @@
- imports for typescript, javascript files should be without extension: `./index`, not `./index.ts`
- if file resides in the same directory or in subdirectory import should be relative: `./types/something`
-- otherwise it should start from `@app` namespace: `@app/common/store` which mapped to `/app/common/store.ts` in webpack, tsconfig and jest
+- otherwise it should start from ``namespace:`common/store`which mapped to`/appcommon/store.ts` in webpack, tsconfig and jest
### Testing
diff --git a/frontend/app/@types/__webpack_public_path__.d.ts b/frontend/app/@types/__webpack_public_path__.d.ts
deleted file mode 100644
index 15684cf4..00000000
--- a/frontend/app/@types/__webpack_public_path__.d.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-/**
- * Veriable responsive for dynamic setting public path for
- * assets. Dynamic imports with relative url will be resolved over this path.
- *
- * https://webpack.js.org/guides/public-path/#on-the-fly
- */
-declare var __webpack_public_path__: string;
diff --git a/frontend/app/@types/bem-react-helper.d.ts b/frontend/app/@types/bem-react-helper.d.ts
deleted file mode 100644
index 459fe670..00000000
--- a/frontend/app/@types/bem-react-helper.d.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-declare module 'bem-react-helper' {
- export interface Mods {
- [key: string]: string | number | boolean | undefined | null;
- }
- export type Mix = Array | string;
- export default function b(
- classname: string,
- props?: {
- mods?: Mods;
- mix?: Mix;
- },
- override_props?: Mods
- ): string;
-}
diff --git a/frontend/app/@types/define-modules.d.ts b/frontend/app/@types/define-modules.d.ts
deleted file mode 100644
index 672a166a..00000000
--- a/frontend/app/@types/define-modules.d.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-declare module 'intersection-observer';
-declare module 'whatwg-fetch';
diff --git a/frontend/app/@types/global.d.ts b/frontend/app/@types/global.d.ts
deleted file mode 100644
index 0e417ea5..00000000
--- a/frontend/app/@types/global.d.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { Theme } from '@app/common/types';
-import { CommentsConfig } from '@app/common/config-types';
-
-declare global {
- interface Window {
- remark_config: CommentsConfig;
- REMARK42: {
- changeTheme?: (theme: Theme) => void;
- destroy?: () => void;
- createInstance: (
- remark_config: CommentsConfig
- ) =>
- | {
- changeTheme(theme: Theme): void;
- destroy(): void;
- }
- | undefined;
- };
- }
-
- namespace NodeJS {
- interface Global {
- Headers: typeof Headers;
- localStorage: typeof Storage;
- fetch: typeof fetch;
- }
- }
-}
diff --git a/frontend/app/testUtils/mockHeaders.ts b/frontend/app/__mocks__/headers.ts
similarity index 82%
rename from frontend/app/testUtils/mockHeaders.ts
rename to frontend/app/__mocks__/headers.ts
index 52a9873c..e89fbad5 100644
--- a/frontend/app/testUtils/mockHeaders.ts
+++ b/frontend/app/__mocks__/headers.ts
@@ -1,4 +1,4 @@
-global.Headers = class HeadersMock implements Headers {
+global.Headers = class HeadersMock extends Headers implements Headers {
private headers = new Map();
append(key: string, value: string) {
@@ -16,7 +16,6 @@ global.Headers = class HeadersMock implements Headers {
delete(key: string) {
this.headers.delete(key);
}
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
forEach(callbackfn: (value: string, key: string, parent: Headers) => void, thisArg?: any) {
this.headers.forEach((value, key) => {
callbackfn.call(thisArg || this, value, key, this);
diff --git a/frontend/app/testUtils/mocks/jwt.ts b/frontend/app/__stubs__/jwt.ts
similarity index 100%
rename from frontend/app/testUtils/mocks/jwt.ts
rename to frontend/app/__stubs__/jwt.ts
diff --git a/frontend/app/testUtils/index.ts b/frontend/app/__stubs__/static-config.ts
similarity index 58%
rename from frontend/app/testUtils/index.ts
rename to frontend/app/__stubs__/static-config.ts
index ba6d34bb..3dcb4d2b 100644
--- a/frontend/app/testUtils/index.ts
+++ b/frontend/app/__stubs__/static-config.ts
@@ -1,15 +1,4 @@
-import 'jest-extended';
-import 'jest-enzyme';
-import { configure } from 'enzyme';
-import PreactAdapter from 'enzyme-adapter-preact-pure';
-
-import { StaticStore } from '@app/common/static_store';
-
-import './mockHeaders';
-
-configure({ adapter: new PreactAdapter() });
-
-require('document-register-element/pony')(window);
+import { StaticStore } from 'common/static-store';
beforeEach(() => {
StaticStore.config = {
diff --git a/frontend/app/testUtils/mockStore.ts b/frontend/app/__stubs__/store.ts
similarity index 64%
rename from frontend/app/testUtils/mockStore.ts
rename to frontend/app/__stubs__/store.ts
index ccb6da27..cd97280d 100644
--- a/frontend/app/testUtils/mockStore.ts
+++ b/frontend/app/__stubs__/store.ts
@@ -2,4 +2,6 @@ import createMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export const mockStore = createMockStore([thunk]);
+const mockStore = createMockStore([thunk]);
+
+export default mockStore;
diff --git a/frontend/app/testUtils/mocks/user.ts b/frontend/app/__stubs__/user.ts
similarity index 87%
rename from frontend/app/testUtils/mocks/user.ts
rename to frontend/app/__stubs__/user.ts
index 1331f190..80035204 100644
--- a/frontend/app/testUtils/mocks/user.ts
+++ b/frontend/app/__stubs__/user.ts
@@ -1,4 +1,4 @@
-import { User } from '@app/common/types';
+import type { User } from 'common/types';
const user: Readonly = {
id: 'email_1',
diff --git a/frontend/app/common/__mocks__/constants.ts b/frontend/app/common/__mocks__/constants.ts
index 3fb7aef8..44374a0a 100644
--- a/frontend/app/common/__mocks__/constants.ts
+++ b/frontend/app/common/__mocks__/constants.ts
@@ -1,6 +1,6 @@
// @ts-ignore
-const mock: typeof import('@app/common/constants') = {
- ...jest.requireActual('@app/common/constants'),
+const mock: typeof import('common/constants') = {
+ ...jest.requireActual('common/constants'),
BASE_URL: 'https://demo.remark42.com/',
};
diff --git a/frontend/app/common/__mocks__/settings.ts b/frontend/app/common/__mocks__/settings.ts
index adcb398c..6b0133c5 100644
--- a/frontend/app/common/__mocks__/settings.ts
+++ b/frontend/app/common/__mocks__/settings.ts
@@ -1,6 +1,6 @@
// @ts-ignore
-const mock: typeof import('@app/common/settings') = {
- ...jest.requireActual('@app/common/settings'),
+const mock: typeof import('common/settings') = {
+ ...jest.requireActual('common/settings'),
siteId: 'remark',
pageTitle: 'remark test',
url: 'https://remark42.com/test',
diff --git a/frontend/app/common/api.test.ts b/frontend/app/common/api.test.ts
index 4fdcb2b5..611c0c76 100644
--- a/frontend/app/common/api.test.ts
+++ b/frontend/app/common/api.test.ts
@@ -2,12 +2,12 @@ import jestFetchMock from 'jest-fetch-mock';
import { emailVerificationForSubscribe } from './api';
-jest.mock('@app/common/constants', () => ({
+jest.mock('common/constants', () => ({
BASE_URL: 'https://example.com',
API_BASE: '/api',
}));
-jest.mock('@app/common/settings', () => ({
+jest.mock('common/settings', () => ({
siteId: 'remark42',
}));
@@ -29,11 +29,11 @@ describe('api', () => {
expect(jestFetchMock.mock.calls.length).toEqual(1);
const url = jestFetchMock.mock.calls[0][0] as string;
- const match = url.match(/address=(\S+)$/);
+ const match = url.match(/address=(\S+)$/) as string[];
- expect(match).toBeArray();
- expect((match as string[]).length).toBeGreaterThan(1);
- expect((match as string[])[1]).toBe(
+ expect(Array.isArray(match)).toBe(true);
+ expect(match.length).toBeGreaterThan(1);
+ expect(match[1]).toBe(
"address.!%23%24%25%26'*%2B-%2F%3D%3F%5E_%60%7B%7C%7D~()%2C%3A%3B%3C%3E%5B%5C%5D%40example.com"
);
});
diff --git a/frontend/app/common/api.ts b/frontend/app/common/api.ts
index a618cab9..c469b31f 100644
--- a/frontend/app/common/api.ts
+++ b/frontend/app/common/api.ts
@@ -6,7 +6,7 @@ import fetcher from './fetcher';
/* common */
const __loginAnonymously = (username: string): Promise => {
const url = `/auth/anonymous/login?user=${encodeURIComponent(username)}&aud=${siteId}&from=${encodeURIComponent(
- location.origin + location.pathname + '?selfClose'
+ `${window.location.origin}${window.location.pathname}?selfClose`
)}`;
return fetcher.get({ url, withCredentials: true, overriddenApiBase: '' });
};
@@ -35,7 +35,7 @@ export const logIn = (provider: AuthProvider): Promise => {
return new Promise((resolve, reject) => {
const url = `${BASE_URL}/auth/${provider.name}/login?from=${encodeURIComponent(
- location.origin + location.pathname + '?selfClose'
+ `${window.location.origin}${window.location.pathname}?selfClose`
)}&site=${siteId}`;
const newWindow = window.open(url);
@@ -267,7 +267,7 @@ export const uploadImage = (image: File): Promise => {
name: image.name,
size: image.size,
type: image.type,
- url: BASE_URL + API_BASE + '/picture/' + resp.id,
+ url: `${BASE_URL + API_BASE}/picture/${resp.id}`,
}));
};
diff --git a/frontend/app/common/closest-polyfill.ts b/frontend/app/common/closest-polyfill.ts
deleted file mode 100644
index 5ed1eea7..00000000
--- a/frontend/app/common/closest-polyfill.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-if (!Element.prototype.matches) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- Element.prototype.matches = (Element.prototype as any).msMatchesSelector || Element.prototype.webkitMatchesSelector;
-}
-
-if (!Element.prototype.closest) {
- Element.prototype.closest = function (s: string) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-this-alias
- let el: any = this;
-
- do {
- if (el.matches(s)) return el;
- el = el.parentElement || el.parentNode;
- } while (el !== null && el.nodeType === 1);
- return null;
- };
-}
diff --git a/frontend/app/common/config-types.ts b/frontend/app/common/config-types.ts
deleted file mode 100644
index e2fdd913..00000000
--- a/frontend/app/common/config-types.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { UserInfo, Theme } from './types';
-
-export interface CounterConfig {
- host: string;
- site_id: string;
- url?: string;
-}
-
-export type UserInfoConfig = UserInfo;
-
-export interface CommentsConfig {
- host: string;
- site_id: string;
- url?: string;
- max_shown_comments?: number;
- theme?: Theme;
- page_title?: string;
- node?: string | HTMLElement;
- locale?: string;
- show_email_subscription?: boolean;
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- __colors__?: any;
-}
-
-export interface LastCommentsConfig {
- host: string;
- site_id: string;
- max_last_comments: number;
- locale?: string;
-}
diff --git a/frontend/app/common/copy.ts b/frontend/app/common/copy.ts
index 9858b6fa..3733b62f 100644
--- a/frontend/app/common/copy.ts
+++ b/frontend/app/common/copy.ts
@@ -1,16 +1,15 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-
// based on https://github.com/sindresorhus/copy-text-to-clipboard, but improved to copy text styles too
-export default (input: string): boolean => {
+export default function copy(input: string): boolean {
const el = document.createElement('div');
el.innerHTML = input;
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (el.style as any).contain = 'strict';
- el.style.position = 'absolute';
- el.style.left = '-9999px';
- el.style.fontSize = '12pt'; // Prevent zooming on iOS
+ Object.assign(el.style, {
+ contain: 'strict',
+ position: 'absolute',
+ left: '-9999px',
+ fontSize: '12pt', // Prevent zooming on iOS
+ });
document.body.appendChild(el);
@@ -60,4 +59,4 @@ export default (input: string): boolean => {
}
return success;
-};
+}
diff --git a/frontend/app/common/fetcher.test.ts b/frontend/app/common/fetcher.test.ts
index f433f27a..23ee9a7b 100644
--- a/frontend/app/common/fetcher.test.ts
+++ b/frontend/app/common/fetcher.test.ts
@@ -8,9 +8,9 @@ describe('fetcher', () => {
error: 'you just cant',
details: 'you just cant at all',
};
- (window.fetch as any) = jest.fn().mockImplementation(async () => ({
+ window.fetch = jest.fn().mockImplementation(async () => ({
status: 400,
- headers: new (window as any).Headers(),
+ headers: new Headers(),
json: async () => response,
text: async () => JSON.stringify(response),
}));
@@ -18,7 +18,7 @@ describe('fetcher', () => {
return fetcher
.get('/api/some')
.then(data => {
- fail(data);
+ throw new Error('Request should be failed');
})
.catch(e => {
expect(e.code).toBe(2);
@@ -40,7 +40,7 @@ describe('fetcher', () => {
return fetcher
.get('/api/some')
.then(data => {
- fail(data);
+ throw new Error('Request should be failed');
})
.catch(e => {
expect(e.code).toBe(401);
@@ -51,16 +51,18 @@ describe('fetcher', () => {
(jest.spyOn(window, 'fetch') as any).mockImplementation(async () => ({
status: 400,
headers: new (window as any).Headers(),
- json: async () => {
+ async json() {
throw new Error('json parse error');
},
- text: async () => 'you given me something wrong',
+ async text() {
+ return 'you given me something wrong';
+ },
}));
return fetcher
.get({ url: '/api/some', logError: false })
.then(data => {
- fail(data);
+ throw new Error('Request should be failed');
})
.catch(e => {
expect(e.code).toBe(0);
diff --git a/frontend/app/common/fetcher.ts b/frontend/app/common/fetcher.ts
index a70596fb..45a8194b 100644
--- a/frontend/app/common/fetcher.ts
+++ b/frontend/app/common/fetcher.ts
@@ -1,8 +1,9 @@
+import { httpErrorMap, isFailedFetch, httpMessages, RequestError } from 'utils/errorUtils';
+
import { BASE_URL, API_BASE, HEADER_X_JWT } from './constants';
import { siteId } from './settings';
-import { StaticStore } from './static_store';
+import { StaticStore } from './static-store';
import { getCookie } from './cookies';
-import { httpErrorMap, isFailedFetch, httpMessages } from '@app/utils/errorUtils';
export type FetcherMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head';
const methods: FetcherMethod[] = ['get', 'post', 'put', 'patch', 'delete', 'head'];
@@ -33,7 +34,7 @@ type FetcherObject = { [K in FetcherMethod]: (data: FetcherInit) =>
let activeJwtToken: string | undefined;
const fetcher = methods.reduce>((acc, method) => {
- acc[method] = (data: FetcherInit): Promise => {
+ acc[method] = async (data: FetcherInit): Promise => {
const {
url,
body = undefined,
@@ -77,7 +78,7 @@ const fetcher = methods.reduce>((acc, method) => {
}
if (siteId && method !== 'post' && !rurl.includes('?site=') && !rurl.includes('&site=')) {
- rurl += (rurl.includes('?') ? '&' : '?') + `site=${siteId}`;
+ rurl += `${rurl.includes('?') ? '&' : '?'}site=${siteId}`;
}
return fetch(rurl, parameters)
@@ -99,10 +100,8 @@ const fetcher = methods.reduce>((acc, method) => {
if (res.status >= 400) {
if (httpErrorMap.has(res.status)) {
const descriptor = httpErrorMap.get(res.status) || httpMessages.unexpectedError;
- throw {
- code: res.status,
- error: descriptor.defaultMessage,
- };
+
+ throw new RequestError(descriptor.defaultMessage, res.status);
}
return res.text().then(text => {
let err;
@@ -110,13 +109,10 @@ const fetcher = methods.reduce>((acc, method) => {
err = JSON.parse(text);
} catch (e) {
if (logError) {
- // eslint-disable-next-line no-console
console.error(err);
}
- throw {
- code: 0,
- error: httpMessages.unexpectedError.defaultMessage,
- };
+
+ throw new RequestError(httpMessages.unexpectedError.defaultMessage, 0);
}
throw err;
});
@@ -130,11 +126,9 @@ const fetcher = methods.reduce>((acc, method) => {
})
.catch(e => {
if (isFailedFetch(e)) {
- throw {
- code: -2,
- error: e.message,
- };
+ throw new RequestError(e.message, -2);
}
+
throw e;
});
};
diff --git a/frontend/app/common/local-storage.test.ts b/frontend/app/common/local-storage.test.ts
index b3ee40d9..68f73e15 100644
--- a/frontend/app/common/local-storage.test.ts
+++ b/frontend/app/common/local-storage.test.ts
@@ -67,7 +67,7 @@ describe('updateJsonItem', () => {
});
it('should set data to empty localStorage', () => {
- updateJsonItem>(LS_KEY, {});
+ updateJsonItem(LS_KEY, {});
expect(localStorage.getItem(LS_KEY)).toBe(JSON.stringify({}));
});
diff --git a/frontend/app/common/local-storage.ts b/frontend/app/common/local-storage.ts
index 18e39b28..3367bb02 100644
--- a/frontend/app/common/local-storage.ts
+++ b/frontend/app/common/local-storage.ts
@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
import { IS_STORAGE_AVAILABLE } from './constants';
const failMessage = 'remark42: localStorage access denied, check browser preferences';
@@ -47,9 +46,9 @@ export function setJsonItem(key: string, data: T) {
}
}
-export function updateJsonItem | any[]>(key: string, value: (data: T) => T): void;
-export function updateJsonItem(key: string, value: T): void;
-export function updateJsonItem>(key: string, value: T) {
+export function updateJsonItem(key: string, value: (data: T) => T): void;
+export function updateJsonItem(key: string, value: T): void;
+export function updateJsonItem>(key: string, value: T) {
const savedData = getJsonItem(key);
if (Array.isArray(value)) {
diff --git a/frontend/app/common/polyfills.ts b/frontend/app/common/polyfills.ts
deleted file mode 100644
index f48f9c7c..00000000
--- a/frontend/app/common/polyfills.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import 'regenerator-runtime/runtime';
-import 'es6-promise/auto';
-import 'focus-visible';
-import '@webcomponents/custom-elements';
-import './closest-polyfill';
-
-export default async function loadPolyfills() {
- function fillCoreJs() {
- if (
- 'startsWith' in String.prototype &&
- 'endsWith' in String.prototype &&
- 'includes' in Array.prototype &&
- 'assign' in Object &&
- 'keys' in Object
- ) {
- return;
- }
-
- return import(/* webpackChunkName: "core-js" */ 'core-js');
- }
-
- function fillFetch() {
- if ('fetch' in window) return;
-
- return import(/* webpackChunkName: "whatwg-fetch" */ 'whatwg-fetch');
- }
-
- function fillIntersectionObserver() {
- if (
- 'IntersectionObserver' in window &&
- 'IntersectionObserverEntry' in window &&
- 'intersectionRatio' in window.IntersectionObserverEntry.prototype
- ) {
- return;
- }
-
- return import(/* webpackChunkName: "intersection-observer" */ 'intersection-observer');
- }
-
- await Promise.all([fillCoreJs(), fillFetch(), fillIntersectionObserver()]);
- return;
-}
diff --git a/frontend/app/common/settings.ts b/frontend/app/common/settings.ts
index e36b3757..0b20e1b2 100644
--- a/frontend/app/common/settings.ts
+++ b/frontend/app/common/settings.ts
@@ -1,6 +1,7 @@
-import { Theme } from './types';
+import parseQuery from 'utils/parseQuery';
+
+import type { Theme } from './types';
import { THEMES, MAX_SHOWN_ROOT_COMMENTS } from './constants';
-import parseQuery from '@app/utils/parseQuery';
export interface QuerySettingsType {
site_id?: string;
@@ -29,9 +30,9 @@ if (!querySettings.theme || THEMES.indexOf(querySettings.theme) === -1) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
querySettings.show_email_subscription = (querySettings.show_email_subscription as any) !== 'false';
-export const siteId = querySettings.site_id;
+export const siteId = querySettings.site_id!;
export const pageTitle = querySettings.page_title;
export const url = querySettings.url;
export const maxShownComments = querySettings.max_shown_comments;
-export const token = querySettings.token;
+export const token = querySettings.token!;
export const theme = querySettings.theme;
diff --git a/frontend/app/common/static_store.ts b/frontend/app/common/static-store.ts
similarity index 100%
rename from frontend/app/common/static_store.ts
rename to frontend/app/common/static-store.ts
diff --git a/frontend/app/common/types.ts b/frontend/app/common/types.ts
index a159e7de..cedfd9d8 100644
--- a/frontend/app/common/types.ts
+++ b/frontend/app/common/types.ts
@@ -118,13 +118,6 @@ export interface Config {
emoji_enabled: boolean;
}
-export interface RemarkConfig {
- site_id: string;
- url: string;
- /** used in last comments widget */
- max_last_comments?: number;
-}
-
export type Sorting = '-time' | '+time' | '-active' | '+active' | '-score' | '+score' | '-controversy' | '+controversy';
export type AuthProvider =
diff --git a/frontend/app/common/user-info-settings.ts b/frontend/app/common/user-info-settings.ts
index 5fee1f02..b352baac 100644
--- a/frontend/app/common/user-info-settings.ts
+++ b/frontend/app/common/user-info-settings.ts
@@ -1,7 +1,8 @@
-import { UserInfo } from './types';
-import parseQuery from '@app/utils/parseQuery';
+import parseQuery from 'utils/parseQuery';
-export const userInfo: Partial = parseQuery();
+import type { UserInfo } from './types';
+
+export const userInfo: UserInfo = parseQuery();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const isDefaultPicture = ((userInfo.isDefaultPicture as any) as string) !== '1';
diff --git a/frontend/app/components/auth-panel/__column/auth-panel__column.css b/frontend/app/components/auth-panel/__column/auth-panel__column.css
new file mode 100644
index 00000000..350f95e4
--- /dev/null
+++ b/frontend/app/components/auth-panel/__column/auth-panel__column.css
@@ -0,0 +1,4 @@
+.auth-panel__column:last-child {
+ margin-left: 8px;
+ text-align: right;
+}
diff --git a/frontend/app/components/auth-panel/__column/auth-panel__column.scss b/frontend/app/components/auth-panel/__column/auth-panel__column.scss
deleted file mode 100644
index 0a93f81c..00000000
--- a/frontend/app/components/auth-panel/__column/auth-panel__column.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-.auth-panel__column {
- &:last-child {
- margin-left: 8px;
- text-align: right;
- }
-}
diff --git a/frontend/app/components/auth-panel/__readonly-label/auth-panel__readonly-label.scss b/frontend/app/components/auth-panel/__readonly-label/auth-panel__readonly-label.css
similarity index 100%
rename from frontend/app/components/auth-panel/__readonly-label/auth-panel__readonly-label.scss
rename to frontend/app/components/auth-panel/__readonly-label/auth-panel__readonly-label.css
diff --git a/frontend/app/components/auth-panel/__select-label-value/auth-panel__select-label-value.scss b/frontend/app/components/auth-panel/__select-label-value/auth-panel__select-label-value.css
similarity index 100%
rename from frontend/app/components/auth-panel/__select-label-value/auth-panel__select-label-value.scss
rename to frontend/app/components/auth-panel/__select-label-value/auth-panel__select-label-value.css
diff --git a/frontend/app/components/auth-panel/__select-label/auth-panel__select-label.scss b/frontend/app/components/auth-panel/__select-label/auth-panel__select-label.css
similarity index 100%
rename from frontend/app/components/auth-panel/__select-label/auth-panel__select-label.scss
rename to frontend/app/components/auth-panel/__select-label/auth-panel__select-label.css
diff --git a/frontend/app/components/auth-panel/__select/auth-panel__select.scss b/frontend/app/components/auth-panel/__select/auth-panel__select.css
similarity index 100%
rename from frontend/app/components/auth-panel/__select/auth-panel__select.scss
rename to frontend/app/components/auth-panel/__select/auth-panel__select.css
diff --git a/frontend/app/components/auth-panel/__sort/auth-panel__sort.scss b/frontend/app/components/auth-panel/__sort/auth-panel__sort.css
similarity index 100%
rename from frontend/app/components/auth-panel/__sort/auth-panel__sort.scss
rename to frontend/app/components/auth-panel/__sort/auth-panel__sort.css
diff --git a/frontend/app/components/auth-panel/__user-id/auth-panel__user-id.scss b/frontend/app/components/auth-panel/__user-id/auth-panel__user-id.css
similarity index 100%
rename from frontend/app/components/auth-panel/__user-id/auth-panel__user-id.scss
rename to frontend/app/components/auth-panel/__user-id/auth-panel__user-id.css
diff --git a/frontend/app/components/auth-panel/_logged-in/auth-panel_logged-in.scss b/frontend/app/components/auth-panel/_logged-in/auth-panel_logged-in.css
similarity index 79%
rename from frontend/app/components/auth-panel/_logged-in/auth-panel_logged-in.scss
rename to frontend/app/components/auth-panel/_logged-in/auth-panel_logged-in.css
index 758c7d36..2d369149 100644
--- a/frontend/app/components/auth-panel/_logged-in/auth-panel_logged-in.scss
+++ b/frontend/app/components/auth-panel/_logged-in/auth-panel_logged-in.css
@@ -1,7 +1,7 @@
.auth-panel_logged-in {
font-size: 12px;
- .auth-panel__column {
+ & .auth-panel__column {
&:first-child {
font-weight: 400;
}
diff --git a/frontend/app/components/auth-panel/_theme/_dark/auth-panel_theme_dark.scss b/frontend/app/components/auth-panel/_theme/_dark/auth-panel_theme_dark.css
similarity index 67%
rename from frontend/app/components/auth-panel/_theme/_dark/auth-panel_theme_dark.scss
rename to frontend/app/components/auth-panel/_theme/_dark/auth-panel_theme_dark.css
index 54f008f1..ff243925 100644
--- a/frontend/app/components/auth-panel/_theme/_dark/auth-panel_theme_dark.scss
+++ b/frontend/app/components/auth-panel/_theme/_dark/auth-panel_theme_dark.css
@@ -1,5 +1,5 @@
.auth-panel_theme_dark {
- .auth-panel__user-id {
+ & .auth-panel__user-id {
color: var(--color5);
}
}
diff --git a/frontend/app/components/auth-panel/_theme/_light/auth-panel_theme_light.scss b/frontend/app/components/auth-panel/_theme/_light/auth-panel_theme_light.css
similarity index 68%
rename from frontend/app/components/auth-panel/_theme/_light/auth-panel_theme_light.scss
rename to frontend/app/components/auth-panel/_theme/_light/auth-panel_theme_light.css
index 5258740b..c96302b7 100644
--- a/frontend/app/components/auth-panel/_theme/_light/auth-panel_theme_light.scss
+++ b/frontend/app/components/auth-panel/_theme/_light/auth-panel_theme_light.css
@@ -1,5 +1,5 @@
.auth-panel_theme_light {
- .auth-panel__user-id {
+ & .auth-panel__user-id {
color: var(--color13);
}
}
diff --git a/frontend/app/components/auth-panel/auth-panel.scss b/frontend/app/components/auth-panel/auth-panel.css
similarity index 100%
rename from frontend/app/components/auth-panel/auth-panel.scss
rename to frontend/app/components/auth-panel/auth-panel.css
diff --git a/frontend/app/components/auth-panel/auth-panel.test.tsx b/frontend/app/components/auth-panel/auth-panel.test.tsx
index 1a2951b3..32df5c62 100644
--- a/frontend/app/components/auth-panel/auth-panel.test.tsx
+++ b/frontend/app/components/auth-panel/auth-panel.test.tsx
@@ -1,16 +1,15 @@
-/** @jsx createElement */
-import { createElement } from 'preact';
+import { h } from 'preact';
import { mount } from 'enzyme';
import createMockStore from 'redux-mock-store';
import { Middleware } from 'redux';
import { Provider } from 'react-redux';
import { IntlProvider } from 'react-intl';
-import enMessages from '@app/locales/en.json';
+import enMessages from 'locales/en.json';
import AuthPanel, { Props } from './auth-panel';
import { Button } from '../button';
-import { StaticStore } from '@app/common/static_store';
+import { StaticStore } from 'common/static-store';
const DefaultProps = {
providers: ['google', 'github'],
@@ -83,7 +82,7 @@ describe('', () => {
const firstCol = element.find('.auth-panel__column').first();
const providerButtons = firstCol.find(Button);
- expect(firstCol.text()).toStartWith('Login:');
+ expect(firstCol.text().startsWith('Login:')).toBe(true);
expect(providerButtons.at(0).text()).toBe('Google');
expect(providerButtons.at(1).text()).toBe('GitHub');
});
diff --git a/frontend/app/components/auth-panel/auth-panel.tsx b/frontend/app/components/auth-panel/auth-panel.tsx
index f030de36..e2d8f5c0 100644
--- a/frontend/app/components/auth-panel/auth-panel.tsx
+++ b/frontend/app/components/auth-panel/auth-panel.tsx
@@ -1,22 +1,21 @@
-/** @jsx createElement */
-import { createElement, Component, Fragment } from 'preact';
+import { h, Component, Fragment } from 'preact';
import { useSelector } from 'react-redux';
import { FormattedMessage, defineMessages, IntlShape, useIntl } from 'react-intl';
import b from 'bem-react-helper';
-import { User, AuthProvider, Sorting, Theme, PostInfo } from '@app/common/types';
-import { IS_STORAGE_AVAILABLE, IS_THIRD_PARTY } from '@app/common/constants';
-import { requestDeletion } from '@app/utils/email';
-import postMessage from '@app/utils/postMessage';
-import { getHandleClickProps } from '@app/common/accessibility';
-import { StoreState } from '@app/store';
-import { ProviderState } from '@app/store/provider/reducers';
-import { Dropdown, DropdownItem } from '@app/components/dropdown';
-import { Button } from '@app/components/button';
-import Auth from '@app/components/auth';
+import { User, AuthProvider, Sorting, Theme, PostInfo } from 'common/types';
+import { IS_STORAGE_AVAILABLE, IS_THIRD_PARTY } from 'common/constants';
+import { requestDeletion } from 'utils/email';
+import postMessage from 'utils/postMessage';
+import { getHandleClickProps } from 'common/accessibility';
+import { StoreState } from 'store';
+import { ProviderState } from 'store/provider/reducers';
+import { Dropdown, DropdownItem } from 'components/dropdown';
+import { Button } from 'components/button';
+import Auth from 'components/auth';
-import useTheme from '@app/hooks/useTheme';
-import { StaticStore } from '@app/common/static_store';
+import useTheme from 'hooks/useTheme';
+import { StaticStore } from 'common/static-store';
export interface OwnProps {
user: User | null;
@@ -132,6 +131,7 @@ export class AuthPanel extends Component {
className="auth-panel__pseudo-link"
href={`${window.location.origin}/web/comments.html${window.location.search}`}
target="_blank"
+ rel="noreferrer"
>
diff --git a/frontend/app/components/auth-panel/index.ts b/frontend/app/components/auth-panel/index.ts
index f2c45fe4..f4ce12a6 100644
--- a/frontend/app/components/auth-panel/index.ts
+++ b/frontend/app/components/auth-panel/index.ts
@@ -1,18 +1,18 @@
+import './auth-panel.css';
+
+import './__readonly-label/auth-panel__readonly-label.css';
+
+import './__column/auth-panel__column.css';
+import './__select/auth-panel__select.css';
+import './__select-label/auth-panel__select-label.css';
+import './__select-label-value/auth-panel__select-label-value.css';
+import './__sort/auth-panel__sort.css';
+
+import './__user-id/auth-panel__user-id.css';
+
+import './_theme/_dark/auth-panel_theme_dark.css';
+import './_theme/_light/auth-panel_theme_light.css';
+
+import './_logged-in/auth-panel_logged-in.css';
+
export { default } from './auth-panel';
-
-import './auth-panel.scss';
-
-import './__readonly-label/auth-panel__readonly-label.scss';
-
-import './__column/auth-panel__column.scss';
-import './__select/auth-panel__select.scss';
-import './__select-label/auth-panel__select-label.scss';
-import './__select-label-value/auth-panel__select-label-value.scss';
-import './__sort/auth-panel__sort.scss';
-
-import './__user-id/auth-panel__user-id.scss';
-
-import './_theme/_dark/auth-panel_theme_dark.scss';
-import './_theme/_light/auth-panel_theme_light.scss';
-
-import './_logged-in/auth-panel_logged-in.scss';
diff --git a/frontend/app/components/auth/__anonymous-login-form/auth__anonymous-login-form.scss b/frontend/app/components/auth/__anonymous-login-form/auth__anonymous-login-form.css
similarity index 100%
rename from frontend/app/components/auth/__anonymous-login-form/auth__anonymous-login-form.scss
rename to frontend/app/components/auth/__anonymous-login-form/auth__anonymous-login-form.css
diff --git a/frontend/app/components/auth/__anonymous-login-form/auth__anonymous-login-form.tsx b/frontend/app/components/auth/__anonymous-login-form/auth__anonymous-login-form.tsx
index 77ab38ba..3473985e 100644
--- a/frontend/app/components/auth/__anonymous-login-form/auth__anonymous-login-form.tsx
+++ b/frontend/app/components/auth/__anonymous-login-form/auth__anonymous-login-form.tsx
@@ -1,11 +1,10 @@
-/** @jsx createElement */
-import { createElement, Component, createRef } from 'preact';
+import { h, Component, createRef } from 'preact';
import b from 'bem-react-helper';
import { IntlShape, defineMessages, FormattedMessage } from 'react-intl';
-import { Theme } from '@app/common/types';
+import { Theme } from 'common/types';
-import { Input } from '@app/components/input';
-import { Button } from '@app/components/button';
+import { Input } from 'components/input';
+import { Button } from 'components/button';
import { validateUserName } from '../validateUserName';
@@ -89,7 +88,7 @@ export class AnonymousLoginForm extends Component {
// TODO: will be great to `b` to accept `string | undefined | (string|undefined)[]` as classname
let className = b('auth-anonymous-login-form', {}, { theme: props.theme });
if (props.className) {
- className += ' ' + b('auth-anonymous-login-form', {}, { theme: props.theme });
+ className += ` ${b('auth-anonymous-login-form', {}, { theme: props.theme })}`;
}
const usernameInvalidReason = this.getUsernameInvalidReason();
diff --git a/frontend/app/components/auth/__anonymous-login-form/index.ts b/frontend/app/components/auth/__anonymous-login-form/index.ts
index 34b6aa60..4b24d0b8 100644
--- a/frontend/app/components/auth/__anonymous-login-form/index.ts
+++ b/frontend/app/components/auth/__anonymous-login-form/index.ts
@@ -1,3 +1,3 @@
-export { AnonymousLoginForm } from './auth__anonymous-login-form';
+import './auth__anonymous-login-form.css';
-import './auth__anonymous-login-form.scss';
+export { AnonymousLoginForm } from './auth__anonymous-login-form';
diff --git a/frontend/app/components/auth/__email-login-form/auth__email-login-form.scss b/frontend/app/components/auth/__email-login-form/auth__email-login-form.css
similarity index 100%
rename from frontend/app/components/auth/__email-login-form/auth__email-login-form.scss
rename to frontend/app/components/auth/__email-login-form/auth__email-login-form.css
diff --git a/frontend/app/components/auth/__email-login-form/auth__email-login-form.test.tsx b/frontend/app/components/auth/__email-login-form/auth__email-login-form.test.tsx
index 6b24b46b..8f7595b0 100644
--- a/frontend/app/components/auth/__email-login-form/auth__email-login-form.test.tsx
+++ b/frontend/app/components/auth/__email-login-form/auth__email-login-form.test.tsx
@@ -1,16 +1,16 @@
-/** @jsx createElement */
-import { createElement } from 'preact';
+import { h } from 'preact';
import { mount, ReactWrapper } from 'enzyme';
import { EmailLoginFormConnected as EmailLoginForm, Props, State } from './auth__email-login-form';
-import { User } from '@app/common/types';
-import { sleep } from '@app/utils/sleep';
-import { validToken } from '@app/testUtils/mocks/jwt';
-import { sendEmailVerificationRequest } from '@app/common/api';
import { IntlProvider } from 'react-intl';
-import enMessages from '../../../locales/en.json';
-import { LS_EMAIL_KEY } from '@app/common/constants';
-jest.mock('@app/utils/jwt', () => ({
+import { validToken } from '__stubs__/jwt';
+import { LS_EMAIL_KEY } from 'common/constants';
+import { User } from 'common/types';
+import { sleep } from 'utils/sleep';
+import { sendEmailVerificationRequest } from 'common/api';
+import enMessages from 'locales/en.json';
+
+jest.mock('utils/jwt', () => ({
isJwtExpired: jest
.fn()
.mockImplementationOnce(() => true)
@@ -18,7 +18,7 @@ jest.mock('@app/utils/jwt', () => ({
.mockImplementationOnce(() => true),
}));
-jest.mock('@app/common/api');
+jest.mock('common/api');
function simulateInput(input: ReactWrapper, value: string) {
input.getDOMNode().value = value;
diff --git a/frontend/app/components/auth/__email-login-form/auth__email-login-form.tsx b/frontend/app/components/auth/__email-login-form/auth__email-login-form.tsx
index 9d004b1f..d8c0c7f2 100644
--- a/frontend/app/components/auth/__email-login-form/auth__email-login-form.tsx
+++ b/frontend/app/components/auth/__email-login-form/auth__email-login-form.tsx
@@ -1,22 +1,20 @@
-/** @jsx createElement */
-import { createElement, Component, createRef } from 'preact';
+import { h, Component, createRef } from 'preact';
import { forwardRef } from 'preact/compat';
import b from 'bem-react-helper';
-import { Theme, User } from '@app/common/types';
-import { sendEmailVerificationRequest } from '@app/common/api';
-import { extractErrorMessageFromResponse } from '@app/utils/errorUtils';
-import { getHandleClickProps } from '@app/common/accessibility';
-import { sleep } from '@app/utils/sleep';
-import TextareaAutosize from '@app/components/comment-form/textarea-autosize';
-import { Input } from '@app/components/input';
-import { Button } from '@app/components/button';
-import { isJwtExpired } from '@app/utils/jwt';
+import { Theme, User } from 'common/types';
+import { LS_EMAIL_KEY } from 'common/constants';
+import { sendEmailVerificationRequest } from 'common/api';
+import { extractErrorMessageFromResponse } from 'utils/errorUtils';
+import { getHandleClickProps } from 'common/accessibility';
+import { sleep } from 'utils/sleep';
+import TextareaAutosize from 'components/comment-form/textarea-autosize';
+import { Input } from 'components/input';
+import { Button } from 'components/button';
+import { isJwtExpired } from 'utils/jwt';
import { defineMessages, IntlShape, useIntl, FormattedMessage } from 'react-intl';
import { validateUserName } from '../validateUserName';
-
import { messages as loginForm } from '../__anonymous-login-form/auth__anonymous-login-form';
-import { LS_EMAIL_KEY } from '@app/common/constants';
interface OwnProps {
onSignIn(token: string): Promise;
@@ -199,7 +197,7 @@ export class EmailLoginForm extends Component {
// TODO: will be great to `b` to accept `string | undefined | (string|undefined)[]` as classname
let className = b('auth-email-login-form', {}, { theme: props.theme });
if (props.className) {
- className += ' ' + b('auth-email-login-form', {}, { theme: props.theme });
+ className += ` ${b('auth-email-login-form', {}, { theme: props.theme })}`;
}
const form1InvalidReason = this.getForm1InvalidReason();
@@ -208,7 +206,7 @@ export class EmailLoginForm extends Component {
return (