mirror of
https://github.com/SomboChea/ui
synced 2026-01-17 08:35:47 +07:00
initial commit
This commit is contained in:
3
src/utils/__setPublicPath__.js
Normal file
3
src/utils/__setPublicPath__.js
Normal file
@@ -0,0 +1,3 @@
|
||||
if (!__DEBUG__) {
|
||||
__webpack_public_path__ = window.VERDACCIO_API_URL.replace(/\/verdaccio\/$/, '/static/'); // eslint-disable-line
|
||||
}
|
||||
62
src/utils/api.js
Normal file
62
src/utils/api.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import storage from './storage';
|
||||
|
||||
class API {
|
||||
request(url, method = 'GET', options = {}) {
|
||||
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))) {
|
||||
url = window.VERDACCIO_API_URL + url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles response according to content type
|
||||
* @param {object} response
|
||||
* @returns {promise}
|
||||
*/
|
||||
function handleResponseType(response) {
|
||||
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 new Promise((resolve, reject) => {
|
||||
fetch(url, {
|
||||
method,
|
||||
credentials: 'same-origin',
|
||||
...options,
|
||||
})
|
||||
.then(handleResponseType)
|
||||
.then(([responseOk, body]) => {
|
||||
if (responseOk) {
|
||||
resolve(body);
|
||||
} else {
|
||||
reject(body);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new API();
|
||||
28
src/utils/asyncComponent.js
Normal file
28
src/utils/asyncComponent.js
Normal file
@@ -0,0 +1,28 @@
|
||||
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});
|
||||
});
|
||||
}
|
||||
}
|
||||
render() {
|
||||
const {Component} = this.state;
|
||||
if (Component) {
|
||||
return <Component {...this.props} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
12
src/utils/calls.js
Normal file
12
src/utils/calls.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import API from './api';
|
||||
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
export async function callDetailPage(packageName) {
|
||||
const readMe = await API.request(`package/readme/${packageName}`, 'GET');
|
||||
const packageMeta = await API.request(`sidebar/${packageName}`, 'GET');
|
||||
|
||||
return {readMe, packageMeta};
|
||||
}
|
||||
33
src/utils/cli-utils.js
Normal file
33
src/utils/cli-utils.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export const copyToClipBoardUtility = (str: string) => (event: SyntheticEvent<HTMLElement>) => {
|
||||
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();
|
||||
range.selectNodeContents(node);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
document.execCommand('copy');
|
||||
// $FlowFixMe
|
||||
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.js
Normal file
9
src/utils/constants.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export const TEXT = {
|
||||
CLIPBOARD_COPY: 'Copy to Clipboard',
|
||||
};
|
||||
|
||||
export const NODE_MANAGER = {
|
||||
npm: 'npm',
|
||||
yarn: 'yarn',
|
||||
pnpm: 'pnpm',
|
||||
};
|
||||
76
src/utils/login.js
Normal file
76
src/utils/login.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import isString from "lodash/isString";
|
||||
import isNumber from "lodash/isNumber";
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
import { Base64 } from "js-base64";
|
||||
import API from "./api";
|
||||
|
||||
const HEADERS = {
|
||||
JSON: "application/json",
|
||||
JSON_CHARSET: "application/json; charset=utf-8",
|
||||
OCTET_STREAM: "application/octet-stream; charset=utf-8",
|
||||
TEXT_CHARSET: "text/plain; charset=utf-8",
|
||||
GZIP: "gzip"
|
||||
};
|
||||
|
||||
export function isTokenExpire(token) {
|
||||
if (!isString(token)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let [, payload] = 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 async function makeLogin(username, password) {
|
||||
// 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 = await API.request("login", "POST", {
|
||||
body: JSON.stringify({ username, password }),
|
||||
headers: {
|
||||
Accept: HEADERS.JSON,
|
||||
"Content-Type": HEADERS.JSON
|
||||
}
|
||||
});
|
||||
const result = {
|
||||
username: response.username,
|
||||
token: response.token
|
||||
};
|
||||
return result;
|
||||
} catch (e) {
|
||||
const error = {
|
||||
title: "Unable to login",
|
||||
type: "error",
|
||||
description: e.error
|
||||
};
|
||||
return { error };
|
||||
}
|
||||
}
|
||||
118
src/utils/package.js
Normal file
118
src/utils/package.js
Normal file
@@ -0,0 +1,118 @@
|
||||
import isString from 'lodash/isString';
|
||||
import isObject from 'lodash/isObject';
|
||||
import format from 'date-fns/format';
|
||||
import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
|
||||
|
||||
export const TIMEFORMAT = 'DD.MM.YYYY, HH:mm:ss';
|
||||
export const DEFAULT_USER = 'Anonymous';
|
||||
|
||||
/**
|
||||
* Formats license field for webui.
|
||||
* @see https://docs.npmjs.com/files/package.json#license
|
||||
*/
|
||||
export function formatLicense(license) {
|
||||
if (isString(license)) {
|
||||
return license;
|
||||
}
|
||||
|
||||
if (isObject(license) && license.type) {
|
||||
return license.type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats repository field for webui.
|
||||
* @see https://docs.npmjs.com/files/package.json#repository
|
||||
*/
|
||||
export function formatRepository(repository) {
|
||||
if (isString(repository)) {
|
||||
return repository;
|
||||
}
|
||||
|
||||
if (isObject(repository) && repository.url) {
|
||||
return repository.url;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Formats author field for webui.
|
||||
* @see https://docs.npmjs.com/files/package.json#author
|
||||
*/
|
||||
export function formatAuthor(author) {
|
||||
let authorDetails = {
|
||||
name: DEFAULT_USER,
|
||||
email: '',
|
||||
avatar: '',
|
||||
};
|
||||
|
||||
if (isString(author)) {
|
||||
authorDetails = {
|
||||
...authorDetails,
|
||||
name: author ? author : authorDetails.name,
|
||||
};
|
||||
}
|
||||
|
||||
if (isObject(author)) {
|
||||
authorDetails = {
|
||||
...authorDetails,
|
||||
name: author.name ? author.name : authorDetails.name,
|
||||
email: author.email ? author.email : authorDetails.email,
|
||||
avatar: author.avatar ? author.avatar : authorDetails.avatar,
|
||||
};
|
||||
}
|
||||
|
||||
return authorDetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* For <LastSync /> component
|
||||
* @param {array} uplinks
|
||||
*/
|
||||
export function getLastUpdatedPackageTime(uplinks = {}) {
|
||||
let lastUpdate = 0;
|
||||
Object.keys(uplinks).forEach((upLinkName) => {
|
||||
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 = {}) {
|
||||
const recent = Object.keys(time).map((version) => ({
|
||||
version,
|
||||
time: formatDate(time[version]),
|
||||
}));
|
||||
return recent.slice(recent.length - 3, recent.length).reverse();
|
||||
}
|
||||
|
||||
|
||||
export function formatDate(lastUpdate) {
|
||||
return format(new Date(lastUpdate), TIMEFORMAT);
|
||||
}
|
||||
|
||||
export function formatDateDistance(lastUpdate) {
|
||||
return distanceInWordsToNow(new Date(lastUpdate));
|
||||
}
|
||||
|
||||
export function getRouterPackageName(match) {
|
||||
const packageName = match.params.package;
|
||||
const scope = match.params.scope;
|
||||
if (scope) {
|
||||
return `@${scope}/${packageName}`;
|
||||
}
|
||||
|
||||
return packageName;
|
||||
}
|
||||
12
src/utils/sec-utils.js
Normal file
12
src/utils/sec-utils.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
// $FlowFixMe
|
||||
import parseXSS from 'xss';
|
||||
|
||||
export function preventXSS(text: string) {
|
||||
const encodedText = parseXSS(text);
|
||||
|
||||
return encodedText;
|
||||
}
|
||||
12
src/utils/storage.js
Normal file
12
src/utils/storage.js
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;
|
||||
34
src/utils/styles/colors.js
Normal file
34
src/utils/styles/colors.js
Normal file
@@ -0,0 +1,34 @@
|
||||
// Verdaccio
|
||||
// -------------------------
|
||||
|
||||
// Main colors
|
||||
// -------------------------
|
||||
const colors = {
|
||||
black: '#000',
|
||||
white: '#fff',
|
||||
red: '#d32f2f',
|
||||
grey: '#808080',
|
||||
greySuperLight: '#f5f5f5',
|
||||
greyLight: '#d3d3d3',
|
||||
greyDark: '#a9a9a9',
|
||||
greyChateau: '#95989a',
|
||||
greyGainsboro: '#e3e3e3',
|
||||
greyAthens: '#d3dddd',
|
||||
|
||||
eclipse: '#3c3c3c',
|
||||
paleNavy: '#e4e8f1',
|
||||
saltpan: '#f7f8f6',
|
||||
snow: '#f9f9f9',
|
||||
|
||||
nobel01: '#999999',
|
||||
nobel02: '#9f9f9f',
|
||||
|
||||
// Main colors
|
||||
// -------------------------
|
||||
|
||||
primary: '#4b5e40',
|
||||
secondary: '#20232a',
|
||||
|
||||
};
|
||||
|
||||
export default colors;
|
||||
27
src/utils/styles/global.js
Normal file
27
src/utils/styles/global.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { injectGlobal } from 'emotion';
|
||||
import { fontFamilyBase } from './fonts';
|
||||
import { fontSize, fontWeight } from './sizes';
|
||||
import { textColor } from './colors';
|
||||
|
||||
export default injectGlobal`
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: ${fontFamilyBase};
|
||||
font-size: ${fontSize.base};
|
||||
color: ${textColor};
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: ${fontWeight.semiBold};
|
||||
}
|
||||
`;
|
||||
29
src/utils/styles/media.js
Normal file
29
src/utils/styles/media.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { css } from 'emotion';
|
||||
|
||||
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;
|
||||
45
src/utils/styles/mixings.js
Normal file
45
src/utils/styles/mixings.js
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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: Array<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;
|
||||
}
|
||||
23
src/utils/styles/sizes.js
Normal file
23
src/utils/styles/sizes.js
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
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.js
Normal file
6
src/utils/styles/spacings.js
Normal file
@@ -0,0 +1,6 @@
|
||||
// Spacings
|
||||
// -------------------------
|
||||
|
||||
export const spacings = {
|
||||
lg: '30px',
|
||||
};
|
||||
12
src/utils/url.js
Normal file
12
src/utils/url.js
Normal file
@@ -0,0 +1,12 @@
|
||||
export function getRegistryURL() {
|
||||
// Don't add slash if it's not a sub directory
|
||||
return `${location.origin}${location.pathname === '/' ? '' : location.pathname}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specified package detail page url
|
||||
* @param {string} packageName
|
||||
*/
|
||||
export function getDetailPageURL(packageName) {
|
||||
return `${getRegistryURL()}/-/web/version/${packageName}`;
|
||||
}
|
||||
3
src/utils/windows.js
Normal file
3
src/utils/windows.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export function goToVerdaccioWebsite() {
|
||||
window.open('https://www.verdaccio.org/', '_blank');
|
||||
}
|
||||
Reference in New Issue
Block a user