refactor: fix client ts error

This commit is contained in:
碎碎酱 2020-03-14 22:13:56 +08:00
parent eab0c04b3b
commit 39e537f9fb
55 changed files with 358 additions and 199 deletions

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}

View File

@ -4,9 +4,8 @@
*/
import bcrypt from 'bcryptjs';
import { promisify } from 'util';
import User from '../server/models/user';
import User, { UserDocument } from '../server/models/user';
import Group from '../server/models/group';
import options from '../utils/commandOptions';
@ -37,10 +36,11 @@ connectDB()
const defaultGroup = await Group.findOne({ isDefault: true });
if (!defaultGroup) {
exitWithError('默认群组不存在');
return;
}
const salt = await promisify(bcrypt.genSalt)(saltRounds);
const hash = await promisify(bcrypt.hash)(password, salt);
const salt = await bcrypt.genSalt(saltRounds);
const hash = await bcrypt.hash(password, salt);
let newUser = null;
try {
@ -52,21 +52,24 @@ connectDB()
});
} catch (createError) {
if (createError.name === 'ValidationError') {
return exitWithError('用户名包含不支持的字符或者长度超过限制');
exitWithError('用户名包含不支持的字符或者长度超过限制');
return;
}
console.error(createError);
exitWithError('创建新用户失败');
}
if (!defaultGroup.creator) {
defaultGroup.creator = newUser;
defaultGroup.creator = newUser as UserDocument;
}
if (newUser) {
defaultGroup.members.push(newUser._id);
}
defaultGroup.members.push(newUser);
await defaultGroup.save();
console.log('注册成功');
return process.exit(0);
process.exit(0);
})
.catch((err) => {
console.error('connect database error!');

View File

@ -14,9 +14,9 @@ process.env.NODE_ENV = 'production';
const spinner = ora('building for production...');
spinner.start();
rm(path.join(config.build.assetsRoot), (err) => {
rm(path.join(config.build.assetsRoot), (err: any) => {
if (err) throw err;
webpack(webpackConfig, (wErr, stats) => {
webpack(webpackConfig, (wErr: any, stats: any) => {
spinner.stop();
if (wErr) throw wErr;
process.stdout.write(`${stats.toString({

View File

@ -6,14 +6,14 @@ import cp from 'child_process';
import packageJson from '../package.json';
function exec(cmd) {
function exec(cmd: any) {
return cp.execSync(cmd).toString().trim();
}
const versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
currentVersion: semver.clean(process.version) as string,
versionRequirement: packageJson.engines.node,
},
{

View File

@ -5,7 +5,7 @@ import 'eventsource-polyfill';
// @ts-ignore
import hotClient from 'webpack-hot-middleware/client?noInfo=true&reload=true'; // eslint-disable-line import/no-unresolved
hotClient.subscribe((event) => {
hotClient.subscribe((event: any) => {
if (event.action === 'reload') {
window.location.reload();
}

View File

@ -5,7 +5,6 @@ import opn from 'opn';
import path from 'path';
import express from 'express';
import webpack from 'webpack';
import proxyMiddleware from 'http-proxy-middleware';
import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackHotMiddleware from 'webpack-hot-middleware';
import connectionHistoryApiFallback from 'connect-history-api-fallback';
@ -20,7 +19,6 @@ if (!process.env.NODE_ENV) {
const host = process.env.HOST || config.dev.host;
const port = process.env.PORT || config.dev.port;
const autoOpenBrowser = !!config.dev.autoOpenBrowser;
const { proxyTable } = config.dev;
const app = express();
const compiler = webpack(webpackConfig);
@ -36,22 +34,14 @@ const hotMiddleware = webpackHotMiddleware(compiler, {
});
compiler.plugin('compilation', (compilation) => {
compilation.plugin('html-webpack-plugin-after-emit', (data, cb) => {
compiler.plugin('compilation', (compilation: any) => {
compilation.plugin('html-webpack-plugin-after-emit', (data: any, cb: any) => {
if (cb) {
cb();
}
});
});
Object.keys(proxyTable).forEach((context) => {
let options = proxyTable[context];
if (typeof options === 'string') {
options = { target: options };
}
app.use(proxyMiddleware(options.filter || context, options));
});
app.use(connectionHistoryApiFallback());
app.use(devMiddleware);

View File

@ -5,7 +5,7 @@ import LessPluginAutoPrefix from 'less-plugin-autoprefix';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
import config from '../config/webpack';
export function assetsPath(_path) {
export function assetsPath(_path: any) {
return path.posix.join('', _path);
}

View File

@ -3,12 +3,12 @@ import * as utils from './utils';
import config from '../config/webpack';
import pages from '../config/pages';
const entry = {};
const entry: { [key: string]: string } = {};
pages.forEach((page) => {
entry[page.entry.key] = page.entry.file;
});
function resolve(dir) {
function resolve(dir: any) {
return path.join(__dirname, '..', dir);
}
@ -36,10 +36,7 @@ export default {
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: [
'babel-loader',
'ts-loader',
],
use: ['babel-loader', 'ts-loader'],
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,

View File

@ -14,6 +14,7 @@ import pages from '../config/pages';
const htmlPlugins = pages.map((page) => new HtmlWebpackPlugin(page));
Object.keys(baseWebpackConfig.entry).forEach((name) => {
// @ts-ignore
baseWebpackConfig.entry[name] = ['react-hot-loader/patch', './build/dev-client'].concat(baseWebpackConfig.entry[name]);
});

View File

@ -35,18 +35,6 @@ const webpackConfig = merge(baseWebpackConfig, {
rules: utils.getStyleLoaders(),
},
devtool: config.build.productionSourceMap ? '#source-map' : false,
// optimization: {
// splitChunks: {
// cacheGroups: {
// vendor: {
// test: module => /node_modules/.test(module.context),
// chunks: 'initial',
// name: 'vendor',
// enforce: true,
// },
// },
// },
// },
plugins: [
// @ts-ignore
new webpack.DefinePlugin({

View File

@ -66,6 +66,7 @@ function App() {
setHeight(getHeightPercent());
};
// @ts-ignore
inobounce($app.current);
}, []);
@ -121,11 +122,11 @@ function App() {
const [groupInfo, setGroupInfo] = useState(null);
const contextValue = useMemo(() => ({
showUserInfo(user) {
showUserInfo(user: any) {
setUserInfo(user);
toggleUserInfoDialog(true);
},
showGroupInfo(group) {
showGroupInfo(group: any) {
setGroupInfo(group);
toggleGroupInfoDialog(true);
},
@ -135,7 +136,7 @@ function App() {
<div className={Style.app} style={style} ref={$app}>
<div className={Style.blur} style={blurStyle} />
<div className={Style.child} style={childStyle}>
<ShowUserOrGroupInfoContext.Provider value={contextValue}>
<ShowUserOrGroupInfoContext.Provider value={contextValue as unknown as null}>
<Sidebar />
<FunctionBarAndLinkmanList />
<Chat />
@ -145,11 +146,13 @@ function App() {
<UserInfo
visible={userInfoDialog}
onClose={() => toggleUserInfoDialog(false)}
// @ts-ignore
user={userInfo}
/>
<GroupInfo
visible={groupInfoDialog}
onClose={() => toggleGroupInfoDialog(false)}
// @ts-ignore
group={groupInfo}
/>
</div>

View File

@ -24,7 +24,7 @@ function Input(props: InputProps) {
onFocus = () => {},
} = props;
function handleInput(e) {
function handleInput(e: any) {
onChange(e.target.value);
}
@ -35,18 +35,19 @@ function Input(props: InputProps) {
function handleIMEEnd() {
setLockEnter(false);
}
function handleKeyDown(e) {
function handleKeyDown(e: any) {
if (lockEnter) {
return;
}
if (e.key === 'Enter') {
onEnter(value);
onEnter(value as string);
}
}
const $input = useRef(null);
function handleClickClear() {
onChange('');
// @ts-ignore
$input.current.focus();
}

View File

@ -5,7 +5,7 @@ import 'rc-notification/dist/rc-notification.min.css';
import Style from './Message.less';
function showMessage(text: string, duration = 1500, type = 'success') {
Notification.newInstance({}, (notification) => {
Notification.newInstance({}, (notification: any) => {
notification.notice({
content: (
<div className={Style.componentMessage}>

View File

@ -47,7 +47,7 @@ export default function useAction() {
});
},
removeLinkman(linkmanId) {
removeLinkman(linkmanId: string) {
dispatch({
type: ActionTypes.RemoveLinkman,
payload: linkmanId,
@ -120,7 +120,7 @@ export default function useAction() {
window.localStorage.setItem(key, value);
},
toggleLoginRegisterDialog(visible) {
toggleLoginRegisterDialog(visible: boolean) {
dispatch({
type: ActionTypes.SetStatus,
payload: {

View File

@ -47,7 +47,9 @@ export default function getData() {
backgroundImage: '',
aero: false,
};
// @ts-ignore
if (theme && config.theme[theme]) {
// @ts-ignore
themeConfig = config.theme[theme];
} else {
themeConfig = {

View File

@ -72,6 +72,7 @@ function Chat() {
action.setLinkmanProperty(focus, 'onlineMembers', onlineMembers);
toggleGroupManagePanel(true);
} else {
// @ts-ignore
context.showUserInfo(linkman);
}
}

View File

@ -29,10 +29,10 @@ function ChatInput() {
const action = useAction();
const isLogin = useIsLogin();
const connect = useSelector((state: State) => state.connect);
const selfId = useSelector((state: State) => state.user._id);
const username = useSelector((state: State) => state.user.username);
const avatar = useSelector((state: State) => state.user.avatar);
const tag = useSelector((state: State) => state.user.tag);
const selfId = useSelector((state: State) => state.user?._id);
const username = useSelector((state: State) => state.user?.username);
const avatar = useSelector((state: State) => state.user?.avatar);
const tag = useSelector((state: State) => state.user?.tag);
const focus = useSelector((state: State) => state.focus);
const linkman = useSelector((state: State) => state.linkmans[focus]);
const selfVoiceSwitch = useSelector((state: State) => state.status.selfVoiceSwitch);
@ -52,6 +52,7 @@ function ChatInput() {
return;
}
e.preventDefault();
// @ts-ignore
$input.current.focus(e);
}
useEffect(() => {
@ -63,13 +64,17 @@ function ChatInput() {
(async () => {
if (expressionDialog && !Expression) {
// @ts-ignore
const ExpressionModule = await import(/* webpackChunkName: "expression" */ './Expression');
const ExpressionModule = await import(
/* webpackChunkName: "expression" */ './Expression',
);
Expression = ExpressionModule.default;
setTimestamp(Date.now());
}
if (codeEditorDialog && !CodeEditor) {
// @ts-ignore
const CodeEditorModule = await import(/* webpackChunkName: "code-editor" */ './CodeEditor');
const CodeEditorModule = await import(
/* webpackChunkName: "code-editor" */ './CodeEditor',
);
CodeEditor = CodeEditorModule.default;
setTimestamp(Date.now());
}
@ -99,14 +104,14 @@ function ChatInput() {
* @param value
*/
function insertAtCursor(value: string) {
const input = $input.current;
const input = ($input.current as unknown) as HTMLInputElement;
if (input.selectionStart || input.selectionStart === 0) {
const startPos = input.selectionStart;
const endPos = input.selectionEnd;
const restoreTop = input.scrollTop;
input.value = input.value.substring(0, startPos)
+ value
+ input.value.substring(endPos, input.value.length);
+ input.value.substring(endPos as number, input.value.length);
if (restoreTop > 0) {
input.scrollTop = restoreTop;
}
@ -124,7 +129,7 @@ function ChatInput() {
insertAtCursor(`#(${expression})`);
}
function addSelfMessage(type, content) {
function addSelfMessage(type: string, content: string) {
const _id = focus + Date.now();
const message = {
_id,
@ -152,7 +157,7 @@ function ChatInput() {
.replace(/#/g, '');
if (text.length > 0 && text.length <= 100) {
voice.push(text, Math.random());
voice.push(text, Math.random().toString());
}
}
@ -160,7 +165,12 @@ function ChatInput() {
}
// eslint-disable-next-line react/destructuring-assignment
async function handleSendMessage(localId, type, content, linkmanId = focus) {
async function handleSendMessage(
localId: string,
type: string,
content: string,
linkmanId = focus,
) {
const [error, message] = await sendMessage(linkmanId, type, content);
if (error) {
action.deleteMessage(focus, localId);
@ -185,6 +195,7 @@ function ChatInput() {
return;
}
// @ts-ignore
const ext = image.type
.split('/')
.pop()
@ -234,7 +245,7 @@ function ChatInput() {
handleSendMessage(id, 'image', huaji);
}
function handleFeatureMenuClick({ key, domEvent }) {
function handleFeatureMenuClick({ key, domEvent }: {key: string, domEvent: any}) {
// Quickly hitting the Enter key causes the button to repeatedly trigger the problem
if (domEvent.keyCode === 13) {
return;
@ -261,7 +272,7 @@ function ChatInput() {
}
}
async function handlePaste(e) {
async function handlePaste(e: any) {
// eslint-disable-next-line react/destructuring-assignment
if (!connect) {
e.preventDefault();
@ -281,11 +292,12 @@ function ChatInput() {
const image = new Image();
image.onload = async () => {
const imageBlob = await compressImage(image, file.type, 0.8);
// @ts-ignore
sendImageMessage({
filename: file.name,
ext: imageBlob.type.split('/').pop(),
length: imageBlob.size,
type: imageBlob.type,
ext: imageBlob?.type.split('/').pop(),
length: imageBlob?.size,
type: imageBlob?.type,
result: imageBlob,
});
};
@ -305,6 +317,7 @@ function ChatInput() {
return Message.error('发送消息失败, 您当前处于离线状态');
}
// @ts-ignore
const message = $input.current.value.trim();
if (message.length === 0) {
return null;
@ -325,11 +338,12 @@ function ChatInput() {
const id = addSelfMessage('text', xss(message));
handleSendMessage(id, 'text', message);
}
// @ts-ignore
$input.current.value = '';
return null;
}
async function handleInputKeyDown(e) {
async function handleInputKeyDown(e: any) {
if (e.key === 'Tab') {
e.preventDefault();
} else if (e.key === 'Enter' && !inputIME) {
@ -342,6 +356,7 @@ function ChatInput() {
e.preventDefault();
} else if (e.key === '@') {
// 如果按下@建, 则进入@计算模式
// @ts-ignore
if (!/@/.test($input.current.value)) {
setAt({
enable: true,
@ -357,6 +372,7 @@ function ChatInput() {
// 延时, 以便拿到新的value和ime状态
setTimeout(() => {
// 如果@已经被删掉了, 退出@计算模式
// @ts-ignore
if (!/@/.test($input.current.value)) {
setAt({ enable: false, content: '' });
return;
@ -375,6 +391,7 @@ function ChatInput() {
if (inputIME) {
return;
}
// @ts-ignore
const regexResult = /@([^ ]*)/.exec($input.current.value);
if (regexResult) {
setAt({ enable: true, content: regexResult[1] });
@ -396,7 +413,8 @@ function ChatInput() {
});
}
function replaceAt(targetUsername) {
function replaceAt(targetUsername: string) {
// @ts-ignore
$input.current.value = $input.current.value.replace(
`@${at.content}`,
`@${targetUsername} `,
@ -405,6 +423,7 @@ function ChatInput() {
enable: false,
content: '',
});
// @ts-ignore
$input.current.focus();
}
@ -475,8 +494,11 @@ function ChatInput() {
iconSize={32}
/>
</Dropdown>
<form className={Style.form} autoComplete="off" onSubmit={(e) => e.preventDefault()}>
<form
className={Style.form}
autoComplete="off"
onSubmit={(e) => e.preventDefault()}
>
<input
className={Style.input}
type="text"
@ -492,7 +514,17 @@ function ChatInput() {
/>
{!isMobile && !inputFocus && (
<Tooltip placement="top" mouseEnterDelay={0.5} overlay={<span><br /> i </span>}>
<Tooltip
placement="top"
mouseEnterDelay={0.5}
overlay={(
<span>
<br />
i
</span>
)}
>
<i className={`iconfont icon-about ${Style.tooltip}`} />
</Tooltip>
)}

View File

@ -180,6 +180,7 @@ function CodeEditor(props: CodeEditorProps) {
<Select
className={Style.languageSelect}
defaultValue={languages[0]}
// @ts-ignore
onSelect={(lang: string) => setLanguage(lang)}
>
{languages.map((lang) => (

View File

@ -45,6 +45,7 @@ function Expression(props: ExpressionProps) {
className={Style.defaultExpressionBlock}
key={e}
data-name={e}
// @ts-ignore
onClick={(event) => onSelectText(event.currentTarget.dataset.name)}
role="button"
>
@ -60,7 +61,7 @@ function Expression(props: ExpressionProps) {
</div>
);
function handleClickExpression(e) {
function handleClickExpression(e: any) {
const $target = e.target;
const url = addParam($target.src, {
width: $target.naturalWidth,

View File

@ -31,7 +31,7 @@ function GroupManagePanel(props: GroupManagePanelProps) {
const action = useAction();
const isLogin = useIsLogin();
const selfId = useSelector((state: State) => state.user._id);
const selfId = useSelector((state: State) => state.user?._id);
const [deleteConfirmDialog, setDialogStatus] = useState(false);
const [groupName, setGroupName] = useState('');
const context = useContext(ShowUserOrGroupInfoContext);
@ -100,6 +100,7 @@ function GroupManagePanel(props: GroupManagePanelProps) {
if (userInfo._id === selfId) {
return;
}
// @ts-ignore
context.showUserInfo(userInfo);
onClose();
}

View File

@ -1,6 +1,6 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import CopyToClipboard from 'react-copy-to-clipboard';
import { isMobile } from '../../../utils/ua';
import { State } from '../../state/reducer';

View File

@ -16,6 +16,7 @@ function CodeDialog(props: CodeDialogProps) {
const { visible, onClose, language, code } = props;
const html = language === 'text'
? xss(code)
// @ts-ignore
: Prism.highlight(code, Prism.languages[language]);
setTimeout(Prism.highlightAll.bind(Prism), 0); // TODO: https://github.com/PrismJS/prism/issues/1487

View File

@ -4,7 +4,11 @@ import Style from './CodeMessage.less';
let CodeDialog: any = null;
const languagesMap = {
type LanguageMap = {
[language: string]: string;
}
const languagesMap: LanguageMap = {
javascript: 'javascript',
typescript: 'typescript',
java: 'java',

View File

@ -55,7 +55,7 @@ interface MessageState {
class Message extends Component<MessageProps, MessageState> {
$container = createRef<HTMLDivElement>();
constructor(props) {
constructor(props: MessageProps) {
super(props);
this.state = {
showButtonList: false,
@ -65,6 +65,7 @@ class Message extends Component<MessageProps, MessageState> {
componentDidMount() {
const { shouldScroll } = this.props;
if (shouldScroll) {
// @ts-ignore
this.$container.current.scrollIntoView();
}
}
@ -169,6 +170,7 @@ class Message extends Component<MessageProps, MessageState> {
className={Style.avatar}
src={avatar}
size={44}
// @ts-ignore
onClick={() => this.handleClickAvatar(context.showUserInfo)}
/>
)}

View File

@ -11,7 +11,7 @@ import Style from './MessageList.less';
function MessageList() {
const action = useAction();
const selfId = useSelector((state: State) => state.user._id);
const selfId = useSelector((state: State) => state.user?._id);
const focus = useSelector((state: State) => state.focus);
const isGroup = useSelector((state: State) => state.linkmans[focus].type === 'group');
const creator = useSelector((state: State) => state.linkmans[focus].creator);
@ -22,7 +22,7 @@ function MessageList() {
const $list = useRef(null);
let isFetching = false;
async function handleScroll(e) {
async function handleScroll(e: any) {
// Don't know why the code-view dialog will also trigger when scrolling
if ($list.current && e.target !== $list.current) {
return;
@ -56,6 +56,7 @@ function MessageList() {
const isSelf = message.from._id === selfId;
let shouldScroll = true;
if ($list.current) {
// @ts-ignore
const { scrollHeight, clientHeight, scrollTop } = $list.current;
shouldScroll = isSelf
|| scrollHeight === clientHeight

View File

@ -10,13 +10,18 @@ import { search } from '../../service';
import Style from './FunctionBar.less';
import Input from '../../components/Input';
type SearchResult = {
users: any[];
groups: any[];
}
function FunctionBar() {
const [keywords, setKeywords] = useState('');
const [addButtonVisible, toggleAddButtonVisible] = useState(true);
const [searchResultVisible, toggleSearchResultVisible] = useState(false);
const [searchResultActiveKey, setSearchResultActiveKey] = useState('all');
const [createGroupDialogVisible, toggleCreateGroupDialogVisible] = useState(false);
const [searchResult, setSearchResult] = useState({ users: [], groups: [] });
const [searchResult, setSearchResult] = useState<SearchResult>({ users: [], groups: [] });
const context = useContext(ShowUserOrGroupInfoContext);
const placeholder = '搜索群组/用户';
@ -29,7 +34,7 @@ function FunctionBar() {
setKeywords('');
}
function handleBodyClick(e) {
function handleBodyClick(e: any) {
if (e.target.getAttribute('placeholder') === placeholder || !searchResultVisible) {
return;
}
@ -70,7 +75,8 @@ function FunctionBar() {
const { users } = searchResult;
count = Math.min(count, users.length);
function handleClick(targetUser) {
function handleClick(targetUser: any) {
// @ts-ignore
context.showUserInfo(targetUser);
resetSearch();
}
@ -95,7 +101,8 @@ function FunctionBar() {
const { groups } = searchResult;
count = Math.min(count, groups.length);
function handleClick(targetGroup) {
function handleClick(targetGroup: any) {
// @ts-ignore
context.showGroupInfo(targetGroup);
resetSearch();
}

View File

@ -22,7 +22,7 @@ function FunctionBarAndLinkmanList() {
return null;
}
function handleClick(e) {
function handleClick(e: any) {
if (e.target === e.currentTarget) {
action.setStatus('functionBarAndLinkmanListVisible', false);
}

View File

@ -25,7 +25,8 @@ function GroupInfo(props: GroupInfoProps) {
const { visible, onClose, group } = props;
const action = useAction();
const hasLinkman = useSelector((state: State) => !!state.linkmans[group && group._id]);
// @ts-ignore
const hasLinkman = useSelector((state: State) => !!state.linkmans[group?._id]);
const [largerAvatar, toggleLargetAvatar] = useState(false);
if (!group) {
@ -34,6 +35,10 @@ function GroupInfo(props: GroupInfoProps) {
async function handleJoinGroup() {
onClose();
if (!group) {
return;
}
const groupRes = await joinGroup(group._id);
if (groupRes) {
groupRes.type = 'group';
@ -48,6 +53,10 @@ function GroupInfo(props: GroupInfoProps) {
function handleFocusGroup() {
onClose();
if (!group) {
return;
}
action.setFocus(group._id);
}

View File

@ -23,7 +23,7 @@ function Login() {
const user = await login(
username,
password,
platform.os.family,
platform.os?.family,
platform.name,
platform.description,
);
@ -33,11 +33,12 @@ function Login() {
window.localStorage.setItem('token', user.token);
const linkmanIds = [
...user.groups.map((group) => group._id),
...user.friends.map((friend) => getFriendId(friend.from, friend.to._id)),
...user.groups.map((group: any) => group._id),
...user.friends.map((friend: any) => getFriendId(friend.from, friend.to._id)),
];
const linkmanMessages = await getLinkmansLastMessages(linkmanIds);
Object.values(linkmanMessages).forEach(
// @ts-ignore
(messages: Message[]) => messages.forEach(convertMessage),
);
dispatch({

View File

@ -22,7 +22,7 @@ function Register() {
const user = await register(
username,
password,
platform.os.family,
platform.os?.family,
platform.name,
platform.description,
);
@ -32,11 +32,12 @@ function Register() {
window.localStorage.setItem('token', user.token);
const linkmanIds = [
...user.groups.map((group) => group._id),
...user.friends.map((friend) => getFriendId(friend.from, friend.to._id)),
...user.groups.map((group: any) => group._id),
...user.friends.map((friend: any) => getFriendId(friend.from, friend.to._id)),
];
const linkmanMessages = await getLinkmansLastMessages(linkmanIds);
Object.values(linkmanMessages).forEach(
// @ts-ignore
(messages: Message[]) => messages.forEach(convertMessage),
);
dispatch({

View File

@ -28,8 +28,8 @@ function SelfInfo(props: SelfInfoProps) {
const { visible, onClose } = props;
const action = useAction();
const userId = useSelector((state: State) => state.user._id);
const avatar = useSelector((state: State) => state.user.avatar);
const userId = useSelector((state: State) => state.user?._id);
const avatar = useSelector((state: State) => state.user?.avatar);
const primaryColor = useSelector((state: State) => state.status.primaryColor);
const [loading, toggleLoading] = useState(false);
const [cropper, setCropper] = useState({
@ -90,7 +90,8 @@ function SelfInfo(props: SelfInfoProps) {
}
function handleChangeAvatar() {
$cropper.current.getCroppedCanvas().toBlob(async (blob) => {
// @ts-ignore
$cropper.current.getCroppedCanvas().toBlob(async (blob: any) => {
uploadAvatar(blob, cropper.ext);
});
}
@ -127,7 +128,7 @@ function SelfInfo(props: SelfInfoProps) {
}
}
function handleCloseDialog(event) {
function handleCloseDialog(event: any) {
/**
* , ,
*/
@ -137,7 +138,12 @@ function SelfInfo(props: SelfInfoProps) {
}
return (
<Dialog className={Style.selfInfo} visible={visible} title="个人信息设置" onClose={handleCloseDialog}>
<Dialog
className={Style.selfInfo}
visible={visible}
title="个人信息设置"
onClose={handleCloseDialog}
>
<div className={Common.container}>
<div className={Common.block}>
<p className={Common.title}></p>
@ -146,6 +152,7 @@ function SelfInfo(props: SelfInfoProps) {
<div className={Style.cropper}>
<Cropper
className={loading ? 'blur' : ''}
// @ts-ignore
ref={$cropper}
src={cropper.src}
style={{ width: 460, height: 460 }}

View File

@ -25,6 +25,14 @@ interface SettingProps {
onClose: () => void;
}
type Color = {
rgb: {
r: number;
g: number;
b: number;
}
}
function Setting(props: SettingProps) {
const { visible, onClose } = props;
@ -39,13 +47,14 @@ function Setting(props: SettingProps) {
const primaryTextColor = useSelector((state: State) => state.status.primaryTextColor);
const backgroundImage = useSelector((state: State) => state.status.backgroundImage);
const aero = useSelector((state: State) => state.status.aero);
const userId = useSelector((state: State) => state.user._id);
const userId = useSelector((state: State) => state.user?._id);
const tagColorMode = useSelector((state: State) => state.status.tagColorMode);
const [backgroundLoading, toggleBackgroundLoading] = useState(false);
function setTheme(themeName: string) {
action.setStatus('theme', themeName);
// @ts-ignore
const themeConfig = config.theme[themeName];
if (themeConfig) {
action.setStatus('primaryColor', themeConfig.primaryColor);
@ -66,7 +75,7 @@ function Setting(props: SettingProps) {
}
}
function handleSelectSound(newSound) {
function handleSelectSound(newSound: string) {
playSound(newSound);
action.setStatus('sound', newSound);
}
@ -93,13 +102,13 @@ function Setting(props: SettingProps) {
}
}
function handlePrimaryColorChange(color) {
function handlePrimaryColorChange(color: Color) {
const newPrimaryColor = `${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b}`;
action.setStatus('primaryColor', newPrimaryColor);
setCssVariable(newPrimaryColor, primaryTextColor);
}
function handlePrimaryTextColorChange(color) {
function handlePrimaryTextColorChange(color: Color) {
const mewPrimaryTextColor = `${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b}`;
action.setStatus('primaryTextColor', mewPrimaryTextColor);
setCssVariable(primaryColor, mewPrimaryTextColor);
@ -251,6 +260,7 @@ function Setting(props: SettingProps) {
<span>{`rgb(${primaryColor})`}</span>
</div>
<TwitterPicker
// @ts-ignore
className={Style.colorPicker}
color={`rgb(${primaryColor})`}
onChange={handlePrimaryColorChange}
@ -265,6 +275,7 @@ function Setting(props: SettingProps) {
<span>{`rgb(${primaryTextColor})`}</span>
</div>
<TwitterPicker
// @ts-ignore
className={Style.colorPicker}
color={`rgb(${primaryTextColor})`}
onChange={handlePrimaryTextColorChange}

View File

@ -69,7 +69,7 @@ function Sidebar() {
socket.connect();
}
function renderTooltip(text, component) {
function renderTooltip(text: string, component: JSX.Element) {
const children = <div>{component}</div>;
if (isMobile) {
return children;
@ -84,7 +84,7 @@ function Sidebar() {
return (
<>
<div className={Style.sidebar} {...aero}>
{isLogin && (
{isLogin && avatar && (
<Avatar
className={Style.avatar}
src={avatar}

View File

@ -42,7 +42,8 @@ function UserInfo(props: UserInfoProps) {
/** 获取原始用户id */
const originUserId = user && user._id.replace(selfId, '');
const linkman = useSelector((state: State) => state.linkmans[user && user._id]);
// @ts-ignore
const linkman = useSelector((state: State) => state.linkmans[user?._id]);
const isFriend = linkman && linkman.type === 'friend';
const isAdmin = useSelector((state: State) => state.user && state.user.isAdmin);
const [largerAvatar, toggleLargetAvatar] = useState(false);
@ -64,13 +65,16 @@ function UserInfo(props: UserInfoProps) {
function handleFocusUser() {
onClose();
// @ts-ignore
action.setFocus(user._id);
}
async function handleAddFriend() {
// @ts-ignore
const friend = await addFriend(originUserId);
if (friend) {
onClose();
// @ts-ignore
const { _id } = user;
let existCount = 0;
if (linkman) {
@ -99,15 +103,18 @@ function UserInfo(props: UserInfoProps) {
}
async function handleDeleteFriend() {
// @ts-ignore
const isSuccess = await deleteFriend(originUserId);
if (isSuccess) {
onClose();
// @ts-ignore
action.removeLinkman(user._id);
Message.success('删除好友成功');
}
}
async function handleSeal() {
// @ts-ignore
const isSuccess = await sealUser(user.username);
if (isSuccess) {
Message.success('封禁用户成功');
@ -115,6 +122,7 @@ function UserInfo(props: UserInfoProps) {
}
async function handleSealIp() {
// @ts-ignore
const isSuccess = await sealUserOnlineIp(originUserId);
if (isSuccess) {
Message.success('封禁ip成功');

View File

@ -1,7 +1,7 @@
import fetch from '../utils/fetch';
import { User } from './state/reducer';
function saveUsername(username) {
function saveUsername(username: string) {
window.localStorage.setItem('username', username);
}
@ -16,9 +16,9 @@ function saveUsername(username) {
export async function register(
username: string,
password: string,
os: string,
browser: string,
environment: string,
os = '',
browser = '',
environment = '',
) {
const [err, user] = await fetch(
'register',
@ -50,9 +50,9 @@ export async function register(
export async function login(
username: string,
password: string,
os: string,
browser: string,
environment: string,
os = '',
browser = '',
environment = '',
) {
const [err, user] = await fetch(
'login',
@ -82,9 +82,9 @@ export async function login(
*/
export async function loginByToken(
token: string,
os: string,
browser: string,
environment: string,
os = '',
browser = '',
environment = '',
) {
const [err, user] = await fetch(
'loginByToken',
@ -111,7 +111,7 @@ export async function loginByToken(
* @param browser
* @param environment
*/
export async function guest(os: string, browser: string, environment: string) {
export async function guest(os = '', browser = '', environment = '') {
const [err, res] = await fetch('guest', { os, browser, environment });
if (err) {
return null;
@ -123,7 +123,7 @@ export async function guest(os: string, browser: string, environment: string) {
*
* @param avatar
*/
export async function changeAvatar(avatar) {
export async function changeAvatar(avatar: string) {
const [error] = await fetch('changeAvatar', { avatar });
return !error;
}

View File

@ -17,10 +17,10 @@ const { dispatch } = store;
const options = {
// reconnectionDelay: 1000,
};
const socket = new IO(config.server, options);
const socket = IO(config.server, options);
async function loginFailback() {
const defaultGroup = await guest(platform.os.family, platform.name, platform.description);
const defaultGroup = await guest(platform.os?.family, platform.name, platform.description);
if (defaultGroup) {
const { messages } = defaultGroup;
dispatch({
@ -40,13 +40,14 @@ async function loginFailback() {
}
socket.on('connect', async () => {
// @ts-ignore
dispatch({ type: ActionTypes.Connect, payload: null });
const token = window.localStorage.getItem('token');
if (token) {
const user = await loginByToken(
token,
platform.os.family,
platform.os?.family,
platform.name,
platform.description,
);
@ -56,11 +57,12 @@ socket.on('connect', async () => {
payload: user,
});
const linkmanIds = [
...user.groups.map((group) => group._id),
...user.friends.map((friend) => getFriendId(friend.from, friend.to._id)),
...user.groups.map((group: any) => group._id),
...user.friends.map((friend: any) => getFriendId(friend.from, friend.to._id)),
];
const linkmanMessages = await getLinkmansLastMessages(linkmanIds);
Object.values(linkmanMessages).forEach(
// @ts-ignore
(messages: Message[]) => messages.forEach(convertMessage),
);
dispatch({
@ -75,6 +77,7 @@ socket.on('connect', async () => {
});
socket.on('disconnect', () => {
// @ts-ignore
dispatch({ type: ActionTypes.Disconnect, payload: null });
});
@ -82,14 +85,14 @@ let windowStatus = 'focus';
window.onfocus = () => { windowStatus = 'focus'; };
window.onblur = () => { windowStatus = 'blur'; };
let prevFrom = '';
let prevFrom: string | null = '';
let prevName = '';
socket.on('message', async (message) => {
socket.on('message', async (message: any) => {
convertMessage(message);
const state = store.getState();
const isSelfMessage = message.from._id === state.user._id;
if (isSelfMessage && message.from.tag !== state.user.tag) {
const isSelfMessage = message.from._id === state.user?._id;
if (isSelfMessage && message.from.tag !== state.user?.tag) {
dispatch({
type: ActionTypes.UpdateUserInfo,
payload: {
@ -119,7 +122,7 @@ socket.on('message', async (message) => {
return;
}
const newLinkman = {
_id: getFriendId(state.user._id, message.from._id),
_id: getFriendId(state.user?._id as string, message.from._id),
type: 'temporary',
createTime: Date.now(),
avatar: message.from.avatar,
@ -181,13 +184,13 @@ socket.on('message', async (message) => {
prevFrom = from;
prevName = message.from.username;
} else if (message.type === 'system') {
voice.push(message.from.originUsername + message.content, null);
voice.push(message.from.originUsername + message.content, '');
prevFrom = null;
}
}
});
socket.on('changeGroupName', ({ groupId, name }) => {
socket.on('changeGroupName', ({ groupId, name }: {groupId: string, name: string}) => {
dispatch({
type: ActionTypes.SetLinkmanProperty,
payload: {
@ -198,7 +201,7 @@ socket.on('changeGroupName', ({ groupId, name }) => {
});
});
socket.on('deleteGroup', ({ groupId }) => {
socket.on('deleteGroup', ({ groupId }: {groupId: string}) => {
dispatch({
type: ActionTypes.RemoveLinkman,
payload: groupId,
@ -214,7 +217,7 @@ socket.on('changeTag', (tag: string) => {
});
});
socket.on('deleteMessage', ({ linkmanId, messageId }) => {
socket.on('deleteMessage', ({ linkmanId, messageId }: {linkmanId: string, messageId: string}) => {
dispatch({
type: ActionTypes.DeleteMessage,
payload: {

View File

@ -87,13 +87,13 @@ export interface User {
/** redux store state */
export interface State {
/** 用户信息 */
user?: {
user: {
_id: string;
username: string;
avatar: string;
tag: string;
isAdmin: boolean;
};
} | null;
linkmans: LinkmansMap;
/** 聚焦的联系人 */
focus: string;
@ -300,7 +300,9 @@ function reducer(state: State = initialState, action: Action): State {
} = action.payload as SetUserPayload;
// @ts-ignore
const linkmans: Linkman[] = [
// @ts-ignore
...groups.map(transformGroup),
// @ts-ignore
...friends.map(transformFriend),
];
linkmans.forEach((linkman) => {
@ -335,6 +337,7 @@ function reducer(state: State = initialState, action: Action): State {
const payload = action.payload as UpdateUserInfoPayload;
return {
...state,
// @ts-ignore
user: {
...state.user,
...payload,
@ -354,6 +357,7 @@ function reducer(state: State = initialState, action: Action): State {
case ActionTypes.SetAvatar: {
return {
...state,
// @ts-ignore
user: {
...state.user,
avatar: action.payload as string,
@ -449,6 +453,7 @@ function reducer(state: State = initialState, action: Action): State {
const { linkmans } = state;
const newState = { ...state, linkmans: {} };
Object.keys(linkmanMessages).forEach((linkmanId) => {
// @ts-ignore
newState.linkmans[linkmanId] = {
...linkmans[linkmanId],
messages: {

View File

@ -51,7 +51,6 @@ export default {
port: 8080,
autoOpenBrowser: false,
assetsPublicPath: '/',
proxyTable: {},
cssSourceMap: false,
},
};

View File

@ -6,7 +6,7 @@
"scripts": {
"start": "./node_modules/.bin/cross-env NODE_ENV=production && ./node_modules/.bin/ts-node --transpile-only server/main.ts",
"server": "./node_modules/.bin/cross-env NODE_ENV=development && nodemon server/main.ts --exec \"./node_modules/.bin/ts-node --files\" --config .nodemonrc",
"client": "./node_modules/.bin/ts-node build/dev-server.ts",
"client": "./node_modules/.bin/ts-node --files build/dev-server.ts",
"dashboard": "webpack-dashboard -- ./node_modules/.bin/ts-node build/dev-server.ts",
"build": "./node_modules/.bin/ts-node build/build.ts",
"lint": "./node_modules/.bin/eslint ./ --ext js,jsx,ts,tsx --ignore-pattern .eslintignore --cache --fix",
@ -111,11 +111,19 @@
"@types/mongoose": "^5.5.12",
"@types/node": "13.9.1",
"@types/platform": "^1.3.2",
"@types/prismjs": "^1.16.0",
"@types/pure-render-decorator": "^0.2.28",
"@types/randomcolor": "^0.5.4",
"@types/rc-tooltip": "^3.7.2",
"@types/react": "^16.9.1",
"@types/react-color": "^3.0.1",
"@types/react-copy-to-clipboard": "^4.3.0",
"@types/react-cropper": "^0.10.7",
"@types/react-dom": "^16.8.5",
"@types/react-redux": "^7.1.1",
"@types/redux": "^3.6.0",
"@types/socket.io": "^2.1.2",
"@types/socket.io-client": "^1.4.32",
"@typescript-eslint/eslint-plugin": "^2.0.0",
"@typescript-eslint/parser": "^2.0.0",
"babel-loader": "^8.0.6",

View File

@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "esnext",
"target": "es2018",
"strict": true,
"moduleResolution": "node",
"experimentalDecorators": true,

26
types/global.d.ts vendored
View File

@ -1,3 +1,29 @@
declare module 'opn';
declare module 'webpack';
declare module 'http-proxy-middleware';
declare module 'webpack-dev-middleware';
declare module 'webpack-hot-middleware';
declare module 'connect-history-api-fallback';
declare module 'ora';
declare module 'rimraf';
declare module 'less-plugin-autoprefix';
declare module 'extract-text-webpack-plugin';
declare module 'webpack-merge';
declare module 'html-webpack-plugin';
declare module 'friendly-errors-webpack-plugin';
declare module 'webpack-dashboard/plugin';
declare module 'copy-webpack-plugin';
declare module 'optimize-css-assets-webpack-plugin';
declare module 'script-ext-html-webpack-plugin';
declare module 'webpack-bundle-analyzer';
declare module 'react-radio-buttons';
declare module 'rc-tabs';
declare module 'rc-tabs/lib/TabContent';
declare module 'rc-tabs/lib/ScrollableInkTabBar';
declare module 'rc-menu';
declare module 'rc-dropdown';
declare module 'rc-notification';
declare module '*.less';
declare module '*.json';

View File

@ -1,39 +0,0 @@
/**
* state添加boolean状态值,
* ,
* @param {Object} values key: state键名 value: state默认值
*/
export default function booleanStateDecorator(values) {
return function wrapper(target) {
class BooleanStateDecoratorWrap extends target {
constructor(...args) {
super(...args);
// @ts-ignore
this.state = this.state || {};
Object.keys(values).forEach((key) => {
// 忽略非boolean类型
if (typeof values[key] !== 'boolean') {
return;
}
this.state[key] = values[key];
const upperKey = key[0].toUpperCase() + key.substr(1);
this[`toggle${upperKey}`] = (value) => {
if (typeof value === 'boolean') {
this.setState({ [key]: value });
} else {
// eslint-disable-next-line react/no-access-state-in-setstate
this.setState({ [key]: !this.state[key] });
}
};
});
}
render() {
return super.render();
}
}
return BooleanStateDecoratorWrap;
};
}

View File

@ -8,14 +8,18 @@ export default function compressImage(
image: HTMLImageElement,
mimeType: string,
quality = 1,
): Promise<Blob> {
): Promise<Blob | null> {
return new Promise((resolve) => {
const canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
canvas.toBlob(resolve, mimeType, quality);
if (ctx) {
ctx.drawImage(image, 0, 0);
canvas.toBlob(resolve, mimeType, quality);
} else {
resolve(null);
}
});
}

View File

@ -13,7 +13,7 @@
// }
// }
function convertSystemMessage(message) {
function convertSystemMessage(message: any) {
if (message.type === 'system') {
message.from._id = 'system';
message.from.originUsername = message.from.username;
@ -38,6 +38,6 @@ function convertSystemMessage(message) {
}
}
export default function convertMessage(message) {
export default function convertMessage(message: any) {
convertSystemMessage(message);
}

View File

@ -8,13 +8,13 @@ let isSeal = false;
export default function fetch<T = any>(event: string, data = {}, {
toast = true,
} = {}): Promise<[string, T]> {
} = {}): Promise<[string | null, T | null]> {
if (isSeal) {
Message.error(SealText);
return Promise.resolve([SealText, null]);
}
return new Promise((resolve) => {
socket.emit(event, data, (res) => {
socket.emit(event, data, (res: any) => {
if (typeof res === 'string') {
if (toast) {
Message.error(res);

View File

@ -1,23 +1,30 @@
import randomColor from 'randomcolor';
type ColorMode = 'dark' | 'bright' | 'light' | 'random';
/**
* ,
* @param seed when passed will cause randomColor to return the same color each time
*/
export function getRandomColor(seed: string, luminosity = 'dark') {
export function getRandomColor(seed: string, luminosity: ColorMode = 'dark') {
return randomColor({
luminosity,
seed,
});
}
const cache = {};
type Cache = {
[key: string]: string;
};
const cache: Cache = {};
/**
* ,
* @param seed
* @param luminosity
*/
export function getPerRandomColor(seed: string, luminosity = 'dark') {
export function getPerRandomColor(seed: string, luminosity: ColorMode = 'dark') {
if (cache[seed]) {
return cache[seed];
}

View File

@ -1,4 +1,8 @@
const huaji = {
type Huaji = {
[key: number]: string;
};
const huaji: Huaji = {
0: `${require('../client/assets/images/huaji/0.jpg')}?width=250&height=250&huaji=true`,
1: `${require('../client/assets/images/huaji/1.gif')}?width=300&height=300&huaji=true`,
2: `${require('../client/assets/images/huaji/2.jpeg')}?width=245&height=206&huaji=true`,

View File

@ -1,4 +1,4 @@
export default function notification(title, icon, body, tag = 'tag', duration = 3000) {
export default function notification(title: string, icon: string, body: string, tag = 'tag', duration = 3000) {
if (window.Notification && window.Notification.permission === 'granted') {
const n = new window.Notification(
title,

View File

@ -1,4 +1,8 @@
const sounds = {
type Sounds = {
[key: string]: string;
}
const sounds: Sounds = {
default: require('../client/assets/audios/default.mp3'),
apple: require('../client/assets/audios/apple.mp3'),
pcqq: require('../client/assets/audios/pcqq.mp3'),

View File

@ -17,13 +17,14 @@ export interface ReadFileResult {
* @param {string} accept , * / *
*/
export default async function readDiskFIle(resultType = 'blob', accept = '*/*') {
const result: ReadFileResult = await new Promise((resolve) => {
const result: ReadFileResult | null = await new Promise((resolve) => {
const $input = document.createElement('input');
$input.style.display = 'none';
$input.setAttribute('type', 'file');
$input.setAttribute('accept', accept);
// 判断用户是否点击取消, 原生没有提供专门事件, 用hack的方法实现
$input.onclick = () => {
// @ts-ignore
$input.value = null;
document.body.onfocus = () => {
// onfocus事件会比$input.onchange事件先触发, 因此需要延迟一段时间
@ -44,6 +45,7 @@ export default async function readDiskFIle(resultType = 'blob', accept = '*/*')
const reader = new FileReader();
reader.onloadend = function handleLoad() {
// @ts-ignore
resolve({
filename: file.name,
ext: file.name
@ -51,6 +53,7 @@ export default async function readDiskFIle(resultType = 'blob', accept = '*/*')
.pop()
.toLowerCase(),
type: file.type,
// @ts-ignore
result: this.result,
length:
resultType === 'blob'

View File

@ -1,4 +1,9 @@
export default function setCssVariable(color, textColor) {
/**
* set global css variable
* @param color primary color, three numbers split with comma, like 255,255,255
* @param textColor text colore, format like color
*/
export default function setCssVariable(color: string, textColor: string) {
let cssText = '';
for (let i = 0; i <= 10; i++) {
cssText += `--primary-color-${i}:rgba(${color}, ${i / 10});--primary-color-${i}_5:rgba(${color}, ${(i + 0.5) / 10});--primary-text-color-${i}:rgba(${textColor}, ${i / 10});`;

View File

@ -1,12 +1,12 @@
export default {
isToday(time1, time2) {
isToday(time1: Date, time2: Date) {
return (
time1.getFullYear() === time2.getFullYear()
&& time1.getMonth() === time2.getMonth()
&& time1.getDate() === time2.getDate()
);
},
isYesterday(time1, time2) {
isYesterday(time1: Date, time2: Date) {
const prevDate = new Date(time1);
prevDate.setDate(time1.getDate() - 1);
return (
@ -15,12 +15,12 @@ export default {
&& prevDate.getDate() === time2.getDate()
);
},
getHourMinute(time) {
getHourMinute(time: Date) {
const hours = time.getHours();
const minutes = time.getMinutes();
return `${hours < 10 ? `0${hours}` : hours}:${minutes < 10 ? `0${minutes}` : minutes}`;
},
getMonthDate(time) {
getMonthDate(time: Date) {
return `${time.getMonth() + 1}/${time.getDate()}`;
},
};

View File

@ -66,7 +66,7 @@ export default async function uploadFile(
qiniuNextEventCallback(info);
}
},
error: (qiniuErr) => {
error: (qiniuErr: Error) => {
reject(qiniuErr);
},
complete: async (info: QiniuUploadInfo) => {

View File

@ -10,7 +10,7 @@ $audio.appendChild($source);
document.body.appendChild($audio);
let baiduToken = '';
async function read(text, cuid) {
async function read(text: string, cuid: string) {
if (!baiduToken) {
const [err, result] = await fetch('getBaiduToken');
if (err) {
@ -40,7 +40,12 @@ async function read(text, cuid) {
}
}
const taskQueue = [];
type Task = {
text: string;
cuid: string;
}
const taskQueue: Task[] = [];
let isWorking = false;
async function handleTaskQueue() {
isWorking = true;
@ -54,7 +59,7 @@ async function handleTaskQueue() {
}
const voice = {
push(text, cuid) {
push(text: string, cuid: string) {
taskQueue.push({ text, cuid });
if (!isWorking) {
handleTaskQueue();

View File

@ -1360,21 +1360,65 @@
resolved "https://registry.npm.taobao.org/@types/platform/download/@types/platform-1.3.2.tgz#c0404c4e2a08e04295ab3d1241a856c2a3cab06e"
integrity sha1-wEBMTioI4EKVqz0SQahWwqPKsG4=
"@types/prismjs@^1.16.0":
version "1.16.0"
resolved "https://registry.npm.taobao.org/@types/prismjs/download/@types/prismjs-1.16.0.tgz#4328c9f65698e59f4feade8f4e5d928c748fd643"
integrity sha1-QyjJ9laY5Z9P6t6PTl2SjHSP1kM=
"@types/prop-types@*":
version "15.7.3"
resolved "https://registry.npm.taobao.org/@types/prop-types/download/@types/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
integrity sha1-KrDV2i5YFflLC51LldHl8kOrLKc=
"@types/pure-render-decorator@^0.2.28":
version "0.2.28"
resolved "https://registry.npm.taobao.org/@types/pure-render-decorator/download/@types/pure-render-decorator-0.2.28.tgz#dc58f93b274aa65c72820470989972df3048152a"
integrity sha1-3Fj5OydKplxyggRwmJly3zBIFSo=
"@types/q@^1.5.1":
version "1.5.2"
resolved "https://registry.npm.taobao.org/@types/q/download/@types/q-1.5.2.tgz?cache=0&sync_timestamp=1572589392471&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fq%2Fdownload%2F%40types%2Fq-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8"
integrity sha1-aQoUdbhPKohP0HzXl8APXzE1bqg=
"@types/randomcolor@^0.5.4":
version "0.5.4"
resolved "https://registry.npm.taobao.org/@types/randomcolor/download/@types/randomcolor-0.5.4.tgz#75230e780326bf372c13015c361dddca1087eb0f"
integrity sha1-dSMOeAMmvzcsEwFcNh3dyhCH6w8=
"@types/range-parser@*":
version "1.2.3"
resolved "https://registry.npm.taobao.org/@types/range-parser/download/@types/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
integrity sha1-fuMwunyq+5gJC+zoal7kQRWQTCw=
"@types/rc-tooltip@^3.7.2":
version "3.7.2"
resolved "https://registry.npm.taobao.org/@types/rc-tooltip/download/@types/rc-tooltip-3.7.2.tgz#bbf35284cc1adf6fb61cc49262fd4199ebcc7bff"
integrity sha1-u/NShMwa32+2HMSSYv1BmevMe/8=
dependencies:
"@types/react" "*"
"@types/react-color@^3.0.1":
version "3.0.1"
resolved "https://registry.npm.taobao.org/@types/react-color/download/@types/react-color-3.0.1.tgz#5433e2f503ea0e0831cbc6fd0c20f8157d93add0"
integrity sha1-VDPi9QPqDggxy8b9DCD4FX2TrdA=
dependencies:
"@types/react" "*"
"@types/react-copy-to-clipboard@^4.3.0":
version "4.3.0"
resolved "https://registry.npm.taobao.org/@types/react-copy-to-clipboard/download/@types/react-copy-to-clipboard-4.3.0.tgz#8e07becb4f11cfced4bd36038cb5bdf5c2658be5"
integrity sha1-jge+y08Rz87UvTYDjLW99cJli+U=
dependencies:
"@types/react" "*"
"@types/react-cropper@^0.10.7":
version "0.10.7"
resolved "https://registry.npm.taobao.org/@types/react-cropper/download/@types/react-cropper-0.10.7.tgz#1a8a7abf4699d20cd83fa6b0ffcb9a849ed4820d"
integrity sha1-Gop6v0aZ0gzYP6aw/8uahJ7Ugg0=
dependencies:
"@types/react" "*"
cropperjs "^1.3.0"
"@types/react-dom@^16.8.5":
version "16.9.4"
resolved "https://registry.npm.taobao.org/@types/react-dom/download/@types/react-dom-16.9.4.tgz#0b58df09a60961dcb77f62d4f1832427513420df"
@ -1420,6 +1464,11 @@
"@types/express-serve-static-core" "*"
"@types/mime" "*"
"@types/socket.io-client@^1.4.32":
version "1.4.32"
resolved "https://registry.npm.taobao.org/@types/socket.io-client/download/@types/socket.io-client-1.4.32.tgz#988a65a0386c274b1c22a55377fab6a30789ac14"
integrity sha1-mIploDhsJ0scIqVTd/q2oweJrBQ=
"@types/socket.io@^2.1.2":
version "2.1.4"
resolved "https://registry.npm.taobao.org/@types/socket.io/download/@types/socket.io-2.1.4.tgz#674e7bc193c5ccdadd4433f79f3660d31759e9ac"
@ -3287,7 +3336,7 @@ create-react-class@^15.5.2, create-react-class@^15.6.0:
loose-envify "^1.3.1"
object-assign "^4.1.1"
cropperjs@^1.5.5:
cropperjs@^1.3.0, cropperjs@^1.5.5:
version "1.5.6"
resolved "https://registry.npm.taobao.org/cropperjs/download/cropperjs-1.5.6.tgz#82faf432bec709d828f2f7a96d1179198edaf0e2"
integrity sha1-gvr0Mr7HCdgo8vepbRF5GY7a8OI=