@@ -12,6 +12,7 @@ REDIS_URL=redis://redis:6379
|
||||
URL=http://localhost:3000
|
||||
DEPLOYMENT=self
|
||||
ENABLE_UPDATES=true
|
||||
SUBDOMAINS_ENABLED=false
|
||||
DEBUG=sql,cache,presenters,events
|
||||
|
||||
# Third party signin credentials (at least one is required)
|
||||
|
||||
@@ -32,7 +32,7 @@ const RealButton = styled.button`
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.8;
|
||||
opacity: 0.6;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as React from 'react';
|
||||
import styled, { withTheme } from 'styled-components';
|
||||
import { ExpandedIcon } from 'outline-icons';
|
||||
import Flex from 'shared/components/Flex';
|
||||
import TeamLogo from './TeamLogo';
|
||||
import TeamLogo from 'shared/components/TeamLogo';
|
||||
|
||||
type Props = {
|
||||
teamName: string,
|
||||
|
||||
@@ -36,7 +36,7 @@ class RevisionMenu extends React.Component<Props> {
|
||||
|
||||
render() {
|
||||
const { label, className, onOpen, onClose } = this.props;
|
||||
const url = `${process.env.URL}${documentHistoryUrl(
|
||||
const url = `${window.location.origin}${documentHistoryUrl(
|
||||
this.props.document,
|
||||
this.props.revision.id
|
||||
)}`;
|
||||
|
||||
@@ -25,11 +25,14 @@ class Details extends React.Component<Props> {
|
||||
form: ?HTMLFormElement;
|
||||
|
||||
@observable name: string;
|
||||
@observable subdomain: ?string;
|
||||
@observable avatarUrl: ?string;
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.auth.team) {
|
||||
this.name = this.props.auth.team.name;
|
||||
const { team } = this.props.auth;
|
||||
if (team) {
|
||||
this.name = team.name;
|
||||
this.subdomain = team.subdomain;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,23 +43,32 @@ class Details extends React.Component<Props> {
|
||||
handleSubmit = async (ev: SyntheticEvent<*>) => {
|
||||
ev.preventDefault();
|
||||
|
||||
await this.props.auth.updateTeam({
|
||||
name: this.name,
|
||||
avatarUrl: this.avatarUrl,
|
||||
});
|
||||
this.props.ui.showToast('Settings saved', 'success');
|
||||
try {
|
||||
await this.props.auth.updateTeam({
|
||||
name: this.name,
|
||||
avatarUrl: this.avatarUrl,
|
||||
subdomain: this.subdomain,
|
||||
});
|
||||
this.props.ui.showToast('Settings saved', 'success');
|
||||
} catch (err) {
|
||||
this.props.ui.showToast(err.message);
|
||||
}
|
||||
};
|
||||
|
||||
handleNameChange = (ev: SyntheticInputEvent<*>) => {
|
||||
this.name = ev.target.value;
|
||||
};
|
||||
|
||||
handleSubdomainChange = (ev: SyntheticInputEvent<*>) => {
|
||||
this.subdomain = ev.target.value.toLowerCase();
|
||||
};
|
||||
|
||||
handleAvatarUpload = (avatarUrl: string) => {
|
||||
this.avatarUrl = avatarUrl;
|
||||
};
|
||||
|
||||
handleAvatarError = (error: ?string) => {
|
||||
this.props.ui.showToast(error || 'Unable to upload new avatar');
|
||||
this.props.ui.showToast(error || 'Unable to upload new logo');
|
||||
};
|
||||
|
||||
get isValid() {
|
||||
@@ -72,18 +84,10 @@ class Details extends React.Component<Props> {
|
||||
<CenteredContent>
|
||||
<PageTitle title="Details" />
|
||||
<h1>Details</h1>
|
||||
{team.slackConnected && (
|
||||
<HelpText>
|
||||
This team is connected to a <strong>Slack</strong> team. Your
|
||||
colleagues can join by signing in with their Slack account details.
|
||||
</HelpText>
|
||||
)}
|
||||
{team.googleConnected && (
|
||||
<HelpText>
|
||||
This team is connected to a <strong>Google</strong> domain. Your
|
||||
colleagues can join by signing in with their Google account.
|
||||
</HelpText>
|
||||
)}
|
||||
<HelpText>
|
||||
These details affect the way that your Outline appears to everyone on
|
||||
the team.
|
||||
</HelpText>
|
||||
|
||||
<ProfilePicture column>
|
||||
<LabelText>Logo</LabelText>
|
||||
@@ -104,11 +108,32 @@ class Details extends React.Component<Props> {
|
||||
<form onSubmit={this.handleSubmit} ref={ref => (this.form = ref)}>
|
||||
<Input
|
||||
label="Name"
|
||||
name="name"
|
||||
value={this.name}
|
||||
onChange={this.handleNameChange}
|
||||
required
|
||||
short
|
||||
/>
|
||||
{process.env.SUBDOMAINS_ENABLED && (
|
||||
<React.Fragment>
|
||||
<Input
|
||||
label="Subdomain"
|
||||
name="subdomain"
|
||||
value={this.subdomain || ''}
|
||||
onChange={this.handleSubdomainChange}
|
||||
autocomplete="off"
|
||||
minLength={4}
|
||||
maxLength={32}
|
||||
short
|
||||
/>
|
||||
{this.subdomain && (
|
||||
<HelpText small>
|
||||
Your knowledgebase will be accessed at{' '}
|
||||
<strong>{this.subdomain}.getoutline.com</strong>
|
||||
</HelpText>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Button type="submit" disabled={isSaving || !this.isValid}>
|
||||
{isSaving ? 'Saving…' : 'Save'}
|
||||
</Button>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
import { observable, action, computed, autorun, runInAction } from 'mobx';
|
||||
import invariant from 'invariant';
|
||||
import Cookie from 'js-cookie';
|
||||
import localForage from 'localforage';
|
||||
import { client } from 'utils/ApiClient';
|
||||
import { stripSubdomain } from 'shared/utils/domains';
|
||||
import type { User, Team } from 'types';
|
||||
|
||||
const AUTH_STORE = 'AUTH_STORE';
|
||||
@@ -102,8 +102,20 @@ class AuthStore {
|
||||
this.user = null;
|
||||
this.token = null;
|
||||
|
||||
// remove authentication token itself
|
||||
Cookie.remove('accessToken', { path: '/' });
|
||||
await localForage.clear();
|
||||
|
||||
// remove session record on apex cookie
|
||||
const team = this.team;
|
||||
if (team) {
|
||||
const sessions = Cookie.getJSON('sessions') || {};
|
||||
delete sessions[team.subdomain || 'root'];
|
||||
|
||||
Cookie.set('sessions', sessions, {
|
||||
domain: stripSubdomain(window.location.hostname),
|
||||
});
|
||||
this.team = null;
|
||||
}
|
||||
|
||||
// add a timestamp to force reload from server
|
||||
window.location.href = `${BASE_URL}?done=${new Date().getTime()}`;
|
||||
@@ -119,10 +131,7 @@ class AuthStore {
|
||||
}
|
||||
this.user = data.user;
|
||||
this.team = data.team;
|
||||
|
||||
// load token from state for backwards compatability with
|
||||
// sessions created pre-google auth
|
||||
this.token = Cookie.get('accessToken') || data.token;
|
||||
this.token = Cookie.get('accessToken');
|
||||
|
||||
if (this.token) setImmediate(() => this.fetch());
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ export type Team = {
|
||||
slackConnected: boolean,
|
||||
googleConnected: boolean,
|
||||
sharing: boolean,
|
||||
subdomain?: string,
|
||||
url: string,
|
||||
};
|
||||
|
||||
export type NavigationNode = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @flow
|
||||
import _ from 'lodash';
|
||||
import { map } from 'lodash';
|
||||
import invariant from 'invariant';
|
||||
import stores from 'stores';
|
||||
|
||||
@@ -39,6 +39,8 @@ class ApiClient {
|
||||
const headers = new Headers({
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'cache-control': 'no-cache',
|
||||
pragma: 'no-cache',
|
||||
});
|
||||
if (stores.auth.authenticated) {
|
||||
invariant(stores.auth.token, 'JWT token not set properly');
|
||||
@@ -51,7 +53,8 @@ class ApiClient {
|
||||
body,
|
||||
headers,
|
||||
redirect: 'follow',
|
||||
credentials: 'include',
|
||||
credentials: 'omit',
|
||||
cache: 'no-cache',
|
||||
});
|
||||
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
@@ -68,6 +71,14 @@ class ApiClient {
|
||||
const error = {};
|
||||
error.statusCode = response.status;
|
||||
error.response = response;
|
||||
|
||||
try {
|
||||
const data = await response.json();
|
||||
error.message = data.message || '';
|
||||
} catch (_err) {
|
||||
// we're trying to parse an error so JSON may not be valid
|
||||
}
|
||||
|
||||
throw error;
|
||||
};
|
||||
|
||||
@@ -81,7 +92,7 @@ class ApiClient {
|
||||
|
||||
// Helpers
|
||||
constructQueryString = (data: Object) => {
|
||||
return _.map(data, (v, k) => {
|
||||
return map(data, (v, k) => {
|
||||
return `${encodeURIComponent(k)}=${encodeURIComponent(v)}`;
|
||||
}).join('&');
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ services:
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- .:/opt/outline
|
||||
- .:/opt/outline:cached
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
|
||||
201
flow-typed/npm/koa_v2.x.x.js
vendored
201
flow-typed/npm/koa_v2.x.x.js
vendored
@@ -14,17 +14,22 @@ declare module 'koa' {
|
||||
// Currently, import type doesn't work well ?
|
||||
// so copy `Server` from flow/lib/node.js#L820
|
||||
declare class Server extends net$Server {
|
||||
listen(port?: number, hostname?: string, backlog?: number, callback?: Function): Server,
|
||||
listen(path: string, callback?: Function): Server,
|
||||
listen(handle: Object, callback?: Function): Server,
|
||||
close(callback?: Function): Server,
|
||||
maxHeadersCount: number,
|
||||
setTimeout(msecs: number, callback: Function): Server,
|
||||
timeout: number,
|
||||
listen(
|
||||
port?: number,
|
||||
hostname?: string,
|
||||
backlog?: number,
|
||||
callback?: Function
|
||||
): Server;
|
||||
listen(path: string, callback?: Function): Server;
|
||||
listen(handle: Object, callback?: Function): Server;
|
||||
close(callback?: Function): Server;
|
||||
maxHeadersCount: number;
|
||||
setTimeout(msecs: number, callback: Function): Server;
|
||||
timeout: number;
|
||||
}
|
||||
declare type ServerType = Server;
|
||||
|
||||
declare type JSON = | string | number | boolean | null | JSONObject | JSONArray;
|
||||
declare type JSON = string | number | boolean | null | JSONObject | JSONArray;
|
||||
declare type JSONObject = { [key: string]: JSON };
|
||||
declare type JSONArray = Array<JSON>;
|
||||
|
||||
@@ -34,11 +39,11 @@ declare module 'koa' {
|
||||
};
|
||||
|
||||
declare type RequestJSON = {
|
||||
'method': string,
|
||||
'url': string,
|
||||
'header': SimpleHeader,
|
||||
method: string,
|
||||
url: string,
|
||||
header: SimpleHeader,
|
||||
};
|
||||
declare type RequestInspect = void|RequestJSON;
|
||||
declare type RequestInspect = void | RequestJSON;
|
||||
declare type Request = {
|
||||
app: Application,
|
||||
req: http$IncomingMessage,
|
||||
@@ -60,7 +65,7 @@ declare module 'koa' {
|
||||
originalUrl: string,
|
||||
path: string,
|
||||
protocol: string,
|
||||
query: {[key: string]: string}, // always string
|
||||
query: { [key: string]: string }, // always string
|
||||
querystring: string,
|
||||
search: string,
|
||||
secure: boolean, // Shorthand for ctx.protocol == "https" to check if a request was issued via TLS.
|
||||
@@ -70,55 +75,55 @@ declare module 'koa' {
|
||||
type: string,
|
||||
url: string,
|
||||
|
||||
charset: string|void,
|
||||
length: number|void,
|
||||
charset: string | void,
|
||||
length: number | void,
|
||||
|
||||
// Those functions comes from https://github.com/jshttp/accepts/blob/master/index.js
|
||||
// request.js$L445
|
||||
// https://github.com/jshttp/accepts/blob/master/test/type.js
|
||||
accepts: ((args: string[]) => string|false)&
|
||||
// ToDo: There is an issue https://github.com/facebook/flow/issues/3009
|
||||
// if you meet some error here, temporarily add an additional annotation
|
||||
// like: `request.accepts((['json', 'text']:Array<string>))` to fix it.
|
||||
((arg: string, ...args: string[]) => string|false) &
|
||||
( () => string[] ) , // return the old value.
|
||||
// Those functions comes from https://github.com/jshttp/accepts/blob/master/index.js
|
||||
// request.js$L445
|
||||
// https://github.com/jshttp/accepts/blob/master/test/type.js
|
||||
accepts: ((args: string[]) => string | false) &
|
||||
// ToDo: There is an issue https://github.com/facebook/flow/issues/3009
|
||||
// if you meet some error here, temporarily add an additional annotation
|
||||
// like: `request.accepts((['json', 'text']:Array<string>))` to fix it.
|
||||
((arg: string, ...args: string[]) => string | false) &
|
||||
(() => string[]), // return the old value.
|
||||
|
||||
// https://github.com/jshttp/accepts/blob/master/index.js#L153
|
||||
// https://github.com/jshttp/accepts/blob/master/test/charset.js
|
||||
acceptsCharsets: ( (args: string[]) => buffer$Encoding|false)&
|
||||
// ToDo: https://github.com/facebook/flow/issues/3009
|
||||
// if you meet some error here, see L70.
|
||||
( (arg: string, ...args: string[]) => buffer$Encoding|false ) &
|
||||
( () => string[] ),
|
||||
// https://github.com/jshttp/accepts/blob/master/index.js#L153
|
||||
// https://github.com/jshttp/accepts/blob/master/test/charset.js
|
||||
acceptsCharsets: ((args: string[]) => buffer$Encoding | false) &
|
||||
// ToDo: https://github.com/facebook/flow/issues/3009
|
||||
// if you meet some error here, see L70.
|
||||
((arg: string, ...args: string[]) => buffer$Encoding | false) &
|
||||
(() => string[]),
|
||||
|
||||
// https://github.com/jshttp/accepts/blob/master/index.js#L119
|
||||
// https://github.com/jshttp/accepts/blob/master/test/encoding.js
|
||||
acceptsEncodings: ( (args: string[]) => string|false)&
|
||||
// ToDo: https://github.com/facebook/flow/issues/3009
|
||||
// if you meet some error here, see L70.
|
||||
( (arg: string, ...args: string[]) => string|false ) &
|
||||
( () => string[] ),
|
||||
// https://github.com/jshttp/accepts/blob/master/index.js#L119
|
||||
// https://github.com/jshttp/accepts/blob/master/test/encoding.js
|
||||
acceptsEncodings: ((args: string[]) => string | false) &
|
||||
// ToDo: https://github.com/facebook/flow/issues/3009
|
||||
// if you meet some error here, see L70.
|
||||
((arg: string, ...args: string[]) => string | false) &
|
||||
(() => string[]),
|
||||
|
||||
// https://github.com/jshttp/accepts/blob/master/index.js#L185
|
||||
// https://github.com/jshttp/accepts/blob/master/test/language.js
|
||||
acceptsLanguages: ( (args: string[]) => string|false) &
|
||||
// ToDo: https://github.com/facebook/flow/issues/3009
|
||||
// if you meet some error here, see L70.
|
||||
( (arg: string, ...args: string[]) => string|false ) &
|
||||
( () => string[] ),
|
||||
// https://github.com/jshttp/accepts/blob/master/index.js#L185
|
||||
// https://github.com/jshttp/accepts/blob/master/test/language.js
|
||||
acceptsLanguages: ((args: string[]) => string | false) &
|
||||
// ToDo: https://github.com/facebook/flow/issues/3009
|
||||
// if you meet some error here, see L70.
|
||||
((arg: string, ...args: string[]) => string | false) &
|
||||
(() => string[]),
|
||||
|
||||
get: (field: string) => string,
|
||||
|
||||
/* https://github.com/jshttp/type-is/blob/master/test/test.js
|
||||
/* https://github.com/jshttp/type-is/blob/master/test/test.js
|
||||
* Check if the incoming request contains the "Content-Type"
|
||||
* header field, and it contains any of the give mime `type`s.
|
||||
* If there is no request body, `null` is returned.
|
||||
* If there is no content type, `false` is returned.
|
||||
* Otherwise, it returns the first `type` that matches.
|
||||
*/
|
||||
is: ( (args: string[]) => null|false|string)&
|
||||
( (arg: string, ...args: string[]) => null|false|string ) &
|
||||
( () => string ), // should return the mime type
|
||||
is: ((args: string[]) => null | false | string) &
|
||||
((arg: string, ...args: string[]) => null | false | string) &
|
||||
(() => string), // should return the mime type
|
||||
|
||||
toJSON: () => RequestJSON,
|
||||
inspect: () => RequestInspect,
|
||||
@@ -127,15 +132,15 @@ declare module 'koa' {
|
||||
};
|
||||
|
||||
declare type ResponseJSON = {
|
||||
'status': mixed,
|
||||
'message': mixed,
|
||||
'header': mixed,
|
||||
status: mixed,
|
||||
message: mixed,
|
||||
header: mixed,
|
||||
};
|
||||
declare type ResponseInspect = {
|
||||
'status': mixed,
|
||||
'message': mixed,
|
||||
'header': mixed,
|
||||
'body': mixed,
|
||||
status: mixed,
|
||||
message: mixed,
|
||||
header: mixed,
|
||||
body: mixed,
|
||||
};
|
||||
declare type Response = {
|
||||
app: Application,
|
||||
@@ -145,7 +150,7 @@ declare module 'koa' {
|
||||
request: Request,
|
||||
|
||||
// docs/api/response.md#L113.
|
||||
body: string|Buffer|stream$Stream|Object|Array<mixed>|null, // JSON contains null
|
||||
body: string | Buffer | stream$Stream | Object | Array<mixed> | null, // JSON contains null
|
||||
etag: string,
|
||||
header: SimpleHeader,
|
||||
headers: SimpleHeader, // alias as header
|
||||
@@ -160,21 +165,21 @@ declare module 'koa' {
|
||||
writable: boolean,
|
||||
|
||||
// charset: string, // doesn't find in response.js
|
||||
length: number|void,
|
||||
length: number | void,
|
||||
|
||||
append: (field: string, val: string | string[]) => void,
|
||||
attachment: (filename?: string) => void,
|
||||
get: (field: string) => string,
|
||||
// https://github.com/jshttp/type-is/blob/master/test/test.js
|
||||
// https://github.com/koajs/koa/blob/v2.x/lib/response.js#L382
|
||||
is: ( (arg: string[]) => false|string) &
|
||||
( (arg: string, ...args: string[]) => false|string ) &
|
||||
( () => string ), // should return the mime type
|
||||
is: ((arg: string[]) => false | string) &
|
||||
((arg: string, ...args: string[]) => false | string) &
|
||||
(() => string), // should return the mime type
|
||||
redirect: (url: string, alt?: string) => void,
|
||||
remove: (field: string) => void,
|
||||
// https://github.com/koajs/koa/blob/v2.x/lib/response.js#L418
|
||||
set: ((field: string, val: string | string[]) => void)&
|
||||
((field: {[key: string]: string | string[]}) => void),
|
||||
set: ((field: string, val: string | string[]) => void) &
|
||||
((field: { [key: string]: string | string[] }) => void),
|
||||
|
||||
vary: (field: string) => void,
|
||||
|
||||
@@ -183,7 +188,7 @@ declare module 'koa' {
|
||||
inspect(): ResponseInspect,
|
||||
|
||||
[key: string]: mixed, // props added by middlewares.
|
||||
}
|
||||
};
|
||||
|
||||
declare type ContextJSON = {
|
||||
request: RequestJSON,
|
||||
@@ -197,7 +202,7 @@ declare module 'koa' {
|
||||
// https://github.com/pillarjs/cookies
|
||||
declare type CookiesSetOptions = {
|
||||
domain: string, // domain of the cookie (no default).
|
||||
maxAge: number, // milliseconds from Date.now() for expiry
|
||||
maxAge?: number, // milliseconds from Date.now() for expiry
|
||||
expires?: Date, //cookie's expiration date (expires at the end of session by default).
|
||||
path?: string, // the path of the cookie (/ by default).
|
||||
secure?: boolean, // false by default for HTTP, true by default for HTTPS
|
||||
@@ -207,10 +212,14 @@ declare module 'koa' {
|
||||
overwrite?: boolean, // whether to overwrite previously set cookies of the same name (false by default).
|
||||
};
|
||||
declare type Cookies = {
|
||||
get: (name: string, options?: {signed: boolean}) => string|void,
|
||||
set: ((name: string, value: string, options?: CookiesSetOptions) => Context)&
|
||||
// delete cookie (an outbound header with an expired date is used.)
|
||||
( (name: string) => Context),
|
||||
get: (name: string, options?: { signed: boolean }) => string | void,
|
||||
set: ((
|
||||
name: string,
|
||||
value: string,
|
||||
options?: CookiesSetOptions
|
||||
) => Context) &
|
||||
// delete cookie (an outbound header with an expired date is used.)
|
||||
((name: string) => Context),
|
||||
};
|
||||
// The default props of context come from two files
|
||||
// `application.createContext` & `context.js`
|
||||
@@ -228,12 +237,17 @@ declare module 'koa' {
|
||||
state: Object,
|
||||
|
||||
// context.js#L55
|
||||
assert: (test: mixed, status: number, message?: string, opts?: mixed) => void,
|
||||
assert: (
|
||||
test: mixed,
|
||||
status: number,
|
||||
message?: string,
|
||||
opts?: mixed
|
||||
) => void,
|
||||
// context.js#L107
|
||||
// if (!(err instanceof Error)) err = new Error(`non-error thrown: ${err}`);
|
||||
onerror: (err?: mixed) => void,
|
||||
// context.md#L88
|
||||
throw: ( status: number, msg?: string, opts?: Object) => void,
|
||||
throw: (status: number, msg?: string, opts?: Object) => void,
|
||||
toJSON(): ContextJSON,
|
||||
inspect(): ContextJSON,
|
||||
|
||||
@@ -287,32 +301,37 @@ declare module 'koa' {
|
||||
ip: $PropertyType<Request, 'ip'>,
|
||||
|
||||
[key: string]: any, // props added by middlewares.
|
||||
}
|
||||
};
|
||||
|
||||
declare type Middleware =
|
||||
(ctx: Context, next: () => Promise<void>) => Promise<void>|void;
|
||||
declare type Middleware = (
|
||||
ctx: Context,
|
||||
next: () => Promise<void>
|
||||
) => Promise<void> | void;
|
||||
declare type ApplicationJSON = {
|
||||
'subdomainOffset': mixed,
|
||||
'proxy': mixed,
|
||||
'env': string,
|
||||
subdomainOffset: mixed,
|
||||
proxy: mixed,
|
||||
env: string,
|
||||
};
|
||||
declare class Application extends events$EventEmitter {
|
||||
context: Context,
|
||||
context: Context;
|
||||
// request handler for node's native http server.
|
||||
callback: () => (req: http$IncomingMessage, res: http$ServerResponse) => void,
|
||||
env: string,
|
||||
keys?: Array<string>|Object, // https://github.com/crypto-utils/keygrip
|
||||
middleware: Array<Middleware>,
|
||||
proxy: boolean, // when true proxy header fields will be trusted
|
||||
request: Request,
|
||||
response: Response,
|
||||
server: Server,
|
||||
subdomainOffset: number,
|
||||
callback: () => (
|
||||
req: http$IncomingMessage,
|
||||
res: http$ServerResponse
|
||||
) => void;
|
||||
env: string;
|
||||
keys?: Array<string> | Object; // https://github.com/crypto-utils/keygrip
|
||||
middleware: Array<Middleware>;
|
||||
proxy: boolean; // when true proxy header fields will be trusted
|
||||
request: Request;
|
||||
response: Response;
|
||||
server: Server;
|
||||
subdomainOffset: number;
|
||||
|
||||
listen: $PropertyType<Server, 'listen'>,
|
||||
toJSON(): ApplicationJSON,
|
||||
inspect(): ApplicationJSON,
|
||||
use(fn: Middleware): this,
|
||||
listen: $PropertyType<Server, 'listen'>;
|
||||
toJSON(): ApplicationJSON;
|
||||
inspect(): ApplicationJSON;
|
||||
use(fn: Middleware): this;
|
||||
}
|
||||
|
||||
declare module.exports: Class<Application>;
|
||||
|
||||
84
flow-typed/npm/localforage_v1.5.x.js
vendored
84
flow-typed/npm/localforage_v1.5.x.js
vendored
@@ -1,84 +0,0 @@
|
||||
// flow-typed signature: 37b164ad4c10b3c89887d1fd5b7ca096
|
||||
// flow-typed version: 9c854fa980/localforage_v1.5.x/flow_>=v0.25.x
|
||||
|
||||
type PartialConfig = {
|
||||
driver?: string | Array<string>,
|
||||
name?: string,
|
||||
size?: number,
|
||||
storeName?: string,
|
||||
version?: string,
|
||||
description?: string,
|
||||
};
|
||||
|
||||
type Driver = {
|
||||
_driver: string,
|
||||
_initStorage(config: PartialConfig): void,
|
||||
|
||||
getItem<T>(
|
||||
key: string,
|
||||
successCallback?: (err?: Error, value?: T) => mixed,
|
||||
): ?Promise<?T>,
|
||||
setItem<T>(
|
||||
key: string,
|
||||
value: T,
|
||||
successCallback?: (err?: Error, value?: T) => mixed,
|
||||
): ?Promise<T>,
|
||||
removeItem(
|
||||
key: string,
|
||||
successCallback?: (err?: Error) => mixed,
|
||||
): ?Promise<void>,
|
||||
clear(successCallback?: ?(numberOfKeys: number) => mixed): ?Promise<number>,
|
||||
length(successCallback?: (numberOfKeys: number) => mixed): ?Promise<number>,
|
||||
key(
|
||||
keyIndex: number,
|
||||
successCallback?: (keyName: string) => mixed,
|
||||
): ?Promise<string>,
|
||||
keys(
|
||||
successCallback?: (keyNames: Array<string>) => mixed,
|
||||
): ?Promise<Array<string>>,
|
||||
};
|
||||
|
||||
type localforageInstance = {
|
||||
INDEXEDDB: 'asyncStorage',
|
||||
WEBSQL: 'webSQLStorage',
|
||||
LOCALSTORAGE: 'localStorageWrapper',
|
||||
|
||||
getItem<T>(
|
||||
key: string,
|
||||
successCallback?: (err?: Error, value?: T) => mixed,
|
||||
): Promise<?T>,
|
||||
setItem<T>(
|
||||
key: string,
|
||||
value: T,
|
||||
successCallback?: (err?: Error, value?: T) => mixed,
|
||||
): Promise<T>,
|
||||
removeItem(
|
||||
key: string,
|
||||
successCallback?: (err?: Error) => mixed,
|
||||
): Promise<void>,
|
||||
clear(successCallback?: ?(numberOfKeys: number) => mixed): Promise<number>,
|
||||
length(successCallback?: (numberOfKeys: number) => mixed): Promise<number>,
|
||||
key(
|
||||
keyIndex: number,
|
||||
successCallback?: (keyName: string) => mixed,
|
||||
): Promise<string>,
|
||||
keys(
|
||||
successCallback?: (keyNames: Array<string>) => mixed,
|
||||
): Promise<Array<string>>,
|
||||
iterate<T>(
|
||||
iteratorCallback: (value: T, key: string, iterationNumber: number) => mixed,
|
||||
successCallback?: (result: void | [string, T]) => mixed,
|
||||
): Promise<void | [string, T]>,
|
||||
setDriver(driverNames: string | Array<string>): void,
|
||||
config(config?: PartialConfig): boolean | PartialConfig,
|
||||
defineDriver(driver: Driver): void,
|
||||
driver(): string,
|
||||
ready(): Promise<void>,
|
||||
supports(driverName: string): boolean,
|
||||
createInstance(config?: PartialConfig): localforageInstance,
|
||||
dropInstance(config?: PartialConfig): Promise<void>,
|
||||
};
|
||||
|
||||
declare module 'localforage' {
|
||||
declare module.exports: localforageInstance;
|
||||
}
|
||||
@@ -35,6 +35,7 @@
|
||||
"shared"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"^shared/(.*)$": "<rootDir>/shared/$1",
|
||||
"^.*[.](s?css|css)$": "<rootDir>/__mocks__/styleMock.js",
|
||||
"^.*[.](gif|ttf|eot|svg)$": "<rootDir>/__test__/fileMock.js"
|
||||
},
|
||||
@@ -128,7 +129,6 @@
|
||||
"koa-sendfile": "2.0.0",
|
||||
"koa-static": "^4.0.1",
|
||||
"koa-sslify": "2.1.2",
|
||||
"localforage": "^1.5.0",
|
||||
"lodash": "^4.17.4",
|
||||
"mobx": "^3.1.9",
|
||||
"mobx-react": "^4.1.8",
|
||||
@@ -139,6 +139,7 @@
|
||||
"normalizr": "2.0.1",
|
||||
"outline-icons": "^1.3.2",
|
||||
"oy-vey": "^0.10.0",
|
||||
"parse-domain": "2.1.6",
|
||||
"pg": "^6.1.5",
|
||||
"pg-hstore": "2.3.2",
|
||||
"polished": "1.2.1",
|
||||
|
||||
@@ -38,7 +38,7 @@ describe('#hooks.unfurl', async () => {
|
||||
links: [
|
||||
{
|
||||
domain: 'getoutline.com',
|
||||
url: document.getUrl(),
|
||||
url: document.url,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -12,13 +12,17 @@ const { authorize } = policy;
|
||||
const router = new Router();
|
||||
|
||||
router.post('team.update', auth(), async ctx => {
|
||||
const { name, avatarUrl, sharing } = ctx.body;
|
||||
const { name, avatarUrl, subdomain, sharing } = ctx.body;
|
||||
const endpoint = publicS3Endpoint();
|
||||
|
||||
const user = ctx.state.user;
|
||||
const team = await Team.findById(user.teamId);
|
||||
authorize(user, 'update', team);
|
||||
|
||||
if (process.env.SUBDOMAINS_ENABLED === 'true') {
|
||||
team.subdomain = subdomain === '' ? null : subdomain;
|
||||
}
|
||||
|
||||
if (name) team.name = name;
|
||||
if (sharing !== undefined) team.sharing = sharing;
|
||||
if (avatarUrl && avatarUrl.startsWith(`${endpoint}/uploads/${user.id}`)) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// @flow
|
||||
import crypto from 'crypto';
|
||||
import Router from 'koa-router';
|
||||
import addMonths from 'date-fns/add_months';
|
||||
import { capitalize } from 'lodash';
|
||||
import { OAuth2Client } from 'google-auth-library';
|
||||
import { User, Team } from '../models';
|
||||
import auth from '../middlewares/authentication';
|
||||
|
||||
const router = new Router();
|
||||
const client = new OAuth2Client(
|
||||
@@ -29,7 +29,7 @@ router.get('google', async ctx => {
|
||||
});
|
||||
|
||||
// signin callback from Google
|
||||
router.get('google.callback', async ctx => {
|
||||
router.get('google.callback', auth({ required: false }), async ctx => {
|
||||
const { code } = ctx.request.query;
|
||||
ctx.assertPresent(code, 'code is required');
|
||||
const response = await client.getToken(code);
|
||||
@@ -52,7 +52,8 @@ router.get('google.callback', async ctx => {
|
||||
}
|
||||
|
||||
const googleId = profile.data.hd;
|
||||
const teamName = capitalize(profile.data.hd.split('.')[0]);
|
||||
const hostname = profile.data.hd.split('.')[0];
|
||||
const teamName = capitalize(hostname);
|
||||
|
||||
// attempt to get logo from Clearbit API. If one doesn't exist then
|
||||
// fall back to using tiley to generate a placeholder logo
|
||||
@@ -91,22 +92,12 @@ router.get('google.callback', async ctx => {
|
||||
});
|
||||
|
||||
if (isFirstUser) {
|
||||
await team.createFirstCollection(user.id);
|
||||
await team.provisionFirstCollection(user.id);
|
||||
await team.provisionSubdomain(hostname);
|
||||
}
|
||||
|
||||
// not awaiting the promise here so that the request is not blocked
|
||||
user.updateSignedIn(ctx.request.ip);
|
||||
|
||||
ctx.cookies.set('lastSignedIn', 'google', {
|
||||
httpOnly: false,
|
||||
expires: new Date('2100'),
|
||||
});
|
||||
ctx.cookies.set('accessToken', user.getJwtToken(), {
|
||||
httpOnly: false,
|
||||
expires: addMonths(new Date(), 1),
|
||||
});
|
||||
|
||||
ctx.redirect('/');
|
||||
// set cookies on response and redirect to team subdomain
|
||||
ctx.signIn(user, team, 'google');
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -3,18 +3,40 @@ import bodyParser from 'koa-bodyparser';
|
||||
import Koa from 'koa';
|
||||
import Router from 'koa-router';
|
||||
import validation from '../middlewares/validation';
|
||||
import auth from '../middlewares/authentication';
|
||||
import addMonths from 'date-fns/add_months';
|
||||
import { Team } from '../models';
|
||||
import { stripSubdomain } from '../../shared/utils/domains';
|
||||
|
||||
import slack from './slack';
|
||||
import google from './google';
|
||||
|
||||
const auth = new Koa();
|
||||
const app = new Koa();
|
||||
const router = new Router();
|
||||
|
||||
router.use('/', slack.routes());
|
||||
router.use('/', google.routes());
|
||||
|
||||
auth.use(bodyParser());
|
||||
auth.use(validation());
|
||||
auth.use(router.routes());
|
||||
router.get('/redirect', auth(), async ctx => {
|
||||
const user = ctx.state.user;
|
||||
|
||||
export default auth;
|
||||
// transfer access token cookie from root to subdomain
|
||||
ctx.cookies.set('accessToken', undefined, {
|
||||
httpOnly: true,
|
||||
domain: stripSubdomain(ctx.request.hostname),
|
||||
});
|
||||
|
||||
ctx.cookies.set('accessToken', user.getJwtToken(), {
|
||||
httpOnly: false,
|
||||
expires: addMonths(new Date(), 3),
|
||||
});
|
||||
|
||||
const team = await Team.findById(user.teamId);
|
||||
ctx.redirect(`${team.url}/dashboard`);
|
||||
});
|
||||
|
||||
app.use(bodyParser());
|
||||
app.use(validation());
|
||||
app.use(router.routes());
|
||||
|
||||
export default app;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import Router from 'koa-router';
|
||||
import auth from '../middlewares/authentication';
|
||||
import addHours from 'date-fns/add_hours';
|
||||
import addMonths from 'date-fns/add_months';
|
||||
import { stripSubdomain } from '../../shared/utils/domains';
|
||||
import { slackAuth } from '../../shared/utils/routeHelpers';
|
||||
import { Authentication, Integration, User, Team } from '../models';
|
||||
import * as Slack from '../slack';
|
||||
@@ -18,18 +18,19 @@ router.get('slack', async ctx => {
|
||||
ctx.cookies.set('state', state, {
|
||||
httpOnly: false,
|
||||
expires: addHours(new Date(), 1),
|
||||
domain: stripSubdomain(ctx.request.hostname),
|
||||
});
|
||||
ctx.redirect(slackAuth(state));
|
||||
});
|
||||
|
||||
// signin callback from Slack
|
||||
router.get('slack.callback', async ctx => {
|
||||
router.get('slack.callback', auth({ required: false }), async ctx => {
|
||||
const { code, error, state } = ctx.request.query;
|
||||
ctx.assertPresent(code || error, 'code is required');
|
||||
ctx.assertPresent(state, 'state is required');
|
||||
|
||||
if (state !== ctx.cookies.get('state') || error) {
|
||||
ctx.redirect('/?notice=auth-error');
|
||||
ctx.redirect(`/?notice=auth-error`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -60,22 +61,12 @@ router.get('slack.callback', async ctx => {
|
||||
});
|
||||
|
||||
if (isFirstUser) {
|
||||
await team.createFirstCollection(user.id);
|
||||
await team.provisionFirstCollection(user.id);
|
||||
await team.provisionSubdomain(data.team.domain);
|
||||
}
|
||||
|
||||
// not awaiting the promise here so that the request is not blocked
|
||||
user.updateSignedIn(ctx.request.ip);
|
||||
|
||||
ctx.cookies.set('lastSignedIn', 'slack', {
|
||||
httpOnly: false,
|
||||
expires: new Date('2100'),
|
||||
});
|
||||
ctx.cookies.set('accessToken', user.getJwtToken(), {
|
||||
httpOnly: false,
|
||||
expires: addMonths(new Date(), 1),
|
||||
});
|
||||
|
||||
ctx.redirect('/');
|
||||
// set cookies on response and redirect to team subdomain
|
||||
ctx.signIn(user, team, 'slack');
|
||||
});
|
||||
|
||||
router.get('slack.commands', auth(), async ctx => {
|
||||
|
||||
@@ -6,7 +6,9 @@ describe('Mailer', () => {
|
||||
let sendMailOutput;
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.URL = 'http://localhost:3000';
|
||||
process.env.SMTP_FROM_EMAIL = 'hello@example.com';
|
||||
jest.resetModules();
|
||||
|
||||
fakeMailer = new Mailer();
|
||||
fakeMailer.transporter = {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// @flow
|
||||
import { type Context } from 'koa';
|
||||
import type { Context } from 'koa';
|
||||
|
||||
export default function subdomainRedirect() {
|
||||
return async function subdomainRedirectMiddleware(
|
||||
export default function apexRedirect() {
|
||||
return async function apexRedirectMiddleware(
|
||||
ctx: Context,
|
||||
next: () => Promise<void>
|
||||
next: () => Promise<*>
|
||||
) {
|
||||
if (ctx.headers.host === 'getoutline.com') {
|
||||
ctx.redirect(`https://www.${ctx.headers.host}${ctx.path}`);
|
||||
@@ -3,6 +3,9 @@ import JWT from 'jsonwebtoken';
|
||||
import { type Context } from 'koa';
|
||||
import { User, ApiKey } from '../models';
|
||||
import { AuthenticationError, UserSuspendedError } from '../errors';
|
||||
import addMonths from 'date-fns/add_months';
|
||||
import addMinutes from 'date-fns/add_minutes';
|
||||
import { stripSubdomain } from '../../shared/utils/domains';
|
||||
|
||||
export default function auth(options?: { required?: boolean } = {}) {
|
||||
return async function authMiddleware(ctx: Context, next: () => Promise<*>) {
|
||||
@@ -28,7 +31,7 @@ export default function auth(options?: { required?: boolean } = {}) {
|
||||
token = ctx.body.token;
|
||||
} else if (ctx.request.query.token) {
|
||||
token = ctx.request.query.token;
|
||||
} else if (ctx.cookies.get('accessToken')) {
|
||||
} else {
|
||||
token = ctx.cookies.get('accessToken');
|
||||
}
|
||||
|
||||
@@ -90,6 +93,56 @@ export default function auth(options?: { required?: boolean } = {}) {
|
||||
ctx.cache[user.id] = user;
|
||||
}
|
||||
|
||||
ctx.signIn = (user, team, service) => {
|
||||
// update the database when the user last signed in
|
||||
user.updateSignedIn(ctx.request.ip);
|
||||
|
||||
const domain = stripSubdomain(ctx.request.hostname);
|
||||
const expires = addMonths(new Date(), 3);
|
||||
|
||||
// set a cookie for which service we last signed in with. This is
|
||||
// only used to display a UI hint for the user for next time
|
||||
ctx.cookies.set('lastSignedIn', service, {
|
||||
httpOnly: false,
|
||||
expires: new Date('2100'),
|
||||
domain,
|
||||
});
|
||||
|
||||
// set a transfer cookie for the access token itself and redirect
|
||||
// to the teams subdomain if subdomains are enabled
|
||||
if (process.env.SUBDOMAINS_ENABLED === 'true' && team.subdomain) {
|
||||
// get any existing sessions (teams signed in) and add this team
|
||||
const existing = JSON.parse(ctx.cookies.get('sessions') || '{}');
|
||||
const sessions = JSON.stringify({
|
||||
...existing,
|
||||
[team.subdomain]: {
|
||||
name: team.name,
|
||||
logoUrl: team.logoUrl,
|
||||
url: team.url,
|
||||
expires,
|
||||
},
|
||||
});
|
||||
ctx.cookies.set('sessions', sessions, {
|
||||
httpOnly: false,
|
||||
expires,
|
||||
domain,
|
||||
});
|
||||
|
||||
ctx.cookies.set('accessToken', user.getJwtToken(), {
|
||||
httpOnly: true,
|
||||
expires: addMinutes(new Date(), 1),
|
||||
domain,
|
||||
});
|
||||
ctx.redirect(`${team.url}/auth/redirect`);
|
||||
} else {
|
||||
ctx.cookies.set('accessToken', user.getJwtToken(), {
|
||||
httpOnly: false,
|
||||
expires,
|
||||
});
|
||||
ctx.redirect(`${team.url}/dashboard`);
|
||||
}
|
||||
};
|
||||
|
||||
return next();
|
||||
};
|
||||
}
|
||||
|
||||
14
server/migrations/20181031015046-add-subdomain-to-team.js
Normal file
14
server/migrations/20181031015046-add-subdomain-to-team.js
Normal file
@@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.addColumn('teams', 'subdomain', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
unique: true
|
||||
});
|
||||
await queryInterface.addIndex('teams', ['subdomain']);
|
||||
},
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.removeColumn('teams', 'subdomain');
|
||||
await queryInterface.removeIndex('teams', ['subdomain']);
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,11 @@ const Collection = sequelize.define(
|
||||
await collection.save();
|
||||
},
|
||||
},
|
||||
getterMethods: {
|
||||
url() {
|
||||
return `/collections/${this.id}`;
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -120,10 +125,6 @@ Collection.addHook('afterUpdate', model =>
|
||||
|
||||
// Instance methods
|
||||
|
||||
Collection.prototype.getUrl = function() {
|
||||
return `/collections/${this.id}`;
|
||||
};
|
||||
|
||||
Collection.prototype.addDocumentToStructure = async function(
|
||||
document,
|
||||
index,
|
||||
|
||||
@@ -6,10 +6,10 @@ import uuid from 'uuid';
|
||||
beforeEach(flushdb);
|
||||
beforeEach(jest.resetAllMocks);
|
||||
|
||||
describe('#getUrl', () => {
|
||||
describe('#url', () => {
|
||||
test('should return correct url for the collection', () => {
|
||||
const collection = new Collection({ id: '1234' });
|
||||
expect(collection.getUrl()).toBe('/collections/1234');
|
||||
expect(collection.url).toBe('/collections/1234');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -104,6 +104,12 @@ const Document = sequelize.define(
|
||||
afterCreate: createRevision,
|
||||
afterUpdate: createRevision,
|
||||
},
|
||||
getterMethods: {
|
||||
url: function() {
|
||||
const slugifiedTitle = slugify(this.title);
|
||||
return `/doc/${slugifiedTitle}-${this.urlId}`;
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -312,18 +318,13 @@ Document.prototype.getSummary = function() {
|
||||
return lines.length >= 1 ? lines[1] : '';
|
||||
};
|
||||
|
||||
Document.prototype.getUrl = function() {
|
||||
const slugifiedTitle = slugify(this.title);
|
||||
return `/doc/${slugifiedTitle}-${this.urlId}`;
|
||||
};
|
||||
|
||||
Document.prototype.toJSON = function() {
|
||||
// Warning: only use for new documents as order of children is
|
||||
// handled in the collection's documentStructure
|
||||
return {
|
||||
id: this.id,
|
||||
title: this.title,
|
||||
url: this.getUrl(),
|
||||
url: this.url,
|
||||
children: [],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// @flow
|
||||
import uuid from 'uuid';
|
||||
import { URL } from 'url';
|
||||
import { DataTypes, sequelize, Op } from '../sequelize';
|
||||
import { publicS3Endpoint, uploadToS3FromUrl } from '../utils/s3';
|
||||
import { RESERVED_SUBDOMAINS } from '../../shared/utils/domains';
|
||||
import Collection from './Collection';
|
||||
import User from './User';
|
||||
|
||||
@@ -14,6 +16,26 @@ const Team = sequelize.define(
|
||||
primaryKey: true,
|
||||
},
|
||||
name: DataTypes.STRING,
|
||||
subdomain: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
validate: {
|
||||
isLowercase: true,
|
||||
is: {
|
||||
args: [/^[a-z\d-]+$/, 'i'],
|
||||
msg: 'Must be only alphanumeric and dashes',
|
||||
},
|
||||
len: {
|
||||
args: [4, 32],
|
||||
msg: 'Must be between 4 and 32 characters',
|
||||
},
|
||||
notIn: {
|
||||
args: [RESERVED_SUBDOMAINS],
|
||||
msg: 'You chose a restricted word, please try another.',
|
||||
},
|
||||
},
|
||||
unique: true,
|
||||
},
|
||||
slackId: { type: DataTypes.STRING, allowNull: true },
|
||||
googleId: { type: DataTypes.STRING, allowNull: true },
|
||||
avatarUrl: { type: DataTypes.STRING, allowNull: true },
|
||||
@@ -21,12 +43,22 @@ const Team = sequelize.define(
|
||||
slackData: DataTypes.JSONB,
|
||||
},
|
||||
{
|
||||
indexes: [
|
||||
{
|
||||
unique: true,
|
||||
fields: ['slackId'],
|
||||
getterMethods: {
|
||||
url() {
|
||||
if (!this.subdomain || process.env.SUBDOMAINS_ENABLED !== 'true') {
|
||||
return process.env.URL;
|
||||
}
|
||||
|
||||
const url = new URL(process.env.URL);
|
||||
url.host = `${this.subdomain}.${url.host}`;
|
||||
return url.href.replace(/\/$/, '');
|
||||
},
|
||||
],
|
||||
logoUrl() {
|
||||
return (
|
||||
this.avatarUrl || (this.slackData ? this.slackData.image_88 : null)
|
||||
);
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -53,7 +85,24 @@ const uploadAvatar = async model => {
|
||||
}
|
||||
};
|
||||
|
||||
Team.prototype.createFirstCollection = async function(userId) {
|
||||
Team.prototype.provisionSubdomain = async function(subdomain) {
|
||||
if (this.subdomain) return this.subdomain;
|
||||
|
||||
let append = 0;
|
||||
while (true) {
|
||||
try {
|
||||
await this.update({ subdomain });
|
||||
break;
|
||||
} catch (err) {
|
||||
// subdomain was invalid or already used, try again
|
||||
subdomain = `${subdomain}${++append}`;
|
||||
}
|
||||
}
|
||||
|
||||
return subdomain;
|
||||
};
|
||||
|
||||
Team.prototype.provisionFirstCollection = async function(userId) {
|
||||
return await Collection.create({
|
||||
name: 'General',
|
||||
description: 'Your first Collection',
|
||||
|
||||
28
server/models/Team.test.js
Normal file
28
server/models/Team.test.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/* eslint-disable flowtype/require-valid-file-annotation */
|
||||
import { flushdb } from '../test/support';
|
||||
import { buildTeam } from '../test/factories';
|
||||
|
||||
beforeEach(flushdb);
|
||||
|
||||
it('should set subdomain if available', async () => {
|
||||
const team = await buildTeam();
|
||||
const subdomain = await team.provisionSubdomain('testy');
|
||||
expect(subdomain).toEqual('testy');
|
||||
expect(team.subdomain).toEqual('testy');
|
||||
});
|
||||
|
||||
it('should set subdomain with append if unavailable', async () => {
|
||||
await buildTeam({ subdomain: 'myteam' });
|
||||
|
||||
const team = await buildTeam();
|
||||
const subdomain = await team.provisionSubdomain('myteam');
|
||||
expect(subdomain).toEqual('myteam1');
|
||||
expect(team.subdomain).toEqual('myteam1');
|
||||
});
|
||||
|
||||
it('should do nothing if subdomain already set', async () => {
|
||||
const team = await buildTeam({ subdomain: 'example' });
|
||||
const subdomain = await team.provisionSubdomain('myteam');
|
||||
expect(subdomain).toEqual('example');
|
||||
expect(team.subdomain).toEqual('example');
|
||||
});
|
||||
@@ -4,8 +4,9 @@ import { Helmet } from 'react-helmet';
|
||||
import styled from 'styled-components';
|
||||
import Grid from 'styled-components-grid';
|
||||
import breakpoint from 'styled-components-breakpoint';
|
||||
import Notice from '../../shared/components/Notice';
|
||||
import AuthErrors from './components/AuthErrors';
|
||||
import Hero from './components/Hero';
|
||||
import HeroText from './components/HeroText';
|
||||
import Centered from './components/Centered';
|
||||
import SigninButtons from './components/SigninButtons';
|
||||
import SlackLogo from '../../shared/components/SlackLogo';
|
||||
@@ -36,24 +37,7 @@ function Home(props: Props) {
|
||||
<p>
|
||||
<SigninButtons {...props} />
|
||||
</p>
|
||||
{props.notice === 'google-hd' && (
|
||||
<Notice>
|
||||
Sorry, Google sign in cannot be used with a personal email. Please
|
||||
try signing in with your company Google account.
|
||||
</Notice>
|
||||
)}
|
||||
{props.notice === 'hd-not-allowed' && (
|
||||
<Notice>
|
||||
Sorry, your Google apps domain is not allowed. Please try again
|
||||
with an allowed company domain.
|
||||
</Notice>
|
||||
)}
|
||||
{props.notice === 'auth-error' && (
|
||||
<Notice>
|
||||
Authentication failed - we were unable to sign you in at this
|
||||
time. Please try again.
|
||||
</Notice>
|
||||
)}
|
||||
<AuthErrors notice={props.notice} />
|
||||
</Hero>
|
||||
<Mask>
|
||||
<Features>
|
||||
@@ -80,7 +64,7 @@ function Home(props: Props) {
|
||||
</Grid.Unit>
|
||||
<Feature size={{ tablet: 2 / 3 }}>
|
||||
<Screenshot
|
||||
srcset="screenshot.png, screenshot@2x.png 2x"
|
||||
srcSet="screenshot.png, screenshot@2x.png 2x"
|
||||
src="/screenshot@2x.png"
|
||||
alt="Outline Screenshot"
|
||||
/>
|
||||
@@ -232,13 +216,4 @@ const Footer = styled.div`
|
||||
`};
|
||||
`;
|
||||
|
||||
const HeroText = styled.p`
|
||||
font-size: 22px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
text-align: left;
|
||||
max-width: 600px;
|
||||
margin-bottom: 2em;
|
||||
`;
|
||||
|
||||
export default Home;
|
||||
|
||||
81
server/pages/SubdomainSignin.js
Normal file
81
server/pages/SubdomainSignin.js
Normal file
@@ -0,0 +1,81 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import styled from 'styled-components';
|
||||
import Grid from 'styled-components-grid';
|
||||
import Hero from './components/Hero';
|
||||
import HeroText from './components/HeroText';
|
||||
import SigninButtons from './components/SigninButtons';
|
||||
import AuthErrors from './components/AuthErrors';
|
||||
import Centered from './components/Centered';
|
||||
import { Team } from '../models';
|
||||
|
||||
type Props = {
|
||||
team: Team,
|
||||
notice?: 'google-hd' | 'auth-error' | 'hd-not-allowed',
|
||||
lastSignedIn: string,
|
||||
googleSigninEnabled: boolean,
|
||||
slackSigninEnabled: boolean,
|
||||
hostname: string,
|
||||
};
|
||||
|
||||
function SubdomainSignin({
|
||||
team,
|
||||
lastSignedIn,
|
||||
notice,
|
||||
googleSigninEnabled,
|
||||
slackSigninEnabled,
|
||||
hostname,
|
||||
}: Props) {
|
||||
googleSigninEnabled = !!team.googleId && googleSigninEnabled;
|
||||
slackSigninEnabled = !!team.slackId && slackSigninEnabled;
|
||||
|
||||
// only show the "last signed in" hint if there is more than one option available
|
||||
const signinHint =
|
||||
googleSigninEnabled && slackSigninEnabled ? lastSignedIn : undefined;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Helmet>
|
||||
<title>Outline - Sign in to {team.name}</title>
|
||||
</Helmet>
|
||||
<Grid>
|
||||
<Hero>
|
||||
<h1>{lastSignedIn ? 'Welcome back,' : 'Hey there,'}</h1>
|
||||
<HeroText>
|
||||
Sign in with your team account to continue to {team.name}.
|
||||
<Subdomain>{hostname}</Subdomain>
|
||||
</HeroText>
|
||||
<p>
|
||||
<SigninButtons
|
||||
googleSigninEnabled={googleSigninEnabled}
|
||||
slackSigninEnabled={slackSigninEnabled}
|
||||
lastSignedIn={signinHint}
|
||||
/>
|
||||
</p>
|
||||
<AuthErrors notice={notice} />
|
||||
</Hero>
|
||||
</Grid>
|
||||
<Alternative>
|
||||
<p>
|
||||
Trying to create or sign in to a different team?{' '}
|
||||
<a href={process.env.URL}>Head to the homepage</a>.
|
||||
</p>
|
||||
</Alternative>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
const Subdomain = styled.span`
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
margin-top: 0;
|
||||
`;
|
||||
|
||||
const Alternative = styled(Centered)`
|
||||
padding: 2em 0;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
export default SubdomainSignin;
|
||||
32
server/pages/components/AuthErrors.js
Normal file
32
server/pages/components/AuthErrors.js
Normal file
@@ -0,0 +1,32 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import Notice from '../../../shared/components/Notice';
|
||||
|
||||
type Props = {
|
||||
notice?: string,
|
||||
};
|
||||
|
||||
export default function AuthErrors({ notice }: Props) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{notice === 'google-hd' && (
|
||||
<Notice>
|
||||
Sorry, Google sign in cannot be used with a personal email. Please try
|
||||
signing in with your company Google account.
|
||||
</Notice>
|
||||
)}
|
||||
{notice === 'hd-not-allowed' && (
|
||||
<Notice>
|
||||
Sorry, your Google apps domain is not allowed. Please try again with
|
||||
an allowed company domain.
|
||||
</Notice>
|
||||
)}
|
||||
{notice === 'auth-error' && (
|
||||
<Notice>
|
||||
Authentication failed - we were unable to sign you in at this time.
|
||||
Please try again.
|
||||
</Notice>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
@@ -11,6 +11,11 @@ const Hero = styled(Centered)`
|
||||
font-size: 3.5em;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2.5em;
|
||||
line-height: 1em;
|
||||
}
|
||||
`;
|
||||
|
||||
export default Hero;
|
||||
|
||||
13
server/pages/components/HeroText.js
Normal file
13
server/pages/components/HeroText.js
Normal file
@@ -0,0 +1,13 @@
|
||||
// @flow
|
||||
import styled from 'styled-components';
|
||||
|
||||
const HeroText = styled.p`
|
||||
font-size: 22px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
text-align: left;
|
||||
max-width: 600px;
|
||||
margin-bottom: 2em;
|
||||
`;
|
||||
|
||||
export default HeroText;
|
||||
@@ -15,9 +15,11 @@ export const screenshotUrl = `${process.env.URL}/screenshot.png`;
|
||||
|
||||
type Props = {
|
||||
children?: React.Node,
|
||||
sessions: Object,
|
||||
loggedIn: boolean,
|
||||
};
|
||||
|
||||
function Layout({ children }: Props) {
|
||||
function Layout({ children, loggedIn, sessions }: Props) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -65,7 +67,7 @@ function Layout({ children }: Props) {
|
||||
{'{{CSS}}'}
|
||||
</head>
|
||||
<Body>
|
||||
<TopNavigation />
|
||||
<TopNavigation sessions={sessions} loggedIn={loggedIn} />
|
||||
{children}
|
||||
<BottomNavigation />
|
||||
</Body>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { sortBy } from 'lodash';
|
||||
import styled from 'styled-components';
|
||||
import breakpoint from 'styled-components-breakpoint';
|
||||
import Centered from './Centered';
|
||||
import TeamLogo from '../../../shared/components/TeamLogo';
|
||||
import { fadeAndScaleIn } from '../../../shared/styles/animations';
|
||||
import {
|
||||
developers,
|
||||
changelog,
|
||||
features,
|
||||
about,
|
||||
privacy,
|
||||
githubUrl,
|
||||
@@ -13,13 +17,29 @@ import {
|
||||
spectrumUrl,
|
||||
} from '../../../shared/utils/routeHelpers';
|
||||
|
||||
function TopNavigation() {
|
||||
type Sessions = {
|
||||
[subdomain: string]: {
|
||||
name: string,
|
||||
logoUrl: string,
|
||||
expires: string,
|
||||
url: string,
|
||||
},
|
||||
};
|
||||
|
||||
type Props = {
|
||||
sessions: ?Sessions,
|
||||
loggedIn: boolean,
|
||||
};
|
||||
|
||||
function TopNavigation({ sessions, loggedIn }: Props) {
|
||||
const orderedSessions = sortBy(sessions, 'name');
|
||||
|
||||
return (
|
||||
<Nav>
|
||||
<Brand href="/">Outline</Brand>
|
||||
<Brand href={process.env.URL}>Outline</Brand>
|
||||
<Menu>
|
||||
<MenuItemDesktop>
|
||||
<a href="/#features">Features</a>
|
||||
<a href={features()}>Features</a>
|
||||
</MenuItemDesktop>
|
||||
<MenuItemDesktop>
|
||||
<a href={about()}>About</a>
|
||||
@@ -33,9 +53,37 @@ function TopNavigation() {
|
||||
<MenuItem>
|
||||
<a href={developers()}>API</a>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<a href="/#signin">Sign In</a>
|
||||
</MenuItem>
|
||||
{loggedIn ? (
|
||||
<React.Fragment>
|
||||
{process.env.SUBDOMAINS_ENABLED === 'true' ? (
|
||||
<MenuItem highlighted>
|
||||
<a>Your Teams</a>
|
||||
<ol>
|
||||
{orderedSessions.map(session => (
|
||||
<MenuItem key={session.url}>
|
||||
<a href={`${session.url}/dashboard`}>
|
||||
<TeamLogo
|
||||
src={session.logoUrl}
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
{session.name}
|
||||
</a>
|
||||
</MenuItem>
|
||||
))}
|
||||
</ol>
|
||||
</MenuItem>
|
||||
) : (
|
||||
<MenuItem highlighted>
|
||||
<a href="/dashboard">Dashboard</a>
|
||||
</MenuItem>
|
||||
)}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<MenuItem>
|
||||
<a href="/#signin">Sign In</a>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Menu>
|
||||
</Nav>
|
||||
);
|
||||
@@ -74,13 +122,8 @@ const MenuLinkStyle = props => `
|
||||
}
|
||||
`;
|
||||
|
||||
const Menu = styled.ul`
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
`;
|
||||
|
||||
const MenuItem = styled.li`
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin: 0 0 0 40px;
|
||||
|
||||
@@ -89,6 +132,34 @@ const MenuItem = styled.li`
|
||||
}
|
||||
|
||||
${MenuLinkStyle};
|
||||
|
||||
${props =>
|
||||
props.highlighted &&
|
||||
`
|
||||
position: relative;
|
||||
border: 2px solid ${props.theme.slate};
|
||||
border-radius: 4px;
|
||||
padding: 6px 8px;
|
||||
margin-top: -6px;
|
||||
margin-bottom: -6px;
|
||||
|
||||
&:hover {
|
||||
border: 2px solid ${props.theme.slateDark};
|
||||
|
||||
> a {
|
||||
color: ${props.theme.slateDark};
|
||||
}
|
||||
}
|
||||
|
||||
> a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
`};
|
||||
|
||||
&:hover ol {
|
||||
animation: ${fadeAndScaleIn} 200ms ease;
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
const MenuItemDesktop = styled(MenuItem)`
|
||||
@@ -99,6 +170,42 @@ const MenuItemDesktop = styled(MenuItem)`
|
||||
`};
|
||||
`;
|
||||
|
||||
const Menu = styled.ul`
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
ol {
|
||||
display: none;
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
right: 0;
|
||||
top: 34px;
|
||||
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
min-width: 160px;
|
||||
padding: 0 0.5em;
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 4px 8px rgba(0, 0, 0, 0.08),
|
||||
0 2px 4px rgba(0, 0, 0, 0.08);
|
||||
|
||||
${MenuItem} {
|
||||
padding: 0.5em 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
${MenuItem} a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
${TeamLogo} {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Nav = styled(Centered)`
|
||||
display: flex;
|
||||
padding: 20px 0;
|
||||
|
||||
@@ -8,7 +8,7 @@ import SlackLogo from '../../../shared/components/SlackLogo';
|
||||
import breakpoint from 'styled-components-breakpoint';
|
||||
|
||||
type Props = {
|
||||
lastSignedIn: string,
|
||||
lastSignedIn?: string,
|
||||
googleSigninEnabled: boolean,
|
||||
slackSigninEnabled: boolean,
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ async function present(ctx: Object, collection: Collection) {
|
||||
|
||||
const data = {
|
||||
id: collection.id,
|
||||
url: collection.getUrl(),
|
||||
url: collection.url,
|
||||
name: collection.name,
|
||||
description: collection.description,
|
||||
color: collection.color || '#4E5C6E',
|
||||
|
||||
@@ -25,7 +25,7 @@ async function present(ctx: Object, document: Document, options: ?Options) {
|
||||
|
||||
const data = {
|
||||
id: document.id,
|
||||
url: document.getUrl(),
|
||||
url: document.url,
|
||||
urlId: document.urlId,
|
||||
title: document.title,
|
||||
text: document.text,
|
||||
|
||||
@@ -6,7 +6,7 @@ function present(ctx: Object, share: Share) {
|
||||
return {
|
||||
id: share.id,
|
||||
documentTitle: share.document.title,
|
||||
documentUrl: share.document.getUrl(),
|
||||
documentUrl: share.document.url,
|
||||
url: `${process.env.URL}/share/${share.id}`,
|
||||
createdBy: presentUser(ctx, share.user),
|
||||
createdAt: share.createdAt,
|
||||
|
||||
@@ -11,7 +11,7 @@ function present(document: Document, context?: string) {
|
||||
return {
|
||||
color: document.collection.color,
|
||||
title: document.title,
|
||||
title_link: `${process.env.URL}${document.getUrl()}`,
|
||||
title_link: `${process.env.URL}${document.url}`,
|
||||
footer: document.collection.name,
|
||||
text,
|
||||
ts: document.getTimestamp(),
|
||||
|
||||
@@ -7,11 +7,12 @@ function present(ctx: Object, team: Team) {
|
||||
return {
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
avatarUrl:
|
||||
team.avatarUrl || (team.slackData ? team.slackData.image_88 : null),
|
||||
avatarUrl: team.logoUrl,
|
||||
slackConnected: !!team.slackId,
|
||||
googleConnected: !!team.googleId,
|
||||
sharing: team.sharing,
|
||||
subdomain: team.subdomain,
|
||||
url: team.url,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,12 @@ import Koa from 'koa';
|
||||
import Router from 'koa-router';
|
||||
import sendfile from 'koa-sendfile';
|
||||
import serve from 'koa-static';
|
||||
import subdomainRedirect from './middlewares/subdomainRedirect';
|
||||
import parseDomain from 'parse-domain';
|
||||
import apexRedirect from './middlewares/apexRedirect';
|
||||
import renderpage from './utils/renderpage';
|
||||
import { robotsResponse } from './utils/robots';
|
||||
import { NotFoundError } from './errors';
|
||||
import { Team } from './models';
|
||||
|
||||
import Home from './pages/Home';
|
||||
import About from './pages/About';
|
||||
@@ -16,6 +18,7 @@ import Changelog from './pages/Changelog';
|
||||
import Privacy from './pages/Privacy';
|
||||
import Pricing from './pages/Pricing';
|
||||
import Api from './pages/Api';
|
||||
import SubdomainSignin from './pages/SubdomainSignin';
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
const koa = new Koa();
|
||||
@@ -63,21 +66,47 @@ router.get('/changelog', async ctx => {
|
||||
// home page
|
||||
router.get('/', async ctx => {
|
||||
const lastSignedIn = ctx.cookies.get('lastSignedIn');
|
||||
const domain = parseDomain(ctx.request.hostname);
|
||||
const subdomain = domain ? domain.subdomain : undefined;
|
||||
const accessToken = ctx.cookies.get('accessToken');
|
||||
|
||||
ctx.set('Cache-Control', 'no-cache');
|
||||
|
||||
if (accessToken) {
|
||||
await renderapp(ctx);
|
||||
} else {
|
||||
await renderpage(
|
||||
ctx,
|
||||
<Home
|
||||
notice={ctx.request.query.notice}
|
||||
lastSignedIn={lastSignedIn}
|
||||
googleSigninEnabled={!!process.env.GOOGLE_CLIENT_ID}
|
||||
slackSigninEnabled={!!process.env.SLACK_KEY}
|
||||
/>
|
||||
);
|
||||
return renderapp(ctx);
|
||||
}
|
||||
|
||||
if (subdomain) {
|
||||
const team = await Team.find({
|
||||
where: { subdomain },
|
||||
});
|
||||
if (team && process.env.SUBDOMAINS_ENABLED) {
|
||||
return renderpage(
|
||||
ctx,
|
||||
<SubdomainSignin
|
||||
team={team}
|
||||
notice={ctx.request.query.notice}
|
||||
lastSignedIn={lastSignedIn}
|
||||
googleSigninEnabled={!!process.env.GOOGLE_CLIENT_ID}
|
||||
slackSigninEnabled={!!process.env.SLACK_KEY}
|
||||
hostname={ctx.request.hostname}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
ctx.redirect(`${process.env.URL}?notice=invalid-auth`);
|
||||
return;
|
||||
}
|
||||
|
||||
return renderpage(
|
||||
ctx,
|
||||
<Home
|
||||
notice={ctx.request.query.notice}
|
||||
lastSignedIn={lastSignedIn}
|
||||
googleSigninEnabled={!!process.env.GOOGLE_CLIENT_ID}
|
||||
slackSigninEnabled={!!process.env.SLACK_KEY}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
// Other
|
||||
@@ -90,7 +119,7 @@ router.get('*', async ctx => {
|
||||
});
|
||||
|
||||
// middleware
|
||||
koa.use(subdomainRedirect());
|
||||
koa.use(apexRedirect());
|
||||
koa.use(router.routes());
|
||||
|
||||
export default koa;
|
||||
|
||||
@@ -48,7 +48,7 @@ class Slack {
|
||||
{
|
||||
color: collection.color,
|
||||
title: collection.name,
|
||||
title_link: `${process.env.URL}${collection.getUrl()}`,
|
||||
title_link: `${process.env.URL}${collection.url}`,
|
||||
text: collection.description,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -13,10 +13,17 @@ import theme from '../../shared/styles/theme';
|
||||
const sheet = new ServerStyleSheet();
|
||||
|
||||
export default function renderpage(ctx: Object, children: React.Node) {
|
||||
const sessions = JSON.parse(ctx.cookies.get('sessions') || '{}');
|
||||
const loggedIn = !!(
|
||||
ctx.cookies.get('accessToken') || Object.keys(sessions).length
|
||||
);
|
||||
|
||||
const html = ReactDOMServer.renderToString(
|
||||
<StyleSheetManager sheet={sheet.instance}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<Layout>{children}</Layout>
|
||||
<Layout sessions={sessions} loggedIn={loggedIn}>
|
||||
{children}
|
||||
</Layout>
|
||||
</ThemeProvider>
|
||||
</StyleSheetManager>
|
||||
);
|
||||
|
||||
@@ -10,4 +10,4 @@ const snap = children => {
|
||||
};
|
||||
|
||||
global.localStorage = localStorage;
|
||||
global.snap = snap;
|
||||
global.snap = snap;
|
||||
66
shared/utils/domains.js
Normal file
66
shared/utils/domains.js
Normal file
@@ -0,0 +1,66 @@
|
||||
// @flow
|
||||
import parseDomain from 'parse-domain';
|
||||
|
||||
export function stripSubdomain(hostname: string) {
|
||||
const parsed = parseDomain(hostname);
|
||||
if (!parsed) return hostname;
|
||||
|
||||
if (parsed.tld) return `${parsed.domain}.${parsed.tld}`;
|
||||
return parsed.domain;
|
||||
}
|
||||
|
||||
export const RESERVED_SUBDOMAINS = [
|
||||
'about',
|
||||
'account',
|
||||
'admin',
|
||||
'advertising',
|
||||
'api',
|
||||
'assets',
|
||||
'archive',
|
||||
'beta',
|
||||
'billing',
|
||||
'blog',
|
||||
'cache',
|
||||
'cdn',
|
||||
'code',
|
||||
'community',
|
||||
'dashboard',
|
||||
'developer',
|
||||
'developers',
|
||||
'forum',
|
||||
'help',
|
||||
'home',
|
||||
'http',
|
||||
'https',
|
||||
'imap',
|
||||
'localhost',
|
||||
'mail',
|
||||
'mobile',
|
||||
'news',
|
||||
'newsletter',
|
||||
'ns1',
|
||||
'ns2',
|
||||
'ns3',
|
||||
'ns4',
|
||||
'password',
|
||||
'profile',
|
||||
'sandbox',
|
||||
'script',
|
||||
'scripts',
|
||||
'setup',
|
||||
'signin',
|
||||
'signup',
|
||||
'smtp',
|
||||
'support',
|
||||
'status',
|
||||
'static',
|
||||
'stats',
|
||||
'test',
|
||||
'update',
|
||||
'updates',
|
||||
'www',
|
||||
'www1',
|
||||
'www2',
|
||||
'www3',
|
||||
'www4',
|
||||
];
|
||||
17
shared/utils/domains.test.js
Normal file
17
shared/utils/domains.test.js
Normal file
@@ -0,0 +1,17 @@
|
||||
/* eslint-disable flowtype/require-valid-file-annotation */
|
||||
import { stripSubdomain } from './domains';
|
||||
|
||||
describe('#stripSubdomain', () => {
|
||||
test('to work with localhost', () => {
|
||||
expect(stripSubdomain('localhost')).toBe('localhost');
|
||||
});
|
||||
test('to return domains without a subdomain', () => {
|
||||
expect(stripSubdomain('example')).toBe('example');
|
||||
expect(stripSubdomain('example.com')).toBe('example.com');
|
||||
expect(stripSubdomain('example.org:3000')).toBe('example.org');
|
||||
});
|
||||
test('to remove subdomains', () => {
|
||||
expect(stripSubdomain('test.example.com')).toBe('example.com');
|
||||
expect(stripSubdomain('test.example.com:3000')).toBe('example.com');
|
||||
});
|
||||
});
|
||||
@@ -53,22 +53,26 @@ export function mailToUrl(): string {
|
||||
return 'mailto:hello@getoutline.com';
|
||||
}
|
||||
|
||||
export function features(): string {
|
||||
return `${process.env.URL}/#features`;
|
||||
}
|
||||
|
||||
export function developers(): string {
|
||||
return '/developers';
|
||||
return `${process.env.URL}/developers`;
|
||||
}
|
||||
|
||||
export function changelog(): string {
|
||||
return '/changelog';
|
||||
return `${process.env.URL}/changelog`;
|
||||
}
|
||||
|
||||
export function signin(service: string = 'slack'): string {
|
||||
return `/auth/${service}`;
|
||||
return `${process.env.URL}/auth/${service}`;
|
||||
}
|
||||
|
||||
export function about(): string {
|
||||
return '/about';
|
||||
return `${process.env.URL}/about`;
|
||||
}
|
||||
|
||||
export function privacy(): string {
|
||||
return '/privacy';
|
||||
return `${process.env.URL}/privacy`;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@ const definePlugin = new webpack.DefinePlugin({
|
||||
DEPLOYMENT: JSON.stringify(process.env.DEPLOYMENT || 'hosted'),
|
||||
'process.env': {
|
||||
URL: JSON.stringify(process.env.URL),
|
||||
SLACK_KEY: JSON.stringify(process.env.SLACK_KEY)
|
||||
SLACK_KEY: JSON.stringify(process.env.SLACK_KEY),
|
||||
SUBDOMAINS_ENABLED: JSON.stringify(process.env.SUBDOMAINS_ENABLED)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -31,6 +32,9 @@ module.exports = {
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: [
|
||||
path.join(__dirname, 'node_modules')
|
||||
],
|
||||
include: [
|
||||
path.join(__dirname, 'app'),
|
||||
path.join(__dirname, 'shared'),
|
||||
@@ -54,9 +58,7 @@ module.exports = {
|
||||
}),
|
||||
},
|
||||
{ test: /\.md/, loader: 'raw-loader' },
|
||||
],
|
||||
// Silence warning https://github.com/localForage/localForage/issues/599
|
||||
noParse: [new RegExp('node_modules/localforage/dist/localforage.js')],
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
modules: [
|
||||
|
||||
@@ -37,6 +37,7 @@ productionWebpackConfig.plugins = [
|
||||
URL: JSON.stringify(process.env.URL),
|
||||
NODE_ENV: JSON.stringify('production'),
|
||||
GOOGLE_ANALYTICS_ID: JSON.stringify(process.env.GOOGLE_ANALYTICS_ID),
|
||||
SUBDOMAINS_ENABLED: JSON.stringify(process.env.SUBDOMAINS_ENABLED)
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
364
yarn.lock
364
yarn.lock
@@ -87,6 +87,10 @@
|
||||
version "0.6.6"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.6.6.tgz#004b98298d04c7ca3b4f50ca2035d4f60d2eed1b"
|
||||
|
||||
"@sindresorhus/is@^0.7.0":
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
|
||||
|
||||
"@tommoor/remove-markdown@0.3.1":
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@tommoor/remove-markdown/-/remove-markdown-0.3.1.tgz#25e7b845d52fcfadf149a3a6a468a931fee7619b"
|
||||
@@ -155,10 +159,6 @@ acorn-jsx@^3.0.0:
|
||||
dependencies:
|
||||
acorn "^3.0.4"
|
||||
|
||||
acorn@^1.0.3:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-1.2.2.tgz#c8ce27de0acc76d896d2b1fad3df588d9e82f014"
|
||||
|
||||
acorn@^3.0.4:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
|
||||
@@ -417,14 +417,14 @@ assert@^1.1.1:
|
||||
dependencies:
|
||||
util "0.10.3"
|
||||
|
||||
assertion-error@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
|
||||
|
||||
ast-types-flow@0.0.7:
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad"
|
||||
|
||||
ast-types@0.8.15:
|
||||
version "0.8.15"
|
||||
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.8.15.tgz#8eef0827f04dff0ec8857ba925abe3fea6194e52"
|
||||
|
||||
astral-regex@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
|
||||
@@ -1206,10 +1206,6 @@ balanced-match@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
|
||||
base62@0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/base62/-/base62-0.1.1.tgz#7b4174c2f94449753b11c2651c083da841a7b084"
|
||||
|
||||
base64-js@^1.0.2:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886"
|
||||
@@ -1399,6 +1395,10 @@ browser-resolve@^1.11.2:
|
||||
dependencies:
|
||||
resolve "1.1.7"
|
||||
|
||||
browser-stdout@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f"
|
||||
|
||||
browserify-aes@^1.0.0, browserify-aes@^1.0.4:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.0.6.tgz#5e7725dbdef1fd5930d4ebab48567ce451c48a0a"
|
||||
@@ -1569,6 +1569,18 @@ cacache@^10.0.4:
|
||||
unique-filename "^1.1.0"
|
||||
y18n "^4.0.0"
|
||||
|
||||
cacheable-request@^2.1.1:
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d"
|
||||
dependencies:
|
||||
clone-response "1.0.2"
|
||||
get-stream "3.0.0"
|
||||
http-cache-semantics "3.8.1"
|
||||
keyv "3.0.0"
|
||||
lowercase-keys "1.0.0"
|
||||
normalize-url "2.0.1"
|
||||
responselike "1.0.2"
|
||||
|
||||
caller-path@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
|
||||
@@ -1660,6 +1672,17 @@ center-align@^0.1.1:
|
||||
align-text "^0.1.3"
|
||||
lazy-cache "^1.0.3"
|
||||
|
||||
chai@^4.1.2:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5"
|
||||
dependencies:
|
||||
assertion-error "^1.1.0"
|
||||
check-error "^1.0.2"
|
||||
deep-eql "^3.0.1"
|
||||
get-func-name "^2.0.0"
|
||||
pathval "^1.1.0"
|
||||
type-detect "^4.0.5"
|
||||
|
||||
chainsaw@~0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98"
|
||||
@@ -1735,6 +1758,10 @@ charenc@~0.0.1:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
|
||||
|
||||
check-error@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
|
||||
|
||||
cheerio@^0.22.0:
|
||||
version "0.22.0"
|
||||
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e"
|
||||
@@ -1892,6 +1919,12 @@ clone-buffer@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
|
||||
|
||||
clone-response@1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
|
||||
dependencies:
|
||||
mimic-response "^1.0.0"
|
||||
|
||||
clone-stats@^0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1"
|
||||
@@ -2004,6 +2037,10 @@ combined-stream@^1.0.5, combined-stream@~1.0.5:
|
||||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
commander@2.11.0:
|
||||
version "2.11.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
|
||||
|
||||
commander@2.8.x:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
|
||||
@@ -2554,15 +2591,15 @@ debug@2.6.9, debug@^2.6.9:
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.0.1.tgz#0564c612b521dc92d9f2988f0549e34f9c98db64"
|
||||
debug@3.1.0, debug@^3.0.1, debug@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@^3.0.1, debug@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||
debug@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.0.1.tgz#0564c612b521dc92d9f2988f0549e34f9c98db64"
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
@@ -2580,12 +2617,22 @@ decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||
|
||||
decompress-response@^3.2.0:
|
||||
decode-uri-component@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
|
||||
|
||||
decompress-response@^3.2.0, decompress-response@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
|
||||
dependencies:
|
||||
mimic-response "^1.0.0"
|
||||
|
||||
deep-eql@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df"
|
||||
dependencies:
|
||||
type-detect "^4.0.0"
|
||||
|
||||
deep-equal@^1.0.1, deep-equal@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
|
||||
@@ -2696,6 +2743,10 @@ detect-newline@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
|
||||
|
||||
diff@3.3.1:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75"
|
||||
|
||||
diff@3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
|
||||
@@ -3058,14 +3109,6 @@ es-to-primitive@^1.1.1:
|
||||
is-date-object "^1.0.1"
|
||||
is-symbol "^1.0.1"
|
||||
|
||||
es3ify@^0.1.3:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/es3ify/-/es3ify-0.1.4.tgz#ad9fa5df1ae34f3f31e1211b5818b2d51078dfd1"
|
||||
dependencies:
|
||||
esprima-fb "~3001.0001.0000-dev-harmony-fb"
|
||||
jstransform "~3.0.0"
|
||||
through "~2.3.4"
|
||||
|
||||
es5-ext@^0.10.12, es5-ext@^0.10.14, es5-ext@^0.10.30, es5-ext@^0.10.9, es5-ext@~0.10.14, es5-ext@~0.10.2:
|
||||
version "0.10.30"
|
||||
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.30.tgz#7141a16836697dbabfaaaeee41495ce29f52c939"
|
||||
@@ -3138,7 +3181,7 @@ escape-html@~1.0.1, escape-html@~1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||
|
||||
escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
|
||||
escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||
|
||||
@@ -3295,10 +3338,6 @@ eslint@^4.14.0:
|
||||
table "^4.0.1"
|
||||
text-table "~0.2.0"
|
||||
|
||||
esmangle-evaluator@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/esmangle-evaluator/-/esmangle-evaluator-1.0.1.tgz#620d866ef4861b3311f75766d52a8572bb3c6336"
|
||||
|
||||
espree@^3.5.2:
|
||||
version "3.5.2"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.2.tgz#756ada8b979e9dcfcdb30aad8d1a9304a905e1ca"
|
||||
@@ -3306,14 +3345,6 @@ espree@^3.5.2:
|
||||
acorn "^5.2.1"
|
||||
acorn-jsx "^3.0.0"
|
||||
|
||||
esprima-fb@~15001.1001.0-dev-harmony-fb:
|
||||
version "15001.1001.0-dev-harmony-fb"
|
||||
resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz#43beb57ec26e8cf237d3dd8b33e42533577f2659"
|
||||
|
||||
esprima-fb@~3001.0001.0000-dev-harmony-fb, esprima-fb@~3001.1.0-dev-harmony-fb:
|
||||
version "3001.1.0-dev-harmony-fb"
|
||||
resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz#b77d37abcd38ea0b77426bb8bc2922ce6b426411"
|
||||
|
||||
esprima@^2.6.0:
|
||||
version "2.7.3"
|
||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
|
||||
@@ -3510,15 +3541,6 @@ extsprintf@1.3.0, extsprintf@^1.2.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
||||
|
||||
falafel@^1.0.1:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/falafel/-/falafel-1.2.0.tgz#c18d24ef5091174a497f318cd24b026a25cddab4"
|
||||
dependencies:
|
||||
acorn "^1.0.3"
|
||||
foreach "^2.0.5"
|
||||
isarray "0.0.1"
|
||||
object-keys "^1.0.6"
|
||||
|
||||
fancy-log@^1.1.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.0.tgz#45be17d02bb9917d60ccffd4995c999e6c8c9948"
|
||||
@@ -3835,7 +3857,7 @@ fresh@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.0.tgz#f474ca5e6a9246d6fd8e0953cfa9b9c805afa78e"
|
||||
|
||||
from2@^2.1.0:
|
||||
from2@^2.1.0, from2@^2.1.1:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
|
||||
dependencies:
|
||||
@@ -3995,11 +4017,15 @@ get-document@1:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/get-document/-/get-document-1.0.0.tgz#4821bce66f1c24cb0331602be6cb6b12c4f01c4b"
|
||||
|
||||
get-func-name@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
|
||||
|
||||
get-stdin@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
|
||||
|
||||
get-stream@^3.0.0:
|
||||
get-stream@3.0.0, get-stream@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
|
||||
|
||||
@@ -4057,16 +4083,7 @@ glob2base@^0.0.12:
|
||||
dependencies:
|
||||
find-index "^0.1.1"
|
||||
|
||||
glob@^4.3.1:
|
||||
version "4.5.3"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f"
|
||||
dependencies:
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^2.0.1"
|
||||
once "^1.3.0"
|
||||
|
||||
glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2:
|
||||
glob@7.1.2, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
|
||||
dependencies:
|
||||
@@ -4077,6 +4094,15 @@ glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2:
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@^4.3.1:
|
||||
version "4.5.3"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f"
|
||||
dependencies:
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^2.0.1"
|
||||
once "^1.3.0"
|
||||
|
||||
glob@~3.1.21:
|
||||
version "3.1.21"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-3.1.21.tgz#d29e0a055dea5138f4d07ed40e8982e83c2066cd"
|
||||
@@ -4212,6 +4238,28 @@ got@^7.1.0:
|
||||
url-parse-lax "^1.0.0"
|
||||
url-to-options "^1.0.1"
|
||||
|
||||
got@^8.0.1:
|
||||
version "8.3.2"
|
||||
resolved "https://registry.yarnpkg.com/got/-/got-8.3.2.tgz#1d23f64390e97f776cac52e5b936e5f514d2e937"
|
||||
dependencies:
|
||||
"@sindresorhus/is" "^0.7.0"
|
||||
cacheable-request "^2.1.1"
|
||||
decompress-response "^3.3.0"
|
||||
duplexer3 "^0.1.4"
|
||||
get-stream "^3.0.0"
|
||||
into-stream "^3.1.0"
|
||||
is-retry-allowed "^1.1.0"
|
||||
isurl "^1.0.0-alpha5"
|
||||
lowercase-keys "^1.0.0"
|
||||
mimic-response "^1.0.0"
|
||||
p-cancelable "^0.4.0"
|
||||
p-timeout "^2.0.1"
|
||||
pify "^3.0.0"
|
||||
safe-buffer "^5.1.1"
|
||||
timed-out "^4.0.1"
|
||||
url-parse-lax "^3.0.0"
|
||||
url-to-options "^1.0.1"
|
||||
|
||||
graceful-fs@^3.0.0:
|
||||
version "3.0.11"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-3.0.11.tgz#7613c778a1afea62f25c630a086d7f3acbbdd818"
|
||||
@@ -4230,6 +4278,10 @@ graceful-fs@~1.2.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
||||
|
||||
growl@1.10.3:
|
||||
version "1.10.3"
|
||||
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f"
|
||||
|
||||
growly@^1.2.0, growly@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
|
||||
@@ -4422,7 +4474,7 @@ hawk@~6.0.2:
|
||||
hoek "4.x.x"
|
||||
sntp "2.x.x"
|
||||
|
||||
he@1.1.x:
|
||||
he@1.1.1, he@1.1.x:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
|
||||
|
||||
@@ -4596,6 +4648,10 @@ http-assert@^1.1.0:
|
||||
deep-equal "~1.0.1"
|
||||
http-errors "~1.6.1"
|
||||
|
||||
http-cache-semantics@3.8.1:
|
||||
version "3.8.1"
|
||||
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2"
|
||||
|
||||
http-errors@1.4.0, http-errors@^1.2.8, http-errors@^1.3.1:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.4.0.tgz#6c0242dea6b3df7afda153c71089b31c6e82aabf"
|
||||
@@ -4792,13 +4848,6 @@ ini@^1.3.4, ini@~1.3.0:
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e"
|
||||
|
||||
inline-process-browser@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/inline-process-browser/-/inline-process-browser-1.0.0.tgz#46a61b153dd3c9b1624b1a00626edb4f7f414f22"
|
||||
dependencies:
|
||||
falafel "^1.0.1"
|
||||
through2 "^0.6.5"
|
||||
|
||||
inquirer@^3.0.6:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9"
|
||||
@@ -4822,6 +4871,13 @@ interpret@^1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90"
|
||||
|
||||
into-stream@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "http://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6"
|
||||
dependencies:
|
||||
from2 "^2.1.1"
|
||||
p-is-promise "^1.1.0"
|
||||
|
||||
invariant@^2.0.0, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
|
||||
@@ -5100,7 +5156,7 @@ is-resolvable@^1.0.0:
|
||||
dependencies:
|
||||
tryit "^1.0.1"
|
||||
|
||||
is-retry-allowed@^1.0.0:
|
||||
is-retry-allowed@^1.0.0, is-retry-allowed@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34"
|
||||
|
||||
@@ -5647,6 +5703,10 @@ jsesc@~0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
|
||||
|
||||
json-buffer@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
|
||||
|
||||
json-loader@0.5.4:
|
||||
version "0.5.4"
|
||||
resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.4.tgz#8baa1365a632f58a3c46d20175fc6002c96e37de"
|
||||
@@ -5731,14 +5791,6 @@ jsprim@^1.2.2:
|
||||
json-schema "0.2.3"
|
||||
verror "1.10.0"
|
||||
|
||||
jstransform@~3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jstransform/-/jstransform-3.0.0.tgz#a2591ab6cee8d97bf3be830dbfa2313b87cd640b"
|
||||
dependencies:
|
||||
base62 "0.1.1"
|
||||
esprima-fb "~3001.1.0-dev-harmony-fb"
|
||||
source-map "0.1.31"
|
||||
|
||||
jsx-ast-utils@^1.4.0:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz#3867213e8dd79bf1e8f2300c0cfc1efb182c0df1"
|
||||
@@ -5784,6 +5836,12 @@ keygrip@~1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.0.2.tgz#ad3297c557069dea8bcfe7a4fa491b75c5ddeb91"
|
||||
|
||||
keyv@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373"
|
||||
dependencies:
|
||||
json-buffer "3.0.0"
|
||||
|
||||
kind-of@^3.0.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
|
||||
@@ -5992,15 +6050,6 @@ levn@^0.3.0, levn@~0.3.0:
|
||||
prelude-ls "~1.1.2"
|
||||
type-check "~0.3.2"
|
||||
|
||||
lie@3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/lie/-/lie-3.0.2.tgz#ffda21d7bba26f377cad865d3649b2fc8ce39fea"
|
||||
dependencies:
|
||||
es3ify "^0.1.3"
|
||||
immediate "~3.0.5"
|
||||
inline-process-browser "^1.0.0"
|
||||
unreachable-branch-transform "^0.3.0"
|
||||
|
||||
lie@~3.1.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
|
||||
@@ -6126,12 +6175,6 @@ loader-utils@^1.0.2, loader-utils@^1.1.0:
|
||||
emojis-list "^2.0.0"
|
||||
json5 "^0.5.0"
|
||||
|
||||
localforage@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.5.0.tgz#6b994e19b56611fa85df3992df397ac4ab66e815"
|
||||
dependencies:
|
||||
lie "3.0.2"
|
||||
|
||||
locate-path@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
|
||||
@@ -6490,7 +6533,7 @@ lower-case@^1.1.0, lower-case@^1.1.1, lower-case@^1.1.2:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
|
||||
|
||||
lowercase-keys@^1.0.0:
|
||||
lowercase-keys@1.0.0, lowercase-keys@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
|
||||
|
||||
@@ -6780,7 +6823,7 @@ mississippi@^2.0.0:
|
||||
stream-each "^1.1.0"
|
||||
through2 "^2.0.0"
|
||||
|
||||
"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
|
||||
mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
dependencies:
|
||||
@@ -6800,6 +6843,21 @@ mobx@^3.1.9:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/mobx/-/mobx-3.2.2.tgz#aa671459bededfd9880c948889a3f62bce09279c"
|
||||
|
||||
mocha@^4.0.1:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.1.0.tgz#7d86cfbcf35cb829e2754c32e17355ec05338794"
|
||||
dependencies:
|
||||
browser-stdout "1.3.0"
|
||||
commander "2.11.0"
|
||||
debug "3.1.0"
|
||||
diff "3.3.1"
|
||||
escape-string-regexp "1.0.5"
|
||||
glob "7.1.2"
|
||||
growl "1.10.3"
|
||||
he "1.1.1"
|
||||
mkdirp "0.5.1"
|
||||
supports-color "4.4.0"
|
||||
|
||||
moment-timezone@^0.5.0:
|
||||
version "0.5.14"
|
||||
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.14.tgz#4eb38ff9538b80108ba467a458f3ed4268ccfcb1"
|
||||
@@ -7112,6 +7170,14 @@ normalize-range@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
|
||||
|
||||
normalize-url@2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6"
|
||||
dependencies:
|
||||
prepend-http "^2.0.0"
|
||||
query-string "^5.0.1"
|
||||
sort-keys "^2.0.0"
|
||||
|
||||
normalize-url@^1.4.0:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c"
|
||||
@@ -7198,7 +7264,7 @@ object-is@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6"
|
||||
|
||||
object-keys@^1.0.10, object-keys@^1.0.6, object-keys@^1.0.8:
|
||||
object-keys@^1.0.10, object-keys@^1.0.8:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d"
|
||||
|
||||
@@ -7397,10 +7463,18 @@ p-cancelable@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa"
|
||||
|
||||
p-cancelable@^0.4.0:
|
||||
version "0.4.1"
|
||||
resolved "http://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0"
|
||||
|
||||
p-finally@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
|
||||
|
||||
p-is-promise@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e"
|
||||
|
||||
p-limit@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc"
|
||||
@@ -7421,6 +7495,12 @@ p-timeout@^1.1.1:
|
||||
dependencies:
|
||||
p-finally "^1.0.0"
|
||||
|
||||
p-timeout@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038"
|
||||
dependencies:
|
||||
p-finally "^1.0.0"
|
||||
|
||||
package-json@^1.0.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/package-json/-/package-json-1.2.0.tgz#c8ecac094227cdf76a316874ed05e27cc939a0e0"
|
||||
@@ -7460,6 +7540,15 @@ parse-asn1@^5.0.0:
|
||||
evp_bytestokey "^1.0.0"
|
||||
pbkdf2 "^3.0.3"
|
||||
|
||||
parse-domain@2.1.6:
|
||||
version "2.1.6"
|
||||
resolved "https://registry.yarnpkg.com/parse-domain/-/parse-domain-2.1.6.tgz#3baac0a1c6b7028dfea0013c99a83a1ecd806ed0"
|
||||
dependencies:
|
||||
chai "^4.1.2"
|
||||
got "^8.0.1"
|
||||
mkdirp "^0.5.1"
|
||||
mocha "^4.0.1"
|
||||
|
||||
parse-entities@^1.0.2:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/parse-entities/-/parse-entities-1.1.1.tgz#8112d88471319f27abae4d64964b122fe4e1b890"
|
||||
@@ -7585,6 +7674,10 @@ path-type@^2.0.0:
|
||||
dependencies:
|
||||
pify "^2.0.0"
|
||||
|
||||
pathval@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0"
|
||||
|
||||
pause-stream@0.0.11:
|
||||
version "0.0.11"
|
||||
resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445"
|
||||
@@ -7976,6 +8069,10 @@ prepend-http@^1.0.0, prepend-http@^1.0.1:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
|
||||
|
||||
prepend-http@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
|
||||
|
||||
preserve@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
|
||||
@@ -8008,7 +8105,7 @@ prismjs@^1.13.0:
|
||||
optionalDependencies:
|
||||
clipboard "^2.0.0"
|
||||
|
||||
private@^0.1.6, private@^0.1.7, private@~0.1.5:
|
||||
private@^0.1.6, private@^0.1.7:
|
||||
version "0.1.7"
|
||||
resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1"
|
||||
|
||||
@@ -8159,6 +8256,14 @@ query-string@^4.1.0, query-string@^4.3.4:
|
||||
object-assign "^4.1.0"
|
||||
strict-uri-encode "^1.0.0"
|
||||
|
||||
query-string@^5.0.1:
|
||||
version "5.1.1"
|
||||
resolved "http://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb"
|
||||
dependencies:
|
||||
decode-uri-component "^0.2.0"
|
||||
object-assign "^4.1.0"
|
||||
strict-uri-encode "^1.0.0"
|
||||
|
||||
querystring-es3@^0.2.0:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
|
||||
@@ -8521,15 +8626,6 @@ realpath-native@^1.0.0:
|
||||
dependencies:
|
||||
util.promisify "^1.0.0"
|
||||
|
||||
recast@^0.10.1:
|
||||
version "0.10.43"
|
||||
resolved "https://registry.yarnpkg.com/recast/-/recast-0.10.43.tgz#b95d50f6d60761a5f6252e15d80678168491ce7f"
|
||||
dependencies:
|
||||
ast-types "0.8.15"
|
||||
esprima-fb "~15001.1001.0-dev-harmony-fb"
|
||||
private "~0.1.5"
|
||||
source-map "~0.5.0"
|
||||
|
||||
rechoir@^0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
|
||||
@@ -8850,6 +8946,12 @@ response-time@~2.3.1:
|
||||
depd "~1.1.0"
|
||||
on-headers "~1.0.1"
|
||||
|
||||
responselike@1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"
|
||||
dependencies:
|
||||
lowercase-keys "^1.0.0"
|
||||
|
||||
restore-cursor@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
|
||||
@@ -9390,6 +9492,12 @@ sort-keys@^1.0.0:
|
||||
dependencies:
|
||||
is-plain-obj "^1.0.0"
|
||||
|
||||
sort-keys@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128"
|
||||
dependencies:
|
||||
is-plain-obj "^1.0.0"
|
||||
|
||||
source-list-map@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085"
|
||||
@@ -9416,12 +9524,6 @@ source-map-support@^0.5.0:
|
||||
dependencies:
|
||||
source-map "^0.6.0"
|
||||
|
||||
source-map@0.1.31:
|
||||
version "0.1.31"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.31.tgz#9f704d0d69d9e138a81badf6ebb4fde33d151c61"
|
||||
dependencies:
|
||||
amdefine ">=0.0.4"
|
||||
|
||||
source-map@0.1.x:
|
||||
version "0.1.43"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346"
|
||||
@@ -9438,7 +9540,7 @@ source-map@0.5.6:
|
||||
version "0.5.6"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
|
||||
|
||||
source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.6:
|
||||
source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.6:
|
||||
version "0.5.7"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||
|
||||
@@ -9745,6 +9847,12 @@ stylis@^3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.0.tgz#016fa239663d77f868fef5b67cf201c4b7c701e1"
|
||||
|
||||
supports-color@4.4.0, supports-color@^4.0.0, supports-color@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e"
|
||||
dependencies:
|
||||
has-flag "^2.0.0"
|
||||
|
||||
supports-color@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
|
||||
@@ -9755,12 +9863,6 @@ supports-color@^3.1.2, supports-color@^3.2.3:
|
||||
dependencies:
|
||||
has-flag "^1.0.0"
|
||||
|
||||
supports-color@^4.0.0, supports-color@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e"
|
||||
dependencies:
|
||||
has-flag "^2.0.0"
|
||||
|
||||
supports-color@^4.2.1:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b"
|
||||
@@ -9889,7 +9991,7 @@ throat@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a"
|
||||
|
||||
through2@^0.6.1, through2@^0.6.2, through2@^0.6.5:
|
||||
through2@^0.6.1:
|
||||
version "0.6.5"
|
||||
resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48"
|
||||
dependencies:
|
||||
@@ -9903,7 +10005,7 @@ through2@^2.0.0, through2@^2.0.1:
|
||||
readable-stream "^2.1.5"
|
||||
xtend "~4.0.1"
|
||||
|
||||
through@2, through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.1, through@~2.3.4:
|
||||
through@2, through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.1:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
|
||||
@@ -9925,7 +10027,7 @@ timed-out@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-2.0.0.tgz#f38b0ae81d3747d628001f41dafc652ace671c0a"
|
||||
|
||||
timed-out@^4.0.0:
|
||||
timed-out@^4.0.0, timed-out@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
|
||||
|
||||
@@ -10085,6 +10187,10 @@ type-check@~0.3.2:
|
||||
dependencies:
|
||||
prelude-ls "~1.1.2"
|
||||
|
||||
type-detect@^4.0.0, type-detect@^4.0.5:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
|
||||
|
||||
type-is@^1.5.5, type-is@^1.6.14, type-is@~1.6.6:
|
||||
version "1.6.15"
|
||||
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410"
|
||||
@@ -10273,14 +10379,6 @@ unpipe@1.0.0, unpipe@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||
|
||||
unreachable-branch-transform@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/unreachable-branch-transform/-/unreachable-branch-transform-0.3.0.tgz#d99cc4c6e746d264928845b611db54b0f3474caa"
|
||||
dependencies:
|
||||
esmangle-evaluator "^1.0.0"
|
||||
recast "^0.10.1"
|
||||
through2 "^0.6.2"
|
||||
|
||||
unzipper@^0.8.11:
|
||||
version "0.8.13"
|
||||
resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.8.13.tgz#ea889ca10cdda4dcf604e632f19fc5f81871beae"
|
||||
@@ -10343,6 +10441,12 @@ url-parse-lax@^1.0.0:
|
||||
dependencies:
|
||||
prepend-http "^1.0.1"
|
||||
|
||||
url-parse-lax@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c"
|
||||
dependencies:
|
||||
prepend-http "^2.0.0"
|
||||
|
||||
url-to-options@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9"
|
||||
|
||||
Reference in New Issue
Block a user