Merge branch 'master' into empty-state
This commit is contained in:
@@ -291,6 +291,10 @@ const StyledEditor = styled(Editor)`
|
||||
position: relative;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: ${({ readOnly }) => (readOnly ? 'underline' : 'none')};
|
||||
}
|
||||
|
||||
li p {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
@@ -332,6 +336,10 @@ const StyledEditor = styled(Editor)`
|
||||
td {
|
||||
padding: 5px 20px 5px 0;
|
||||
}
|
||||
|
||||
b, strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
`;
|
||||
|
||||
export default MarkdownEditor;
|
||||
|
||||
@@ -90,12 +90,16 @@ export default class BlockInsert extends Component {
|
||||
this.left = Math.round(boxRect.left + window.scrollX - 20);
|
||||
};
|
||||
|
||||
onClickBlock = (
|
||||
insertBlock = (
|
||||
ev: SyntheticEvent,
|
||||
type: string | Object,
|
||||
wrapBlock?: string
|
||||
options: {
|
||||
type: string | Object,
|
||||
wrapper?: string | Object,
|
||||
append?: string | Object,
|
||||
}
|
||||
) => {
|
||||
ev.preventDefault();
|
||||
const { type, wrapper, append } = options;
|
||||
let { state } = this.props;
|
||||
let transform = state.transform();
|
||||
const { document } = state;
|
||||
@@ -112,7 +116,8 @@ export default class BlockInsert extends Component {
|
||||
|
||||
transform = transform.insertBlock(type);
|
||||
|
||||
if (wrapBlock) transform = transform.wrapBlock(wrapBlock);
|
||||
if (wrapper) transform = transform.wrapBlock(wrapper);
|
||||
if (append) transform = transform.insertBlock(append);
|
||||
|
||||
state = transform.focus().apply();
|
||||
this.props.onChange(state);
|
||||
@@ -134,6 +139,7 @@ export default class BlockInsert extends Component {
|
||||
render() {
|
||||
const style = { top: `${this.top}px`, left: `${this.left}px` };
|
||||
const todo = { type: 'list-item', data: { checked: false } };
|
||||
const rule = { type: 'horizontal-rule', isVoid: true };
|
||||
|
||||
return (
|
||||
<Portal isOpened>
|
||||
@@ -148,9 +154,14 @@ export default class BlockInsert extends Component {
|
||||
label={<Icon type="PlusCircle" />}
|
||||
onPickImage={this.onPickImage}
|
||||
onInsertList={ev =>
|
||||
this.onClickBlock(ev, 'list-item', 'bulleted-list')}
|
||||
onInsertTodoList={ev => this.onClickBlock(ev, todo, 'todo-list')}
|
||||
onInsertBreak={ev => this.onClickBlock(ev, 'horizontal-rule')}
|
||||
this.insertBlock(ev, {
|
||||
type: 'list-item',
|
||||
wrapper: 'bulleted-list',
|
||||
})}
|
||||
onInsertTodoList={ev =>
|
||||
this.insertBlock(ev, { type: todo, wrapper: 'todo-list' })}
|
||||
onInsertBreak={ev =>
|
||||
this.insertBlock(ev, { type: rule, append: 'paragraph' })}
|
||||
onOpen={this.handleMenuOpen}
|
||||
onClose={this.handleMenuClose}
|
||||
/>
|
||||
|
||||
17
frontend/components/Editor/components/HorizontalRule.js
Normal file
17
frontend/components/Editor/components/HorizontalRule.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import type { Props } from '../types';
|
||||
import { color } from 'styles/constants';
|
||||
|
||||
function HorizontalRule(props: Props) {
|
||||
const { state, node } = props;
|
||||
const active = state.isFocused && state.selection.hasEdgeIn(node);
|
||||
return <StyledHr active={active} />;
|
||||
}
|
||||
|
||||
const StyledHr = styled.hr`
|
||||
border-bottom: 1px solid ${props => (props.active ? color.slate : color.slateLight)};
|
||||
`;
|
||||
|
||||
export default HorizontalRule;
|
||||
@@ -112,19 +112,17 @@ export default function MarkdownShortcuts() {
|
||||
|
||||
if (chars === '--') {
|
||||
ev.preventDefault();
|
||||
const transform = state
|
||||
return state
|
||||
.transform()
|
||||
.extendToStartOf(startBlock)
|
||||
.delete()
|
||||
.setBlock({
|
||||
type: 'horizontal-rule',
|
||||
isVoid: true,
|
||||
});
|
||||
state = transform
|
||||
})
|
||||
.collapseToStartOfNextBlock()
|
||||
.insertBlock('paragraph')
|
||||
.apply();
|
||||
return state;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
import Code from './components/Code';
|
||||
import HorizontalRule from './components/HorizontalRule';
|
||||
import InlineCode from './components/InlineCode';
|
||||
import Image from './components/Image';
|
||||
import Link from './components/Link';
|
||||
@@ -33,7 +34,7 @@ const createSchema = () => {
|
||||
'block-quote': (props: Props) => (
|
||||
<blockquote>{props.children}</blockquote>
|
||||
),
|
||||
'horizontal-rule': (props: Props) => <hr />,
|
||||
'horizontal-rule': HorizontalRule,
|
||||
'bulleted-list': (props: Props) => <ul>{props.children}</ul>,
|
||||
'ordered-list': (props: Props) => <ol>{props.children}</ol>,
|
||||
'todo-list': (props: Props) => <TodoList>{props.children}</TodoList>,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import React from 'react';
|
||||
import { Redirect } from 'react-router';
|
||||
import queryString from 'query-string';
|
||||
import { observable } from 'mobx';
|
||||
import { observer, inject } from 'mobx-react';
|
||||
import { client } from 'utils/ApiClient';
|
||||
|
||||
@@ -12,17 +13,15 @@ type Props = {
|
||||
location: Object,
|
||||
};
|
||||
|
||||
type State = {
|
||||
redirectTo: string,
|
||||
};
|
||||
|
||||
@observer class SlackAuth extends React.Component {
|
||||
props: Props;
|
||||
state: State;
|
||||
state = {};
|
||||
@observable redirectTo: string;
|
||||
|
||||
// $FlowIssue Flow doesn't like async lifecycle components https://github.com/facebook/flow/issues/1803
|
||||
async componentDidMount(): void {
|
||||
componentDidMount() {
|
||||
this.redirect();
|
||||
}
|
||||
|
||||
async redirect() {
|
||||
const { error, code, state } = queryString.parse(
|
||||
this.props.location.search
|
||||
);
|
||||
@@ -30,18 +29,18 @@ type State = {
|
||||
if (error) {
|
||||
if (error === 'access_denied') {
|
||||
// User selected "Deny" access on Slack OAuth
|
||||
this.setState({ redirectTo: '/dashboard' });
|
||||
this.redirectTo = '/dashboard';
|
||||
} else {
|
||||
this.setState({ redirectTo: '/auth/error' });
|
||||
this.redirectTo = '/auth/error';
|
||||
}
|
||||
} else {
|
||||
if (this.props.location.pathname === '/auth/slack/commands') {
|
||||
// User adding webhook integrations
|
||||
try {
|
||||
await client.post('/auth.slackCommands', { code });
|
||||
this.setState({ redirectTo: '/dashboard' });
|
||||
this.redirectTo = '/dashboard';
|
||||
} catch (e) {
|
||||
this.setState({ redirectTo: '/auth/error' });
|
||||
this.redirectTo = '/auth/error';
|
||||
}
|
||||
} else {
|
||||
// Regular Slack authentication
|
||||
@@ -50,18 +49,15 @@ type State = {
|
||||
|
||||
const { success } = await this.props.auth.authWithSlack(code, state);
|
||||
success
|
||||
? this.setState({ redirectTo: redirectTo || '/dashboard' })
|
||||
: this.setState({ redirectTo: '/auth/error' });
|
||||
? (this.redirectTo = redirectTo || '/dashboard')
|
||||
: (this.redirectTo = '/auth/error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{this.state.redirectTo && <Redirect to={this.state.redirectTo} />}
|
||||
</div>
|
||||
);
|
||||
if (this.redirectTo) return <Redirect to={this.redirectTo} />;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@tommoor/slate-drop-or-paste-images": "0.5.1",
|
||||
"aws-sdk": "^2.135.0",
|
||||
"babel-core": "^6.24.1",
|
||||
"babel-eslint": "^7.2.3",
|
||||
"babel-loader": "6.2.5",
|
||||
|
||||
@@ -47,6 +47,10 @@ router.post('auth.slack', async ctx => {
|
||||
await team.createFirstCollection(user.id);
|
||||
}
|
||||
|
||||
// Update user's avatar
|
||||
await user.updateAvatar();
|
||||
await user.save();
|
||||
|
||||
ctx.body = {
|
||||
data: {
|
||||
user: await presentUser(ctx, user),
|
||||
|
||||
12
server/migrations/20171019071915-user-avatar-url.js
Normal file
12
server/migrations/20171019071915-user-avatar-url.js
Normal file
@@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
up: function(queryInterface, Sequelize) {
|
||||
queryInterface.addColumn('users', 'avatarUrl', {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
});
|
||||
},
|
||||
|
||||
down: function(queryInterface, Sequelize) {
|
||||
queryInterface.removeColumn('users', 'avatarUrl');
|
||||
},
|
||||
};
|
||||
@@ -1,7 +1,9 @@
|
||||
// @flow
|
||||
import crypto from 'crypto';
|
||||
import bcrypt from 'bcrypt';
|
||||
import uuid from 'uuid';
|
||||
import { DataTypes, sequelize, encryptedFields } from '../sequelize';
|
||||
import { uploadToS3FromUrl } from '../utils/s3';
|
||||
|
||||
import JWT from 'jsonwebtoken';
|
||||
|
||||
@@ -18,6 +20,7 @@ const User = sequelize.define(
|
||||
email: { type: DataTypes.STRING },
|
||||
username: { type: DataTypes.STRING },
|
||||
name: DataTypes.STRING,
|
||||
avatarUrl: { type: DataTypes.STRING, allowNull: true },
|
||||
password: DataTypes.VIRTUAL,
|
||||
passwordDigest: DataTypes.STRING,
|
||||
isAdmin: DataTypes.BOOLEAN,
|
||||
@@ -66,6 +69,12 @@ User.prototype.verifyPassword = function(password) {
|
||||
});
|
||||
});
|
||||
};
|
||||
User.prototype.updateAvatar = async function() {
|
||||
this.avatarUrl = await uploadToS3FromUrl(
|
||||
this.slackData.image_192,
|
||||
`avatars/${this.id}/${uuid.v4()}`
|
||||
);
|
||||
};
|
||||
|
||||
const setRandomJwtSecret = model => {
|
||||
model.jwtSecret = crypto.randomBytes(64).toString('hex');
|
||||
|
||||
@@ -8,7 +8,8 @@ function present(ctx: Object, user: User) {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
name: user.name,
|
||||
avatarUrl: user.slackData ? user.slackData.image_192 : null,
|
||||
avatarUrl: user.avatarUrl ||
|
||||
(user.slackData ? user.slackData.image_192 : null),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ export async function request(endpoint: string, body: Object) {
|
||||
} catch (e) {
|
||||
throw httpErrors.BadRequest();
|
||||
}
|
||||
console.log('DATA', data);
|
||||
if (!data.ok) throw httpErrors.BadRequest(data.error);
|
||||
|
||||
return data;
|
||||
@@ -28,7 +27,7 @@ export async function oauthAccess(
|
||||
return request('oauth.access', {
|
||||
client_id: process.env.SLACK_KEY,
|
||||
client_secret: process.env.SLACK_SECRET,
|
||||
redirect_uri: `${process.env.URL || ''}/auth/slack`,
|
||||
redirect_uri,
|
||||
code,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
// @flow
|
||||
import crypto from 'crypto';
|
||||
import moment from 'moment';
|
||||
import AWS from 'aws-sdk';
|
||||
import invariant from 'invariant';
|
||||
import fetch from 'isomorphic-fetch';
|
||||
import bugsnag from 'bugsnag';
|
||||
|
||||
AWS.config.update({
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
||||
});
|
||||
|
||||
const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY;
|
||||
const AWS_S3_UPLOAD_BUCKET_NAME = process.env.AWS_S3_UPLOAD_BUCKET_NAME;
|
||||
|
||||
const makePolicy = () => {
|
||||
const policy = {
|
||||
@@ -19,13 +32,37 @@ const makePolicy = () => {
|
||||
return new Buffer(JSON.stringify(policy)).toString('base64');
|
||||
};
|
||||
|
||||
const signPolicy = policy => {
|
||||
const signPolicy = (policy: any) => {
|
||||
invariant(AWS_SECRET_ACCESS_KEY, 'AWS_SECRET_ACCESS_KEY not set');
|
||||
const signature = crypto
|
||||
.createHmac('sha1', process.env.AWS_SECRET_ACCESS_KEY)
|
||||
.createHmac('sha1', AWS_SECRET_ACCESS_KEY)
|
||||
.update(policy)
|
||||
.digest('base64');
|
||||
|
||||
return signature;
|
||||
};
|
||||
|
||||
export { makePolicy, signPolicy };
|
||||
const uploadToS3FromUrl = async (url: string, key: string) => {
|
||||
const s3 = new AWS.S3();
|
||||
invariant(AWS_S3_UPLOAD_BUCKET_NAME, 'AWS_S3_UPLOAD_BUCKET_NAME not set');
|
||||
|
||||
try {
|
||||
// $FlowIssue dunno it's fine
|
||||
const res = await fetch(url);
|
||||
const buffer = await res.buffer();
|
||||
await s3
|
||||
.putObject({
|
||||
Bucket: process.env.AWS_S3_UPLOAD_BUCKET_NAME,
|
||||
Key: key,
|
||||
ContentType: res.headers['content-type'],
|
||||
ContentLength: res.headers['content-length'],
|
||||
Body: buffer,
|
||||
})
|
||||
.promise();
|
||||
return `https://s3.amazonaws.com/${AWS_S3_UPLOAD_BUCKET_NAME}/${key}`;
|
||||
} catch (e) {
|
||||
bugsnag.notify(e);
|
||||
}
|
||||
};
|
||||
|
||||
export { makePolicy, signPolicy, uploadToS3FromUrl };
|
||||
|
||||
70
yarn.lock
70
yarn.lock
@@ -339,6 +339,21 @@ autoprefixer@^6.3.1:
|
||||
postcss "^5.2.16"
|
||||
postcss-value-parser "^3.2.3"
|
||||
|
||||
aws-sdk@^2.135.0:
|
||||
version "2.135.0"
|
||||
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.135.0.tgz#81f4a47b99212f2f236bf5b11b0b3a3a02086db4"
|
||||
dependencies:
|
||||
buffer "4.9.1"
|
||||
crypto-browserify "1.0.9"
|
||||
events "^1.1.1"
|
||||
jmespath "0.15.0"
|
||||
querystring "0.2.0"
|
||||
sax "1.2.1"
|
||||
url "0.10.3"
|
||||
uuid "3.1.0"
|
||||
xml2js "0.4.17"
|
||||
xmlbuilder "4.2.1"
|
||||
|
||||
aws-sign2@~0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
|
||||
@@ -1268,7 +1283,7 @@ buffer-xor@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
|
||||
|
||||
buffer@^4.3.0, buffer@^4.9.0:
|
||||
buffer@4.9.1, buffer@^4.3.0, buffer@^4.9.0:
|
||||
version "4.9.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
|
||||
dependencies:
|
||||
@@ -1959,6 +1974,10 @@ cryptiles@2.x.x:
|
||||
dependencies:
|
||||
boom "2.x.x"
|
||||
|
||||
crypto-browserify@1.0.9:
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-1.0.9.tgz#cc5449685dfb85eb11c9828acc7cb87ab5bbfcc0"
|
||||
|
||||
crypto-browserify@^3.11.0:
|
||||
version "3.11.1"
|
||||
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.11.1.tgz#948945efc6757a400d6e5e5af47194d10064279f"
|
||||
@@ -2923,7 +2942,7 @@ event-stream@~3.3.0:
|
||||
stream-combiner "~0.0.4"
|
||||
through "~2.3.1"
|
||||
|
||||
events@^1.0.0:
|
||||
events@^1.0.0, events@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
|
||||
|
||||
@@ -4816,6 +4835,10 @@ jest-validate@^20.0.3:
|
||||
leven "^2.1.0"
|
||||
pretty-format "^20.0.3"
|
||||
|
||||
jmespath@0.15.0:
|
||||
version "0.15.0"
|
||||
resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217"
|
||||
|
||||
joi@^6.10.1, joi@~6.10.1:
|
||||
version "6.10.1"
|
||||
resolved "https://registry.yarnpkg.com/joi/-/joi-6.10.1.tgz#4d50c318079122000fe5f16af1ff8e1917b77e06"
|
||||
@@ -7806,7 +7829,11 @@ sane@~1.6.0:
|
||||
walker "~1.0.5"
|
||||
watch "~0.10.0"
|
||||
|
||||
sax@^1.2.1, sax@~1.2.1:
|
||||
sax@1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a"
|
||||
|
||||
sax@>=0.6.0, sax@^1.2.1, sax@~1.2.1:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||
|
||||
@@ -8036,8 +8063,8 @@ slate-edit-list@^0.7.0:
|
||||
resolved "https://registry.yarnpkg.com/slate-edit-list/-/slate-edit-list-0.7.1.tgz#84ee960d2d5b5a20ce267ad9df894395a91b93d5"
|
||||
|
||||
slate-markdown-serializer@tommoor/slate-markdown-serializer:
|
||||
version "0.5.0"
|
||||
resolved "https://codeload.github.com/tommoor/slate-markdown-serializer/tar.gz/22bdaa096777f5dd7f7c366841a0c6e4392adeb6"
|
||||
version "0.5.2"
|
||||
resolved "https://codeload.github.com/tommoor/slate-markdown-serializer/tar.gz/75708cf421c0a0ac0d7d541b295315cbff0839c0"
|
||||
|
||||
slate-paste-linkify@^0.2.1:
|
||||
version "0.2.1"
|
||||
@@ -8849,16 +8876,16 @@ 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"
|
||||
|
||||
url@^0.11.0:
|
||||
version "0.11.0"
|
||||
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
|
||||
url@0.10.3, url@~0.10.1:
|
||||
version "0.10.3"
|
||||
resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64"
|
||||
dependencies:
|
||||
punycode "1.3.2"
|
||||
querystring "0.2.0"
|
||||
|
||||
url@~0.10.1:
|
||||
version "0.10.3"
|
||||
resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64"
|
||||
url@^0.11.0:
|
||||
version "0.11.0"
|
||||
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
|
||||
dependencies:
|
||||
punycode "1.3.2"
|
||||
querystring "0.2.0"
|
||||
@@ -8899,14 +8926,14 @@ uuid@2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.2.tgz#48bd5698f0677e3c7901a1c46ef15b1643794726"
|
||||
|
||||
uuid@3.1.0, uuid@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"
|
||||
|
||||
uuid@^2.0.1, uuid@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
|
||||
|
||||
uuid@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"
|
||||
|
||||
v8flags@^2.0.2:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4"
|
||||
@@ -9202,6 +9229,19 @@ xml-name-validator@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635"
|
||||
|
||||
xml2js@0.4.17:
|
||||
version "0.4.17"
|
||||
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.17.tgz#17be93eaae3f3b779359c795b419705a8817e868"
|
||||
dependencies:
|
||||
sax ">=0.6.0"
|
||||
xmlbuilder "^4.1.0"
|
||||
|
||||
xmlbuilder@4.2.1, xmlbuilder@^4.1.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-4.2.1.tgz#aa58a3041a066f90eaa16c2f5389ff19f3f461a5"
|
||||
dependencies:
|
||||
lodash "^4.0.0"
|
||||
|
||||
"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||
|
||||
Reference in New Issue
Block a user