initial commit

This commit is contained in:
Priscila Oliveira
2019-02-03 11:23:33 +01:00
commit e2d478d65b
163 changed files with 19925 additions and 0 deletions

View 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
View 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();

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;

View 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;

View 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
View 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;

View 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
View 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,
};

View File

@@ -0,0 +1,6 @@
// Spacings
// -------------------------
export const spacings = {
lg: '30px',
};

12
src/utils/url.js Normal file
View 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
View File

@@ -0,0 +1,3 @@
export function goToVerdaccioWebsite() {
window.open('https://www.verdaccio.org/', '_blank');
}