mirror of
https://github.com/yinxin630/fiora.git
synced 2026-06-04 21:03:18 +08:00
refactor: fix client ts error
This commit is contained in:
parent
eab0c04b3b
commit
39e537f9fb
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
@ -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!');
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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,
|
||||
},
|
||||
{
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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)(\?.*)?$/,
|
||||
|
||||
@ -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]);
|
||||
});
|
||||
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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}>
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -72,6 +72,7 @@ function Chat() {
|
||||
action.setLinkmanProperty(focus, 'onlineMembers', onlineMembers);
|
||||
toggleGroupManagePanel(true);
|
||||
} else {
|
||||
// @ts-ignore
|
||||
context.showUserInfo(linkman);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
)}
|
||||
|
||||
@ -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) => (
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ function FunctionBarAndLinkmanList() {
|
||||
return null;
|
||||
}
|
||||
|
||||
function handleClick(e) {
|
||||
function handleClick(e: any) {
|
||||
if (e.target === e.currentTarget) {
|
||||
action.setStatus('functionBarAndLinkmanListVisible', false);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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 }}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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成功');
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -51,7 +51,6 @@ export default {
|
||||
port: 8080,
|
||||
autoOpenBrowser: false,
|
||||
assetsPublicPath: '/',
|
||||
proxyTable: {},
|
||||
cssSourceMap: false,
|
||||
},
|
||||
};
|
||||
|
||||
10
package.json
10
package.json
@ -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",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"target": "es2018",
|
||||
"strict": true,
|
||||
"moduleResolution": "node",
|
||||
"experimentalDecorators": true,
|
||||
|
||||
26
types/global.d.ts
vendored
26
types/global.d.ts
vendored
@ -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';
|
||||
|
||||
|
||||
@ -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;
|
||||
};
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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];
|
||||
}
|
||||
|
||||
@ -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`,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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'),
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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});`;
|
||||
|
||||
@ -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()}`;
|
||||
},
|
||||
};
|
||||
|
||||
@ -66,7 +66,7 @@ export default async function uploadFile(
|
||||
qiniuNextEventCallback(info);
|
||||
}
|
||||
},
|
||||
error: (qiniuErr) => {
|
||||
error: (qiniuErr: Error) => {
|
||||
reject(qiniuErr);
|
||||
},
|
||||
complete: async (info: QiniuUploadInfo) => {
|
||||
|
||||
@ -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();
|
||||
|
||||
51
yarn.lock
51
yarn.lock
@ -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=
|
||||
|
||||
Loading…
Reference in New Issue
Block a user