mirror of
https://github.com/SomboChea/ui
synced 2026-01-17 08:35:47 +07:00
feat: migrating flow to typescript (#47)
This PR convert the code base to Typescript, the changes are the following: - migrate code base to Typescript (3.4.x) - enable `eslint` and `@typescript-eslint/eslint-plugin` (warnings still need to be addressed in future pull request - update relevant dependencies for this PR (linting, etc) - enable `bundlezise` (it was disabled for some reason) * refactor: refactoring to typescript * refactor: migrating to typescript * refactor: applied feedbacks * fix: fixed conflicts * refactored: changed registry * refactor: updated registry & removed unnecessary lib * fix: fixed registry ur * fix: fixed page load * refactor: refactored footer wip * refactor: converting to ts..wip * refactor: converting to ts. wip * refactor: converting to ts. wip * refactor: converting to ts * refactor: converting to ts * fix: fixed load errors * refactor: converted files to ts * refactor: removed flow from tests * fix: removed transpiled files * refactor: added ts-ignore * fix: fixed errors * fix: fixed types * fix: fixing jest import -.- * fix: fixing lint errors * fix: fixing lint errors * fix: fixed lint errors * refactor: removed unnecessary tsconfig's config * fix: fixing errors * fix: fixed warning * fix: fixed test * refactor: wip * refactor: wip * refactor: wip * fix: fixing tests: wip * wip * wip * fix: fixed search test * wip * fix: fixing lint errors * fix: re-added stylelint * refactor: updated stylelint script * fix: fixed: 'styles.js' were found. * fix: fixed Search tests * chore: enable eslint eslint needs expecitely to know which file has to lint, by default is JS, in this case we need also ts,tsx files eslint . --ext .js,.ts * chore: vcode eslint settings * chore: restore eslint previous conf * chore: clean jest config * chore: fix eslint warnings * chore: eslint errors cleared chore: clean warnings chore: remove github actions test phases chore: remove dupe rule * chore: update handler name * chore: restore logo from img to url css prop - loading images with css is more performant than using img html tags, switching this might be a breaking change - restore no-empty-source seems the linting do not accept false - update snapshots - remove @material-ui/styles * chore: update stylelint linting * chore: update stylelint linting * chore: fix a mistake on move tabs to a function * chore: eanble bundlezie * chore: use default_executor in circleci * chore: update readme
This commit is contained in:
committed by
Juan Picado @jotadeveloper
parent
7d1764458b
commit
6b5d0b7e2e
5
src/utils/.eslintrc
Normal file
5
src/utils/.eslintrc
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-invalid-this": 0
|
||||
}
|
||||
}
|
||||
5
src/utils/__setPublicPath__.ts
Normal file
5
src/utils/__setPublicPath__.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// @ts-ignore
|
||||
if (!__DEBUG__) {
|
||||
// @ts-ignore
|
||||
__webpack_public_path__ = window.VERDACCIO_API_URL.replace(/\/verdaccio\/$/, '/static/') // eslint-disable-line
|
||||
}
|
||||
67
src/utils/api.ts
Normal file
67
src/utils/api.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import storage from './storage';
|
||||
import '../../types';
|
||||
|
||||
/**
|
||||
* Handles response according to content type
|
||||
* @param {object} response
|
||||
* @returns {promise}
|
||||
*/
|
||||
function handleResponseType(response): Promise<any> {
|
||||
if (response.headers) {
|
||||
const contentType = response.headers.get('Content-Type');
|
||||
if (contentType.includes('application/pdf')) {
|
||||
return Promise.all([response.ok, response.blob()]);
|
||||
}
|
||||
if (contentType.includes('application/json')) {
|
||||
return Promise.all([response.ok, response.json()]);
|
||||
}
|
||||
// it includes all text types
|
||||
if (contentType.includes('text/')) {
|
||||
return Promise.all([response.ok, response.text()]);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
class API {
|
||||
public request(url: string, method = 'GET', options: any = {}): Promise<any> {
|
||||
if (!window.VERDACCIO_API_URL) {
|
||||
throw new Error('VERDACCIO_API_URL is not defined!');
|
||||
}
|
||||
|
||||
const token = storage.getItem('token');
|
||||
if (token) {
|
||||
if (!options.headers) options.headers = {};
|
||||
|
||||
options.headers.authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
if (!['http://', 'https://', '//'].some(prefix => url.startsWith(prefix))) {
|
||||
// @ts-ignore
|
||||
url = window.VERDACCIO_API_URL + url;
|
||||
}
|
||||
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
fetch(url, {
|
||||
method,
|
||||
credentials: 'same-origin',
|
||||
...options,
|
||||
})
|
||||
// @ts-ignore
|
||||
.then(handleResponseType)
|
||||
.then(([responseOk, body]) => {
|
||||
if (responseOk) {
|
||||
resolve(body);
|
||||
} else {
|
||||
reject(body);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new API();
|
||||
33
src/utils/asyncComponent.tsx
Normal file
33
src/utils/asyncComponent.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
|
||||
export function asyncComponent(getComponent) {
|
||||
return class AsyncComponent extends React.Component {
|
||||
static Component = null;
|
||||
state = { Component: AsyncComponent.Component };
|
||||
|
||||
componentDidMount() {
|
||||
const { Component } = this.state;
|
||||
if (!Component) {
|
||||
getComponent()
|
||||
.then(({ default: Component }) => {
|
||||
AsyncComponent.Component = Component;
|
||||
/* eslint react/no-did-mount-set-state:0 */
|
||||
this.setState({ Component });
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
render() {
|
||||
const { Component } = this.state;
|
||||
if (Component) {
|
||||
// eslint-disable-next-line verdaccio/jsx-spread
|
||||
// @ts-ignore
|
||||
return <Component {...this.props} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
13
src/utils/calls.ts
Normal file
13
src/utils/calls.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import API from './api';
|
||||
|
||||
export interface DetailPage {
|
||||
readMe: any;
|
||||
packageMeta: any;
|
||||
}
|
||||
|
||||
export async function callDetailPage(packageName): Promise<DetailPage> {
|
||||
const readMe = await API.request(`package/readme/${packageName}`, 'GET');
|
||||
const packageMeta = await API.request(`sidebar/${packageName}`, 'GET');
|
||||
|
||||
return { readMe, packageMeta };
|
||||
}
|
||||
31
src/utils/cli-utils.ts
Normal file
31
src/utils/cli-utils.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { SyntheticEvent } from 'react';
|
||||
|
||||
export const copyToClipBoardUtility = (str: string): any => (event: SyntheticEvent<HTMLElement>): void => {
|
||||
event.preventDefault();
|
||||
|
||||
const node = document.createElement('div');
|
||||
node.innerText = str;
|
||||
if (document.body) {
|
||||
document.body.appendChild(node);
|
||||
|
||||
const range = document.createRange();
|
||||
const selection = window.getSelection() as Selection;
|
||||
range.selectNodeContents(node);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(node);
|
||||
}
|
||||
};
|
||||
|
||||
export function getCLISetConfigRegistry(command: string, scope: string, registryUrl: string): string {
|
||||
return `${command} ${scope}registry ${registryUrl}`;
|
||||
}
|
||||
|
||||
export function getCLISetRegistry(command: string, registryUrl: string): string {
|
||||
return `${command} --registry ${registryUrl}`;
|
||||
}
|
||||
|
||||
export function getCLIChangePassword(command: string, registryUrl: string): string {
|
||||
return `${command} profile set password --registry ${registryUrl}`;
|
||||
}
|
||||
9
src/utils/constants.ts
Normal file
9
src/utils/constants.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export const TEXT = {
|
||||
CLIPBOARD_COPY: 'Copy to Clipboard',
|
||||
};
|
||||
|
||||
export const NODE_MANAGER = {
|
||||
npm: 'npm',
|
||||
yarn: 'yarn',
|
||||
pnpm: 'pnpm',
|
||||
};
|
||||
3
src/utils/file-size.ts
Normal file
3
src/utils/file-size.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function fileSizeSI(a?: any, b?: any, c?: any, d?: any, e?: any) {
|
||||
return ((b = Math), (c = b.log), (d = 1e3), (e = (c(a) / c(d)) | 0), a / b.pow(d, e)).toFixed(2) + ' ' + (e ? 'kMGTPEZY'[--e] + 'B' : 'Bytes');
|
||||
}
|
||||
90
src/utils/login.test.ts
Normal file
90
src/utils/login.test.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { isTokenExpire, makeLogin } from './login';
|
||||
|
||||
import { generateTokenWithTimeRange, generateTokenWithExpirationAsString, generateTokenWithOutExpiration } from '../../jest/unit/components/__mocks__/token';
|
||||
/* eslint-disable no-console */
|
||||
console.error = jest.fn();
|
||||
|
||||
jest.mock('./api', () => ({
|
||||
request: require('../../jest/unit/components/__mocks__/api').default.request,
|
||||
}));
|
||||
|
||||
describe('isTokenExpire', (): void => {
|
||||
test('isTokenExpire - token is not present', () => {
|
||||
expect(isTokenExpire()).toBeTruthy();
|
||||
});
|
||||
|
||||
test('isTokenExpire - token is not a valid payload', (): void => {
|
||||
expect(isTokenExpire('not_a_valid_token')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('isTokenExpire - token should not expire in 24 hrs range', (): void => {
|
||||
const token = generateTokenWithTimeRange(24);
|
||||
expect(isTokenExpire(token)).toBeFalsy();
|
||||
});
|
||||
|
||||
test('isTokenExpire - token should expire for current time', (): void => {
|
||||
const token = generateTokenWithTimeRange();
|
||||
expect(isTokenExpire(token)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('isTokenExpire - token expiration is not available', (): void => {
|
||||
const token = generateTokenWithOutExpiration();
|
||||
expect(isTokenExpire(token)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('isTokenExpire - token is not a valid json token', (): void => {
|
||||
const token = generateTokenWithExpirationAsString();
|
||||
const result = ['Invalid token:', new SyntaxError('Unexpected token o in JSON at position 1'), 'xxxxxx.W29iamVjdCBPYmplY3Rd.xxxxxx'];
|
||||
expect(isTokenExpire(token)).toBeTruthy();
|
||||
expect(console.error).toHaveBeenCalledWith(...result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('makeLogin', (): void => {
|
||||
test('makeLogin - should give error for blank username and password', async (): Promise<void> => {
|
||||
const result = {
|
||||
error: {
|
||||
description: "Username or password can't be empty!",
|
||||
title: 'Unable to login',
|
||||
type: 'error',
|
||||
},
|
||||
};
|
||||
const login = await makeLogin();
|
||||
expect(login).toEqual(result);
|
||||
});
|
||||
|
||||
test('makeLogin - should login successfully', async (): Promise<void> => {
|
||||
const { username, password } = { username: 'sam', password: '1234' };
|
||||
const result = { token: 'TEST_TOKEN', username: 'sam' };
|
||||
const login = await makeLogin(username, password);
|
||||
expect(login).toEqual(result);
|
||||
});
|
||||
|
||||
test('makeLogin - login should failed with 401', async () => {
|
||||
const result = {
|
||||
error: {
|
||||
description: 'bad username/password, access denied',
|
||||
title: 'Unable to login',
|
||||
type: 'error',
|
||||
},
|
||||
};
|
||||
|
||||
const { username, password } = { username: 'sam', password: '123456' };
|
||||
const login = await makeLogin(username, password);
|
||||
expect(login).toEqual(result);
|
||||
});
|
||||
|
||||
test('makeLogin - login should failed with when no data is sent', async () => {
|
||||
const result = {
|
||||
error: {
|
||||
title: 'Unable to login',
|
||||
type: 'error',
|
||||
description: "Username or password can't be empty!",
|
||||
},
|
||||
};
|
||||
|
||||
const { username, password } = { username: '', password: '' };
|
||||
const login = await makeLogin(username, password);
|
||||
expect(login).toEqual(result);
|
||||
});
|
||||
});
|
||||
81
src/utils/login.ts
Normal file
81
src/utils/login.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import isString from 'lodash/isString';
|
||||
import isNumber from 'lodash/isNumber';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { Base64 } from 'js-base64';
|
||||
import API from './api';
|
||||
import { HEADERS } from '../../lib/constants';
|
||||
|
||||
export function isTokenExpire(token?: any) {
|
||||
if (!isString(token)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let [, payload]: any = token.split('.');
|
||||
|
||||
if (!payload) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
payload = JSON.parse(Base64.decode(payload));
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line
|
||||
console.error('Invalid token:', error, token);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!payload.exp || !isNumber(payload.exp)) {
|
||||
return true;
|
||||
}
|
||||
// Report as expire before (real expire time - 30s)
|
||||
const jsTimestamp = payload.exp * 1000 - 30000;
|
||||
const expired = Date.now() >= jsTimestamp;
|
||||
|
||||
return expired;
|
||||
}
|
||||
|
||||
export interface LoginBody {
|
||||
username?: string;
|
||||
token?: string;
|
||||
error?: LoginError;
|
||||
}
|
||||
|
||||
export interface LoginError {
|
||||
title: string;
|
||||
type: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export async function makeLogin(username?: string, password?: string): Promise<LoginBody> {
|
||||
// checks isEmpty
|
||||
if (isEmpty(username) || isEmpty(password)) {
|
||||
const error = {
|
||||
title: 'Unable to login',
|
||||
type: 'error',
|
||||
description: "Username or password can't be empty!",
|
||||
};
|
||||
return { error };
|
||||
}
|
||||
|
||||
try {
|
||||
const response: LoginBody = await API.request('login', 'POST', {
|
||||
body: JSON.stringify({ username, password }),
|
||||
headers: {
|
||||
Accept: HEADERS.JSON,
|
||||
'Content-Type': HEADERS.JSON,
|
||||
},
|
||||
});
|
||||
const result: LoginBody = {
|
||||
username: response.username,
|
||||
token: response.token,
|
||||
};
|
||||
return result;
|
||||
} catch (e) {
|
||||
const error = {
|
||||
title: 'Unable to login',
|
||||
type: 'error',
|
||||
description: e.error,
|
||||
};
|
||||
return { error };
|
||||
}
|
||||
}
|
||||
94
src/utils/package.test.ts
Normal file
94
src/utils/package.test.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { formatLicense, formatRepository, formatDate, formatDateDistance, getLastUpdatedPackageTime, getRecentReleases } from './package';
|
||||
|
||||
import { packageMeta } from '../../jest/unit/components/store/packageMeta';
|
||||
|
||||
describe('formatLicense', (): void => {
|
||||
test('should check license field different values', (): void => {
|
||||
expect(formatLicense('MIT')).toEqual('MIT');
|
||||
});
|
||||
|
||||
test('should check license field for object value', (): void => {
|
||||
const license = { type: 'ISC', url: 'https://opensource.org/licenses/ISC' };
|
||||
expect(formatLicense(license)).toEqual('ISC');
|
||||
});
|
||||
|
||||
test('should check license field for other value', (): void => {
|
||||
expect(formatLicense(null)).toBeNull();
|
||||
expect(formatLicense({})).toBeNull();
|
||||
expect(formatLicense([])).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatRepository', (): void => {
|
||||
test('should check repository field different values', (): void => {
|
||||
const repository = 'https://github.com/verdaccio/verdaccio';
|
||||
expect(formatRepository(repository)).toEqual(repository);
|
||||
});
|
||||
|
||||
test('should check repository field for object value', (): void => {
|
||||
const license = {
|
||||
type: 'git',
|
||||
url: 'https://github.com/verdaccio/verdaccio',
|
||||
};
|
||||
expect(formatRepository(license)).toEqual(license.url);
|
||||
});
|
||||
|
||||
test('should check repository field for other value', (): void => {
|
||||
expect(formatRepository(null)).toBeNull();
|
||||
expect(formatRepository({})).toBeNull();
|
||||
expect(formatRepository([])).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatDate', (): void => {
|
||||
test('should format the date', (): void => {
|
||||
const date = 1532211072138;
|
||||
expect(formatDate(date)).toEqual('21.07.2018, 22:11:12');
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatDateDistance', (): void => {
|
||||
test('should calculate the distance', (): void => {
|
||||
// const dateAboutTwoMonthsAgo = () => {
|
||||
// const date = new Date();
|
||||
// date.setMonth(date.getMonth() - 1);
|
||||
// date.setDate(date.getDay() - 20);
|
||||
// return date;
|
||||
// };
|
||||
const dateTwoMonthsAgo = (): Date => {
|
||||
const date = new Date();
|
||||
date.setMonth(date.getMonth() - 2);
|
||||
return date;
|
||||
};
|
||||
// const date1 = dateAboutTwoMonthsAgo();
|
||||
const date2 = dateTwoMonthsAgo();
|
||||
// FIXME: we need to review this expect, fails every x time.
|
||||
// expect(formatDateDistance(date1)).toEqual('about 2 months');
|
||||
expect(formatDateDistance(date2)).toEqual('2 months');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLastUpdatedPackageTime', (): void => {
|
||||
test('should get the last update time', (): void => {
|
||||
const lastUpdated = packageMeta._uplinks;
|
||||
expect(getLastUpdatedPackageTime(lastUpdated)).toEqual('22.07.2018, 22:11:12');
|
||||
});
|
||||
|
||||
test('should get the last update time for blank uplink', (): void => {
|
||||
const lastUpdated = {};
|
||||
expect(getLastUpdatedPackageTime(lastUpdated)).toEqual('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRecentReleases', (): void => {
|
||||
test('should get the recent releases', (): void => {
|
||||
const { time } = packageMeta;
|
||||
const result = [
|
||||
{ time: '14.12.2017, 15:43:27', version: '2.7.1' },
|
||||
{ time: '05.12.2017, 23:25:06', version: '2.7.0' },
|
||||
{ time: '08.11.2017, 22:47:16', version: '2.6.6' },
|
||||
];
|
||||
expect(getRecentReleases(time)).toEqual(result);
|
||||
expect(getRecentReleases()).toEqual([]);
|
||||
});
|
||||
});
|
||||
102
src/utils/package.ts
Normal file
102
src/utils/package.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { UpLinks } from '@verdaccio/types';
|
||||
import isString from 'lodash/isString';
|
||||
import format from 'date-fns/format';
|
||||
import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
|
||||
import { isObject } from 'util';
|
||||
|
||||
export const TIMEFORMAT = 'DD.MM.YYYY, HH:mm:ss';
|
||||
|
||||
export interface License {
|
||||
type: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats license field for webui.
|
||||
* @see https://docs.npmjs.com/files/package.json#license
|
||||
*/
|
||||
// License should use type License defined above, but conflicts with the unit test that provide array or empty object
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
export function formatLicense(license: any): string | null {
|
||||
if (isString(license)) {
|
||||
return license;
|
||||
}
|
||||
|
||||
if (license && isObject(license) && license.type) {
|
||||
return license.type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export interface Repository {
|
||||
type: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats repository field for webui.
|
||||
* @see https://docs.npmjs.com/files/package.json#repository
|
||||
*/
|
||||
|
||||
// Repository should use type Repository defined above, but conflicts with the unit test that provide array or empty object
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
export function formatRepository(repository: any): string | null {
|
||||
if (isString(repository)) {
|
||||
return repository;
|
||||
}
|
||||
|
||||
if (repository && isObject(repository) && repository.url) {
|
||||
return repository.url;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function formatDate(lastUpdate): string {
|
||||
return format(new Date(lastUpdate), TIMEFORMAT);
|
||||
}
|
||||
|
||||
export function formatDateDistance(lastUpdate): string {
|
||||
return distanceInWordsToNow(new Date(lastUpdate));
|
||||
}
|
||||
|
||||
export function getRouterPackageName(match): string {
|
||||
const packageName = match.params.package;
|
||||
const scope = match.params.scope;
|
||||
if (scope) {
|
||||
return `@${scope}/${packageName}`;
|
||||
}
|
||||
|
||||
return packageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* For <LastSync /> component
|
||||
* @param {array} uplinks
|
||||
*/
|
||||
export function getLastUpdatedPackageTime(uplinks: UpLinks = {}): string {
|
||||
let lastUpdate = 0;
|
||||
Object.keys(uplinks).forEach(function computeUplink(upLinkName): void {
|
||||
const status = uplinks[upLinkName];
|
||||
if (status.fetched > lastUpdate) {
|
||||
lastUpdate = status.fetched;
|
||||
}
|
||||
});
|
||||
|
||||
return lastUpdate ? formatDate(lastUpdate) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* For <LastSync /> component
|
||||
* @param {Object} time
|
||||
* @returns {Array} last 3 releases
|
||||
*/
|
||||
export function getRecentReleases(time = {}): unknown {
|
||||
const recent = Object.keys(time).map((version): unknown => ({
|
||||
version,
|
||||
time: formatDate(time[version]),
|
||||
}));
|
||||
|
||||
return recent.slice(recent.length - 3, recent.length).reverse();
|
||||
}
|
||||
7
src/utils/sec-utils.ts
Normal file
7
src/utils/sec-utils.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import parseXSS from 'xss';
|
||||
|
||||
export function preventXSS(text: string) {
|
||||
const encodedText = parseXSS.filterXSS(text);
|
||||
|
||||
return encodedText;
|
||||
}
|
||||
12
src/utils/storage.ts
Normal file
12
src/utils/storage.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import memoryStorage from 'localstorage-memory';
|
||||
|
||||
let storage;
|
||||
try {
|
||||
localStorage.setItem('__TEST__', '');
|
||||
localStorage.removeItem('__TEST__');
|
||||
storage = localStorage;
|
||||
} catch (err) {
|
||||
storage = memoryStorage;
|
||||
}
|
||||
|
||||
export default storage;
|
||||
38
src/utils/styles/colors.ts
Normal file
38
src/utils/styles/colors.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
// Verdaccio
|
||||
// -------------------------
|
||||
|
||||
// Main colors
|
||||
// -------------------------
|
||||
const colors = {
|
||||
black: '#000',
|
||||
white: '#fff',
|
||||
red: '#d32f2f',
|
||||
grey: '#808080',
|
||||
greySuperLight: '#f5f5f5',
|
||||
greyLight: '#d3d3d3',
|
||||
greyLight2: '#908ba1',
|
||||
greyLight3: '#f3f4f240',
|
||||
greyDark: '#a9a9a9',
|
||||
greyDark2: '#586069',
|
||||
greyChateau: '#95989a',
|
||||
greyGainsboro: '#e3e3e3',
|
||||
greyAthens: '#d3dddd',
|
||||
|
||||
eclipse: '#3c3c3c',
|
||||
paleNavy: '#e4e8f1',
|
||||
saltpan: '#f7f8f6',
|
||||
snow: '#f9f9f9',
|
||||
love: '#e25555',
|
||||
|
||||
nobel01: '#999999',
|
||||
nobel02: '#9f9f9f',
|
||||
|
||||
// Main colors
|
||||
// -------------------------
|
||||
|
||||
// @ts-ignore
|
||||
primary: window.VERDACCIO_PRIMARY_COLOR || '#4b5e40',
|
||||
secondary: '#20232a',
|
||||
};
|
||||
|
||||
export default colors;
|
||||
23
src/utils/styles/global.ts
Normal file
23
src/utils/styles/global.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { injectGlobal } from 'emotion';
|
||||
import { fontSize, fontWeight } from './sizes';
|
||||
|
||||
export default injectGlobal`
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: ${fontSize.base};
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: ${fontWeight.semiBold};
|
||||
}
|
||||
`;
|
||||
22
src/utils/styles/media.ts
Normal file
22
src/utils/styles/media.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { css } from 'emotion';
|
||||
|
||||
export const breakpoints = {
|
||||
small: 576,
|
||||
medium: 768,
|
||||
large: 1024,
|
||||
xlarge: 1275,
|
||||
};
|
||||
|
||||
const mq = Object.keys(breakpoints).reduce((accumulator, label) => {
|
||||
const prefix = typeof breakpoints[label] === 'string' ? '' : 'min-width:';
|
||||
const suffix = typeof breakpoints[label] === 'string' ? '' : 'px';
|
||||
accumulator[label] = cls =>
|
||||
css`
|
||||
@media (${prefix + breakpoints[label] + suffix}) {
|
||||
${cls};
|
||||
}
|
||||
`;
|
||||
return accumulator;
|
||||
}, {});
|
||||
|
||||
export default mq;
|
||||
40
src/utils/styles/mixings.ts
Normal file
40
src/utils/styles/mixings.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* CSS to represent truncated text with an ellipsis.
|
||||
*/
|
||||
export function ellipsis(width: string | number) {
|
||||
return {
|
||||
display: 'inline-block',
|
||||
maxWidth: width,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
wordWrap: 'normal',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand that accepts up to four values, including null to skip a value, and maps them to their respective directions.
|
||||
*/
|
||||
interface SpacingShortHand<type> {
|
||||
top?: type;
|
||||
right?: type;
|
||||
bottom?: type;
|
||||
left?: type;
|
||||
}
|
||||
|
||||
const positionMap = ['Top', 'Right', 'Bottom', 'Left'];
|
||||
|
||||
export function spacing(property: 'padding' | 'margin', ...values: SpacingShortHand<number | string>[]) {
|
||||
const [firstValue = 0, secondValue = 0, thirdValue = 0, fourthValue = 0] = values;
|
||||
const valuesWithDefaults = [firstValue, secondValue, thirdValue, fourthValue];
|
||||
let styles = {};
|
||||
for (let i = 0; i < valuesWithDefaults.length; i += 1) {
|
||||
if (valuesWithDefaults[i] || valuesWithDefaults[i] === 0) {
|
||||
styles = {
|
||||
...styles,
|
||||
[`${property}${positionMap[i]}`]: valuesWithDefaults[i],
|
||||
};
|
||||
}
|
||||
}
|
||||
return styles;
|
||||
}
|
||||
22
src/utils/styles/sizes.ts
Normal file
22
src/utils/styles/sizes.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export const fontSize = {
|
||||
xxl: '26px',
|
||||
xl: '24px',
|
||||
lg: '21px',
|
||||
md: '18px',
|
||||
base: '16px',
|
||||
sm: '14px',
|
||||
};
|
||||
|
||||
export const lineHeight = {
|
||||
xl: '30px',
|
||||
sm: '18px',
|
||||
xs: '2',
|
||||
xxs: '1.5',
|
||||
};
|
||||
|
||||
export const fontWeight = {
|
||||
light: 300,
|
||||
regular: 400,
|
||||
semiBold: 500,
|
||||
bold: 700,
|
||||
};
|
||||
6
src/utils/styles/spacings.ts
Normal file
6
src/utils/styles/spacings.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// Spacings
|
||||
// -------------------------
|
||||
|
||||
export const spacings = {
|
||||
lg: '30px',
|
||||
};
|
||||
27
src/utils/url.ts
Normal file
27
src/utils/url.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import isURLValidator from 'validator/lib/isURL';
|
||||
import isEmailValidator from 'validator/lib/isEmail';
|
||||
import '../../types';
|
||||
|
||||
export function isURL(url): boolean {
|
||||
return isURLValidator(url || '', {
|
||||
protocols: ['http', 'https', 'git+https'],
|
||||
require_protocol: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function isEmail(email): boolean {
|
||||
return isEmailValidator(email || '');
|
||||
}
|
||||
|
||||
export function getRegistryURL(): string {
|
||||
// Don't add slash if it's not a sub directory
|
||||
return `${location.origin}${location.pathname === '/' ? '' : location.pathname}`;
|
||||
}
|
||||
|
||||
export function getBaseNamePath(): string {
|
||||
return window.__VERDACCIO_BASENAME_UI_OPTIONS && window.__VERDACCIO_BASENAME_UI_OPTIONS.url_prefix;
|
||||
}
|
||||
|
||||
export function getRootPath(): string {
|
||||
return window.__VERDACCIO_BASENAME_UI_OPTIONS && window.__VERDACCIO_BASENAME_UI_OPTIONS.base;
|
||||
}
|
||||
3
src/utils/windows.ts
Normal file
3
src/utils/windows.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function goToVerdaccioWebsite(): void {
|
||||
window.open('https://verdaccio.org', '_blank');
|
||||
}
|
||||
Reference in New Issue
Block a user