Showing preview only (2,058K chars total). Download the full file or copy to clipboard to get everything.
Repository: Same-Page/front-and-back
Branch: master
Commit: 967be22b5f1a
Files: 231
Total size: 1.9 MB
Directory structure:
gitextract_332hnacs/
├── .gitignore
├── .prettierrc
├── README.md
├── README_EN.md
├── chatbox/
│ ├── .gitignore
│ ├── .prettierrc
│ ├── .storybook/
│ │ ├── addons.js
│ │ └── config.js
│ ├── README.md
│ ├── package.json
│ ├── public/
│ │ ├── index.html
│ │ └── manifest.json
│ └── src/
│ ├── .eslintrc.json
│ ├── App.js
│ ├── components/
│ │ ├── Emoji/
│ │ │ ├── Emoji.css
│ │ │ ├── Emoji.js
│ │ │ └── index.js
│ │ ├── Iframe/
│ │ │ ├── Iframe.css
│ │ │ ├── Iframe.js
│ │ │ └── index.js
│ │ ├── InputWithPicker/
│ │ │ ├── InputWithPicker.css
│ │ │ ├── InputWithPicker.js
│ │ │ └── index.js
│ │ ├── MusicPlayer/
│ │ │ ├── MusicPlayer.css
│ │ │ ├── MusicPlayer.js
│ │ │ └── index.js
│ │ └── OutsideClickDetector.js
│ ├── config/
│ │ ├── index.js
│ │ ├── logger.js
│ │ └── urls.js
│ ├── containers/
│ │ ├── Account/
│ │ │ ├── Account.js
│ │ │ ├── AvatarUploader/
│ │ │ │ ├── AvatarUploader.css
│ │ │ │ ├── AvatarUploader.js
│ │ │ │ └── index.js
│ │ │ ├── Blacklist.js
│ │ │ ├── EditProfile.js
│ │ │ ├── Follow/
│ │ │ │ ├── Follow.css
│ │ │ │ ├── Follow.js
│ │ │ │ ├── event.js
│ │ │ │ └── index.js
│ │ │ ├── Login.js
│ │ │ ├── Profile/
│ │ │ │ ├── Profile.css
│ │ │ │ ├── Profile.js
│ │ │ │ └── index.js
│ │ │ ├── ResetPassword.js
│ │ │ └── index.js
│ │ ├── Chat/
│ │ │ ├── Body.css
│ │ │ ├── Body.js
│ │ │ ├── Chat.js
│ │ │ ├── Footer/
│ │ │ │ ├── Footer.css
│ │ │ │ ├── Footer.js
│ │ │ │ └── index.js
│ │ │ ├── Header/
│ │ │ │ ├── Header.css
│ │ │ │ ├── Header.js
│ │ │ │ ├── Invite.js
│ │ │ │ ├── RoomHeader.js
│ │ │ │ ├── RoomInfo.js
│ │ │ │ ├── Users.js
│ │ │ │ └── index.js
│ │ │ ├── Message/
│ │ │ │ ├── Body.css
│ │ │ │ ├── Body.js
│ │ │ │ ├── Message.css
│ │ │ │ ├── Message.js
│ │ │ │ └── index.js
│ │ │ ├── ResizableMedia.js
│ │ │ ├── View.js
│ │ │ └── index.js
│ │ ├── Comment/
│ │ │ ├── Body.js
│ │ │ ├── Comment.css
│ │ │ ├── Comment.js
│ │ │ ├── Header.js
│ │ │ ├── Message/
│ │ │ │ ├── Message.css
│ │ │ │ ├── Message.js
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ ├── Home/
│ │ │ ├── Comments/
│ │ │ │ ├── Comment.css
│ │ │ │ ├── Comments.js
│ │ │ │ └── index.js
│ │ │ ├── CreateRoom.js
│ │ │ ├── Danmus/
│ │ │ │ ├── Danmus.js
│ │ │ │ └── index.js
│ │ │ ├── Discover.js
│ │ │ ├── Home.css
│ │ │ ├── Home.js
│ │ │ ├── Rooms/
│ │ │ │ ├── Room.css
│ │ │ │ ├── Rooms.js
│ │ │ │ └── index.js
│ │ │ ├── RoomsWrapper.js
│ │ │ ├── Users/
│ │ │ │ ├── Users.css
│ │ │ │ ├── Users.js
│ │ │ │ └── index.js
│ │ │ ├── VideoRoom/
│ │ │ │ ├── VideoRoom.js
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ ├── Inbox/
│ │ │ ├── Conversation/
│ │ │ │ ├── Conversation.css
│ │ │ │ ├── Conversation.js
│ │ │ │ └── index.js
│ │ │ ├── Inbox.css
│ │ │ ├── Inbox.js
│ │ │ └── index.js
│ │ ├── Music/
│ │ │ ├── MusicTab.js
│ │ │ ├── Playlist/
│ │ │ │ ├── Playlist.css
│ │ │ │ ├── Playlist.js
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ ├── OtherProfile/
│ │ │ ├── AvatarWithHoverCard.js
│ │ │ ├── OtherProfile.js
│ │ │ ├── ProfileBody/
│ │ │ │ ├── ProfileBody.js
│ │ │ │ └── index.js
│ │ │ ├── ProfileCard.css
│ │ │ ├── ProfileCard.js
│ │ │ ├── ProfileMeta/
│ │ │ │ ├── ProfileMeta.js
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ └── Tab/
│ │ ├── Tab.css
│ │ ├── Tab.js
│ │ └── index.js
│ ├── context/
│ │ └── preference-context.js
│ ├── i18n/
│ │ ├── en.json
│ │ └── zh.json
│ ├── index.css
│ ├── index.js
│ ├── redux/
│ │ ├── actions/
│ │ │ ├── chat/
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ ├── reducers/
│ │ │ └── index.js
│ │ └── store/
│ │ └── index.js
│ ├── serviceWorker.js
│ ├── services/
│ │ ├── account.js
│ │ ├── comment.js
│ │ ├── danmu.js
│ │ ├── follow.js
│ │ ├── index.js
│ │ ├── message.js
│ │ ├── room.js
│ │ └── user.js
│ ├── socket/
│ │ ├── index.js
│ │ └── socket.js
│ ├── stories/
│ │ ├── IframeWithSrcInput.js
│ │ ├── data/
│ │ │ ├── chats.js
│ │ │ └── comments.js
│ │ ├── iframe.css
│ │ └── index.js
│ └── utils/
│ ├── pageTitle.js
│ ├── storage.js
│ └── url.js
├── extension/
│ ├── build/
│ │ ├── _locales/
│ │ │ ├── en/
│ │ │ │ └── messages.json
│ │ │ └── zh_CN/
│ │ │ └── messages.json
│ │ ├── background.js
│ │ ├── content-static/
│ │ │ ├── css/
│ │ │ │ └── main.css
│ │ │ └── js/
│ │ │ └── main.js
│ │ ├── manifest.json
│ │ ├── popup.css
│ │ ├── popup.html
│ │ ├── popup_menu/
│ │ │ ├── css/
│ │ │ │ └── main.css
│ │ │ └── js/
│ │ │ └── main.js
│ │ └── popup_old.js
│ └── popup/
│ ├── .gitignore
│ ├── README.md
│ ├── config/
│ │ ├── env.js
│ │ ├── jest/
│ │ │ ├── cssTransform.js
│ │ │ └── fileTransform.js
│ │ ├── paths.js
│ │ ├── webpack.config.js
│ │ └── webpackDevServer.config.js
│ ├── package.json
│ ├── public/
│ │ ├── index.html
│ │ └── manifest.json
│ ├── scripts/
│ │ ├── build.js
│ │ ├── start.js
│ │ └── test.js
│ └── src/
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ └── serviceWorker.js
└── inject-script/
├── .gitignore
├── .storybook/
│ ├── addons.js
│ ├── config.js
│ └── webpack_empty.config.js
├── README.md
├── config/
│ ├── env.js
│ ├── jest/
│ │ ├── cssTransform.js
│ │ └── fileTransform.js
│ ├── paths.js
│ ├── webpack.config.js
│ └── webpackDevServer.config.js
├── package.json
├── public/
│ ├── index.html
│ └── manifest.json
├── scripts/
│ ├── build.js
│ ├── start.js
│ └── test.js
├── src/
│ ├── config/
│ │ ├── iframe.js
│ │ ├── index.js
│ │ ├── logger.js
│ │ └── urls.js
│ ├── containers/
│ │ ├── App/
│ │ │ ├── App.js
│ │ │ ├── App.test.js
│ │ │ └── index.js
│ │ ├── ChatDanmu/
│ │ │ ├── AnimationDanmu.css
│ │ │ ├── AnimationDanmu.js
│ │ │ └── Danmu.js
│ │ ├── ChatIcon/
│ │ │ ├── ChatIcon.js
│ │ │ └── index.js
│ │ ├── ChatboxIframe/
│ │ │ ├── ChatboxIframe.css
│ │ │ ├── ChatboxIframe.js
│ │ │ └── index.js
│ │ ├── ImageModal/
│ │ │ ├── ImageModal.css
│ │ │ ├── ImageModal.js
│ │ │ └── index.js
│ │ ├── Room/
│ │ │ ├── Room.css
│ │ │ ├── Room.js
│ │ │ └── index.js
│ │ └── User/
│ │ ├── User.css
│ │ ├── User.js
│ │ └── index.js
│ ├── index.css
│ ├── index.js
│ ├── serviceWorker.js
│ ├── services/
│ │ ├── account/
│ │ │ ├── account.js
│ │ │ └── index.js
│ │ ├── room/
│ │ │ ├── index.js
│ │ │ └── room.js
│ │ ├── socket/
│ │ │ ├── index.js
│ │ │ └── socket.js
│ │ └── user/
│ │ ├── index.js
│ │ └── user.js
│ ├── storage.js
│ └── utils/
│ ├── iframe.js
│ └── url.js
└── stories/
└── index.stories.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.DS_Store
client/extension/build/content-static/*
client/extension/build.zip
client/.vscode/launch.json
*.zip
================================================
FILE: .prettierrc
================================================
{
"trailingComma": "none",
"arrowParens": "avoid",
"semi": false,
"useTabs": true,
"printWidth": 80
}
================================================
FILE: README.md
================================================
#### [Read English Version](https://github.com/Same-Page/client/blob/master/README_EN.md)
# 一叶
《一叶》是一款[浏览器插件](https://chrome.google.com/webstore/detail/same-page/bldcellajihanglphncgjmceklbibjkk),它让你可以在任意网页上实时聊天。
你也可以将一叶聊天盒部署在你自己的网站上,一叶的前后端代码都是开源的。
主要功能包括有:
- 同网页聊天
- 同网站聊天
- 创建个性化房间
- 用户之间可以关注,发私信等
## 截图
<img src="https://yiyechat.com/images/sp-screenshots.png" />
## 项目结构
一叶的前端代码在本项目里。
Client 文件夹主要有 chatbox 和 inject-script 两部分,都是用 create-react-app 创建的。工作方法是首先在网页里引入 inject-script,然后 inject-script 运行时会生成一个包含了聊天盒的 iframe。
这种分离的设计有两个优点:
1. 聊天盒因为功能很多所以文件较大,所以不用在每次加载页面的时候就加载它,而是当用户想要使用的时候才打开。
2. Iframe 可以起到很好的隔离作用,你的网站和一叶之间不会互相影响,不论是 javascript 还是 css 样式。
Inject script 部分的文件比较小,它的功能有:
1. 在网页上增加一个按钮,点击按钮可以打开含有聊天盒的 iframe。
2. 自动连接聊天服务器,显示多少人在当前网页或网站,显示实时聊天弹幕。
## 如何开发与本地运行
### 本地运行客户端
前面提到了客户端分有 inject-script 和 chatbox 两部分,两者都要启动聊天盒才能正常使用。
首先运行 chatbox 部分
```
cd client/chatbox
npm install .
npm start
```
第一次运行需要先 `npm install .`安装依赖的库,之后则不用。项目会运行于 localhost:3000,但聊天盒并不能独立工作,接下来我们运行 injection-script 的部分。
```
cd client/inject-script
npm install .
npm start
```
现在如果你去 localhost:3210,就可以看到客户端正常运行了,点击右下角的圆圈打开聊天盒。
客户端默认会和官方的服务器通讯,而不是你的本地后端,你可以更改默认的设定,让客户端和你的本地后端通讯。如何运行后端代码请见后端的代码库
[聊天系统后端](https://github.com/Same-Page/chat-backend)
[基本系统后端](https://github.com/Same-Page/web-backend)
## 如何部署前端
### 生成客户端文件
Inject-script 和 chatbox 两部分要分别生成。
生成聊天盒的客户端文件。
```
cd client/chatbox
npm run build
```
生成 inject-script 的客户端文件。
```
cd client/inject-scirpt
npm run build
```
生成的文件在对应的 build 文件夹里,更多细节可以参考 Facebook 官方的 create-react-app 教程。
### 部署到你的网站上
将 inject-script 的`client/inject-scirpt/build/content-static`文件夹上传到你的服务器,确保可以以`your-website.com/build/content-static`的形式访问里面的文件. 接下来在你的网站里引入下面两行即可
```
<link href="https://your-website.com/build/content-static/css/main.css" rel="stylesheet">
<script src="https://your-website.com/build/content-static/js/main.js" ></script>
```
如果聊天盒的部分也要使用你自己生成的版本,相似的,上传你的`client/chatbox/build/`文件夹到你的服务器,确保可以访问`your-website.com/build/static`里面的文件。同时要记得修改 injection script 中设置的 chatbox iframe 的地址指向你自己上传的这个版本,`your-website.com/build/index.html`,也可以在你的网站定义下面的设置。
```
window.spConfig = {
chatboxUrl: 'your-website.com/build/index.html'
}
```
================================================
FILE: README_EN.md
================================================
# Same Page
Same Page (previously called Chat Anywhere) is a [Chrome extension](https://chrome.google.com/webstore/detail/same-page/bldcellajihanglphncgjmceklbibjkk) that adds a chat box to any website. You can also customize the source code and use it on your own website.
## Project structure
This repository contains frontend code only.
The client folder includes the chat box and an injection script, both of them are built with create-react-app. The way it works is that once you include the injection script to your website, the script can create an iframe with chat box. This design has two major benefits:
1. The chat box's file size is quite big because it includes so many features, therefore you probably don't want to load it on every page load. It makes a lot more sense to load the chat box iframe only when user wants to use it.
2. Iframe provides great isolation of the chat box and your website, no need to worry about CSS or Javascript pollution.
The injection script itself is rather small, it's able to
1. Add a button that creates the iframe with chat box.
2. Connect to chat socket server and show how many users are on the same page or site.
3. Show Danmu (scrolling text) for live messages.
We include the 2nd and the 3rd functionality to the injection script rather than the chat box because they are very useful for the Chrome extension.
## How to develop and run locally
### Run the client locally
Remeber that the client contains two pieces, so there are two steps to run client locally.
First, run the chat box app
```
cd client/chatbox
npm install .
npm start
```
The first time you run this will be slower, because it needs to install all the packages needed. The chat box will be running on localhost:3000, but the chat box is not designed to be used by itself, instead, it must be used in an iframe. So the next thing you need to do is to run the injection script app.
```
cd client/injection-script
npm install .
npm start
```
Now you should see the client running on localhost:3210, the chatbox should also show up there.
The client is talking to the official backend by default. If you want the client to talk to your local backend, it's very easy. Instead of `npm start`, type `npm start:dev`. Then it will be talking to your localhost:8080 for websocket connections for live chat feature and localhost:8081 for ajax calls for all other purposes. So you need to have those running. Please read the next section to learn how to run backend locally.
### Run websocket server locally for live chat
```
cd server/chat-socket
npm install .
node index.js
```
### Run API backend locally
```
cd server/api
(Highly recommend that you create a Python virtual env first)
pip install -r requirements.txt
python run.py
```
## How to deploy frontend
### Build
Like mentioned before, the client has two parts - chat box and injection script. They need to be built separately.
To build the chat box.
```
cd client/chatbox
npm run build
```
To build the injection script.
```
cd client/injection-scirpt
npm run build
```
The final build is placed in the `/build` folder.
### Deploy
To use your own version of injection script, upload your `client/injection-scirpt/build/content-static` folder to your server so it's reachable as `your-website.com/build/content-static`. Then include the scripts by adding following lines to your website.
```
<link href="https://your-website.com/build/content-static/css/main.css" rel="stylesheet">
<script src="https://your-website.com/build/content-static/js/main.js" ></script>
```
To use your own version of chat box, upload your `client/chatbox/build/content-static` folder to your server so it's reachable as `your-website.com/build/static`. You'll need to modify the injection script, specifically the iframe src to point to your own chat box, otherwise it will use the offical build of the chat box.
This doc needs update...
================================================
FILE: chatbox/.gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
================================================
FILE: chatbox/.prettierrc
================================================
{
"tabWidth": 2,
"semi": false
}
================================================
FILE: chatbox/.storybook/addons.js
================================================
import '@storybook/addon-actions/register';
import '@storybook/addon-links/register';
================================================
FILE: chatbox/.storybook/config.js
================================================
import { configure } from '@storybook/react';
function loadStories() {
require('../src/stories');
}
configure(loadStories, module);
================================================
FILE: chatbox/README.md
================================================
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.<br>
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br>
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.<br>
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.<br>
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br>
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
### Analyzing the Bundle Size
This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
### Making a Progressive Web App
This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
### Advanced Configuration
This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
### Deployment
This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
### `npm run build` fails to minify
This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
================================================
FILE: chatbox/package.json
================================================
{
"name": "same-page",
"homepage": "./",
"version": "0.1.0",
"private": true,
"dependencies": {
"@material-ui/core": "^3.9.3",
"antd": "^3.16.1",
"axios": "^0.18.0",
"emoji-mart": "^2.11.0",
"fsevents": "^1.2.9",
"material-ui": "^0.20.2",
"moment": "^2.24.0",
"re-resizable": "^6.2.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-images-upload": "^1.2.6",
"react-intl": "^3.6.0",
"react-redux": "^6.0.1",
"react-scripts": "2.1.8",
"redux": "^4.0.1",
"rsuite": "^3.8.2",
"video.js": "^7.5.4",
"videojs-flash": "^2.2.0",
"videojs-playlist": "^4.3.1",
"videojs-youtube": "^2.6.0"
},
"scripts": {
"start:no_ssl": "react-scripts start",
"start": "export HTTPS=true; react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject-nonono": "react-scripts eject",
"storybook": "start-storybook -p 9009 -s public",
"build-storybook": "build-storybook -s public"
},
"eslintConfig": {
"extends": "react-app",
"plugins": [
"react-hooks"
],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
],
"devDependencies": {
"@babel/core": "^7.4.3",
"@storybook/addon-actions": "^5.0.6",
"@storybook/addon-links": "^5.0.6",
"@storybook/addons": "^5.0.6",
"@storybook/react": "^5.0.6",
"babel-loader": "^8.0.5",
"eslint-config-airbnb": "^17.1.0",
"eslint-plugin-import": "^2.17.3",
"eslint-plugin-jsx-a11y": "^6.2.1",
"eslint-plugin-react": "^7.13.0",
"eslint-plugin-react-hooks": "^1.6.0"
}
}
================================================
FILE: chatbox/public/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
================================================
FILE: chatbox/public/manifest.json
================================================
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
================================================
FILE: chatbox/src/.eslintrc.json
================================================
{
"extends": "react-app",
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
================================================
FILE: chatbox/src/App.js
================================================
import axios from "axios"
import moment from "moment"
import { IntlProvider, createIntl } from "react-intl"
import msg_zh from "i18n/zh.json"
import msg_en from "i18n/en.json"
import React from "react"
import { Icon, message } from "antd"
import { connect } from "react-redux"
import Tab from "containers/Tab"
// import socketManager from "socket/socket"
import storageManager from "utils/storage"
import urls from "config/urls"
import {
changeChatView,
setChatModes,
joinManMadeRoom
} from "redux/actions/chat"
import { changeTab, setAccount, setBlacklist } from "redux/actions"
import store from "redux/store"
import { setPageTitle } from "utils/pageTitle"
import { setUrl } from "utils/url"
require("moment/locale/zh-cn") //moment.js bug, has to manually include
const i18nMsg = {
zh: msg_zh,
en: msg_en
}
const locale = window.navigator.userLanguage || window.navigator.language
const msg = i18nMsg[locale.substring(0, 2)]
const intl = createIntl({
locale: locale,
messages: msg
})
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
// account: null,
// A few steps before mounting the app
// 1. Check local/chrome storage to see if there's account data
// , if so, mount the app.
// 2. If no account in storage, check if there's credential data
// in storage, if so, mount the app and auto login
// 3. If neither account nor credential data is in storage,
// mount the app and auto register
// TODO: remove 1 and 2?
// In short, do not mount until done loading account/credential
// from storage
// above comment needs update
loadingAccountFromStorage: true,
waitingForConfigFromParent: true,
// Only if there is no account data in storage on page load
// autoLogin only once per page load
autoLogin: false
// mode: null,
// room: null,
// realRoom: DEFAULT_REAL_ROOM
}
// if (locale.indexOf("zh") > -1) {
// moment.locale("zh-cn")
// }
moment.locale(locale)
// window.spDebug(locale)
message.config({
top: 80,
duration: 2,
maxCount: 3
})
}
componentDidMount() {
// TODO: this componentDidMount is registering a
// couple different things, maybe move them to dedicated module
// General settings for ajax calls
axios.interceptors.response.use(
response => {
// Do something with response data
return response
},
error => {
let errorMessage = "出错了"
// set account to null when we receive 401
if (error.response && error.response.status === 401) {
storageManager.set("account", null)
errorMessage = intl.formatMessage({ id: "not.login" })
}
if (
error.response &&
error.response.data &&
error.response.data.error
) {
errorMessage = error.response.data.error
}
if (error.response && error.response.status === 403) {
errorMessage = "禁止通行"
}
if (error.response && error.response.status === 409) {
errorMessage = "权限不足"
}
if (error.response && error.response.status === 413) {
errorMessage = "文件太大"
}
if (error.response && error.response.status === 429) {
errorMessage = "操作频繁,请稍后再试"
}
if (error.response && error.response.status === 402) {
errorMessage = "积分不足"
}
message.error(errorMessage)
console.error(error)
return Promise.reject(error)
}
)
// storageManager.get("mode", mode => {
// if (mode) {
// this.setState({ mode: mode })
// }
// })
if (window.parent && window.parent !== window) {
window.parent.postMessage({ action: "sp-parent-data" }, "*")
// storageManager.pushToParentWindow()
// shouldn't need to push to parent on chatbox load
// should pull from parent instead
} else {
window.spConfig = {}
this.setState({ waitingForConfigFromParent: false })
console.error("no parent window, this won't work without config!")
}
// storageManager.get("realRoom", realRoom => {
// if (realRoom) {
// this.setState({ realRoom: realRoom })
// }
// })
// console.log("get account from storage, register account change listener")
storageManager.get("room", room => {
if (room) {
this.props.joinManMadeRoom(room)
}
})
storageManager.get("account", account => {
if (account) {
window.spDebug("found account in storage")
// window.spDebug(account)
this.props.setAccount(account)
} else {
this.setState({ autoLogin: true })
window.spDebug("no account found in storage")
}
this.setState({ loadingAccountFromStorage: false })
})
storageManager.addEventListener("account", account => {
this.props.setAccount(account)
})
storageManager.get("blacklist", blacklist => {
// simple implementation for blacklist, it's per browser device
// not per account...
blacklist = blacklist || []
this.props.setBlacklist(blacklist)
})
storageManager.addEventListener("blacklist", blacklist => {
this.props.setBlacklist(blacklist)
})
window.addEventListener(
"message",
e => {
if (!(e && e.data)) return
const data = e.data.data
const type = e.data.type
if (type === "sp-parent-data") {
const spConfig = data.spConfig
window.spConfig = spConfig
urls.dbAPI = spConfig.apiUrl || urls.dbAPI
urls.socketAPI = spConfig.socketUrl || urls.socketAPI
store.dispatch(setChatModes(spConfig.chatModes))
spConfig.defaultChatView =
spConfig.defaultChatView || spConfig.chatModes[0]
store.dispatch(changeChatView(spConfig.defaultChatView))
store.dispatch(changeTab(spConfig.defaultTab))
if (data.account) {
storageManager.set("account", data.account)
}
if (data.blacklist) {
storageManager.set("blacklist", data.blacklist)
}
this.setState({ waitingForConfigFromParent: false })
}
if (type === "sp-blacklist") {
storageManager.set("blacklist", data)
}
if (type === "sp-url-changed") {
setUrl(data.url)
console.log("url changed to " + data.url)
setPageTitle(data.title)
}
},
false
)
}
componentDidUpdate(prevProps, prevState, snapshot) {
// Need to differentiate login/logout with profile info update
// maybe shouldn't have grouped them in one object?
// const login = !prevState.account && this.state.account
// const logout = prevState.account && !this.state.account
// if (login) {
// window.spDebug("logged in")
// axios.defaults.headers.common["token"] = this.state.account.token
// }
// if (logout) {
// window.spDebug("logged out")
// axios.defaults.headers.common["token"] = null
// // clear storage
// storageManager.set("unread", false)
// storageManager.set("inbox", null)
// storageManager.set("inbox-offset", 0)
// // TODO: change tab?
// }
}
// setAccount = account => {
// // window.spDebug("set account")
// storageManager.set("account", account)
// }
stopAutoLogin = () => {
this.setState({ autoLogin: false })
}
render() {
if (
this.state.loadingAccountFromStorage ||
this.state.waitingForConfigFromParent
) {
return (
<center>
<Icon style={{ marginTop: "50%" }} type="loading" />
</center>
)
}
// if (!this.props.account) {
// tab = "account"
// }
return (
<IntlProvider locale={locale} messages={msg}>
<Tab />
</IntlProvider>
)
}
}
// export default App
const stateToProps = state => {
return {
account: state.account
}
}
export default connect(stateToProps, {
setAccount,
joinManMadeRoom,
setBlacklist
})(App)
================================================
FILE: chatbox/src/components/Emoji/Emoji.css
================================================
.emoji-mart-preview {
display: none;
}
.emoji-mart {
max-width: 100%;
position: fixed;
bottom: 39px;
left: 0px;
border-bottom: none !important;
z-index: 10;
}
.emoji-mart .emoji-mart-scroll {
height: 200px;
}
.emoji-mart-category-label span {
margin-bottom: 5px;
}
.emoji-mart-emoji-custom span {
width: 72px !important;
height: 72px !important;
}
.emoji-mart-category .emoji-mart-emoji span {
cursor: pointer;
}
.emoji-mart-anchor {
cursor: pointer;
}
================================================
FILE: chatbox/src/components/Emoji/Emoji.js
================================================
import "./Emoji.css"
import "emoji-mart/css/emoji-mart.css"
import React from "react"
import { createIntl } from "react-intl"
import { Picker } from "emoji-mart"
import ClickWrapper from "../OutsideClickDetector"
import msg_zh from "i18n/zh.json"
import msg_en from "i18n/en.json"
const i18nMsg = {
zh: msg_zh,
en: msg_en
}
// const { intl } = new IntlProvider(
// { locale: language, messages: messages },
// {}
// ).getChildContext()
const intl = createIntl({
locale: navigator.language,
messages: i18nMsg[navigator.language.substring(0, 2)]
})
const i18n = {
search: intl.formatMessage({ id: "search" }),
// clear: "清除", // Accessible label on "clear" button
notfound: intl.formatMessage({ id: "not.found" }),
// skintext: "Choose your default skin tone",
categories: {
search: intl.formatMessage({ id: "search.result" }),
recent: intl.formatMessage({ id: "recently.used" }),
people: intl.formatMessage({ id: "people" }),
nature: intl.formatMessage({ id: "nature" }),
foods: intl.formatMessage({ id: "food" }),
activity: intl.formatMessage({ id: "activity" }),
places: intl.formatMessage({ id: "places" }),
objects: intl.formatMessage({ id: "objects" }),
symbols: intl.formatMessage({ id: "symbols" }),
flags: intl.formatMessage({ id: "flags" })
// custom:
// "<a href='https://www.weibo.com/u/1736856114' target='_blank'> 欢乐兔</a>"
},
categorieslabel: "Emoji categories", // Accessible title for the list of categories
skintones: {
1: "Default Skin Tone",
2: "Light Skin Tone",
3: "Medium-Light Skin Tone",
4: "Medium Skin Tone",
5: "Medium-Dark Skin Tone",
6: "Dark Skin Tone"
}
}
// const customEmojis = []
// for (let i = 1; i <= 90; i++) {
// const imageUrl = `stickers/happy_bun/${i}.gif`
// customEmojis.push({
// name: "happy bun " + i,
// short_names: [`happy_bun_` + i],
// imageUrl: imageUrl
// })
// }
function Emoji(props) {
return (
<ClickWrapper exceptionClass={props.exceptionClass} close={props.close}>
<Picker
i18n={i18n}
emojiTooltip={true}
include={["people", "nature", "foods", "activity", "places"]}
// custom={customEmojis}
onSelect={props.addEmoji}
set="apple"
showPreview={false}
/>
</ClickWrapper>
)
}
export default Emoji
================================================
FILE: chatbox/src/components/Emoji/index.js
================================================
export { default } from "./Emoji"
================================================
FILE: chatbox/src/components/Iframe/Iframe.css
================================================
.sp-iframe-modal .ant-modal-content {
height: 80%;
position: fixed;
/* width: 80%; */
top: 7% !important;
left: 7%;
right: 7%;
}
================================================
FILE: chatbox/src/components/Iframe/Iframe.js
================================================
import "./Iframe.css"
import React from "react"
import { Modal } from "antd"
function Iframe({ title, url, show, setShow }) {
return (
<Modal
transitionName="none"
closable={false}
wrapClassName="sp-iframe-modal"
bodyStyle={{
padding: 0,
width: "100%",
height: "100%"
// maxHeight: "calc(100% - 10px)",
// overflowY: "auto"
}}
title={title}
visible={show}
onCancel={() => {
setShow(false)
}}
footer={null}
>
<iframe style={{ height: "100%", width: "100%" }} src={url} />
</Modal>
)
}
export default Iframe
================================================
FILE: chatbox/src/components/Iframe/index.js
================================================
export { default } from "./Iframe"
================================================
FILE: chatbox/src/components/InputWithPicker/InputWithPicker.css
================================================
.sp-input-with-picker .ant-input-group-addon {
border-radius: unset;
}
.sp-input-with-picker .ant-input-group-addon:last-child {
border-right: 0;
border-left: 1px solid lightgray;
cursor: pointer;
}
.sp-input-with-picker .ant-input-group-addon:first-child {
border-left: 0;
border-right: 1px solid lightgray;
}
.sp-input-with-picker .ant-input {
border-left: none;
border-right: none;
}
.sp-input-with-picker .ant-input-group-addon {
/* font-size: 20px; */
padding: 0px;
}
.sp-input-with-picker .ant-input-group-addon button {
/* padding: 5px; */
border: none;
background: none;
}
================================================
FILE: chatbox/src/components/InputWithPicker/InputWithPicker.js
================================================
import "./InputWithPicker.css"
import { useIntl } from "react-intl"
import React, { useState, useRef, useEffect } from "react"
import { Button, Input, Icon, Upload } from "antd"
import urls from "config/urls"
import Emoji from "../Emoji"
import axios from "axios"
function InputWithPicker(props) {
const uploadUrl = `${urls.dbAPI}/api/v1/chat_upload`
const [input, setInput] = useState("")
const [uploading, setUploading] = useState(false)
const intl = useIntl()
const inputRef = useRef()
// show emoji is one step slower than will show emoji
// so that we can show a loading icon
const [showEmoji, setShowEmoji] = useState(false)
const [willShowEmoji, setWillShowEmoji] = useState(false)
const sending = props.sending
const autoFocus = props.autoFocus || false
const uploadProps = {
name: "file",
action: uploadUrl,
customRequest: options => {
const data = new FormData()
data.append("file", options.file)
const fileName = options.file.name || "no-file-name"
setUploading(true)
axios
.post(options.action, data)
.then(resp => {
const payload = {
fileName: fileName,
url: resp.data.url,
type: "file"
}
props.send(payload)
})
.catch(err => {
console.error(err)
})
.then(() => {
setUploading(false)
})
},
showUploadList: false,
onChange(info) {
// if (info.file.status !== "uploading") {
// console.log(info.file, info.fileList)
// }
// if (info.file.status === "done") {
// message.success(`${info.file.name} file uploaded successfully`)
// } else if (info.file.status === "error") {
// message.error(`${info.file.name} file upload failed.`)
// }
}
}
useEffect(() => {
if (autoFocus) {
inputRef.current.focus()
}
}, [sending, autoFocus])
useEffect(() => {
setShowEmoji(willShowEmoji)
}, [willShowEmoji])
const handleKeyDown = e => {
if (e.key === "Enter") {
setWillShowEmoji(false)
const payload = {
text: input,
type: "text"
}
const shouldClear = props.send(payload)
if (shouldClear) {
setInput("")
}
}
}
const addEmoji = emoji => {
if (emoji.custom) {
const payload = {
text: emoji.imageUrl,
type: "text"
}
props.send(payload)
setWillShowEmoji(false)
} else {
setInput(input => {
return input + emoji.native
})
}
inputRef.current.focus()
}
const handleChange = e => {
setInput(e.target.value)
}
const addonBefore = (
<span>
<Button
className="emojiOpener"
onClick={e => {
setWillShowEmoji(prevState => {
setWillShowEmoji(!prevState)
})
}}
icon="smile"
/>
<Upload {...uploadProps} disabled={uploading}>
<Button icon="upload" loading={uploading} />
</Upload>
</span>
)
return (
<div className="sp-input-with-picker">
{willShowEmoji && <Icon style={{ margin: 10 }} type="loading" />}
{showEmoji && (
<Emoji
addEmoji={addEmoji}
exceptionClass="emojiOpener"
close={() => {
setWillShowEmoji(false)
}}
/>
)}
<Input
ref={inputRef}
size="large"
onKeyDown={handleKeyDown}
value={input}
addonBefore={addonBefore}
addonAfter={props.addonAfter}
onChange={handleChange}
disabled={sending}
placeholder={
sending
? intl.formatMessage({ id: "sending" })
: intl.formatMessage({ id: "input.here" })
}
/>
</div>
)
}
export default InputWithPicker
================================================
FILE: chatbox/src/components/InputWithPicker/index.js
================================================
export { default } from "./InputWithPicker"
================================================
FILE: chatbox/src/components/MusicPlayer/MusicPlayer.css
================================================
.wmp-title {
/* text-align: center; */
/* margin: 20px; */
}
/* .wmp-container {
min-height: 200px;
} */
/* .wmp-container div span img {
width: 100%;
height: auto;
} */
.video-js {
width: 100%;
/* height: calc(100% - 180px); */
height: 100%;
}
.video-js .vjs-big-play-button {
display: none;
}
.video-js .vjs-control-bar {
display: flex;
}
.media-close-button {
font-weight: bold;
font-size: 14px !important;
margin-right: 10px;
}
.hide-player {
display: none;
}
.video-js:hover .vjs-control-bar {
display: flex !important;
z-index: 2;
}
================================================
FILE: chatbox/src/components/MusicPlayer/MusicPlayer.js
================================================
import "video.js/dist/video-js.css"
import "./MusicPlayer.css"
import React from "react"
import videojs from "video.js"
import "videojs-playlist"
import "videojs-youtube"
import "videojs-flash"
class VideoPlayer extends React.Component {
componentDidMount() {
// instantiate Video.js
this.player = videojs(this.videoNode, this.props, function onPlayerReady() {
// console.log("onPlayerReady", this)
})
// console.log("create player")
window.player = this.player
// this.props.setPlayer(this.player)
this.props.playerRef.current = this.player
var myButton = this.player.controlBar.addChild("button")
// There are many functions available for button component
// like below mentioned in this docs
// https://docs.videojs.com/button.
// You can set attributes and clasess as well.
// Getting html DOM
var myButtonDom = myButton.el()
// Since now you have the html dom element
// you can add click events
// Now I am setting the text as you needed.
myButtonDom.innerHTML = "X"
myButtonDom.className = "vjs-play-control media-close-button"
myButtonDom.onclick = () => {
if (
document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement
) {
if (document.exitFullscreen) {
document.exitFullscreen()
} else if (document.mozCancelFullScreen) {
/* Firefox */
document.mozCancelFullScreen()
} else if (document.webkitExitFullscreen) {
/* Chrome, Safari and Opera */
document.webkitExitFullscreen()
} else if (document.msExitFullscreen) {
/* IE/Edge */
document.msExitFullscreen()
}
}
this.props.closePlayer()
}
// window.player = this.player
// window.playMedia = src => {
// window.player.src(src)
// window.player.play()
// }
// var player = videojs("myvideo")
}
// destroy player on unmount
componentWillUnmount() {
if (this.player) {
this.player.dispose()
// this.props.setPlayer(null)
this.props.playerRef.current = null
// window.player = null
}
}
// wrap the player in a div with a `data-vjs-player` attribute
// so videojs won't create additional wrapper in the DOM
// see https://github.com/videojs/video.js/pull/3856
render() {
let className = "video-js"
// if (!this.props.show) {
// className += " hide-player"
// }
return (
// <div>
<div data-vjs-player>
<video ref={node => (this.videoNode = node)} className={className} />
</div>
// </div>
)
}
}
function Player(props) {
// console.log(props.sources)
const videoJsOptions = {
autoplay: false,
dataSetup: { techOrder: ["youtube"] },
controls: true,
responsive: true,
width: "100%",
// src:
// "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_5mb.mp4"
...props
}
return <VideoPlayer {...videoJsOptions} />
}
export default Player
================================================
FILE: chatbox/src/components/MusicPlayer/index.js
================================================
export { default } from "./MusicPlayer"
================================================
FILE: chatbox/src/components/OutsideClickDetector.js
================================================
import React from "react"
class OutsideAlerter extends React.Component {
constructor(props) {
super(props)
this.setWrapperRef = this.setWrapperRef.bind(this)
this.handleClickOutside = this.handleClickOutside.bind(this)
}
componentDidMount() {
document.addEventListener("mousedown", this.handleClickOutside)
}
componentWillUnmount() {
document.removeEventListener("mousedown", this.handleClickOutside)
}
/**
* Set the wrapper ref
*/
setWrapperRef(node) {
this.wrapperRef = node
}
/**
* Alert if clicked on outside of element
*/
handleClickOutside(event) {
// console.log(event.target.className)
if (
event.target.className &&
event.target.className.indexOf &&
event.target.className.indexOf(this.props.exceptionClass) > -1
)
return
if (this.wrapperRef && !this.wrapperRef.contains(event.target)) {
this.props.close()
event.isOutsideClick = true
}
}
render() {
return <div ref={this.setWrapperRef}>{this.props.children}</div>
}
}
export default OutsideAlerter
================================================
FILE: chatbox/src/config/index.js
================================================
================================================
FILE: chatbox/src/config/logger.js
================================================
const spDebug = str => {
if (window.spConfig && window.spConfig.debug) {
console.debug(str)
}
}
window.spDebug = spDebug
export default spDebug
================================================
FILE: chatbox/src/config/urls.js
================================================
const urls = {
// debugMsgSrc: "http://localhost:9009",
// dbAPI: "https://api-v2.yiyechat.com",
// dbAPI: "http://localhost:8080",
// used to get pop rooms
// socketAPI: "https://chat.yiyechat.com"
}
// Override by spConfig from injection script
// from parent window
// TBD: for chrome extension, config from injection script is hard to
// change, should override the other way
export default urls
================================================
FILE: chatbox/src/containers/Account/Account.js
================================================
import React, { useState, useEffect } from "react"
import { Button } from "antd"
import { connect } from "react-redux"
import ResetPassword from "./ResetPassword"
import EditProfile from "./EditProfile"
import Profile from "./Profile"
import Follow from "./Follow"
import Login from "containers/Account/Login"
import { getAccount } from "services/account"
// import { setAccount } from "redux/actions"
import storageManager from "utils/storage"
import Blacklist from "./Blacklist"
import { viewOtherUser } from "redux/actions"
import Discover from "containers/Home/Discover"
function setAccount(account) {
storageManager.set("account", account)
}
function Account({ account, blacklist, viewOtherUser }) {
const [resettingPassword, setResetPasswordState] = useState(false)
const [edittingProfile, setEdittingProfileState] = useState(false)
// showingFollow is for toggling the Follow view
// showFollowers is for toggling follower vs following
const [showingFollow, setShowingFollowState] = useState(false)
const [showFollowers, setShowFollowersState] = useState(false)
const [showBlacklist, setShowBlacklist] = useState(false)
const [showRooms, setShowRooms] = useState(false)
const [loadingAccount, setLoadingAccount] = useState(false)
useEffect(() => {
// load account for once if user is logged in or user
// switch to account tab, otherwise the account info
// is only loaded when login which becomes stale easily
if (account) {
setLoadingAccount(true)
window.spDebug("refresh account data")
getAccount()
.then(resp => {
setAccount(resp.data)
})
.catch(err => {})
.then(() => {
setLoadingAccount(false)
})
}
}, [])
const backToMainPage = () => {
// called by the back button
setResetPasswordState(false)
setEdittingProfileState(false)
setShowingFollowState(false)
setShowBlacklist(false)
setShowRooms(false)
}
if (!account) {
return <Login setAccount={setAccount} />
}
return (
<div>
{loadingAccount && (
<Button size="small" icon="loading" className="sp-back-btn" />
)}
{resettingPassword && <ResetPassword back={backToMainPage} />}
{showRooms && (
<Discover
user={account}
showCreateRoomBtn={true}
back={backToMainPage}
/>
)}
{showingFollow && (
<Follow
account={account}
showFollowers={showFollowers}
followingCount={account.followingCount}
followerCount={account.followerCount}
back={backToMainPage}
viewOtherUser={viewOtherUser}
/>
)}
{showBlacklist && (
<Blacklist
blacklist={blacklist}
viewOtherUser={viewOtherUser}
back={backToMainPage}
/>
)}
{edittingProfile && (
<EditProfile
account={account}
setAccount={setAccount}
back={backToMainPage}
/>
)}
{!showBlacklist && !showingFollow && !showRooms && (
<Profile
account={account}
blacklist={blacklist}
showBlacklist={() => {
setShowBlacklist(true)
}}
showResetPassword={setResetPasswordState}
showEditProfile={setEdittingProfileState}
showFollowings={() => {
setShowingFollowState(true)
setShowFollowersState(false)
}}
showFollowers={() => {
setShowingFollowState(true)
setShowFollowersState(true)
}}
showRooms={() => {
setShowRooms(true)
}}
setAccount={setAccount}
/>
)}
</div>
)
}
const stateToProps = state => {
return {
account: state.account,
blacklist: state.blacklist
}
}
export default connect(stateToProps, { viewOtherUser })(Account)
================================================
FILE: chatbox/src/containers/Account/AvatarUploader/AvatarUploader.css
================================================
.fileContainer {
background: none;
box-shadow: none;
position: unset;
border-radius: unset;
padding: unset;
display: unset;
-webkit-align-items: unset;
align-items: unset;
-webkit-justify-content: unset;
justify-content: unset;
-webkit-flex-direction: unset;
flex-direction: unset;
margin: unset;
transition: unset;
}
.fileContainer .uploadPicturesWrapper {
display: block;
-webkit-flex-wrap: unset;
flex-wrap: unset;
-webkit-justify-content: unset;
justify-content: unset;
width: 100px;
}
.fileContainer .chooseFileButton {
height: 32px;
padding: 0 15px;
font-size: 14px;
border-radius: 4px;
line-height: 1.499;
position: relative;
display: inline-block;
font-weight: 400;
margin: 0;
white-space: nowrap;
text-align: center;
background-image: none;
border: 1px solid transparent;
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.015);
cursor: pointer;
transition: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
touch-action: manipulation;
color: rgba(0, 0, 0, 0.65);
background-color: #fff;
border-color: #d9d9d9;
}
.fileContainer .chooseFileButton:hover {
color: #40a9ff;
background-color: #fff;
border-color: #40a9ff;
}
.fileContainer .deleteImage {
/* display: none; */
background: #d0d0d0;
font-size: 20px;
top: -5px;
right: -15px;
/* font-weight: bold; */
}
.fileContainer .deleteImage:hover {
background: #a0a0a0;
}
.fileContainer .uploadPictureContainer {
width: unset;
height: unset;
margin: unset;
padding: unset;
background: unset;
display: unset;
-webkit-align-items: unset;
align-items: unset;
-webkit-justify-content: unset;
justify-content: unset;
height: unset;
box-shadow: unset;
border: unset;
position: unset;
}
.fileContainer .uploadPictureContainer img.uploadPicture {
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 5px;
margin-top: 10px;
margin-bottom: 10px;
}
.uploadPicturesWrapper div {
/* reverse order so last selected one shows up first */
/* display: flex; */
/* flex-direction: column-reverse; */
}
.sp-selected-avatar .fileContainer .chooseFileButton {
display: none;
}
.sp-selected-avatar .fileContainer .errorsContainer {
display: none;
}
================================================
FILE: chatbox/src/containers/Account/AvatarUploader/AvatarUploader.js
================================================
import React, { forwardRef } from "react"
import ImageUploader from "react-images-upload"
import "./AvatarUploader.css"
import { injectIntl } from "react-intl"
class App extends React.Component {
constructor(props) {
super(props)
this.state = { pictures: [] }
this.onDrop = this.onDrop.bind(this)
}
onDrop(picture) {
// console.log(picture)
this.setState({
pictures: picture
})
this.props.setFile(picture[0])
}
render() {
// this lib isn't easy to customize at all
// had to use class to toggle upload button!
let alreadySelectedImageClassName = ""
if (this.state.pictures.length) {
alreadySelectedImageClassName = "sp-selected-avatar"
}
return (
<ImageUploader
className={alreadySelectedImageClassName}
singleImage={true}
buttonClassName="ant-btn"
withPreview={true}
withIcon={false}
withLabel={false}
buttonText={this.props.intl.formatMessage({ id: "choose.image" })}
fileTypeError={this.props.intl.formatMessage({
id: "unsupported.file.type"
})}
fileSizeError={this.props.intl.formatMessage({ id: "file.too.big" })}
onChange={this.onDrop}
label={
this.props.intl.formatMessage({ id: "file.must.smaller.than" }) +
"2MB"
}
imgExtension={[".jpg", ".jpeg", ".png", ".gif"]}
maxFileSize={5242880}
/>
)
}
}
const IntlWrapper = injectIntl(App)
export default forwardRef((props, ref) => (
<IntlWrapper {...props} innerRef={ref}></IntlWrapper>
))
================================================
FILE: chatbox/src/containers/Account/AvatarUploader/index.js
================================================
export { default } from "./AvatarUploader"
================================================
FILE: chatbox/src/containers/Account/Blacklist.js
================================================
import React, { useEffect, useState, useRef } from "react"
import { useIntl } from "react-intl"
import { Avatar, Icon, Radio, Button } from "antd"
function Blacklist({ blacklist, viewOtherUser, back }) {
const intl = useIntl()
return (
<div className="sp-follow-tab">
<Button
onClick={() => {
back()
}}
className="sp-back-btn"
icon="arrow-left"
size="small"
/>
<div className="sp-tab-header">
{" "}
{intl.formatMessage({ id: "blacklist" })}
</div>
<div className="sp-tab-body">
{blacklist.map(user => (
<div
onClick={() => viewOtherUser(user)}
className="sp-follow-row"
key={user.id}
>
<Avatar icon="user" src={user.avatarSrc} />
{user.name}
</div>
))}
</div>
</div>
)
}
export default Blacklist
================================================
FILE: chatbox/src/containers/Account/EditProfile.js
================================================
import React from "react"
import { Form, Input, Button, message } from "antd"
import AvatarUploader from "./AvatarUploader"
import { updateUser } from "services/user"
import { injectIntl } from "react-intl"
// const { Option } = Select
let avatarFile = null
class EditProfileForm extends React.Component {
state = {
submitting: false
}
handleSubmit = e => {
e.preventDefault()
this.props.form.validateFieldsAndScroll((err, values) => {
if (!err) {
// console.log("Received values of form: ", values)
// console.log(avatarFile)
values.avatar = avatarFile
this.setState({ submitting: true })
updateUser(values)
.then(resp => {
message.success(this.props.intl.formatMessage({ id: "success" }))
this.props.setAccount(resp.data)
})
.catch(err => {})
.then(() => {
this.setState({ submitting: false })
})
}
})
}
render() {
const { getFieldDecorator } = this.props.form
const account = this.props.account
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 8 }
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 }
}
}
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0
},
sm: {
span: 16,
offset: 8
}
}
}
return (
<div className="sp-special-tab">
<Button
onClick={this.props.back}
className="sp-back-btn"
// style={{
// position: "fixed",
// marginTop: 5,
// marginLeft: 5,
// border: "none",
// fontSize: "large"
// }}
icon="arrow-left"
size="small"
/>
<center>
<h3 style={{ marginTop: 50, marginBottom: 30 }}>
{this.props.intl.formatMessage({ id: "update.profile" })}
</h3>
</center>{" "}
<Form
style={{ width: "70%", margin: "auto" }}
{...formItemLayout}
onSubmit={this.handleSubmit}
>
<Form.Item
label={this.props.intl.formatMessage({ id: "upload.avatar" })}
>
{getFieldDecorator("upload", {
valuePropName: "fileList",
getValueFromEvent: () => {
return avatarFile
}
})(
<AvatarUploader
setFile={file => {
avatarFile = file
}}
/>
)}
</Form.Item>
<Form.Item
label={
<span>{this.props.intl.formatMessage({ id: "user.name" })}</span>
}
>
{getFieldDecorator("name", {
rules: [
{
message: "用户名不能为空",
whitespace: true
},
{
max: 12,
message: "用户名最多12个字符"
},
{
min: 1,
message: "用户名最少一个字符"
}
],
initialValue: account.name
})(<Input />)}
</Form.Item>
{/*
<Form.Item label="性别">
{getFieldDecorator("gender", {
rules: [{ required: false, message: "请选择你的性别!" }]
})(
<Select
// onChange={this.handleSelectChange}
>
<Option value="male">男</Option>
<Option value="female">女</Option>
<Option value="other">其他</Option>
</Select>
)}
</Form.Item> */}
<Form.Item
label={
<span>{this.props.intl.formatMessage({ id: "user.about" })}</span>
}
>
{getFieldDecorator("about", {
initialValue: account.about
})(<Input.TextArea />)}
</Form.Item>
<Form.Item {...tailFormItemLayout}>
<Button
// size="large"
style={{ marginRight: 20 }}
onClick={this.props.back}
>
{this.props.intl.formatMessage({ id: "cancel" })}
</Button>
<Button
loading={this.state.submitting}
type="primary"
// size="large"
htmlType="submit"
>
{this.props.intl.formatMessage({ id: "save" })}
</Button>
</Form.Item>
</Form>
<br />
<br />
<br />
</div>
)
}
}
const WrappedEditProfileForm = Form.create({ name: "edit-profile" })(
EditProfileForm
)
export default injectIntl(WrappedEditProfileForm)
================================================
FILE: chatbox/src/containers/Account/Follow/Follow.css
================================================
.sp-follow-tab .ant-avatar {
margin-right: 10px;
}
.sp-follow-row {
padding: 10px;
cursor: pointer;
/* border-bottom: 1px solid #ececec; */
}
.sp-follow-row:hover {
background: rgb(236, 236, 236);
}
================================================
FILE: chatbox/src/containers/Account/Follow/Follow.js
================================================
import "./Follow.css"
import axios from "axios"
import React, { useEffect, useState, useRef } from "react"
import { useIntl } from "react-intl"
import { Avatar, Icon, Radio, Button } from "antd"
// import { connect } from "react-redux"
import urls from "config/urls"
import followEventHandler from "./event"
// import { viewOtherUser } from "redux/actions"
function Follow(props) {
const [showFollowers, setShowFollowers] = useState(props.showFollowers)
const [loading, setLoading] = useState(true)
const [loadingMore, setLoadingMore] = useState(false)
const [users, setUsers] = useState([])
const intl = useIntl()
const account = props.account
const usersRef = useRef([])
useEffect(() => {
setUsers([])
loadUsers(0)
if (!showFollowers) {
window.spDebug("register follow handler")
followEventHandler.follow = (followUser, user) => {
window.spDebug("update followings")
let updatedUsers = []
if (!followUser) {
updatedUsers = usersRef.current.filter(u => u.id !== user.id)
} else {
updatedUsers = [user, ...usersRef.current]
}
setUsers(updatedUsers)
}
return () => {
window.spDebug("unregister follow handler")
followEventHandler.follow = () => {
window.spDebug("following handler isn't mounted")
}
}
}
}, [showFollowers])
useEffect(() => {
usersRef.current = users
}, [users])
function shouldShowLoadMoreBtn() {
// if already loaded users and
// haven't loaded all users
if (users.length === 0) return false
if (showFollowers) {
return users.length < props.followerCount
} else {
return users.length < props.followingCount
}
}
const loadUsers = offset => {
let url = urls.dbAPI + "/api/v1/"
if (showFollowers) {
url += "followers"
} else {
url += "followings"
}
url += "?offset=" + offset
if (offset) {
setLoadingMore(true)
} else {
setLoading(true)
}
axios
.get(url)
.then(resp => {
// Notice we have to call prevUsers rather than
// just setUsers(users.concat(data)) because
// update to state is async, it won't work even if the
// call takes 10 mins to return, it always use the user data
// when useEffect is called
setUsers(prevUsers => prevUsers.concat(resp.data))
})
.catch(err => {
console.error(err)
})
.then(() => {
setLoading(false)
setLoadingMore(false)
})
}
return (
<div className="sp-follow-tab">
<center className="sp-tab-header">
<Button
onClick={props.back}
className="sp-back-btn"
// style={{
// position: "fixed",
// marginTop: 1,
// marginLeft: 5,
// border: "none",
// fontSize: "large"
// }}
size="small"
icon="arrow-left"
/>
<Radio.Group
className="sp-toggle-page-site-chat"
size="small"
defaultValue={showFollowers}
buttonStyle="solid"
onChange={e => {
setShowFollowers(e.target.value)
}}
>
<Radio.Button value={false}>
{intl.formatMessage({ id: "following" })} {account.followingCount}
</Radio.Button>
<Radio.Button value={true}>
{intl.formatMessage({ id: "follower" })} {account.followerCount}
</Radio.Button>
</Radio.Group>
</center>
<div className="sp-tab-body">
{loading && (
<center>
<Icon
style={{
marginTop: 10,
border: "none",
fontSize: "large"
}}
type="loading"
/>
</center>
)}
{users.map(user => (
<div
onClick={() => props.viewOtherUser(user)}
className="sp-follow-row"
key={user.id}
>
{/* <AvatarWithHoverCard user={user}/> */}
<Avatar icon="user" src={user.avatarSrc} />
{user.name}
</div>
))}
<center style={{ margin: 20 }}>
{shouldShowLoadMoreBtn() && (
<Button
loading={loadingMore}
type="primary"
onClick={() => {
loadUsers(users.length)
}}
>
{intl.formatMessage({ id: "load.more" })}
</Button>
)}
</center>
</div>
</div>
)
}
// export default connect(null, { viewOtherUser })(Follow)
export default Follow
================================================
FILE: chatbox/src/containers/Account/Follow/event.js
================================================
// If user follow/unfollow anyone, it will call
// follow method, the Follow.js component can 'subscribe'
// to it by providing a definition of the function
const followEventHandler = {
follow: () => {
console.warn("follow not mounted")
}
}
export default followEventHandler
================================================
FILE: chatbox/src/containers/Account/Follow/index.js
================================================
export { default } from "./Follow"
================================================
FILE: chatbox/src/containers/Account/Login.js
================================================
import React from "react"
import { Form, Icon, Input, Button, message } from "antd"
import { injectIntl } from "react-intl"
import { login, register } from "services/account"
import storageManager from "utils/storage"
class NormalLoginForm extends React.Component {
constructor(props) {
super(props)
this.state = {
loading: false,
// registration code may be moved into
// dedicated file in the future
registering: false
}
}
componentDidMount() {
storageManager.get("login", values => {
if (values) {
window.spDebug("found login in storage")
this.props.form.setFieldsValue({
userId: values.userId,
password: values.password
})
// if (this.context.autoLogin) {
// this.loginUser(values)
// window.spDebug("auto login")
// }
} else {
window.spDebug("no login found in storage, register now")
const password = Math.random()
.toString(36)
.slice(-8)
this.setState({ registering: true })
register(password)
.then(resp => {
this.setState({ registering: false })
const account = resp.data
this.props.setAccount(account)
storageManager.set("login", {
userId: account.numId,
password: password
})
message.success(
this.props.intl.formatMessage({ id: "register.success" })
)
})
.catch(err => {
this.setState({ registering: false })
// TODO: put specific error message in each .catch
// Only use global axios interceptor when backend
// return prepared error message
// message.error("注册失败,请刷新重试")
})
.then(() => {})
}
// this.context.stopAutoLogin()
})
}
loginUser = values => {
this.setState({ loading: true })
login(values.userId, values.password)
.then(res => {
window.spDebug(res.data)
const account = res.data
this.setState({ loading: false })
// this.props.setAccount(account)
storageManager.set("account", account)
storageManager.set("login", values)
})
.catch(err => {
console.error(err)
this.setState({ loading: false })
})
.then(() => {
// can't change state here because if succeed
// component will be unmounted before we set loading
// to be false
// this.setState({ loading: false })
})
}
handleSubmit = e => {
e.preventDefault()
this.props.form.validateFields((err, values) => {
window.spDebug("Received values of form: ", values)
if (!err) {
this.loginUser(values)
}
})
}
render() {
if (this.state.registering) {
return (
<div className="sp-special-tab">
<center style={{ marginTop: "50%" }}>注册中...</center>
</div>
)
}
const { getFieldDecorator } = this.props.form
return (
<div className="sp-special-tab">
<Form
style={{ width: "70%", margin: "auto", marginTop: 100 }}
onSubmit={this.handleSubmit}
className="login-form"
>
<Form.Item>
{getFieldDecorator("userId", {
rules: [
{
required: true,
message: this.props.intl.formatMessage({ id: "required" })
}
]
})(
<Input
prefix={
<Icon type="user" style={{ color: "rgba(0,0,0,.25)" }} />
}
placeholder="ID"
/>
)}
</Form.Item>
<Form.Item>
{getFieldDecorator("password", {
rules: [
{
required: true,
message: this.props.intl.formatMessage({ id: "required" })
}
]
})(
<Input
prefix={
<Icon type="lock" style={{ color: "rgba(0,0,0,.25)" }} />
}
type="password"
placeholder={this.props.intl.formatMessage({ id: "password" })}
/>
)}
</Form.Item>
<Form.Item>
<Button
type="primary"
htmlType="submit"
className="login-form-button"
style={{ marginRight: 10 }}
loading={this.state.loading}
>
{this.props.intl.formatMessage({ id: "login" })}
</Button>
{/* 或
{
// eslint-disable-next-line
<a>注册</a>
} */}
</Form.Item>
</Form>
</div>
)
}
}
// NormalLoginForm.contextType = AccountContext
const WrappedNormalLoginForm = Form.create({ name: "normal_login" })(
NormalLoginForm
)
export default injectIntl(WrappedNormalLoginForm)
================================================
FILE: chatbox/src/containers/Account/Profile/Profile.css
================================================
.sp-follow-stats {
cursor: pointer;
}
.sp-follow-stats:hover {
color: rgb(0, 153, 255);
text-decoration: underline;
}
================================================
FILE: chatbox/src/containers/Account/Profile/Profile.js
================================================
import "./Profile.css"
import { useIntl } from "react-intl"
import React, { useState } from "react"
import { Avatar, Button, Row, Col } from "antd"
import storageManager from "utils/storage"
import { logout } from "services/account"
const avatarStyle = {
display: "block",
width: "100%",
height: "auto",
borderRadius: 0
}
const ProfileBodyStyle = {
height: "calc(100% - 35px)",
overflowY: "auto",
overflowX: "hidden",
width: "100%",
position: "fixed",
// background: "rgb(249, 249, 249)",
// padding: 50,
// paddingTop: 10,
paddingBottom: 30
}
const aboutStyle = {
display: "inline-block",
minWidth: 200,
maxWidth: 350,
borderBottom: "1px solid lightgray",
textAlign: "left",
overflow: "auto",
maxHeight: 72,
padding: 5,
paddingLeft: 10,
paddingRight: 10,
wordBreak: "break-word"
}
function Profile(props) {
const account = props.account
const [loggingOut, setLoggingOut] = useState(false)
const intl = useIntl()
return (
<div
// style={ProfileBodyStyle}
>
<div className="sp-tab-header">{account.name}</div>
<div className="sp-tab-body">
<Avatar
style={avatarStyle}
// size={128}
shape="square"
src={account.avatarSrc}
icon="user"
/>
{/* <center style={{ margin: 20, fontSize: "large", fontWeight: "bold" }}>
{account.name}
</center> */}
<div style={{ width: 200, margin: "auto", marginTop: 30 }}>
<Row gutter={50} style={{ textAlign: "center" }}>
<Col style={{ textAlign: "center" }} span={12}>
ID <br />
<b>{account.numId}</b>
</Col>
<Col style={{ textAlign: "center" }} span={12}>
{intl.formatMessage({ id: "credit" })} <br />
<b>{account.credit}</b>
</Col>
</Row>
<Row gutter={50} style={{ marginTop: 10, textAlign: "center" }}>
<Col style={{ textAlign: "center" }} span={12}>
<span className="sp-follow-stats" onClick={props.showFollowings}>
{intl.formatMessage({ id: "following" })}
<br /> <b>{account.followingCount}</b>
</span>
</Col>
<Col style={{ textAlign: "center" }} span={12}>
<span className="sp-follow-stats" onClick={props.showFollowers}>
{intl.formatMessage({ id: "follower" })}
<br /> <b>{account.followerCount}</b>
</span>
</Col>
</Row>
<Row gutter={50} style={{ marginTop: 10, textAlign: "center" }}>
<Col style={{ textAlign: "center" }} span={12}>
<span
className="sp-follow-stats"
onClick={() => {
props.showRooms()
}}
>
{intl.formatMessage({ id: "room" })}
<br /> <b>{account.rooms && account.rooms.length}</b>
</span>
</Col>
<Col style={{ textAlign: "center" }} span={12}>
<span
className="sp-follow-stats"
onClick={() => {
props.showBlacklist()
}}
>
{intl.formatMessage({ id: "blacklist" })}
<br /> <b>{props.blacklist.length}</b>
</span>
</Col>
</Row>
</div>
<br />
<center>
<div style={aboutStyle}>{account.about}</div>
<div style={{ marginTop: 30 }}>
<Button
type="primary"
icon="edit"
style={{ margin: 10 }}
// size="large"
onClick={props.showEditProfile}
>
{intl.formatMessage({ id: "update.profile" })}
</Button>
</div>
<Button onClick={props.showResetPassword} style={{ margin: 10 }}>
{intl.formatMessage({ id: "change.password" })}
</Button>
<Button
onClick={() => {
setLoggingOut(true)
logout()
.then(res => {
window.spDebug("logout success")
})
.catch(err => {
console.error(err)
})
.then(() => {
setLoggingOut(false)
storageManager.set("account", null)
})
}}
loading={loggingOut}
type="danger"
style={{ margin: 10 }}
>
{intl.formatMessage({ id: "logout" })}
</Button>
</center>
</div>
</div>
)
}
export default Profile
================================================
FILE: chatbox/src/containers/Account/Profile/index.js
================================================
export { default } from "./Profile"
================================================
FILE: chatbox/src/containers/Account/ResetPassword.js
================================================
import React from "react"
import { Form, Input, Button, message } from "antd"
import { injectIntl } from "react-intl"
import { resetPassword } from "services/account"
class ResetPasswordForm extends React.Component {
state = {
confirmDirty: false
}
handleSubmit = e => {
e.preventDefault()
this.props.form.validateFieldsAndScroll((err, values) => {
if (!err) {
resetPassword(values.password)
.then(() => {
message.success(this.props.intl.formatMessage({ id: "success" }))
})
.catch()
.then()
window.spDebug("Received values of form: ", values)
}
})
}
handleConfirmBlur = e => {
const value = e.target.value
this.setState({ confirmDirty: this.state.confirmDirty || !!value })
}
compareToFirstPassword = (rule, value, callback) => {
const form = this.props.form
if (value && value !== form.getFieldValue("password")) {
callback(this.props.intl.formatMessage({ id: "confirm.password.fail" }))
} else {
callback()
}
}
validateToNextPassword = (rule, value, callback) => {
const form = this.props.form
if (value && this.state.confirmDirty) {
form.validateFields(["confirm"], { force: true })
}
callback()
}
render() {
const { getFieldDecorator } = this.props.form
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 8 }
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 }
}
}
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0
},
sm: {
span: 16,
offset: 8
}
}
}
return (
<div className="sp-special-tab">
<Button
onClick={this.props.back}
className="sp-back-btn"
// style={{
// position: "fixed",
// marginTop: 5,
// marginLeft: 5,
// border: "none",
// fontSize: "large"
// }}
icon="arrow-left"
size="small"
/>
<center>
<h3 style={{ marginTop: 50, marginBottom: 30 }}>
{this.props.intl.formatMessage({ id: "change.password" })}
</h3>
</center>{" "}
<Form
style={{ width: "70%", margin: "auto" }}
{...formItemLayout}
onSubmit={this.handleSubmit}
>
<Form.Item
label={this.props.intl.formatMessage({ id: "new.password" })}
>
{getFieldDecorator("password", {
rules: [
{
required: true,
message: "Please input your password!"
},
{
validator: this.validateToNextPassword
}
]
})(<Input type="password" />)}
</Form.Item>
<Form.Item
label={this.props.intl.formatMessage({ id: "confirm.password" })}
>
{getFieldDecorator("confirm", {
rules: [
{
required: true,
message: " "
},
{
validator: this.compareToFirstPassword
}
]
})(<Input type="password" onBlur={this.handleConfirmBlur} />)}
</Form.Item>
<br />
<Form.Item {...tailFormItemLayout}>
<Button
size="large"
style={{ marginRight: 20 }}
onClick={this.props.back}
>
{this.props.intl.formatMessage({ id: "cancel" })}
</Button>
<Button size="large" type="primary" htmlType="submit">
{this.props.intl.formatMessage({ id: "save" })}
</Button>
</Form.Item>
</Form>
</div>
)
}
}
const WrappedResetPasswordForm = Form.create({ name: "resetPassword" })(
ResetPasswordForm
)
export default injectIntl(WrappedResetPasswordForm)
================================================
FILE: chatbox/src/containers/Account/index.js
================================================
export { default } from "./Account"
================================================
FILE: chatbox/src/containers/Chat/Body.css
================================================
.room-show-media-false {
display: none;
}
.sp-message-detail-html {
height: 100%;
width: 100%;
overflow-y: auto;
overflow-x: hidden;
padding: 20px;
margin: auto;
border-bottom: 1px solid lightgray;
background-color: rgb(250, 249, 222);
/* box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5); */
}
.sp-message-detail-html img,
.sp-message-detail-html video {
max-width: 100%;
margin-bottom: 10px;
}
.sp-message-detail-html h1 {
font-size: 18px;
}
.sp-message-detail-html p {
font-size: 16px;
}
.sp-resizable-bottom-handle {
height: 15px !important;
bottom: 0px !important;
}
/* .sp-resizable-bottom-handle:hover {
background-color: yellow;
border-top: 1px solid yellow;
border-bottom: 1px solid yellow;
} */
================================================
FILE: chatbox/src/containers/Chat/Body.js
================================================
import "./Body.css"
import React, { useEffect, useRef, useState } from "react"
import moment from "moment"
// import { connect } from "react-redux"
import Message from "./Message"
import socketManager from "socket/socket"
import spDebug from "config/logger"
import ResizableMedia from "./ResizableMedia"
const chatBodyStyle = {
height: "calc(100% - 114px)",
overflowY: "auto",
overflowX: "hidden",
width: "100%",
position: "fixed",
background: "rgb(243, 243, 243)",
padding: 10,
paddingBottom: 50,
scrollBehavior: "smooth"
}
const bodyMaskStyle = {
height: "100%",
width: "100%",
position: "fixed",
background: "black",
opacity: 0,
marginTop: -10,
marginLeft: -10,
zIndex: 10
// pointerEvents: "none" // still allow scrolling
}
const AUTO_SCROLL_TRESHOLD_DISTANCE = 300
const VIDEO_DEFAULT_HEIGHT = 200
const IFRAME_DEFAULT_HEIGHT = 270
// function isMedia(msg) {
// return msg.type === "audio" || msg.type === "video"
// }
function ChatBody({
blacklist,
account,
show,
messages,
setMessages,
room,
playMedia,
pauseMedia,
playerRef,
showMedia,
setShowMedia,
mediaSources
}) {
if (!room) {
return <span />
// console.error("no roomId, should not render ChatBody")
}
const roomId = room.id
spDebug("[ChatBody] " + roomId)
// spDebug(mediaSources)
const resizableDefaultHeight = showMedia
? VIDEO_DEFAULT_HEIGHT
: IFRAME_DEFAULT_HEIGHT
const [resizableHeight, setResizableHeight] = useState(resizableDefaultHeight)
const [iframeUrl, setIframeUrl] = useState()
const [messageDetail, setMessageDetail] = useState()
const msgNum = messages.length
const bodyRef = useRef(null)
const suffixCb = name => {
return name + "_" + roomId
}
const showResizable = show && (showMedia || iframeUrl || messageDetail)
const bodyStyle = { ...chatBodyStyle }
const maskStyle = { ...bodyMaskStyle }
if (room.connectionStatus !== "CONNECTED") {
maskStyle.opacity = 0.5
}
let heightDelta = 114
if (showResizable) {
heightDelta += resizableHeight
}
bodyStyle.height = `calc(100% - ${heightDelta}px)`
if (room.background) {
bodyStyle.backgroundImage = `url('${room.background}')`
bodyStyle.backgroundSize = "cover"
}
// if (height) {
// bodyStyle.height = height
// }
// const chatMsgCallbackName = "display_new_message_" + chatView
useEffect(() => {
scrollToBottomIfNearBottom(10)
}, [msgNum])
useEffect(() => {
scrollToBottom(10)
}, [show])
useEffect(() => {
if (showMedia) {
setIframeUrl(null)
setMessageDetail(null)
setResizableHeight(VIDEO_DEFAULT_HEIGHT)
}
}, [showMedia])
useEffect(() => {
if (iframeUrl) {
setShowMedia(false)
setMessageDetail(null)
pauseMedia()
setResizableHeight(IFRAME_DEFAULT_HEIGHT)
}
}, [iframeUrl])
useEffect(() => {
if (messageDetail) {
setShowMedia(false)
setIframeUrl(null)
pauseMedia()
setResizableHeight(IFRAME_DEFAULT_HEIGHT)
}
}, [messageDetail])
useEffect(() => {
setShowMedia(false)
pauseMedia()
setIframeUrl(null)
setMessageDetail(null)
// clear room message when room change (only for man made rooms)
setMessages([])
// console.log("roomId")
// console.log(roomId)
}, [roomId])
useEffect(() => {
// TODO: seems no need to remove socket handler when account state change
if (!account) {
console.warn("[Body.js] no account, won't register socket events")
return
}
if (!roomId) {
return
}
window.spDebug("[Body.js] register socket events " + roomId)
socketManager.addHandler(
"chat message",
suffixCb("display_new_message"),
data => {
if (data.roomId !== roomId) return
data.self = data.user.id.toString() === account.id.toString()
data.time = moment()
spDebug("[chatbox] chat message")
setMessages(prevMessages => {
// update own message
let i = 0
for (; i < prevMessages.length; i++) {
if (prevMessages[i].id.toString() === data.id.toString()) {
break
}
}
if (i < prevMessages.length) {
// if found existing message, update it
const messages = [...prevMessages]
messages[i] = data
return messages
}
return [...prevMessages, data]
})
if (!data.self) {
let timeout = 10
scrollToBottomIfNearBottom(timeout)
}
}
)
socketManager.addHandler(
"delete message",
suffixCb("delete_message"),
data => {
if (data.roomId !== roomId) return
spDebug("[chatbox] delete message")
setMessages(prevMessages => {
return prevMessages.filter(m => {
return m.id !== data.messageId
})
})
}
)
socketManager.addHandler(
"room info",
suffixCb("display_recent_messages"),
roomDict => {
if (roomId in roomDict) {
const room = roomDict[roomId]
if (room.chatHistory) {
room.chatHistory.forEach(msg => {
msg.self = msg.user.id.toString() === account.id.toString()
msg.time = moment.utc(msg.timestamp)
})
setMessages(room.chatHistory)
scrollToBottom(20)
}
// else {
// socketManager.getRoomInfo([roomId])
// }
}
}
)
return () => {
// window.spDebug("[Body.js] unregister socket events")
socketManager.removeHandler(
"chat message",
suffixCb("display_new_message")
)
socketManager.removeHandler(
"room info",
suffixCb("display_recent_messages")
)
}
}, [account, roomId])
// useEffect(() => {
// // Find media messages and pass to playlist
// // TODO: better to use a playlist context
// // rather than window.setPlaylist
// let mediaIndex = 0
// messages.forEach(msg => {
// if (isMedia(msg)) {
// msg.mediaIndex = mediaIndex
// mediaIndex++
// }
// })
// window.setPlaylist(messages.filter(isMedia))
// }, [messages])
const imageLoadedCb = () => {
scrollToBottomIfNearBottom(10)
}
const scrollToBottomIfNearBottom = timeout => {
const bodyDiv = bodyRef.current
if (!bodyDiv) return
if (
bodyDiv.scrollHeight - bodyDiv.scrollTop - bodyDiv.offsetHeight <
AUTO_SCROLL_TRESHOLD_DISTANCE
) {
scrollToBottom(timeout)
}
}
const scrollToBottom = timeout => {
const bodyDiv = bodyRef.current
if (!bodyDiv) return
timeout = timeout || 100
setTimeout(() => {
bodyDiv.scrollTop = bodyDiv.scrollHeight
}, timeout)
}
let res = []
let lastMsg = null
messages.forEach(msg => {
const blacklisted =
blacklist.filter(u => {
return u.id === msg.user.id
}).length > 0
if (blacklisted) {
// spDebug(`[Body.js] blacklisted user ${data.user.name} talking`)
return
}
// If same user is talking, no need to show user's avatar again
let showUser = true
// If it's been more than 5 mins since last msg
let showTimestamp = false
let timeDisplay = null
if (lastMsg) {
if (lastMsg.user.id.toString() === msg.user.id.toString())
showUser = false
if (msg.time.diff(lastMsg.time) > 5 * 60 * 1000) {
showTimestamp = true
showUser = true
}
} else {
showTimestamp = true
showUser = true
}
if (showTimestamp) {
if (moment().diff(msg.time) > 24 * 60 * 60 * 1000)
timeDisplay = msg.time.local().format("MMMDo HH:mm")
else timeDisplay = msg.time.local().format("HH:mm")
}
res.push(
<Message
showMenu={true}
// showMenu={account && (account.isMod || msg.self)}
withHoverCard={true}
key={msg.id}
data={msg}
room={room}
showUser={showUser}
timeDisplay={timeDisplay}
// displayMusicTab={props.displayMusicTab}
imageLoadedCb={imageLoadedCb}
playMedia={src => {
playMedia(src)
setShowMedia(true)
}}
setIframeUrl={setIframeUrl}
setMessageDetail={setMessageDetail}
/>
)
lastMsg = msg
})
return (
<span>
<ResizableMedia
show={showResizable}
showMedia={showMedia}
setShowMedia={setShowMedia}
resizableHeight={resizableHeight}
setResizableHeight={setResizableHeight}
pauseMedia={pauseMedia}
playerRef={playerRef}
mediaSources={mediaSources}
iframeUrl={iframeUrl}
setIframeUrl={setIframeUrl}
messageDetail={messageDetail}
setMessageDetail={setMessageDetail}
/>
{/* {show && ( */}
<div
ref={bodyRef}
style={{ ...bodyStyle, display: show ? "block" : "none" }}
>
{room.connectionStatus !== "CONNECTED" && (
<div style={maskStyle}>Offline</div>
)}
{res}
</div>
{/* )} */}
</span>
)
}
export default ChatBody
// const stateToProps = (state, props) => {
// return { show: state.chatView === props.chatView }
// }
// export default connect(stateToProps)(ChatBody)
================================================
FILE: chatbox/src/containers/Chat/Chat.js
================================================
import React, { useState, useEffect } from "react"
// import { message } from "antd"
import { connect } from "react-redux"
import Header from "./Header"
import View from "./View"
// import Footer from "./Footer"
// import MusicTab from "containers/Music"
import socketManager from "socket"
import { changeChatView, setRoomConnectionStatus } from "redux/actions/chat"
import { viewOtherUser, changeTab } from "redux/actions"
import storageManager from "utils/storage"
function syncRoomsPeriodically() {
setTimeout(() => {
// socketManager.syncRooms()
syncRoomsPeriodically()
}, 20 * 1000)
}
function Chat({
blacklist,
account,
rooms,
manMadeRoom,
chatModes,
activeView,
changeChatView,
changeTab,
viewOtherUser,
setRoomConnectionStatus
}) {
// const [mediaDisplay, setMediaDisplay] = useState("none")
// const [mediaNum, setMediaNum] = useState(0)
const [showRoomList, setShowRoomList] = useState(false)
const [noJoinList, setNoJoinList] = useState([])
const [ready, setReady] = useState(false)
useEffect(() => {
storageManager.get("noJoin", noJoin => {
let autoJoinRooms = rooms
if (noJoin) {
setNoJoinList(noJoin)
autoJoinRooms = rooms.filter(r => {
return !noJoin.includes(r.id)
})
}
socketManager.autoJoinRooms(autoJoinRooms)
autoJoinRooms.forEach(r => {
setRoomConnectionStatus(r.id, "JOINING")
})
setReady(true)
})
// setTimeout(() => {
// // wait a few secs only because if user make chatbox iframe
// // display by default, there is race condition that before
// // parent finish joining rooms, chatbox is already trying to get
// // room chat history, thus get no chat history
// socketManager.getRoomInfo()
// }, 500)
syncRoomsPeriodically()
}, [])
if (!ready) return <span />
return (
<div>
{/* <span style={{ display: mediaDisplay }}>
<MusicTab
back={() => {
setMediaDisplay("none")
}}
setMediaNum={setMediaNum}
/>
</span>
*/}
<Header
account={account}
chatModes={chatModes}
activeView={activeView}
changeChatView={changeChatView}
viewOtherUser={viewOtherUser}
manMadeRoom={manMadeRoom}
rooms={rooms}
changeTab={changeTab}
setRoomConnectionStatus={setRoomConnectionStatus}
showRoomList={showRoomList}
setShowRoomList={setShowRoomList}
// mediaNum={mediaNum}
// showMusic={() => {
// setMediaDisplay("block")
// }}
/>
{/* <MusicPlayer /> */}
{chatModes.map(mode => {
let room = rooms.filter(r => {
return r.type === mode
})
if (room.length) {
room = room[0]
} else {
room = null
}
return (
<View
blacklist={blacklist}
account={account}
chatView={mode}
show={mode === activeView}
key={mode}
room={room}
showRoomList={showRoomList}
setShowRoomList={setShowRoomList}
changeTab={changeTab}
noJoinList={noJoinList}
setRoomConnectionStatus={setRoomConnectionStatus}
// displayMusicTab={() => {
// setMediaDisplay("block")
// }}
/>
)
})}
</div>
)
}
const stateToProps = state => {
return {
chatModes: state.chatModes,
activeView: state.chatView,
manMadeRoom: state.manMadeRoom,
rooms: state.rooms,
account: state.account,
blacklist: state.blacklist
}
}
export default connect(stateToProps, {
changeChatView,
viewOtherUser,
changeTab,
setRoomConnectionStatus
})(Chat)
================================================
FILE: chatbox/src/containers/Chat/Footer/Footer.css
================================================
.sp-chat-bottom {
position: fixed;
bottom: 0;
width: 100%;
}
================================================
FILE: chatbox/src/containers/Chat/Footer/Footer.js
================================================
import "./Footer.css"
import React, { useState, useEffect } from "react"
import { message, Button, Modal, Tooltip } from "antd"
import { useIntl } from "react-intl"
// import { connect } from "react-redux"
import moment from "moment"
import InputWithPicker from "components/InputWithPicker"
import socketManager from "socket/socket"
import { getUrl } from "utils/url"
const MESSAGE_TIME_GAP = 2 * 1000
let lastMsgTime = 0
function Footer({
account,
setMessages,
chatView,
room,
setRoomConnectionStatus
// connected,
// isJoining
}) {
const roomId = room.id
const intl = useIntl()
// const [joining, setJoining] = useState(isJoining)
const [showInvitationModal, setShowInvitationModal] = useState(false)
// useEffect(() => {
// if (connected) {
// setJoining(false)
// }
// }, [connected])
// const roomId = room.id
// const connected = room.connected
// window.spDebug("[Footer.js] connected " + connected)
// const [invitationType, setInvitationType] = useState("room")
// const [invitationPurpose, setInvitationPurpose] = useState("chat")
const send = payload => {
const now = new Date()
if (payload.type === "file" || now - lastMsgTime > MESSAGE_TIME_GAP) {
lastMsgTime = now
const data = {
id: Math.ceil(Math.random() * 100000),
token: account.token,
roomType: chatView,
roomId: roomId,
content: payload
}
socketManager.sendMessage(data)
// convert message to be same format as from server
if (payload.type === "text") {
data.content = {
value: payload.text,
type: payload.type
}
data.time = moment()
data.user = account
data.self = true
// TODO: add timeout for server confirmation
// if no confirmation then show error
setMessages(prevMessages => {
return [...prevMessages, data]
})
}
return true
} else {
message.warn(intl.formatMessage({ id: "slow.down" }))
return false
}
}
let content = (
<InputWithPicker
send={send}
addonAfter={
chatView !== "page" && (
<span>
<Modal
title={intl.formatMessage({ id: "share.url" })}
visible={showInvitationModal}
onOk={() => {
const payload = {
// url and title added by content script
type: "url",
url: getUrl()
// invitationType: invitationType,
// invitationPurpose: invitationPurpose
}
send(payload)
// socketManager.sendMessage(payload)
setShowInvitationModal(false)
// if (invitationType !== "room") {
// // room invitation is much faster
// // no db lookup
// message.loading("发送中")
// }
}}
onCancel={() => {
setShowInvitationModal(false)
}}
okText={intl.formatMessage({ id: "yes" })}
cancelText={intl.formatMessage({ id: "cancel" })}
>
<p>{intl.formatMessage({ id: "share.url.privacy" })}</p>
{/* <b style={{ marginRight: 10 }}>邀请目的</b>
<Radio.Group
onChange={e => {
setInvitationPurpose(e.target.value)
}}
value={invitationPurpose}
>
<Radio value="chat">聊天</Radio>
<Radio value="help">求助</Radio>
</Radio.Group>
<br />
<br />
<div style={{ display: "flex" }}>
<b style={{ marginRight: 10 }}>邀请对象</b>
<Radio.Group
onChange={e => {
setInvitationType(e.target.value)
}}
value={invitationType}
>
<Radio style={{ display: "block" }} value="room">
当前房间的用户
</Radio>
<Radio disabled style={{ display: "block" }} value="follower">
关注者
</Radio>
<Radio disabled style={{ display: "block" }} value="all">
全站用户(需20积分)
</Radio>
</Radio.Group>
</div>*/}
</Modal>
<Tooltip
title={intl.formatMessage({ id: "share.url" })}
placement="left"
>
<Button
onClick={() => {
setShowInvitationModal(true)
}}
icon="share-alt"
/>
</Tooltip>
</span>
)
}
/>
)
if (room.connectionStatus !== "CONNECTED") {
content = (
<center
style={{
padding: 20,
background: "white",
borderTop: "lightgray 1px solid"
}}
>
<Button
style={{ width: "100%" }}
onClick={() => {
socketManager.joinRoom(room)
setRoomConnectionStatus(room.id, "JOINING")
// setJoining(true)
}}
loading={room.connectionStatus === "JOINING"}
type="primary"
size="large"
>
{intl.formatMessage({ id: "join.room" })}
</Button>
</center>
)
}
if (!account) {
content = (
<center style={{ padding: 20, background: "lightgray" }}>
{intl.formatMessage({ id: "not.login" })}
</center>
)
}
return <div className="sp-chat-bottom">{content}</div>
}
export default Footer
// const stateToProps = (state, props) => {
// let roomId = "lobby"
// if (state.chatView == "page") {
// roomId = getUrl()
// }
// if (state.chatView == "site") {
// roomId = getDomain()
// }
// return {
// roomId: roomId
// }
// }
// export default connect(null, { setRoomConnectionStatus })(Footer)
================================================
FILE: chatbox/src/containers/Chat/Footer/index.js
================================================
export { default } from "./Footer"
================================================
FILE: chatbox/src/containers/Chat/Header/Header.css
================================================
.sp-toggle-online {
float: left;
display: none;
}
.sp-toggle-page-site-chat {
/* z-index: 0; */
/* position: relative; */
/* padding-left: 5px; */
/* font-size: 10px; */
}
.ant-tooltip-inner {
/* this is for tooltip of page/site toggle
sometimes the url is tooo long
TODO: This general css is dangerous!
*/
max-height: 200px;
overflow: hidden;
}
.sp-online-user {
display: inline-block;
margin: 5px;
cursor: pointer;
}
.sp-online-user-username {
font-size: 10px;
max-width: 64px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
text-align: center;
line-height: 10px;
padding-top: 5px;
}
.yiyelink {
float: right;
margin-top: 30px;
color: yellowgreen;
}
/* .ant-badge {
margin-top: -3px;
} */
================================================
FILE: chatbox/src/containers/Chat/Header/Header.js
================================================
import "./Header.css"
// import { FormattedMessage, useIntl } from "react-intl"
import { useIntl } from "react-intl"
import React, { useState, useEffect } from "react"
import { Badge, Radio, Button, Tooltip, Icon } from "antd"
import { connect } from "react-redux"
import socketManager from "socket/socket"
import RoomHeader from "./RoomHeader"
import RoomInfo from "./RoomInfo"
import { setRoomConnectionStatus } from "redux/actions/chat"
import { getUrl } from "utils/url"
function ChatHeader({
chatModes,
activeView,
changeTab,
changeChatView,
viewOtherUser,
rooms,
account,
setShowRoomList,
setRoomConnectionStatus
}) {
// const chatModes = props.chatModes
// const activeView = props.activeView
const intl = useIntl()
const [showHelp, setShowHelp] = useState(false)
const [unreads, setUnreads] = useState({})
const [showUsers, toggleUsers] = useState(false)
useEffect(() => {
setShowHelp(false)
setUnreads(unreads => {
const res = { ...unreads }
res[activeView] = 0
return res
})
socketManager.addHandler("chat message", "mark_unread_chat", data => {
const roomType = data.roomType
if (roomType !== activeView) {
setUnreads(unreads => {
const res = { ...unreads }
if (res[roomType]) {
res[roomType]++
} else {
res[roomType] = 1
}
return res
})
}
})
return () => {
socketManager.removeHandler("chat message", "mark_unread_chat")
}
}, [activeView])
const getRoom = mode => {
let room = rooms.filter(r => {
return r.type === mode
})
if (room.length) {
room = room[0]
} else {
room = null
}
return { ...room }
}
let content = (
<center>
<Button
onClick={() => {
changeTab("profile")
}}
size="small"
type="primary"
>
{intl.formatMessage({ id: "login" })}
</Button>
</center>
)
if (account) {
const room = getRoom(activeView)
content = (
<div>
{showHelp && (
<RoomInfo
account={account}
rooms={rooms}
room={room}
setShowHelp={setShowHelp}
/>
)}
{/* <Button
style={{ border: "none", position: "absolute", left: 5 }}
onClick={() => props.showMusic()}
size="small"
icon="unordered-list"
>
<span style={{ marginLeft: 5 }}>{props.mediaNum}</span>
</Button> */}
<span style={{ float: "left", marginLeft: 10 }}>
<Radio.Group
className="sp-toggle-page-site-chat"
size="small"
value={activeView}
// buttonStyle="solid"
onChange={e => {
const chatView = e.target.value
changeChatView(chatView)
}}
>
{chatModes.map(mode => {
const room = getRoom(mode)
let roomTitle = ""
let unreadCount = unreads[mode] || 0
if (room) {
if (mode === "room") {
roomTitle = room.name
} else {
roomTitle = room.id
}
}
return (
//<Tooltip key={mode} placement="bottom" title={roomTitle}>
<Radio.Button key={mode} value={mode}>
<span title={roomTitle}>
{intl.formatMessage({ id: mode })}
</span>
<Badge offset={[3, -3]} count={unreadCount}></Badge>
</Radio.Button>
//</Tooltip>
)
})}
</Radio.Group>
<Button
style={{ border: "none", boxShadow: "none" }}
onClick={() => setShowHelp(true)}
size="small"
// icon="info-circle"
>
{/* <Icon type="info-circle" theme="twoTone" /> */}
<Icon type="setting" />
</Button>
{activeView === "room" &&
room &&
!getUrl().includes("yiyechat.com/room/") && (
<Button
style={{ border: "none", boxShadow: "none" }}
onClick={() => {
window.open(
`https://yiyechat.com/room/?id=${room.id}`,
"_blank"
)
}}
size="small"
>
<Icon type="fullscreen" />
</Button>
)}
</span>
{/* <Col style={{ textAlign: "right" }} span={8}> */}
{chatModes.map((mode, i) => {
const room = getRoom(mode)
if (room) {
return (
<RoomHeader
account={account}
viewOtherUser={viewOtherUser}
chatView={mode}
show={mode === activeView}
key={mode}
room={room}
showUsers={showUsers}
toggleUsers={toggleUsers}
setShowRoomList={setShowRoomList}
setRoomConnectionStatus={setRoomConnectionStatus}
/>
)
} else {
return <span key={mode} />
}
})}
</div>
)
}
return <div className="sp-tab-header">{content}</div>
}
// const stateToProps = state => {
// return { chatView: state.chatView, chatModes: state.chatModes }
// }
// export default connect(null, { changeChatView, viewOtherUser })(
// ChatHeader
// )
// export default ChatHeader
export default connect(null, { setRoomConnectionStatus })(ChatHeader)
================================================
FILE: chatbox/src/containers/Chat/Header/Invite.js
================================================
import React, { useState } from "react"
import { Popover, Button } from "antd"
function Users(props) {
// const users = props.users || []
const [visible, setVisibility] = useState(false)
return (
<Popover
content={<a onClick={() => setVisibility(false)}>Close</a>}
title="Title"
visible={visible}
onVisibleChange={setVisibility}
trigger="click"
>
<Button icon="like" />
</Popover>
)
}
export default Users
================================================
FILE: chatbox/src/containers/Chat/Header/RoomHeader.js
================================================
import React, { useState, useEffect } from "react"
import { Button, Icon, message } from "antd"
import { connect } from "react-redux"
import { useIntl } from "react-intl"
import Users from "./Users"
import socketManager from "socket/socket"
import { setRoomConnectionStatus } from "redux/actions/chat"
function RoomHeader({
account,
chatView,
show,
room,
viewOtherUser,
setRoomConnectionStatus,
toggleUsers,
showUsers,
setShowRoomList
}) {
const connected = room.connectionStatus === "CONNECTED"
const [users, setUsers] = useState([])
const intl = useIntl()
let roomId = room.id
let userNum = users.length
// if (userNum >= 50) {
// userNum = "50+"
// }
const suffixCb = handlerName => {
return handlerName + "_" + roomId.toString()
}
useEffect(() => {
if (!roomId) return
// console.log("register user join/left handlers " + roomId)
socketManager.addHandler(
"other join",
suffixCb("add_user_to_room"),
data => {
if (data.roomId === roomId) {
setUsers(users => {
const user = data.user
const existingUsersWithoutNewUser = users.filter(u => {
return u.id.toString() !== user.id.toString()
})
return [...existingUsersWithoutNewUser, user]
})
}
}
)
socketManager.addHandler(
"other left",
suffixCb("remove_user_from_room"),
data => {
if (data.roomId === roomId) {
setUsers(users => {
return users.filter(u => {
return u.id.toString() !== data.user.id.toString()
})
})
}
}
)
socketManager.addHandler(
"room info",
suffixCb("set_users_in_room"),
roomDict => {
if (roomId in roomDict) {
// console.log("set connected")
const room = roomDict[roomId]
if (room.users) {
setUsers(room.users)
// make sure user himself is in the response
// sometimes user isn't added to room properly
const userInRoom = room.users.filter(u => {
return u.id === account.id
})
if (userInRoom.length > 0) {
setRoomConnectionStatus(roomId, "CONNECTED")
// message.success(
// intl.formatMessage({ id: room.type }) +
// " " +
// intl.formatMessage({ id: "connected" }),
// 2
// )
// console.log("setRoomConnectionStatus " + roomId)
} else {
setRoomConnectionStatus(roomId, "DISCONNECTED")
console.error("User not added to room properly! ")
}
}
}
}
)
socketManager.addHandler(
"disconnect",
suffixCb("clear_users_in_room"),
() => {
setUsers([])
}
)
return () => {
socketManager.removeHandler("other join", suffixCb("add_user_to_room"))
socketManager.removeHandler(
"other left",
suffixCb("remove_user_from_room")
)
socketManager.removeHandler("room info", suffixCb("set_users_in_room"))
socketManager.removeHandler("disconnect", suffixCb("clear_users_in_room"))
}
}, [roomId])
// console.log(room)
if (show) {
return (
<span
style={{
float: "right",
marginRight: 10
// position: "absolute",
// right: 0
}}
>
{/* {chatView === "room" && (
<Button
style={
{
// border: "none",
// boxShadow: "none"
// position: "absolute",
// left: 5
}
}
onClick={() =>
setShowRoomList(srl => {
return !srl
})
}
size="small"
icon="menu"
></Button>
)} */}
{connected && (
<span
style={
{
// position: "absolute",
// right: 5
}
}
>
<Button
style={
{
// border: "none",
// boxShadow: "none"
// position: "absolute",
// right: 0
}
}
onClick={() => toggleUsers(!showUsers)}
size="small"
icon="team"
>
{/* fix width so buttons don't shift when user number
is different in different room */}
<span style={{ marginLeft: 3, width: 20 }}>{userNum}</span>
</Button>
<Button
style={
{
// color: "red",
// border: "none",
// boxShadow: "none"
// position: "absolute",
// right: 20
}
}
onClick={() => {
window.spDebug("leave" + room.id)
setRoomConnectionStatus(room.id, "DISCONNECTED")
socketManager.leaveRoom(room)
setUsers([])
}}
size="small"
icon="logout"
>
{/* <Icon type="info-circle" theme="twoTone" /> */}
{/* <Icon type="logout" /> */}
</Button>
{showUsers && <Users viewOtherUser={viewOtherUser} users={users} />}
</span>
)}
</span>
)
}
return <span />
}
// export default RoomHeader
export default connect(null, { setRoomConnectionStatus })(RoomHeader)
================================================
FILE: chatbox/src/containers/Chat/Header/RoomInfo.js
================================================
import React, { useState, useEffect } from "react"
import { Modal, Switch, Avatar, Button } from "antd"
import { useIntl } from "react-intl"
import { connect } from "react-redux"
import CreateRoomForm from "containers/Home/CreateRoom"
import storageManager from "utils/storage"
// import socketManager from "socket"
import { setRoomConnectionStatus, joinManMadeRoom } from "redux/actions/chat"
import { viewOtherUser } from "redux/actions"
import AvatarWithHoverCard from "containers/OtherProfile/AvatarWithHoverCard"
function RoomInfo({
account,
rooms,
room,
setShowHelp,
setRoomConnectionStatus,
viewOtherUser,
activeTab,
joinManMadeRoom
}) {
const [join, setJoin] = useState(true)
const [editing, setEditing] = useState(false)
const intl = useIntl()
useEffect(() => {
storageManager.get("noJoin", noJoin => {
if (noJoin && noJoin.includes(room.id)) {
setJoin(false)
}
})
}, [])
useEffect(() => {
if (activeTab !== "chat") {
setShowHelp(false)
}
}, [activeTab])
let helpContent,
helpTitle = ""
if (room.type === "room") {
helpTitle = room.name
helpContent = (
<div>
<h4>{intl.formatMessage({ id: "room.about" })}</h4>
{room.about}
<br />
<br />
{room.owner && room.owner.avatarSrc && (
<div>
<h4>{intl.formatMessage({ id: "room.owner" })}</h4>
<div>
<span style={{ cursor: "pointer", marginRight: 10 }}>
<AvatarWithHoverCard
extraClickCb={() => {
setShowHelp(false)
}}
// className="sp-chat-message-avatar"
size="large"
user={room.owner}
// 1001 so it's above the modal
zIndex={1001}
/>
</span>
{/* <Avatar
src={room.owner.avatarSrc}
size="large"
style={{ cursor: "pointer", marginRight: 10 }}
onClick={() => {
setShowHelp(false)
viewOtherUser(room.owner)
}}
/> */}
{room.owner.name}
</div>
</div>
)}
</div>
)
}
if (room.type === "site") {
helpTitle = intl.formatMessage({ id: "same.site.chat" })
helpContent = (
<div>
<h4>{intl.formatMessage({ id: "room.about" })}</h4>
{intl.formatMessage({ id: "same.site.chat" })} @{" "}
<span style={{ color: "#1890ff" }}>{room.id}</span>
</div>
)
}
if (room.type === "page") {
helpTitle = intl.formatMessage({ id: "same.page.chat" })
helpContent = (
<div>
<h4>{intl.formatMessage({ id: "room.about" })}</h4>
{intl.formatMessage({ id: "same.page.chat" })} @{" "}
<span style={{ color: "#1890ff" }}>{room.id}</span>
</div>
)
}
// if (mode === "tags") {
// helpTitle = intl.formatMessage({ id: "room" }) + " " + room.id
// helpContent = (
// <div>
// {/* <p>浏览相似内容的用户会进入该聊天室</p> */}
// <p>{intl.formatMessage({ id: "keywords.of.room" })}</p>
// <b>{room.tags && room.tags.join(", ")}</b>
// {(!room.tags || !room.tags.length) && (
// <span>{intl.formatMessage({ id: "no.keyword" })}</span>
// )}
// </div>
// )
// }
return (
<Modal
transitionName="none"
title={helpTitle}
visible={true}
onCancel={() => {
setShowHelp(false)
}}
wrapClassName="sp-modal"
footer={null}
>
{!editing && (
<div>
<h4>
{intl.formatMessage({ id: "auto.join" })}
{" "}
<Switch
checked={join}
onChange={joining => {
setJoin(joining)
storageManager.get("noJoin", noJoin => {
noJoin = noJoin || []
if (joining) {
noJoin = noJoin.filter(n => {
return n !== room.id
})
} else {
noJoin.push(room.id)
}
noJoin = [...new Set(noJoin)]
storageManager.set("noJoin", noJoin)
// Join or leave room is decided by noJoin filter
})
}}
/>{" "}
</h4>
<br />
{helpContent}
<br />
{room.media && (
<span>
<h4>播放列表</h4>
<ul>
{room.media.map((m, index) => {
const mediaName = m["name"]
return (
<li key={mediaName}>
<a
onClick={() => {
window.playMediaFromRoomMediaList(index)
setShowHelp(false)
}}
>
{mediaName}
</a>
</li>
)
})}
</ul>
</span>
)}
{room.owner && account.id === room.owner.id && (
<Button
onClick={() => {
setEditing(true)
}}
type="primary"
icon="edit"
style={{
margin: "auto",
marginTop: 30,
marginBottom: 30,
display: "block"
}}
>
{intl.formatMessage({ id: "edit" })}
</Button>
)}
</div>
)}
{editing && (
<span>
<CreateRoomForm
back={() => {
setEditing(false)
}}
room={room}
afterUpdateCb={room => {
joinManMadeRoom(room)
}}
// loadRooms={loadRooms}
/>
</span>
)}
{/* <a
className="yiyelink"
target="_blank"
rel="noopener noreferrer"
href="https://yiyechat.com"
>
{intl.formatMessage({ id: "sp" })}
</a> */}
</Modal>
)
}
const stateToProps = state => {
return {
activeTab: state.tab
}
}
export default connect(stateToProps, {
setRoomConnectionStatus,
viewOtherUser,
joinManMadeRoom
})(RoomInfo)
================================================
FILE: chatbox/src/containers/Chat/Header/Users.js
================================================
import React from "react"
import { connect } from "react-redux"
import { useIntl } from "react-intl"
import { Avatar } from "antd"
import { viewOtherUser } from "redux/actions/"
const usersStyle = {
whiteSpace: "normal",
textAlign: "center",
background: "white",
position: "fixed",
zIndex: 10,
marginTop: -1, //to hide above border-botom
left: 0,
width: "100%",
overflow: "auto",
maxHeight: "50%",
padding: 5,
paddingTop: 10,
borderBottom: "1px solid lightgray"
}
function Users({ users, viewOtherUser, blacklist }) {
const intl = useIntl()
users = (users || []).map(user => {
const blacklisted = blacklist.find(b => {
return b.id.toString() === user.id.toString()
})
let username = user.name
let avatar = (
<Avatar
title={username}
size={64}
shape="square"
icon="user"
src={user.avatarSrc}
/>
)
if (blacklisted) {
username = intl.formatMessage({ id: "blocked" })
avatar = (
<Avatar
title={username}
size={64}
shape="square"
icon="user"
// src={user.avatarSrc}
/>
)
}
return (
<div
className="sp-online-user"
onClick={() => viewOtherUser(user)}
key={user.id}
>
{avatar}
<div className="sp-online-user-username">{username}</div>
</div>
)
})
return <div style={usersStyle}>{users}</div>
}
const stateToProps = state => {
return { blacklist: state.blacklist }
}
export default connect(stateToProps, { viewOtherUser })(Users)
================================================
FILE: chatbox/src/containers/Chat/Header/index.js
================================================
export { default } from "./Header"
================================================
FILE: chatbox/src/containers/Chat/Message/Body.css
================================================
.sp-message-body {
max-width: calc(100% - 50px);
margin-top: 5px;
display: inline-block;
}
.sp-message-body.file,
.sp-message-body.text,
.sp-message-body.url {
background: rgba(255, 255, 255);
border-radius: 5px;
padding: 10px;
/* padding-top: 5px; */
/* padding-bottom: 5px; */
word-wrap: break-word;
white-space: pre-wrap;
/* border: 1px solid #e0e0e0; */
color: black;
text-align: left;
}
.sp-message-body.invite {
text-align: left;
}
.sp-message-body.emoji {
font-size: 25px;
color: black;
padding-left: 5px;
padding-right: 5px;
}
.self .sp-message-body.text {
background: #bf0;
}
.sp-message-body img {
/* max-width: 100px; */
}
img.sp-message-image {
max-width: 100%;
max-height: 350px;
cursor: pointer;
}
.sp-chat-message-avatar {
cursor: pointer;
}
.sp-message-media {
cursor: pointer;
border-radius: 5px;
padding: 10px;
/* padding-top: 5px; */
/* padding-bottom: 5px; */
word-wrap: break-word;
white-space: pre-wrap;
/* border: 1px solid #e0e0e0; */
color: #ffffff;
background: #1890ff;
text-align: left;
}
.sp-message-menu .ant-popover-inner-content {
padding: 0;
}
.sp-message-menu .ant-btn {
border: none;
}
.sp-message-body .sp-invitation-chat a {
color: lightcoral;
}
.sp-message-body .sp-invitation-help a {
color: rgb(59, 160, 255);
}
================================================
FILE: chatbox/src/containers/Chat/Message/Body.js
================================================
import "./Body.css"
import React, { useState } from "react"
import { Popover, Button, Icon } from "antd"
import { useIntl } from "react-intl"
import { connect } from "react-redux"
import socketManager from "socket"
// import Iframe from "components/Iframe"
import { getData } from "services"
function isPureEmoji(string) {
if (!string) return false
var regex = /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/g
return string.replace(regex, "") === ""
}
function MessageBody(props) {
// const [showIframe, setShowIframe] = useState(false)
const data = props.data.content
const self = props.data.self
let content = data.value
let contentType = data.type
const intl = useIntl()
if (isPureEmoji(content)) {
// This should be done in backend
contentType = "emoji"
}
let className = "sp-message-body " + contentType
if (contentType === "sticker") {
let imgSrc = content
content = <img alt={imgSrc} src={imgSrc} />
}
if (contentType === "image") {
let imgSrc = content
content = (
<img
onClick={() => {
window.spDebug("click on image")
window.parent.postMessage({ imgSrc: imgSrc }, "*")
}}
onLoad={() => {
props.imageLoadedCb()
}}
className="sp-message-image"
alt={""}
src={imgSrc}
/>
)
}
if (contentType === "file") {
content = (
<a href={data.url} rel="noopener noreferrer" target="_blank" download>
<Icon type="cloud-download" style={{ marginRight: 5 }} />
{data.fileName}
</a>
)
}
if (contentType === "media") {
const src = {
src: content
}
if (content.includes("youtube.com") || content.includes("youtu.be")) {
src.type = "video/youtube"
}
content = (
<div
className="sp-message-media"
onClick={() => {
props.playMedia(src)
// if (window.player) {
// window.playMedia(src)
// } else {
// console.error("no player")
// }
}}
>
<span>
<Icon theme="twoTone" style={{ marginRight: 5 }} type="play-circle" />
{content}
</span>
</div>
)
}
if (contentType === "url") {
// const invitationData = data.metadata
// const purposeStr = invitationData.purpose === "chat" ? "聊天邀请" : "求助"
// const iconType =
// invitationData.purpose === "chat" ? "message" : "question-circle"
// const iconType = "link"
content = (
<div
// title="点击打开网页"
// className={"sp-invitation-" + invitationData.purpose}
>
<Icon style={{ marginRight: 5, color: "#1890ff" }} type="link" />
<a
onClick={() => {
// if (data.htmlContent) {
// props.setMessageDetail(data.htmlContent)
// return
// }
if (data.dataSrc) {
props.setMessageDetail(intl.formatMessage({ id: "loading" }))
getData(data.dataSrc)
.then(resp => {
// window.foo = resp
// console.log(resp)
props.setMessageDetail(resp.data)
})
.catch(err => {
props.setMessageDetail(
intl.formatMessage({ id: "load.failed" })
)
})
.then(() => {})
return
}
props.setIframeUrl(data.iframe_url || data.url)
}}
>
{data.title}
</a>
{/* <a target="_blank" rel="noopener noreferrer" href={data.url}>
<Icon style={{ marginLeft: 5, color: "black" }} type={iconType} />
</a> */}
</div>
)
}
const popoverContent = (
<div>
<Button
onClick={() => {
const payload = {
action: "delete_message",
data: {
messageId: props.data.id,
roomId: props.room.id,
token: props.account.token
}
}
socketManager.sendEvent(payload)
}}
>
{/* <a */}
{/* // style={{ border: "none" }}
// icon="delete"
> */}
<Icon type="delete" />
</Button>
{contentType === "media" && (
<div>
<Button>
<a
// style={{ color: "white" }}
target="_blank"
rel="noopener noreferrer"
href={data.value}
>
<Icon type="link" />
</a>
</Button>
</div>
)}
{contentType === "url" && (
<div>
<Button>
<a target="_blank" rel="noopener noreferrer" href={data.url}>
<Icon type="link" />
</a>
</Button>
</div>
)}
{contentType === "image" && (
<div>
<Button
onClick={() => {
window.parent.postMessage(
{
type: "sp-change-bg",
data: data.value
},
"*"
)
}}
>
<Icon type="pushpin" />
</Button>
</div>
)}
</div>
)
const popoverPlacement = self ? "left" : "right"
let contentWrapper = <div className={className}>{content}</div>
if (props.showMenu) {
contentWrapper = (
<Popover
overlayClassName="sp-message-menu"
placement={popoverPlacement}
content={popoverContent}
trigger="hover"
>
{contentWrapper}
</Popover>
)
}
return (
<div>
{contentWrapper}
{/* {contentType === "url" && (
<Iframe
// title={" "}
show={showIframe}
setShow={setShowIframe}
url={data.iframe_url || data.raw_url || data.url}
/>
)} */}
</div>
)
}
// export default MessageBody
const stateToProps = state => {
return {
account: state.account
}
}
export default connect(stateToProps)(MessageBody)
================================================
FILE: chatbox/src/containers/Chat/Message/Message.css
================================================
.sp-message-username {
font-size: smaller;
vertical-align: middle;
margin-left: 5px;
max-width: 60px;
text-overflow: ellipsis;
display: inline-block;
margin-right: 5px;
overflow: hidden;
}
================================================
FILE: chatbox/src/containers/Chat/Message/Message.js
================================================
import "./Message.css"
import React from "react"
import MessageBody from "./Body"
import AvatarWithHoverCard from "containers/OtherProfile/AvatarWithHoverCard"
import { Avatar } from "antd"
/*
This is used by chat messages and direct messages
props includes:
user: object
content
type: text/sticker
self
*/
function ChatMessage(props) {
const data = props.data
const user = data.user
const showUser = props.showUser // avatar and name
const timeDisplay = props.timeDisplay
let userInfo = ""
let avatar = <Avatar size="large" icon="user" src={user.avatarSrc} />
if (!data.self && props.withHoverCard) {
avatar = (
<AvatarWithHoverCard
className="sp-chat-message-avatar"
size="large"
user={user}
/>
)
}
let messageTime = ""
if (timeDisplay) {
messageTime = (
<center
style={{
marginTop: 30,
marginBottom: -10,
color: "gray",
fontSize: "smaller"
}}
>
{timeDisplay}
</center>
)
}
if (showUser) {
if (data.self) {
userInfo = (
<div style={{ marginTop: 20 }}>
<span className="sp-message-username">{user.name}</span>
{avatar}
</div>
)
} else {
userInfo = (
<div style={{ marginTop: 20 }}>
{avatar}
<span className="sp-message-username">{user.name}</span>
</div>
)
}
}
return (
<div
className={data.self ? "self" : "other"}
style={{ textAlign: data.self ? "right" : "left" }}
>
{messageTime}
{userInfo}
<MessageBody
imageLoadedCb={props.imageLoadedCb}
displayMusicTab={props.displayMusicTab}
data={data}
room={props.room}
showMenu={props.showMenu}
playMedia={props.playMedia}
setIframeUrl={props.setIframeUrl}
setMessageDetail={props.setMessageDetail}
/>
</div>
)
}
export default ChatMessage
================================================
FILE: chatbox/src/containers/Chat/Message/index.js
================================================
export { default } from "./Message"
================================================
FILE: chatbox/src/containers/Chat/ResizableMedia.js
================================================
import React, { useEffect, useRef, useState } from "react"
import { Resizable } from "re-resizable"
import MusicPlayer from "components/MusicPlayer"
import { Button } from "antd"
function ResizableMedia({
show,
showMedia,
setShowMedia,
resizableHeight,
setResizableHeight,
pauseMedia,
playerRef,
mediaSources,
iframeUrl,
setIframeUrl,
messageDetail,
setMessageDetail
}) {
const resizableStyle = {}
if (!show) {
resizableStyle.display = "none"
}
const resizableRef = useRef()
useEffect(() => {
if (resizableRef && resizableRef.current) {
window.foo = resizableRef.current
resizableRef.current.addEventListener("click", e => {
console.log(e)
if (e && e.target && e.target.tagName.toUpperCase() === "IMG") {
e.preventDefault()
window.parent.postMessage({ imgSrc: e.target.src }, "*")
}
return false
})
}
}, [])
return (
<Resizable
handleClasses={{ bottom: "sp-resizable-bottom-handle" }}
style={resizableStyle}
size={{
width: "100%",
height: resizableHeight
}}
enable={{
top: true,
right: false,
bottom: true,
left: false,
topRight: false,
bottomRight: false,
bottomLeft: false,
topLeft: false
}}
defaultSize={{
width: "100%",
height: resizableHeight
}}
minHeight={30}
// bounds={bodyRef}
onResize={(e, dir, elm, delta) => {
setResizableHeight(elm.clientHeight)
}}
>
<span ref={resizableRef}>
<span style={{ display: showMedia ? "unset" : "none" }}>
<MusicPlayer
// show={showMedia}
closePlayer={() => {
pauseMedia()
setShowMedia(false)
}}
playerRef={playerRef}
sources={mediaSources}
/>
</span>
{(iframeUrl || messageDetail) && (
<span>
<span
className="resizable-close"
onClick={() => {
setIframeUrl(null)
setMessageDetail(null)
}}
>
<Button>X</Button>
</span>
{iframeUrl && (
<iframe
style={{
background: "#d9d9d9",
height: "100%",
width: "100%",
border: "none",
borderBottom: "1px solid lightgray"
}}
src={iframeUrl}
/>
)}
{messageDetail && (
<div
className="sp-message-detail-html"
dangerouslySetInnerHTML={{ __html: messageDetail }}
></div>
)}
</span>
)}
</span>
</Resizable>
)
}
export default ResizableMedia
================================================
FILE: chatbox/src/containers/Chat/View.js
================================================
import React, { useState, useRef, useEffect } from "react"
// import { connect } from "react-redux"
import { useIntl } from "react-intl"
import { Icon } from "antd"
import Body from "./Body"
import Footer from "./Footer"
// import RoomsWrapper from "containers/Home/RoomsWrapper"
function View({
chatView,
show,
room,
account,
changeTab,
blacklist,
setRoomConnectionStatus
}) {
const [messages, setMessages] = useState([])
const playerRef = useRef(null)
const intl = useIntl()
const mediaSrc = room && room.media
if (mediaSrc) {
window.playMediaFromRoomMediaList = index => {
setShowMedia(true)
playerRef.current.playlist(mediaSrc)
playerRef.current.playlist.currentItem(index)
playerRef.current.playlist.autoadvance(0)
playerRef.current.play()
}
}
const playMedia = src => {
// This function is called when click
// on chat message that's media
setShowMedia(true)
playerRef.current.src(src)
playerRef.current.play()
}
const setPlaylist = mediaList => {
// setShowMedia(true)
// playerRef.current.src(src)
// playerRef.current.play()
// console.log(mediaList)
playerRef.current.playlist(mediaList)
playerRef.current.playlist.currentItem(0)
playerRef.current.playlist.autoadvance(0)
// playerRef.current.play()
}
const pauseMedia = () => {
if (playerRef && playerRef.current) {
playerRef.current.pause()
}
}
const showMediaInit = !!mediaSrc
const [showMedia, setShowMedia] = useState(showMediaInit)
window.spDebug("[View.js] " + chatView)
useEffect(() => {
if (mediaSrc) {
setPlaylist(mediaSrc)
setShowMedia(true)
} else {
setShowMedia(false)
}
}, [mediaSrc])
// Body component is always mounted because of the socket handlers
return (
<span>
<Body
account={account}
show={show}
blacklist={blacklist}
messages={messages}
setMessages={setMessages}
room={room}
playerRef={playerRef}
playMedia={playMedia}
pauseMedia={pauseMedia}
showMedia={showMedia}
setShowMedia={setShowMedia}
/>
{show && room && (
<Footer
account={account}
room={room}
chatView={chatView}
setMessages={setMessages}
setRoomConnectionStatus={setRoomConnectionStatus}
/>
)}
{show && !room && (
<div
style={{
width: "100%",
top: "50%",
left: "50%",
textAlign: "center",
position: "fixed",
transform: "translate(-50%, -50%)",
fontSize: "larger"
}}
>
<a
onClick={() => {
changeTab("discover")
}}
>
<Icon theme="twoTone" type="compass" />{" "}
{intl.formatMessage({ id: "discover.room.to.join" })}
</a>
</div>
)}
</span>
)
}
export default View
================================================
FILE: chatbox/src/containers/Chat/index.js
================================================
export { default } from "./Chat"
================================================
FILE: chatbox/src/containers/Comment/Body.js
================================================
import React from "react"
import Message from "./Message"
function CommentBody(props) {
const data = props.data || []
// let index = 0
let comments = data.map(comment => {
return (
<Message
vote={props.vote}
reply={props.reply}
// key={index++}
key={comment.id}
data={comment}
/>
)
})
if (!comments.length) {
comments = <center>没有评论</center>
}
return <div>{comments}</div>
}
export default CommentBody
================================================
FILE: chatbox/src/containers/Comment/Comment.css
================================================
.sp-comment-footer {
position: fixed;
bottom: 0;
width: 100%;
background: #eceff1;
}
.sp-comment-footer textarea.ant-input {
border-left: 0;
border-right: 0;
}
.sp-comment-message-avatar {
cursor: pointer;
}
================================================
FILE: chatbox/src/containers/Comment/Comment.js
================================================
import "./Comment.css"
import React from "react"
import { Button, Icon, Input } from "antd"
import axios from "axios"
import moment from "moment"
import urls from "config/urls"
import { getUrl } from "utils/url"
// import AccountContext from "context/account-context"
import Header from "./Header"
import Body from "./Body"
const LIMIT = 10
const commentBodyStyle = {
height: "calc(100% - 100px)",
overflow: "auto",
width: "100%",
position: "fixed",
background: "#eceff1",
padding: 10,
paddingBottom: 30
}
const { TextArea } = Input
class CommentTab extends React.Component {
constructor(props) {
super(props)
this.state = {
loading: false,
hasMore: true,
submitting: false,
comments: [],
input: "",
inputFocus: false,
replyTo: "",
replyToUserId: ""
}
this.inputRef = React.createRef()
this.bodyRef = React.createRef()
this.offset = 0
this.order = "best"
}
onFocus = e => {
this.setState({ inputFocus: true })
}
reply = (userId, username) => {
this.setState({ replyTo: username, replyToUserId: userId })
this.inputRef.current.focus()
}
vote = commentId => {
const payload = {
comment_id: commentId
}
axios
.post(urls.dbAPI + "/api/v1/vote_comment", payload)
.then(res => {})
.catch(err => {})
.then(() => {})
}
submit = () => {
const payload = {
url: getUrl(),
content: this.state.input,
reply_to_user_id: this.state.replyToUserId,
reply_to_user_name: this.state.replyTo
}
this.setState({ submitting: true })
axios
.post(urls.dbAPI + "/api/v1/post_comment", payload)
.then(res => {
let content = this.state.input
if (this.state.replyTo) {
content = "@" + this.state.replyTo + " \n" + content
}
const account = this.context.account
const selfMsg = {
id: Math.random(100), // whatever unique
userId: account.id,
avatarSrc: account.avatarSrc,
name: account.name,
time: moment().fromNow(),
content: content,
self: true,
noFooter: true // can't support any action since there is no id
// maybe backend can return id
}
this.setState({ comments: [selfMsg].concat(this.state.comments) })
this.clearInput()
setTimeout(() => {
window.spDebug("[Comment] scroll to top")
this.bodyRef.current.scrollTop = 0
}, 500)
})
.catch(err => {
console.error(err)
})
.then(() => {
this.setState({ submitting: false })
})
}
handleInput = e => {
this.setState({ input: e.target.value })
}
clearInput = () => {
this.setState({
input: "",
inputFocus: false,
replyTo: null,
replyToUserId: null
})
}
orderBy = val => {
this.setState({ comments: [], hasMore: true })
this.offset = 0
this.order = val
this.loadComments()
}
loadMore = () => {
this.offset = this.state.comments.length
this.loadComments()
}
loadComments = () => {
this.setState({ loading: true })
const payload = {
url: getUrl(),
offset: this.offset,
limit: LIMIT,
order: this.order
}
axios
.post(urls.dbAPI + "/api/v1/get_comments", payload)
.then(res => {
res.data.forEach(comment => {
comment.time = moment.utc(comment.created).fromNow()
})
this.setState({
comments: this.state.comments.concat(res.data),
hasMore: res.data.length === LIMIT
})
})
.catch(err => {
console.error(err)
})
.then(() => {
this.setState({ loading: false })
})
}
componentDidMount() {
this.loadComments()
}
render() {
let rowNum = 1
if (this.state.inputFocus) {
rowNum = 5
}
let placeholder = "留言。。。"
if (this.state.replyTo) {
placeholder = "@" + this.state.replyTo
}
let footer = (
<center style={{ padding: 10, background: "lightgray" }}>尚未登录</center>
)
if (this.context.account) {
footer = (
<div>
<TextArea
size="large"
value={this.state.input}
onFocus={this.onFocus}
onChange={this.handleInput}
placeholder={placeholder}
rows={rowNum}
ref={this.inputRef}
/>
{this.state.inputFocus && (
<div
style={{
width: "100%",
textAlign: "right"
}}
>
<Button onClick={this.clearInput}>取消</Button>
<Button
onClick={this.submit}
style={{ margin: 10 }}
type="primary"
loading={this.state.submitting}
>
提交
</Button>
</div>
)}
</div>
)
}
return (
<div>
<Header orderBy={this.orderBy} />
<div ref={this.bodyRef} style={commentBodyStyle}>
{this.state.loading && this.state.comments.length === 0 && (
// when no comments loaded, show loading icon
// if there are comments loaded, loading icon is
// shown in load more button
<center>
<Icon type="loading" />
</center>
)}
<Body
data={this.state.comments}
vote={this.vote}
reply={this.reply}
/>
{this.state.comments.length >= LIMIT && this.state.hasMore && (
<center style={{ marginTop: 20 }}>
{/*
If comments length < LIMIT, for sure there
isn't any more comment, TODO: backend should be
able to return if there's more to load.
For now, just set a noMore flag when backend
return empty */}
<Button
loading={this.state.loading}
type="primary"
onClick={this.loadMore}
>
加载更多...
</Button>
</center>
)}
</div>
<div className="sp-comment-footer">{footer}</div>
</div>
)
}
}
// CommentTab.contextType = AccountContext
export default CommentTab
================================================
FILE: chatbox/src/containers/Comment/Header.js
================================================
import React from "react"
import { Select } from "antd"
const Option = Select.Option
function CommentHeader(props) {
return (
<center className="sp-tab-header">
{/* <span style={{marginLeft:10}}>网页留言</span> */}
{/* <span style={{ position: "absolute", right: 10 }}> */}
<Select onChange={props.orderBy} size="small" defaultValue="best">
<Option value="newest">按时间排序</Option>
<Option value="best">按好评排序</Option>
</Select>
{/* </span> */}
</center>
)
}
export default CommentHeader
================================================
FILE: chatbox/src/containers/Comment/Message/Message.css
================================================
.sp-comment-message-username {
font-size: smaller;
font-weight: bold;
}
.sp-comment-message-avatar {
vertical-align: top;
margin-top: 5px;
margin-right: 5px;
}
.sp-comment-message-footer {
margin-top: 10px;
color: gray;
}
.sp-comment-message-score {
/* margin-top: -3px; */
margin-left: 3px;
color: #1890ff;
}
.sp-comment-message-footer .anticon {
cursor: pointer;
margin-bottom: -1px;
display: table-caption;
}
.sp-comment-message-footer .anticon:hover {
color: black;
}
.sp-comment-message-reply {
margin-left: 20px;
cursor: pointer;
}
.sp-comment-message-reply:hover {
color: black;
}
.sp-comment-message-time {
font-size: smaller;
margin-left: 15px;
}
================================================
FILE: chatbox/src/containers/Comment/Message/Message.js
================================================
import "./Message.css"
import React, { useState } from "react"
import { Icon } from "antd"
import AvatarWithHoverCard from "containers/OtherProfile/AvatarWithHoverCard"
// import AccountContext from "context/account-context"
function Comment(props) {
const data = props.data
const user = {
id: data.userId,
name: data.name,
avatarSrc: data.avatarSrc,
self: data.self
}
const [score, setScore] = useState(data.score)
const [voted, setVoted] = useState(data.voted)
const account = props.account
function theme() {
if (voted) return "twoTone"
return "outlined"
}
return (
<div style={{ marginTop: 10 }} className={data.self ? "self" : ""}>
<AvatarWithHoverCard className="sp-comment-message-avatar" user={user} />
<div className="sp-message-body text">
<div style={{ marginBottom: 5 }}>
<span className="sp-comment-message-username">{data.name}</span>
<span className="sp-comment-message-time">{data.time}</span>
</div>
<div>{data.content}</div>
{!data.noFooter && (
<div className="sp-comment-message-footer">
<span>
<Icon
theme={theme()}
onClick={() => {
if (!account) {
// TODO: show error msg
return
}
setScore(prevScore => {
if (voted) return prevScore - 1
return prevScore + 1
})
setVoted(prevState => {
return !prevState
})
props.vote(data.id)
}}
type="like"
/>
<span className="sp-comment-message-score">{score}</span>
</span>
<span
onClick={() => props.reply(data.userId, data.name)}
className="sp-comment-message-reply"
>
回复
</span>
</div>
)}
</div>
</div>
)
}
export default Comment
================================================
FILE: chatbox/src/containers/Comment/Message/index.js
================================================
export { default } from "./Message"
================================================
FILE: chatbox/src/containers/Comment/index.js
================================================
export { default } from "./Comment"
================================================
FILE: chatbox/src/containers/Home/Comments/Comment.css
================================================
.sp-comment-url {
color: #1890ff;
cursor: pointer;
}
.sp-home-comment {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-top: 5px;
padding-bottom: 5px;
margin: 10px;
/* border-top: 1px solid lightgray; */
}
/* .sp-home-comment:hover {
background: lightgray;
} */
.sp-home-comment .sp-comment-message {
/* vertical-align: -webkit-baseline-middle; */
margin-left: 10px;
}
.sp-home-comment .sp-comment-body {
display: flex;
white-space: normal;
margin-top: 5px;
}
================================================
FILE: chatbox/src/containers/Home/Comments/Comments.js
================================================
import "./Comment.css"
import React, { useState, useEffect } from "react"
import { Icon } from "antd"
// import moment from "moment"
import AvatarWithHoverCard from "containers/OtherProfile/AvatarWithHoverCard"
import { getLatestComments } from "services/comment"
function Comments(props) {
const [loading, setLoading] = useState(true)
const [comments, setComments] = useState([])
useEffect(() => {
getLatestComments()
.then(resp => {
setComments(resp.data)
})
.catch(err => {})
.then(() => {
setLoading(false)
})
}, [])
if (loading)
return (
<center>
<Icon type="loading" />
</center>
)
return comments.map(comment => (
<div className="sp-home-comment" key={comment.id}>
<a
className="sp-comment-url"
target="_blank"
rel="noopener noreferrer"
href={comment.url}
>
{comment.url}
</a>
<div className="sp-comment-body">
<AvatarWithHoverCard
className="sp-pointer-cursor"
size="small"
user={comment.user}
/>
<span className="sp-comment-message">{comment.content}</span>
</div>
{/* <div className="sp-message-body text">
<div style={{ marginBottom: 5 }}>
<span className="sp-comment-message-username">
{comment.user.name}
</span>
<span className="sp-comment-message-time">
{moment.utc(comment.created).fromNow()}
</span>
</div>
<div>{comment.content}</div>
</div> */}
</div>
))
}
export default Comments
================================================
FILE: chatbox/src/containers/Home/Comments/index.js
================================================
export { default } from "./Comments"
================================================
FILE: chatbox/src/containers/Home/CreateRoom.js
================================================
import React from "react"
import { Form, Input, Button, message } from "antd"
import { createRoom } from "services/room"
// const { Option } = Select
class CreateRoomForm extends React.Component {
state = {
submitting: false
}
room = this.props.room
handleSubmit = e => {
e.preventDefault()
this.props.form.validateFieldsAndScroll((err, values) => {
if (!err) {
window.spDebug("Received values of form: " + values)
if (!(values.name && values.about)) {
message.error("必须填写房间名与介绍")
return
}
this.setState({ submitting: true })
if (this.room) {
values["roomId"] = this.room.id
}
createRoom(values)
.then(resp => {
message.success("成功!")
this.props.back()
this.props.afterUpdateCb(resp.data)
// reload all rooms
})
.catch(err => {})
.then(() => {
this.setState({ submitting: false })
})
}
})
}
render() {
const { getFieldDecorator } = this.props.form
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 8 }
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 }
}
}
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0
},
sm: {
span: 16,
offset: 8
}
}
}
return (
<Form
style={{ width: "95%", margin: "auto" }}
{...formItemLayout}
onSubmit={this.handleSubmit}
>
<Form.Item label={<span>房间名 (必填)</span>}>
{getFieldDecorator("name", {
rules: [
{
message: "房间名不能为空",
whitespace: true
},
{
max: 12,
message: "房间名最多12个字符"
},
{
min: 1,
message: "房间名最少1个字符"
}
],
initialValue: this.room && this.room.name
})(<Input />)}
</Form.Item>
<Form.Item label={<span>房间介绍 (必填)</span>}>
{getFieldDecorator("about", {
initialValue: this.room && this.room.about
})(<Input.TextArea placeholder="房间话题与聊天规则" />)}
</Form.Item>
<Form.Item label={<span>背景图片地址</span>}>
{getFieldDecorator("background", {
initialValue: this.room && this.room.background
})(<Input />)}
</Form.Item>
<Form.Item label={<span>封面图片地址</span>}>
{getFieldDecorator("cover", {
initialValue: this.room && this.room.cover
})(<Input />)}
</Form.Item>
<Form.Item
label={
<span>
资源列表 (一行一条)
<div style={{ color: "gray", lineHeight: "20px" }}>
<div style={{ marginBottom: 10 }}>
请使用markdown格式, 如下:
</div>
<div> [双节棍](http://12.com/34.mp3)</div>
<div> [菊花台](http://56.com/78.mp3)</div>
<br />
</div>
</span>
}
>
{getFieldDecorator("media", {
initialValue: this.room && this.room.mediaRaw
})(<Input.TextArea />)}
</Form.Item>
<Form.Item {...tailFormItemLayout}>
<Button
// size="large"
style={{ marginRight: 10 }}
onClick={this.props.back}
>
取消
</Button>
<Button
loading={this.state.submitting}
type="primary"
// size="large"
htmlType="submit"
>
保存
</Button>
</Form.Item>
</Form>
)
}
}
const WrappedCreateRoomForm = Form.create({ name: "create-room" })(
CreateRoomForm
)
export default WrappedCreateRoomForm
================================================
FILE: chatbox/src/containers/Home/Danmus/Danmus.js
================================================
import React, { useState, useEffect } from "react"
import { Icon } from "antd"
import { getLatestDanmus } from "services/danmu"
import AvatarWithHoverCard from "containers/OtherProfile/AvatarWithHoverCard"
function Danmus(props) {
const [loading, setLoading] = useState(true)
const [danmus, setDanmus] = useState([])
useEffect(() => {
getLatestDanmus()
.then(resp => {
setDanmus(resp.data)
})
.catch(err => {})
.then(() => {
setLoading(false)
})
}, [])
if (loading)
return (
<center>
<Icon type="loading" />
</center>
)
return danmus.map(danmu => (
<div className="sp-home-comment" key={danmu.id}>
<a
className="sp-comment-url"
target="_blank"
rel="noopener noreferrer"
href={danmu.url}
>
{danmu.url}
</a>
<div className="sp-comment-body">
<AvatarWithHoverCard
className="sp-pointer-cursor"
size="small"
user={danmu.user}
/>
<span className="sp-comment-message">{danmu.content}</span>
</div>
</div>
))
}
export default Danmus
================================================
FILE: chatbox/src/containers/Home/Danmus/index.js
================================================
export { default } from "./Danmus"
================================================
FILE: chatbox/src/containers/Home/Discover.js
================================================
import "./Home.css"
import { useIntl } from "react-intl"
import React, { useState, useEffect } from "react"
import { Icon, Modal, Button } from "antd"
import { connect } from "react-redux"
import CreateRoomForm from "./CreateRoom"
import { getRooms } from "services/room"
import {
setDiscoveryRoom,
joinManMadeRoom,
setRoomConnectionStatus
} from "redux/actions/chat"
// import VideoRoom from "./VideoRoom"
import socketManager from "socket"
const roomColorCache = {}
function getRandomRolor(roomId) {
if (roomId in roomColorCache) {
return roomColorCache[roomId]
}
var letters = "0123456789".split("")
var color = "#"
for (var i = 0; i < 6; i++) {
color += letters[Math.round(Math.random() * 10)]
}
roomColorCache[roomId] = color
return color
}
const title = (
<span>
创建房间<span style={{ color: "gray" }}>(需10积分)</span>
</span>
)
function Discover({
joinManMadeRoom,
setRoomConnectionStatus,
back,
showCreateRoomBtn,
user,
activeTab
}) {
// room joined isn't put to redux state, any problem?
const intl = useIntl()
const [loadingRooms, setLoadingRooms] = useState(false)
// rooms here mean room list returned from backend
// do not confuse with state.rooms
const [rooms, setRooms] = useState([])
const [showCreateRoomModal, setShowCreateRoomModal] = useState(false)
let headerTitle = intl.formatMessage({ id: "roomlist" })
// if (user) {
// headerTitle =
// user.name +
// intl.formatMessage({ id: "possessive" }) +
// intl.formatMessage({ id: "roomlist" })
// }
const loadRooms = () => {
setLoadingRooms(true)
// setRooms([])
const params = {}
if (user) {
params["userId"] = user.numId
}
getRooms(params)
.then(resp => {
resp.data.sort((a, b) => {
return b.userCount - a.userCount
})
setRooms(resp.data)
})
.catch(err => {})
.then(() => {
setLoadingRooms(false)
})
}
useEffect(() => {
// console.log(activeTab)
if (activeTab === "discover" || user) {
loadRooms()
}
}, [user, activeTab])
return (
<span>
<div>
<div className="sp-tab-header">
{back && (
<Button
onClick={back}
size="small"
className="sp-back-btn"
icon="arrow-left"
/>
)}
{!back && loadingRooms && (
<Button size="small" className="sp-back-btn" icon="loading" />
)}
{!back && !loadingRooms && (
<Button
size="small"
icon="reload"
onClick={loadRooms}
className="sp-back-btn"
/>
)}
<span>{headerTitle}</span>
{showCreateRoomBtn && (
<span style={{ position: "absolute", right: 10 }}>
<Button
type="primary"
icon="plus"
size="small"
onClick={() => {
setShowCreateRoomModal(true)
}}
>
创建房间
</Button>
</span>
)}
</div>
<div
style={{
padding: 0,
// paddingLeft: 20,
// paddingRight: 20,
background: "#e6d8d8"
// backgroundImage: "linear-gradient(#e6f7ff, #40a9ff)"
}}
className="sp-tab-body discovery"
>
{back && loadingRooms && (
<Icon
style={{
margin: "auto",
marginTop: 30,
marginBottom: 30,
display: "block"
// position: "absolute",
// left: 20
}}
type="loading"
/>
)}
{!loadingRooms && rooms.length === 0 && (
<center style={{ margin: 20 }}>
{intl.formatMessage({ id: "empty" })}
</center>
)}
{rooms.map(r => {
let color = r.color
if (!color) {
color = getRandomRolor(r.id)
r.color = color
}
const style = {
backgroundColor: color
// "#" + Math.floor(Math.random() * 3777215).toString(16)
}
if (r.cover) {
style.backgroundImage = `url('${r.cover}')`
}
// const randomColor = Math.floor(Math.random()*16777215).toString(16);
// else {
// style.backgroundColor = "#acacac"
// }
return (
<div
title={r.about}
key={r.id}
onClick={() => {
joinManMadeRoom(r)
// setDiscoveryRoom(r)
socketManager.joinRoom(r)
setRoomConnectionStatus(r.id, "JOINING")
}}
className="sp-discover-entry"
style={style}
>
<div className="sp-room-wrapper">
<div>
<b>{r.name}</b>
<br />
<Icon style={{ marginRight: 3 }} type="team" />
{r.userCount}
<br />
<b>
{r.media && (
<Icon
type="play-circle"
theme="filled"
style={{ marginRight: 3 }}
/>
)}
{r.title}
</b>
</div>
</div>
</div>
)
})}
<br />
<br />
{/* {!loadingRooms && <div style={{ float: "right" }}>WIP...</div>} */}
</div>
</div>
{/* {room && (
<VideoRoom
room={room}
account={account}
back={() => {
setDiscoveryRoom(null)
socketManager.leaveRoom(room)
}}
/>
)} */}
<Modal
transitionName="none"
title={title}
visible={showCreateRoomModal}
onCancel={() => {
setShowCreateRoomModal(false)
}}
footer={null}
wrapClassName="sp-modal"
>
<CreateRoomForm
back={() => {
setShowCreateRoomModal(false)
}}
afterUpdateCb={loadRooms}
/>
</Modal>
</span>
)
}
// export default Discover
const stateToProps = state => {
// const rooms = state.rooms.filter(r => {
// return r.type === "discovery"
// })
// let room = null
// if (rooms.length === 1) {
// room = { ...rooms[0] }
// }
return {
// room: room,
activeTab: state.tab
}
}
export default connect(stateToProps, {
// setDiscoveryRoom,
joinManMadeRoom,
setRoomConnectionStatus
})(Discover)
================================================
FILE: chatbox/src/containers/Home/Home.css
================================================
.sp-hot-chatrooms-wrapper {
display: -webkit-flex; /* Safari */
-webkit-flex-wrap: wrap; /* Safari 6.1+ */
display: flex;
flex-wrap: wrap;
padding: 10px;
padding-left: 20px;
padding-right: 20px;
}
.sp-discover-entry {
width: 50%;
height: 40%;
max-height: 150px;
/* max-width: 200px; */
/* max-height: 200px; */
/* padding: 10px; */
position: relative;
/* margin: 12px; */
color: white;
/* text-align: center; */
display: inline-block;
cursor: pointer;
background-color: gray;
background-size: cover;
vertical-align: middle;
/* border-bottom: 1px solid white; */
padding: 0px;
}
.sp-room-wrapper {
background: rgba(0, 0, 0, 0.502);
height: 100%;
width: 100%;
/* white-space: nowrap; */
overflow: hidden;
text-overflow: ellipsis;
/* padding: 30px; */
}
.sp-discover-entry:nth-child(odd) {
/* border-right: 1px solid white; */
}
.sp-discover-entry div {
top: 50%;
left: 50%;
text-align: center;
/* margin: 0; */
position: absolute;
transform: translate(-50%, -50%);
overflow: hidden;
/* max-width: 90%; */
text-overflow: ellipsis;
/* padding: 30px; */
}
.sp-discover-entry:hover {
/* background: white !important; */
/* color: black; */
/* font-size: x-large; */
/* zoom: 1.2; */
/* background-color: black; */
/* background-blend-mode: darken; */
/* background-size: cover; */
/* background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)); */
/* background: linear-gradient(rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.7)); */
/* background: #008fff !important; */
}
================================================
FILE: chatbox/src/containers/Home/Home.js
================================================
import "./Home.css"
import React, { useState, useEffect } from "react"
import { Collapse, Modal, Button } from "antd"
import Danmus from "./Danmus"
import Comments from "./Comments"
import Users from "./Users"
import Rooms from "./Rooms"
import CreateRoomForm from "./CreateRoom"
import { getPopularRooms } from "services/room"
const Panel = Collapse.Panel
function Home(props) {
const [showCreateRoomModal, setShowCreateRoomModal] = useState(false)
const [rooms, setRooms] = useState([])
const [loadingRooms, setLoadingRooms] = useState(true)
const loadRooms = () => {
getPopularRooms()
.then(resp => {
setRooms(resp.data)
})
.catch(err => {})
.then(() => {
setLoadingRooms(false)
})
}
useEffect(() => {
loadRooms()
}, [])
const title = (
<span>
创建房间<span style={{ color: "gray" }}>(需60积分)</span>
</span>
)
return (
<div>
<Modal
transitionName="none"
title={title}
visible={showCreateRoomModal}
onCancel={() => {
setShowCreateRoomModal(false)
}}
footer={null}
>
<CreateRoomForm
back={() => {
setShowCreateRoomModal(false)
}}
loadRooms={loadRooms}
/>
</Modal>
<Collapse
bordered={false}
className="sp-special-tab"
defaultActiveKey={["hot-chatrooms"]}
// defaultActiveKey={["latest-comments"]}
onChange={key => {}}
>
<Panel header="热门房间" key="hot-chatrooms">
<Button
style={{ marginLeft: 5, marginBottom: 15 }}
type="primary"
icon="plus"
onClick={() => {
setShowCreateRoomModal(true)
}}
>
创建房间
</Button>
<div className="sp-hot-chatrooms-wrapper">
<Rooms rooms={rooms} loading={loadingRooms} />
</div>
</Panel>
<Panel header="最新网页留言" key="latest-comments">
<Comments />
</Panel>
<Panel header="最新视频弹幕" key="latest-danmus">
<Danmus />
</Panel>
<Panel header="新用户" key="new-users">
<Users />
</Panel>
</Collapse>
</div>
)
}
export default Home
================================================
FILE: chatbox/src/containers/Home/Rooms/Room.css
================================================
.sp-home-chatroom {
/* width: 80px;
height: 70px;
line-height: 70px;
background: #72c1ff;
margin: 5px;
color: white; */
cursor: pointer;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
margin-bottom: 5px;
}
.sp-home-chatroom:hover {
color: #2196f3;
}
.sp-chatroom-metadata {
display: inline-block;
vertical-align: middle;
line-height: normal;
font-size: 12px;
font-weight: bold;
}
================================================
FILE: chatbox/src/containers/Home/Rooms/Rooms.js
================================================
import "./Room.css"
import React from "react"
import { Icon } from "antd"
import { connect } from "react-redux"
import { joinManMadeRoom, setRoomConnectionStatus } from "redux/actions/chat"
import socketManager from "socket"
function Rooms({
loading,
rooms,
joinManMadeRoom,
manMadeRoom,
setShowRoomList
}) {
if (loading)
return (
<center>
<Icon type="loading" />
</center>
)
return rooms.map(room => {
let roomId = room.id
return (
// <Tooltip
// key={roomId}
// // overlayStyle={{ maxWidth: 100 }}
// title={room.about}
// placement="right"
// >
<div
key={roomId}
title={room.about}
className="sp-home-chatroom"
onClick={() => {
joinManMadeRoom(room)
setShowRoomList(false)
if (manMadeRoom) {
socketManager.leaveRoom(manMadeRoom)
}
socketManager.joinRoom(room)
setRoomConnectionStatus(room.id, "JOINING")
}}
>
<span style={{ marginRight: 15, display: "inline-block", width: 25 }}>
<Icon type="user" />
{room.userCount}
</span>
{room.name}
</div>
// </Tooltip>
)
})
}
const stateToProps = state => {
return { manMadeRoom: state.manMadeRoom }
}
export default connect(stateToProps, { joinManMadeRoom })(Rooms)
================================================
FILE: chatbox/src/containers/Home/Rooms/index.js
================================================
export { default } from "./Rooms"
================================================
FILE: chatbox/src/containers/Home/RoomsWrapper.js
================================================
import "./Home.css"
import { useIntl } from "react-intl"
import React, { useState, useEffect } from "react"
import { Icon, Modal, Button } from "antd"
import Rooms from "./Rooms"
import CreateRoomForm from "./CreateRoom"
import { getPopularRooms } from "services/room"
function RoomsWrapper(props) {
const intl = useIntl()
const [rooms, setRooms] = useState([])
const [loadingRooms, setLoadingRooms] = useState(true)
const [showCreateRoomModal, setShowCreateRoomModal] = useState(false)
const loadRooms = () => {
getPopularRooms("room")
.then(resp => {
resp.data.forEach(r => {
r.type = "room"
})
setRooms(resp.data)
})
.catch(err => {})
.then(() => {
setLoadingRooms(false)
})
}
useEffect(() => {
loadRooms()
}, [])
return (
<div className="sp-inbox-tab">
<div
style={{ padding: 10, paddingLeft: 20, paddingRight: 20 }}
className="sp-tab-body"
>
<Button
style={{ marginLeft: 0, marginBottom: 20, width: "100%" }}
type="primary"
icon="plus"
onClick={() => {
setShowCreateRoomModal(true)
}}
>
{intl.formatMessage({ id: "create.room" })}
</Button>
<center>{loadingRooms && <Icon type="loading" />}</center>
<Rooms setShowRoomList={props.setShowRoomList} rooms={rooms} />
</div>
<Modal
transitionName="none"
wrapClassName="sp-modal"
bodyStyle={{
paddingBottom: 0,
maxHeight: "calc(100% - 35px)",
overflowY: "auto"
}}
title={intl.formatMessage({ id: "create.room" })}
visible={showCreateRoomModal}
onCancel={() => {
setShowCreateRoomModal(false)
}}
footer={null}
>
<CreateRoomForm
back={() => {
setShowCreateRoomModal(false)
}}
loadRooms={loadRooms}
/>
</Modal>
</div>
)
}
export default RoomsWrapper
================================================
FILE: chatbox/src/containers/Home/Users/Users.css
================================================
.sp-home-users {
margin: 10px;
}
/* .sp-home-users:hover {
background: lightgray;
} */
.sp-home-users .sp-username {
margin-left: 15px;
}
================================================
FILE: chatbox/src/containers/Home/Users/Users.js
================================================
import "./Users.css"
import React, { useState, useEffect } from "react"
import { Icon } from "antd"
import { getLatestUsers } from "services/user"
import AvatarWithHoverCard from "containers/OtherProfile/AvatarWithHoverCard"
function Users(props) {
const [loading, setLoading] = useState(true)
const [users, setUsers] = useState([])
useEffect(() => {
getLatestUsers()
.then(resp => {
setUsers(resp.data)
})
.catch(err => {})
.then(() => {
setLoading(false)
})
}, [])
if (loading)
return (
<center>
<Icon type="loading" />
</center>
)
return users.map(user => (
<div className="sp-home-users" key={user.id}>
<div>
<AvatarWithHoverCard className="sp-pointer-cursor" user={user} />
<span className="sp-username">{user.name}</span>
</div>
</div>
))
}
export default Users
================================================
FILE: chatbox/src/containers/Home/Users/index.js
================================================
export { default } from "./Users"
================================================
FILE: chatbox/src/containers/Home/VideoRoom/VideoRoom.js
================================================
import React, { useState, useEffect, useRef } from "react"
import { Button, Radio } from "antd"
// import MusicPlayer from "components/MusicPlayer"
import Body from "containers/Chat/Body"
import Footer from "containers/Chat/Footer"
import RoomHeader from "containers/Chat/Header/RoomHeader"
const ROOM_TYPE = "media"
function VideoRoom({
back,
room,
account,
viewOtherUser,
setRoomConnectionStatus
}) {
// const [showHelp, setShowHelp] = useState(false)
const [messages, setMessages] = useState([])
const [showUsers, setShowUsers] = useState(false)
const [showMedia, setShowMedia] = useState(true)
const playerRef = useRef(null)
const playMedia = src => {
setShowMedia(true)
playerRef.current.src(src)
playerRef.current.play()
}
const pauseMedia = () => {
playerRef.current.pause()
}
return (
<div
className="sp-special-tab"
// style={{ backgroundImage: `url('${room.background}')` }}
>
<Button
onClick={() => {
back()
}}
style={{
position: "fixed",
marginTop: 1,
marginLeft: 5,
border: "none",
fontSize: "large"
}}
icon="arrow-left"
size="small"
/>
<div className="sp-tab-header">
<a
onClick={() => {
playMedia(room.src)
}}
>
{room.about}
</a>
{/* <RoomHeader
disconnectBtnLeft={35}
account={account}
viewOtherUser={viewOtherUser}
chatView={ROOM_TYPE}
show={true}
room={room}
showUsers={showUsers}
toggleUsers={setShowUsers}
setRoomConnectionStatus={(roomId, status) => {
setRoom({ ...room, connected: status })
}}
/> */}
{/* <Radio.Group
size="small"
buttonStyle="solid"
value={loopMode}
onChange={e => {
setLoopMode(e.target.value)
}}
>
<Radio.Button value="loopCurrent">循环当前</Radio.Button>
<Radio.Button value="loopAll">循环列表</Radio.Button>
</Radio.Group> */}
{/* <Button
style={{ border: "none", position: "absolute", right: 0 }}
onClick={() => {
setShowHelp(true)
}}
size="small"
icon="question"
/> */}
<span style={{ position: "absolute", right: 0 }}>
<RoomHeader
account={account}
viewOtherUser={viewOtherUser}
chatView={ROOM_TYPE}
show={true}
room={room}
showUsers={showUsers}
toggleUsers={setShowUsers}
setRoomConnectionStatus={setRoomConnectionStatus}
/>
</span>
</div>
<div
style={{ background: "red" }}
// className="sp-tab-body"
>
{/* <MusicPlayer sources={[room.src]} /> */}
{/* <div
style={{
padding: 20,
color: "lightgray",
width: "100%",
height: "180px",
fontSize: "10px",
position: "fixed",
overflowX: "hidden",
overflowY: "auto"
}}
>
</div> */}
{/* <div className="sp-tab-header">
placeholder for keeping header from being hiden
<div
style={{
display: "inline-block"
}}
></div> */}
{/* <RoomHeader
account={account}
viewOtherUser={viewOtherUser}
chatView={ROOM_TYPE}
show={true}
room={room}
showUsers={showUsers}
toggleUsers={setShowUsers}
setRoomConnectionStatus={setRoomConnectionStatus}
/> */}
{/* </div> */}
<Body
// height="calc(60% - 80px)"
account={account}
show={true}
messages={messages}
setMessages={setMessages}
room={room}
playerRef={playerRef}
playMedia={playMedia}
pauseMedia={pauseMedia}
showMedia={showMedia}
setShowMedia={setShowMedia}
sources={[room.src]}
/>
<Footer
account={account}
room={room}
// connected={room.connected}
chatView="video_room"
setMessages={setMessages}
/>
</div>
</div>
)
}
export default VideoRoom
================================================
FILE: chatbox/src/containers/Home/VideoRoom/index.js
================================================
export { default } from "./VideoRoom"
================================================
FILE: chatbox/src/containers/Home/index.js
================================================
export { default } from "./Home"
================================================
FILE: chatbox/src/containers/Inbox/Conversation/Conversation.css
================================================
.sp-inbox-conversation .sp-conversation-username {
color: black;
cursor: pointer;
padding: 5px;
}
.sp-inbox-conversation .sp-conversation-username:hover {
color: #40a9ff;
}
================================================
FILE: chatbox/src/containers/Inbox/Conversation/Conversation.js
================================================
import "./Conversation.css"
import React, { useState, useRef, useEffect } from "react"
import { Button } from "antd"
import moment from "moment"
import { connect } from "react-redux"
import socketManager from "socket"
import Message from "containers/Chat/Message"
import { postMessage } from "services/message"
import InputWithPicker from "components/InputWithPicker"
import { viewOtherUser } from "redux/actions"
const conversationBodyStyle = {
height: "calc(100% - 114px)",
overflowY: "auto",
overflowX: "hidden",
width: "100%",
position: "fixed",
background: "#f6f9fc",
padding: 10,
paddingBottom: 50
}
const AUTO_SCROLL_TRESHOLD_DISTANCE = 500
function Conversation(props) {
const account = props.account
// had to do a deep copy here only because
// <Message /> expects content to be object while
// <Inbox /> expects content to be string
const messages = JSON.parse(JSON.stringify(props.conversation.messages))
const other = props.conversation.user
const offset = props.offset
const [sending, setSending] = useState(false)
const bodyRef = useRef()
let lastMsg = null
const body = messages.map(msg => {
msg.time = moment.utc(msg.created)
// console.log(msg)
// TODO: backend should return same format as chat
// for consistency
if (typeof msg.content === "string") {
msg.content = {
type: msg.type,
value: msg.content
}
}
if (msg.self) {
msg.user = account
msg.userId = account.id
} else {
msg.user = other
msg.userId = other.id
}
// If same user is talking, no need to show user's avatar again
let showUser = true
// If it's been more than 5 mins since last msg
let showTimestamp = false
let timeDisplay = null
if (lastMsg) {
if (lastMsg.userId.toString() === msg.userId.toString()) showUser = false
if (msg.time.diff(lastMsg.time) > 5 * 60 * 1000) {
showTimestamp = true
showUser = true
}
} else {
showTimestamp = true
showUser = true
}
if (showTimestamp) {
if (moment().diff(msg.time) > 24 * 60 * 60 * 1000)
timeDisplay = msg.time.local().format("MM/DD HH:mm")
else timeDisplay = msg.time.local().fromNow()
}
lastMsg = msg
return (
<Message
showMenu={true}
key={msg.id}
data={msg}
showUser={showUser}
timeDisplay={timeDisplay}
imageLoadedCb={scrollToBottomIfNearBottom}
/>
)
})
useEffect(() => {
const bodyDiv = bodyRef.current
bodyDiv.scrollTop = bodyDiv.scrollHeight
}, [])
useEffect(() => {
window.spDebug("auto scroll down")
if (messages && messages.length) {
const lastMsg = messages[messages.length - 1]
let timeout = 50
if (lastMsg.type === "sticker") {
timeout = 500
}
scrollToBottomIfNearBottom(timeout)
}
}, [messages])
function scrollToBottomIfNearBottom(timeout) {
timeout = timeout || 100
const bodyDiv = bodyRef.current
if (
bodyDiv.scrollHeight - bodyDiv.scrollTop - bodyDiv.offsetHeight <
AUTO_SCROLL_TRESHOLD_DISTANCE
) {
setTimeout(() => {
bodyDiv.scrollTop = bodyDiv.scrollHeight
}, timeout)
}
}
function send(input) {
setSending(true)
postMessage(other.id, input, offset)
.then(resp => {
props.mergeAndSaveNewConversations(resp.data)
// TODO: maybe display message locally right away
// let socket server help ping user right away
socketManager.sendEvent("private message", { userId: other.id })
})
.catch(err => {
console.error(err)
})
.then(() => {
setSending(false)
})
return true
}
return (
<div className="sp-inbox-conversation">
<div className="sp-tab-header">
<Button
size="small"
onClick={props.back}
className="sp-back-btn"
icon="arrow-left"
/>
{/* <Button icon="refresh" size="small">
刷新
</Button> */}
<span>
与
<span
className="sp-conversation-username"
onClick={() => props.viewOtherUser(other)}
>
{other.name}
</span>
的对话
</span>
</div>
<div ref={bodyRef} style={conversationBodyStyle}>
{body}
</div>
<div className="sp-chat-bottom">
<InputWithPicker autoFocus={true} sending={sending} send={send} />
</div>
</div>
)
}
export default connect(null, { viewOtherUser })(Conversation)
================================================
FILE: chatbox/src/containers/Inbox/Conversation/index.js
================================================
export { default } from "./Conversation"
================================================
FILE: chatbox/src/containers/Inbox/Inbox.css
================================================
.sp-inbox-row {
padding-left: 5px;
padding-right: 5px;
display: flex;
align-items: center; /* vertically align avatar with text */
margin-top: -1px; /* so that hover background cover border */
}
.sp-inbox-row:hover {
cursor: pointer;
background: rgb(236, 236, 236);
}
.sp-inbox-row .sp-row-right {
padding: 10px;
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: calc(100% - 50px);
border-bottom: 1px solid #ececec;
}
.sp-inbox-row .sp-message-content {
font-weight: initial;
font-size: small;
color: gray;
white-space: nowrap;
overflow: hidden;
margin-top: 5px;
text-overflow: ellipsis;
}
.sp-inbox-row .sp-message-time {
float: right;
color: gray;
font-weight: initial;
font-size: smaller;
}
================================================
FILE: chatbox/src/containers/Inbox/Inbox.js
================================================
import "./Inbox.css"
import React, { useEffect, useState, useRef } from "react"
import { Avatar, Icon, Radio, Button, message } from "antd"
import moment from "moment"
import { useIntl } from "react-intl"
import { connect } from "react-redux"
import Conversation from "./Conversation"
import { getMessages } from "services/message"
import storageManager from "utils/storage"
import { msgOtherUser } from "redux/actions"
function Inbox(props) {
const user = props.user
const setUser = props.msgOtherUser
const intl = useIntl()
const account = props.account
if (!account) {
return (
<div className="sp-inbox-tab">
<center className="sp-tab-header">
{intl.formatMessage({ id: "not.login" })}
</center>
</div>
)
}
const activeTab = props.tab
const storageKey = "inbox-" + account.id
// can't use "inbox-id-offset" yet because injection
// script is still reading inbox-offset
// const storageOffsetKey = storageKey + "-offset"
const storageOffsetKey = "inbox-offset"
const prevAccountRef = useRef()
const activeTabRef = useRef()
const conversationsRef = useRef()
const [conversations, setConversations] = useState({})
const [showNotifications, setShowNotifications] = useState(false)
// offset equals to the biggest message id
const [loading, setLoading] = useState(false)
let selectedConversation = null
if (user) {
if (user.id in conversations) {
selectedConversation = conversations[user.id]
} else {
selectedConversation = {
user: user,
messages: []
}
setConversations({ ...conversations, [user.id]: selectedConversation })
}
}
function getMessagesFromServer(offset, noPopup) {
// usually we can get offset directly from
// conversations variable. But when user login,
// we set conversation and immediately getMessagesFromServer
// before conversation is updated and there isn't a callback
// for useState atm
offset = offset || getOffset(conversationsRef.current)
setLoading(true)
getMessages(offset)
.then(resp => {
// Merge with existing data
// TODO: mergeAndSaveNewConversations doesn't dedup
// it trusts whatever server returns and there could be
// duplicate with data in localstorage when multiple tabs
// pull at the same time?
// Have a simple offset check for now
const newConversations = resp.data
let hasNewMessage = false
const newOffset = getOffset(newConversations)
storageManager.set(storageOffsetKey, Math.max(newOffset, offset))
if (newOffset > offset) {
mergeAndSaveNewConversations(newConversations)
hasNewMessage = true
} else {
// message.info("没有新私信", 2)
console.warn("[Inbox] received offset no bigger than local offset")
}
if (hasNewMessage && offset && !noPopup) {
// `&& offset` because don't want to show popup
// when user login after logout (messages are deleted)
message.success(intl.formatMessage({ id: "new.mail" }), 2)
}
const unreadKey = "unread"
storageManager.set(unreadKey, false)
})
.catch(err => {
console.error(err)
})
.then(() => {
setLoading(false)
})
}
window.getMessagesFromServer = getMessagesFromServer
function mergeAndSaveNewConversations(newConversations) {
// console.log("mergeAndSaveNewConversations")
// merge and save new conversations into storage
storageManager.get(storageKey, conversations => {
conversations = conversations || {}
Object.keys(newConversations).forEach(userId => {
if (userId in conversations) {
conversations[userId].messages.push(
...newConversations[userId].messages
)
// use the new user data
conversations[userId].user = newConversations[userId].user
} else {
conversations[userId] = newConversations[userId]
}
// Ensure unique messages
conversations[userId].messages = [
...new Set(conversations[userId].messages)
]
})
storageManager.set(storageKey, conversations)
const offset = getOffset(conversations)
storageManager.set(storageOffsetKey, offset)
})
}
function getOffset(conversations) {
let offset = 0
conversations = conversations || {}
Object.values(conversations).forEach(c => {
if (c.messages.length) {
c.lastMsg = c.messages[c.messages.length - 1]
offset = Math.max(offset, c.lastMsg.id)
}
})
// window.spDebug(offset)
return offset
}
useEffect(() => {
// whenever switch to inbox tab or
// account updated, fetch mail
// TODO: account update shouldn't fetch mail
// only if account changed
if (activeTab === "inbox" && account) {
window.spDebug("[inbox] logged in, load from storage")
storageManager.get(storageKey, conversations => {
conversations = conversations || {}
setConversations(conversations)
window.spDebug("[inbox] loaded from storage, fetch from server")
const offset = getOffset(conversations)
getMessagesFromServer(offset)
})
}
activeTabRef.current = activeTab
}, [activeTab, account])
useEffect(() => {
if (account) {
if (!prevAccountRef.current) {
window.spDebug("register inbox storage listener")
// TODO: if same account login and logout and login again
// this listener is registered multiple times, should unregister
// when logout
storageManager.addEventListener(storageKey, conversations => {
window.spDebug("[inbox] storage updated")
conversations = conversations || {}
setConversations(conversations)
})
}
} else {
window.spDebug("[inbox] logged out")
setUser(null)
setConversations({})
}
prevAccountRef.current = account
}, [account])
useEffect(() => {
storageManager.addEventListener("unread", unread => {
if (unread) {
if (activeTabRef.current === "inbox") {
getMessagesFromServer(null, true)
}
}
})
}, [])
useEffect(() => {
conversationsRef.current = conversations
}, [conversations])
// useEffect(() => {
// Listen for account change, 2 cases:
// 1. not logged in => logged in (this may not be when
// the whole app logged in, since Inbox component is mounted
// later than the App component)
// 2. logged in => logged out
// There shouldn't be a case that's logged in as user A
// then suddenly changed to user B without going through
// a log out step
// When logged in
// 0. clear messages in memory (not in storage)
// 1. get messages from storage
// 2. get new messages from server using offset and save into storage
// When logged out, clear the memory
// const login = account && !prevAccountRef.current
// const logout = prevAccountRef.current && !account
// if (login) {
// let storageKey = "inbox-" + account.id
// window.spDebug("[inbox] logged in, load from storage")
// storageManager.get(storageKey, conversations => {
// conversations = conversations || {}
// setConversations(conversations)
// window.spDebug("[inbox] loaded from storage, fetch from server")
// getMessagesFromServer(getOffset(conversations))
// })
// window.spDebug("register inbox storage listener")
// // TODO: if same account login and logout and login again
// // this listener is registered multiple times, should unregister
// // when logout
// storageManager.addEventListener(storageKey, conversations => {
// window.spDebug("[inbox] storage updated")
// setConversations(conversations)
// })
// }
// if (logout) {
// window.spDebug("[inbox] logged out")
// setUser(null)
// setConversations({})
// // Clear memory
// }
// prevAccountRef.current = account
// }, [account])
// Backend/storage returns dictionary data structure so
// it's easy to insert new conversation
// Need to convert into array and sort by date to display
// Also get offset
const conversationsArray = Object.keys(conversations).map(userId => {
const c = conversations[userId]
if (c.messages.length) {
c.lastMsg = c.messages[c.messages.length - 1]
c.time = moment.utc(c.lastMsg.created)
} else {
// if no message, this is user attempting to start conversation
c.time = moment.utc()
}
return c
})
conversationsArray.sort((a, b) => {
return b.time - a.time
})
let rows = conversationsArray.map(c => {
const user = c.user
return (
<div
onClick={() => {
setUser(user)
}}
key={user.id}
className="sp-inbox-row"
>
<Avatar icon="user" src={user.avatarSrc} />
<span className="sp-row-right">
<div>
{user.name}
{c.lastMsg && (
<span className="sp-message-time">{c.time.fromNow()}</span>
)}
</div>
{c.lastMsg && (
<div className="sp-message-content">{c.lastMsg.content}</div>
)}
</span>
</div>
)
})
if (conversationsArray.length === 0) {
rows = (
<center style={{ margin: 20 }}>
{intl.formatMessage({ id: "empty" })}
</center>
)
}
return (
<div className="sp-inbox-tab">
{selectedConversation && !showNotifications && (
<Conversation
account={account}
back={() => {
setUser(null)
}}
offset={getOffset(conversations)}
conversation={selectedConversation}
mergeAndSaveNewConversations={mergeAndSaveNewConversations}
/>
)}
{!selectedConversation && (
<div>
<center className="sp-tab-header">
{loading && (
<Button size="small" className="sp-back-btn" icon="loading" />
)}
{!loading && (
<Button
onClick={() => {
getMessagesFromServer()
}}
className="sp-back-btn"
// style={{ border: "none", padding: 0 }}
size="small"
icon="reload"
/>
)}
<Radio.Group
size="small"
defaultValue={showNotifications}
buttonStyle="solid"
onChange={e => {
setShowNotifications(e.target.value)
}}
>
<Radio.Button value={false}>
{intl.formatMessage({ id: "mail" })}
</Radio.Button>
<Radio.Button value={true}>
{" "}
{intl.formatMessage({ id: "notification" })}
</Radio.Button>
</Radio.Group>
</center>
<div className="sp-tab-body" style={{ paddingBottom: 70 }}>
{!showNotifications && rows}
{showNotifications && (
<center style={{ margin: 20 }}>
{intl.formatMessage({ id: "empty" })}
</center>
)}
</div>
</div>
)}
</div>
)
}
const stateToProps = state => {
return { tab: state.tab, user: state.inboxUser, account: state.account }
}
export default connect(stateToProps, { msgOtherUser })(Inbox)
================================================
FILE: chatbox/src/containers/Inbox/index.js
================================================
export { default } from "./Inbox"
================================================
FILE: chatbox/src/containers/Music/MusicTab.js
================================================
import React, { useState } from "react"
import { Button, Radio, Modal } from "antd"
import MusicPlayer from "components/MusicPlayer"
import Playlist from "./Playlist"
function MusicTab(props) {
const [loopMode, setLoopMode] = useState("loopAll")
const [showHelp, setShowHelp] = useState(false)
return (
<div className="sp-special-tab">
<Button
onClick={() => {
props.back()
}}
style={{
position: "fixed",
marginTop: 1,
marginLeft: 5,
border: "none",
fontSize: "large"
}}
size="small"
icon="arrow-left"
/>
<center className="sp-tab-header">
<Radio.Group
size="small"
buttonStyle="solid"
value={loopMode}
onChange={e => {
setLoopMode(e.target.value)
}}
>
<Radio.Button value="loopCurrent">循环当前</Radio.Button>
<Radio.Button value="loopAll">循环列表</Radio.Button>
</Radio.Group>
<Button
style={{ border: "none", position: "absolute", right: 0 }}
onClick={() => {
setShowHelp(true)
}}
size="small"
icon="question"
/>
<Modal
transitionName="none"
title="播放器使用指南(Beta)"
visible={showHelp}
onCancel={() => {
setShowHelp(false)
}}
wrapClassName="sp-modal"
footer={null}
bodyStyle={{ maxHeight: "calc(100% - 55px)", overflowY: "auto" }}
>
<h3>分享原始资源</h3>
<p>
输入原始的视频或音频文件地址,比如
<br />
<span style={{ fontSize: 10 }}>
https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_5mb.mp4
</span>
</p>
<h4>支持以下视频格式:</h4>
<ul>
<li>mp4</li>
<li>webm</li>
<li>ogg</li>
<li>flv</li>
</ul>
<h4>支持以下音频格式:</h4>
<ul>
<li>mp3</li>
<li>wav</li>
</ul>
<br />
<h3>分享网站链接</h3>
<p>
直接输入含有视频或音频的网页地址,比如
<br />
<span style={{ fontSize: 10 }}>
https://music.163.com/#/song?id=640565
</span>
<br />
<span style={{ fontSize: 10 }}>
https://www.youtube.com/watch?v=txthoeUhyBI
</span>
</p>
<h4>支持的网站有:</h4>
<ul>
<li>Youtube</li>
<li>网易云音乐</li>
</ul>
</Modal>
</center>
<div className="sp-tab-body" style={{ background: "#404040" }}>
<MusicPlayer />
<div
style={{
padding: 20,
color: "lightgray",
width: "100%",
height: "180px",
fontSize: "10px",
position: "fixed",
overflowX: "hidden",
overflowY: "auto"
}}
>
<center style={{ marginBottom: 10 }}>播放列表</center>
<Playlist setMediaNum={props.setMediaNum} loopMode={loopMode} />
</div>
</div>
</div>
)
}
export default MusicTab
================================================
FILE: chatbox/src/containers/Music/Playlist/Playlist.css
================================================
.sp-playlist-item {
padding: 10px;
color: lightgray;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.sp-playlist-item:hover {
background: rgb(97, 97, 97);
cursor: pointer;
}
.sp-playlist-item.selected {
color: white;
font-weight: bold;
}
.sp-playlist-item .ant-avatar {
margin-right: 10px;
}
================================================
FILE: chatbox/src/containers/Music/Playlist/Playlist.js
================================================
import "./Playlist.css"
import React, { useState, useEffect } from "react"
import moment from "moment"
import AvatarWithHoverCard from "containers/OtherProfile/AvatarWithHoverCard"
function Playlist(props) {
const [playlist, setPlaylist] = useState([])
const [index, setIndex] = useState()
const setMediaNum = props.setMediaNum
const loopMode = props.loopMode
useEffect(() => {
window.playNextMedia = () => {
setIndex(curIndex => {
if (playlist.length === 0) {
return curIndex
}
let newIndex = curIndex
if (loopMode === "loopAll") {
newIndex = (curIndex + 1) % playlist.length
}
window.playMessage(playlist[newIndex])
return newIndex
})
}
return () => {
window.playNext = null
}
}, [playlist, loopMode])
useEffect(() => {
window.setPlaylist = items => {
// console.log(items)
setPlaylist(items)
gitextract_332hnacs/
├── .gitignore
├── .prettierrc
├── README.md
├── README_EN.md
├── chatbox/
│ ├── .gitignore
│ ├── .prettierrc
│ ├── .storybook/
│ │ ├── addons.js
│ │ └── config.js
│ ├── README.md
│ ├── package.json
│ ├── public/
│ │ ├── index.html
│ │ └── manifest.json
│ └── src/
│ ├── .eslintrc.json
│ ├── App.js
│ ├── components/
│ │ ├── Emoji/
│ │ │ ├── Emoji.css
│ │ │ ├── Emoji.js
│ │ │ └── index.js
│ │ ├── Iframe/
│ │ │ ├── Iframe.css
│ │ │ ├── Iframe.js
│ │ │ └── index.js
│ │ ├── InputWithPicker/
│ │ │ ├── InputWithPicker.css
│ │ │ ├── InputWithPicker.js
│ │ │ └── index.js
│ │ ├── MusicPlayer/
│ │ │ ├── MusicPlayer.css
│ │ │ ├── MusicPlayer.js
│ │ │ └── index.js
│ │ └── OutsideClickDetector.js
│ ├── config/
│ │ ├── index.js
│ │ ├── logger.js
│ │ └── urls.js
│ ├── containers/
│ │ ├── Account/
│ │ │ ├── Account.js
│ │ │ ├── AvatarUploader/
│ │ │ │ ├── AvatarUploader.css
│ │ │ │ ├── AvatarUploader.js
│ │ │ │ └── index.js
│ │ │ ├── Blacklist.js
│ │ │ ├── EditProfile.js
│ │ │ ├── Follow/
│ │ │ │ ├── Follow.css
│ │ │ │ ├── Follow.js
│ │ │ │ ├── event.js
│ │ │ │ └── index.js
│ │ │ ├── Login.js
│ │ │ ├── Profile/
│ │ │ │ ├── Profile.css
│ │ │ │ ├── Profile.js
│ │ │ │ └── index.js
│ │ │ ├── ResetPassword.js
│ │ │ └── index.js
│ │ ├── Chat/
│ │ │ ├── Body.css
│ │ │ ├── Body.js
│ │ │ ├── Chat.js
│ │ │ ├── Footer/
│ │ │ │ ├── Footer.css
│ │ │ │ ├── Footer.js
│ │ │ │ └── index.js
│ │ │ ├── Header/
│ │ │ │ ├── Header.css
│ │ │ │ ├── Header.js
│ │ │ │ ├── Invite.js
│ │ │ │ ├── RoomHeader.js
│ │ │ │ ├── RoomInfo.js
│ │ │ │ ├── Users.js
│ │ │ │ └── index.js
│ │ │ ├── Message/
│ │ │ │ ├── Body.css
│ │ │ │ ├── Body.js
│ │ │ │ ├── Message.css
│ │ │ │ ├── Message.js
│ │ │ │ └── index.js
│ │ │ ├── ResizableMedia.js
│ │ │ ├── View.js
│ │ │ └── index.js
│ │ ├── Comment/
│ │ │ ├── Body.js
│ │ │ ├── Comment.css
│ │ │ ├── Comment.js
│ │ │ ├── Header.js
│ │ │ ├── Message/
│ │ │ │ ├── Message.css
│ │ │ │ ├── Message.js
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ ├── Home/
│ │ │ ├── Comments/
│ │ │ │ ├── Comment.css
│ │ │ │ ├── Comments.js
│ │ │ │ └── index.js
│ │ │ ├── CreateRoom.js
│ │ │ ├── Danmus/
│ │ │ │ ├── Danmus.js
│ │ │ │ └── index.js
│ │ │ ├── Discover.js
│ │ │ ├── Home.css
│ │ │ ├── Home.js
│ │ │ ├── Rooms/
│ │ │ │ ├── Room.css
│ │ │ │ ├── Rooms.js
│ │ │ │ └── index.js
│ │ │ ├── RoomsWrapper.js
│ │ │ ├── Users/
│ │ │ │ ├── Users.css
│ │ │ │ ├── Users.js
│ │ │ │ └── index.js
│ │ │ ├── VideoRoom/
│ │ │ │ ├── VideoRoom.js
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ ├── Inbox/
│ │ │ ├── Conversation/
│ │ │ │ ├── Conversation.css
│ │ │ │ ├── Conversation.js
│ │ │ │ └── index.js
│ │ │ ├── Inbox.css
│ │ │ ├── Inbox.js
│ │ │ └── index.js
│ │ ├── Music/
│ │ │ ├── MusicTab.js
│ │ │ ├── Playlist/
│ │ │ │ ├── Playlist.css
│ │ │ │ ├── Playlist.js
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ ├── OtherProfile/
│ │ │ ├── AvatarWithHoverCard.js
│ │ │ ├── OtherProfile.js
│ │ │ ├── ProfileBody/
│ │ │ │ ├── ProfileBody.js
│ │ │ │ └── index.js
│ │ │ ├── ProfileCard.css
│ │ │ ├── ProfileCard.js
│ │ │ ├── ProfileMeta/
│ │ │ │ ├── ProfileMeta.js
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ └── Tab/
│ │ ├── Tab.css
│ │ ├── Tab.js
│ │ └── index.js
│ ├── context/
│ │ └── preference-context.js
│ ├── i18n/
│ │ ├── en.json
│ │ └── zh.json
│ ├── index.css
│ ├── index.js
│ ├── redux/
│ │ ├── actions/
│ │ │ ├── chat/
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ ├── reducers/
│ │ │ └── index.js
│ │ └── store/
│ │ └── index.js
│ ├── serviceWorker.js
│ ├── services/
│ │ ├── account.js
│ │ ├── comment.js
│ │ ├── danmu.js
│ │ ├── follow.js
│ │ ├── index.js
│ │ ├── message.js
│ │ ├── room.js
│ │ └── user.js
│ ├── socket/
│ │ ├── index.js
│ │ └── socket.js
│ ├── stories/
│ │ ├── IframeWithSrcInput.js
│ │ ├── data/
│ │ │ ├── chats.js
│ │ │ └── comments.js
│ │ ├── iframe.css
│ │ └── index.js
│ └── utils/
│ ├── pageTitle.js
│ ├── storage.js
│ └── url.js
├── extension/
│ ├── build/
│ │ ├── _locales/
│ │ │ ├── en/
│ │ │ │ └── messages.json
│ │ │ └── zh_CN/
│ │ │ └── messages.json
│ │ ├── background.js
│ │ ├── content-static/
│ │ │ ├── css/
│ │ │ │ └── main.css
│ │ │ └── js/
│ │ │ └── main.js
│ │ ├── manifest.json
│ │ ├── popup.css
│ │ ├── popup.html
│ │ ├── popup_menu/
│ │ │ ├── css/
│ │ │ │ └── main.css
│ │ │ └── js/
│ │ │ └── main.js
│ │ └── popup_old.js
│ └── popup/
│ ├── .gitignore
│ ├── README.md
│ ├── config/
│ │ ├── env.js
│ │ ├── jest/
│ │ │ ├── cssTransform.js
│ │ │ └── fileTransform.js
│ │ ├── paths.js
│ │ ├── webpack.config.js
│ │ └── webpackDevServer.config.js
│ ├── package.json
│ ├── public/
│ │ ├── index.html
│ │ └── manifest.json
│ ├── scripts/
│ │ ├── build.js
│ │ ├── start.js
│ │ └── test.js
│ └── src/
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ └── serviceWorker.js
└── inject-script/
├── .gitignore
├── .storybook/
│ ├── addons.js
│ ├── config.js
│ └── webpack_empty.config.js
├── README.md
├── config/
│ ├── env.js
│ ├── jest/
│ │ ├── cssTransform.js
│ │ └── fileTransform.js
│ ├── paths.js
│ ├── webpack.config.js
│ └── webpackDevServer.config.js
├── package.json
├── public/
│ ├── index.html
│ └── manifest.json
├── scripts/
│ ├── build.js
│ ├── start.js
│ └── test.js
├── src/
│ ├── config/
│ │ ├── iframe.js
│ │ ├── index.js
│ │ ├── logger.js
│ │ └── urls.js
│ ├── containers/
│ │ ├── App/
│ │ │ ├── App.js
│ │ │ ├── App.test.js
│ │ │ └── index.js
│ │ ├── ChatDanmu/
│ │ │ ├── AnimationDanmu.css
│ │ │ ├── AnimationDanmu.js
│ │ │ └── Danmu.js
│ │ ├── ChatIcon/
│ │ │ ├── ChatIcon.js
│ │ │ └── index.js
│ │ ├── ChatboxIframe/
│ │ │ ├── ChatboxIframe.css
│ │ │ ├── ChatboxIframe.js
│ │ │ └── index.js
│ │ ├── ImageModal/
│ │ │ ├── ImageModal.css
│ │ │ ├── ImageModal.js
│ │ │ └── index.js
│ │ ├── Room/
│ │ │ ├── Room.css
│ │ │ ├── Room.js
│ │ │ └── index.js
│ │ └── User/
│ │ ├── User.css
│ │ ├── User.js
│ │ └── index.js
│ ├── index.css
│ ├── index.js
│ ├── serviceWorker.js
│ ├── services/
│ │ ├── account/
│ │ │ ├── account.js
│ │ │ └── index.js
│ │ ├── room/
│ │ │ ├── index.js
│ │ │ └── room.js
│ │ ├── socket/
│ │ │ ├── index.js
│ │ │ └── socket.js
│ │ └── user/
│ │ ├── index.js
│ │ └── user.js
│ ├── storage.js
│ └── utils/
│ ├── iframe.js
│ └── url.js
└── stories/
└── index.stories.js
SYMBOL INDEX (1153 symbols across 88 files)
FILE: chatbox/.storybook/config.js
function loadStories (line 3) | function loadStories() {
FILE: chatbox/src/App.js
class App (line 41) | class App extends React.Component {
method constructor (line 42) | constructor(props) {
method componentDidMount (line 81) | componentDidMount() {
method componentDidUpdate (line 218) | componentDidUpdate(prevProps, prevState, snapshot) {
method render (line 247) | render() {
FILE: chatbox/src/components/Emoji/Emoji.js
function Emoji (line 65) | function Emoji(props) {
FILE: chatbox/src/components/Iframe/Iframe.js
function Iframe (line 6) | function Iframe({ title, url, show, setShow }) {
FILE: chatbox/src/components/InputWithPicker/InputWithPicker.js
function InputWithPicker (line 11) | function InputWithPicker(props) {
FILE: chatbox/src/components/MusicPlayer/MusicPlayer.js
class VideoPlayer (line 9) | class VideoPlayer extends React.Component {
method componentDidMount (line 10) | componentDidMount() {
method componentWillUnmount (line 67) | componentWillUnmount() {
method render (line 79) | render() {
function Player (line 94) | function Player(props) {
FILE: chatbox/src/components/OutsideClickDetector.js
class OutsideAlerter (line 3) | class OutsideAlerter extends React.Component {
method constructor (line 4) | constructor(props) {
method componentDidMount (line 11) | componentDidMount() {
method componentWillUnmount (line 15) | componentWillUnmount() {
method setWrapperRef (line 22) | setWrapperRef(node) {
method handleClickOutside (line 29) | handleClickOutside(event) {
method render (line 43) | render() {
FILE: chatbox/src/containers/Account/Account.js
function setAccount (line 17) | function setAccount(account) {
function Account (line 21) | function Account({ account, blacklist, viewOtherUser }) {
FILE: chatbox/src/containers/Account/AvatarUploader/AvatarUploader.js
class App (line 6) | class App extends React.Component {
method constructor (line 7) | constructor(props) {
method onDrop (line 13) | onDrop(picture) {
method render (line 21) | render() {
FILE: chatbox/src/containers/Account/Blacklist.js
function Blacklist (line 5) | function Blacklist({ blacklist, viewOtherUser, back }) {
FILE: chatbox/src/containers/Account/EditProfile.js
class EditProfileForm (line 11) | class EditProfileForm extends React.Component {
method render (line 38) | render() {
FILE: chatbox/src/containers/Account/Follow/Follow.js
function Follow (line 13) | function Follow(props) {
FILE: chatbox/src/containers/Account/Login.js
class NormalLoginForm (line 8) | class NormalLoginForm extends React.Component {
method constructor (line 9) | constructor(props) {
method componentDidMount (line 19) | componentDidMount() {
method render (line 96) | render() {
FILE: chatbox/src/containers/Account/Profile/Profile.js
function Profile (line 41) | function Profile(props) {
FILE: chatbox/src/containers/Account/ResetPassword.js
class ResetPasswordForm (line 7) | class ResetPasswordForm extends React.Component {
method render (line 49) | render() {
FILE: chatbox/src/containers/Chat/Body.js
constant AUTO_SCROLL_TRESHOLD_DISTANCE (line 34) | const AUTO_SCROLL_TRESHOLD_DISTANCE = 300
constant VIDEO_DEFAULT_HEIGHT (line 35) | const VIDEO_DEFAULT_HEIGHT = 200
constant IFRAME_DEFAULT_HEIGHT (line 36) | const IFRAME_DEFAULT_HEIGHT = 270
function ChatBody (line 41) | function ChatBody({
FILE: chatbox/src/containers/Chat/Chat.js
function syncRoomsPeriodically (line 14) | function syncRoomsPeriodically() {
function Chat (line 20) | function Chat({
FILE: chatbox/src/containers/Chat/Footer/Footer.js
constant MESSAGE_TIME_GAP (line 13) | const MESSAGE_TIME_GAP = 2 * 1000
function Footer (line 15) | function Footer({
FILE: chatbox/src/containers/Chat/Header/Header.js
function ChatHeader (line 15) | function ChatHeader({
FILE: chatbox/src/containers/Chat/Header/Invite.js
function Users (line 4) | function Users(props) {
FILE: chatbox/src/containers/Chat/Header/RoomHeader.js
function RoomHeader (line 10) | function RoomHeader({
FILE: chatbox/src/containers/Chat/Header/RoomInfo.js
function RoomInfo (line 13) | function RoomInfo({
FILE: chatbox/src/containers/Chat/Header/Users.js
function Users (line 24) | function Users({ users, viewOtherUser, blacklist }) {
FILE: chatbox/src/containers/Chat/Message/Body.js
function isPureEmoji (line 12) | function isPureEmoji(string) {
function MessageBody (line 18) | function MessageBody(props) {
FILE: chatbox/src/containers/Chat/Message/Message.js
function ChatMessage (line 18) | function ChatMessage(props) {
FILE: chatbox/src/containers/Chat/ResizableMedia.js
function ResizableMedia (line 8) | function ResizableMedia({
FILE: chatbox/src/containers/Chat/View.js
function View (line 10) | function View({
FILE: chatbox/src/containers/Comment/Body.js
function CommentBody (line 5) | function CommentBody(props) {
FILE: chatbox/src/containers/Comment/Comment.js
constant LIMIT (line 15) | const LIMIT = 10
class CommentTab (line 29) | class CommentTab extends React.Component {
method constructor (line 30) | constructor(props) {
method componentDidMount (line 156) | componentDidMount() {
method render (line 160) | render() {
FILE: chatbox/src/containers/Comment/Header.js
function CommentHeader (line 5) | function CommentHeader(props) {
FILE: chatbox/src/containers/Comment/Message/Message.js
function Comment (line 8) | function Comment(props) {
FILE: chatbox/src/containers/Home/Comments/Comments.js
function Comments (line 10) | function Comments(props) {
FILE: chatbox/src/containers/Home/CreateRoom.js
class CreateRoomForm (line 6) | class CreateRoomForm extends React.Component {
method render (line 40) | render() {
FILE: chatbox/src/containers/Home/Danmus/Danmus.js
function Danmus (line 7) | function Danmus(props) {
FILE: chatbox/src/containers/Home/Discover.js
function getRandomRolor (line 18) | function getRandomRolor(roomId) {
function Discover (line 35) | function Discover({
FILE: chatbox/src/containers/Home/Home.js
function Home (line 15) | function Home(props) {
FILE: chatbox/src/containers/Home/Rooms/Rooms.js
function Rooms (line 10) | function Rooms({
FILE: chatbox/src/containers/Home/RoomsWrapper.js
function RoomsWrapper (line 11) | function RoomsWrapper(props) {
FILE: chatbox/src/containers/Home/Users/Users.js
function Users (line 9) | function Users(props) {
FILE: chatbox/src/containers/Home/VideoRoom/VideoRoom.js
constant ROOM_TYPE (line 9) | const ROOM_TYPE = "media"
function VideoRoom (line 10) | function VideoRoom({
FILE: chatbox/src/containers/Inbox/Conversation/Conversation.js
constant AUTO_SCROLL_TRESHOLD_DISTANCE (line 26) | const AUTO_SCROLL_TRESHOLD_DISTANCE = 500
function Conversation (line 28) | function Conversation(props) {
FILE: chatbox/src/containers/Inbox/Inbox.js
function Inbox (line 13) | function Inbox(props) {
FILE: chatbox/src/containers/Music/MusicTab.js
function MusicTab (line 7) | function MusicTab(props) {
FILE: chatbox/src/containers/Music/Playlist/Playlist.js
function Playlist (line 7) | function Playlist(props) {
FILE: chatbox/src/containers/OtherProfile/AvatarWithHoverCard.js
function AvatarWithHoverCard (line 12) | function AvatarWithHoverCard(props) {
function AvatarWrapper (line 67) | function AvatarWrapper(props) {
function MyPoper (line 88) | function MyPoper(props) {
FILE: chatbox/src/containers/OtherProfile/OtherProfile.js
function OtherProfile (line 10) | function OtherProfile({ otherUser, viewOtherUser }) {
FILE: chatbox/src/containers/OtherProfile/ProfileBody/ProfileBody.js
function ProfileBody (line 37) | function ProfileBody(props) {
FILE: chatbox/src/containers/OtherProfile/ProfileCard.js
function ProfileCard (line 24) | function ProfileCard({
function AvatarWithFollowerCount (line 164) | function AvatarWithFollowerCount(props) {
FILE: chatbox/src/containers/OtherProfile/ProfileMeta/ProfileMeta.js
function ProfileMeta (line 8) | function ProfileMeta(props) {
FILE: chatbox/src/containers/Tab/Tab.js
function Tab (line 21) | function Tab({ account, otherUser, activeTab, changeTab, viewOtherUser }) {
FILE: chatbox/src/redux/reducers/index.js
function getRooms (line 15) | function getRooms(modes, manMadeRoom) {
FILE: chatbox/src/serviceWorker.js
function register (line 23) | function register(config) {
function registerValidSW (line 57) | function registerValidSW(swUrl, config) {
function checkValidServiceWorker (line 101) | function checkValidServiceWorker(swUrl, config) {
function unregister (line 129) | function unregister() {
FILE: chatbox/src/stories/IframeWithSrcInput.js
function IframeWithSrcInput (line 6) | function IframeWithSrcInput(props) {
FILE: chatbox/src/stories/index.js
function sendMsgToIframe (line 18) | function sendMsgToIframe(msg) {
function addOnlineUser (line 21) | function addOnlineUser() {
function removeOnlineUser (line 34) | function removeOnlineUser() {
FILE: chatbox/src/utils/storage.js
function updateParentStorage (line 73) | function updateParentStorage(key, value) {
FILE: extension/build/content-static/js/main.js
function n (line 1) | function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{...
function n (line 1) | function n(){return e.exports=n=Object.assign||function(e){for(var t=1;t...
function n (line 1) | function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function n (line 1) | function n(t){return e.exports=n=Object.setPrototypeOf?Object.getPrototy...
function o (line 1) | function o(){for(var e=[],t=0;t<arguments.length;t++){var r=arguments[t]...
function a (line 1) | function a(e){return"[object Array]"===i.call(e)}
function u (line 1) | function u(e){return null!==e&&"object"===typeof e}
function l (line 1) | function l(e){return"[object Function]"===i.call(e)}
function s (line 1) | function s(e,t){if(null!==e&&"undefined"!==typeof e)if("object"!==typeof...
function n (line 1) | function n(n,r){"object"===typeof t[r]&&"object"===typeof n?t[r]=e(t[r],...
function t (line 1) | function t(e,n){var r;(0,a.default)(this,t),(r=(0,l.default)(this,(0,s.d...
function e (line 1) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function s (line 1) | function s(e){return e&&e.__esModule?e:{default:e}}
function e (line 1) | function e(t,n,r){!function(e,t){if(!(e instanceof t))throw new TypeErro...
function n (line 1) | function n(e){return(n="function"===typeof Symbol&&"symbol"===typeof Sym...
function r (line 1) | function r(t){return"function"===typeof Symbol&&"symbol"===n(Symbol.iter...
function e (line 1) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function s (line 1) | function s(e){return e&&e.__esModule?e:{default:e}}
function e (line 1) | function e(t){var n=this;!function(e,t){if(!(e instanceof t))throw new T...
function a (line 1) | function a(e){return e&&e.__esModule?e:{default:e}}
function n (line 1) | function n(e,t){return!1!==t.clone&&t.isMergeableObject(e)?i((n=e,Array....
function r (line 1) | function r(e,t,r){return e.concat(t).map(function(e){return n(e,r)})}
function o (line 1) | function o(e,t,r){var o={};return r.isMergeableObject(e)&&Object.keys(e)...
function i (line 1) | function i(t,i,a){(a=a||{}).arrayMerge=a.arrayMerge||r,a.isMergeableObje...
function i (line 1) | function i(e,t){return Object.keys(t).every(function(n){return e.hasOwnP...
function a (line 1) | function a(e,t){for(var n=(0,o.default)(t),r=0;r<e.length;r+=1){if("func...
function n (line 1) | function n(e,t){return e(t={exports:{}},t.exports),t.exports}
function r (line 1) | function r(e){return function(){return e}}
function s (line 1) | function s(e){this.message=e,this.stack=""}
function c (line 1) | function c(e){var n={},r=0;function i(i,a,c,f,d,h,m){if(f=f||o,h=h||c,m!...
function f (line 1) | function f(e){return c(function(t,n,r,o,i,a){var u=t[n];if(m(u)!==e){var...
function h (line 1) | function h(t){switch(typeof t){case"number":case"string":case"undefined"...
function m (line 1) | function m(e){var t=typeof e;return Array.isArray(e)?"array":e instanceo...
function v (line 1) | function v(e){if("undefined"===typeof e||null===e)return""+e;var t=m(e);...
function y (line 1) | function y(e){var t=v(e);switch(t){case"array":case"object":return"an "+...
function n (line 1) | function n(){for(var e=[],r=0;r<arguments.length;r++){var o=arguments[r]...
function S (line 1) | function S(e,t){for(var n=0,r=e.length;n<r;n++)if(t.apply(t,[e[n],n,e]))...
function k (line 1) | function k(e){return"function"===typeof e||"[object Function]"===Object....
function E (line 1) | function E(e){return"number"===typeof e&&!isNaN(e)}
function C (line 1) | function C(e){return parseInt(e,10)}
function P (line 1) | function P(e,t,n){if(e[t])return new Error("Invalid prop "+t+" passed to...
function T (line 1) | function T(e,t){return t?""+t+function(e){for(var t="",n=!0,r=0;r<e.leng...
function e (line 1) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function I (line 1) | function I(e,t){return L||(L=S(["matches","webkitMatchesSelector","mozMa...
function F (line 1) | function F(e,t,n){var r=e;do{if(I(r,t))return!0;if(r===n)return!1;r=r.pa...
function U (line 1) | function U(e,t,n){e&&(e.attachEvent?e.attachEvent("on"+t,n):e.addEventLi...
function W (line 1) | function W(e,t,n){e&&(e.detachEvent?e.detachEvent("on"+t,n):e.removeEven...
function B (line 1) | function B(e){var t=e.clientHeight,n=e.ownerDocument.defaultView.getComp...
function H (line 1) | function H(e){var t=e.clientWidth,n=e.ownerDocument.defaultView.getCompu...
function V (line 1) | function V(e){var t=e.clientHeight,n=e.ownerDocument.defaultView.getComp...
function Y (line 1) | function Y(e){var t=e.clientWidth,n=e.ownerDocument.defaultView.getCompu...
function X (line 1) | function X(e,t,n){var r=e.x,o=e.y,i="translate("+r+n+","+o+n+")";if(t){v...
function q (line 1) | function q(e){if(e){var t,n,r=e.getElementById("react-draggable-style-el...
function G (line 1) | function G(e){try{e&&e.body&&(t=e.body,n="react-draggable-transparent-se...
function K (line 1) | function K(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0...
function Q (line 1) | function Q(e){return"both"===e.props.axis||"x"===e.props.axis}
function J (line 1) | function J(e){return"both"===e.props.axis||"y"===e.props.axis}
function $ (line 1) | function $(e,t,n){var r="number"===typeof t?function(e,t){return e.targe...
function Z (line 1) | function Z(e,t,n){var r=e.state,o=!E(r.lastX),i=te(e);return o?{node:i,d...
function ee (line 1) | function ee(e,t){var n=e.props.scale;return{node:t.node,x:e.state.x+t.de...
function te (line 1) | function te(t){var n=e.findDOMNode(t);if(!n)throw new Error("<DraggableC...
function r (line 1) | function r(){var t,n,o;M(this,r);for(var i=arguments.length,a=Array(i),u...
function r (line 1) | function r(e){M(this,r);var t=A(this,(r.__proto__||Object.getPrototypeOf...
function a (line 1) | function a(e,t){!r.isUndefined(e)&&r.isUndefined(e["Content-Type"])&&(e[...
function l (line 1) | function l(e){return r.isMemo(e)?a:u[e.$$typeof]||o}
function a (line 1) | function a(e,t){for(var n="",r=0;r<t;r++)n+=" ";return n+e}
function a (line 1) | function a(e,t){return o.default.cloneElement(e,{className:(0,i.default)...
function r (line 1) | function r(e){this.message=e}
function o (line 1) | function o(e){var t="".concat(e).match(r);return t&&t[1]||""}
function f (line 1) | function f(e){return e&&e.__esModule?e:{default:e}}
function e (line 1) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function e (line 1) | function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Ca...
function i (line 1) | function i(e){return e&&e.__esModule?e:{default:e}}
function e (line 1) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function u (line 1) | function u(e){return e&&e.__esModule?e:{default:e}}
function e (line 1) | function e(t,n){var o=this;for(var i in function(e,t){if(!(e instanceof ...
function o (line 1) | function o(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[...
function i (line 1) | function i(e){e=e.substr(1);var t=new RegExp(".{1,".concat(e.length/3,"}...
function a (line 1) | function a(e){if("#"===e.charAt(0))return a(i(e));var t=e.indexOf("("),n...
function u (line 1) | function u(e){var t=e.type,n=e.values;return-1!==t.indexOf("rgb")&&(n=n....
function l (line 1) | function l(e){var t=a(e);if(-1!==t.type.indexOf("rgb")){var n=t.values.m...
function s (line 1) | function s(e,t){if(!e)return e;if(e=a(e),t=o(t),-1!==e.type.indexOf("hsl...
function c (line 1) | function c(e,t){if(!e)return e;if(e=a(e),t=o(t),-1!==e.type.indexOf("hsl...
function f (line 1) | function f(e,t){var n=-1;return e.some(function(e,r){return!!t(e)&&(n=r,...
function d (line 1) | function d(e){return parseInt((0,a.default)(e,"paddingRight")||0,10)}
function e (line 1) | function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0...
function o (line 1) | function o(e,t){t?e.setAttribute("aria-hidden","true"):e.removeAttribute...
function u (line 1) | function u(e){return e&&e.__esModule?e:{default:e}}
function r (line 1) | function r(t,n){var r;r=e.call(this,t,n)||this;var o,i=n.transitionGroup...
function p (line 1) | function p(){}
function r (line 1) | function r(){var e=this.constructor.getDerivedStateFromProps(this.props,...
function o (line 1) | function o(e){this.setState(function(t){var n=this.constructor.getDerive...
function i (line 1) | function i(e,t){try{var n=this.props,r=this.state;this.props=e,this.stat...
function a (line 1) | function a(e){var t=e.prototype;if(!t||!t.isReactComponent)throw new Err...
function r (line 1) | function r(e){var t,n=e.Symbol;return"function"===typeof n?n.observable?...
function g (line 1) | function g(e){for(var t=arguments.length-1,n="https://reactjs.org/docs/e...
function x (line 1) | function x(e,t,n){this.props=e,this.context=t,this.refs=w,this.updater=n...
function S (line 1) | function S(){}
function k (line 1) | function k(e,t,n){this.props=e,this.context=t,this.refs=w,this.updater=n...
function _ (line 1) | function _(e,t,n){var r=void 0,o={},a=null,u=null;if(null!=t)for(r in vo...
function M (line 1) | function M(e){return"object"===typeof e&&null!==e&&e.$$typeof===i}
function N (line 1) | function N(e,t,n,r){if(j.length){var o=j.pop();return o.result=e,o.keyPr...
function D (line 1) | function D(e){e.result=null,e.keyPrefix=null,e.func=null,e.context=null,...
function A (line 1) | function A(e,t,n){return null==e?0:function e(t,n,r,o){var u=typeof t;"u...
function z (line 1) | function z(e,t){return"object"===typeof e&&null!==e&&null!=e.key?functio...
function L (line 1) | function L(e,t){e.func.call(e.context,t,e.count++)}
function I (line 1) | function I(e,t,n){var r=e.result,o=e.keyPrefix;e=e.func.call(e.context,t...
function F (line 1) | function F(e,t,n,r,o){var i="";null!=n&&(i=(""+n).replace(R,"$&/")+"/"),...
function U (line 1) | function U(){var e=C.current;return null===e&&g("321"),e}
function a (line 1) | function a(e){for(var t=arguments.length-1,n="https://reactjs.org/docs/e...
function d (line 1) | function d(e,t,n,r,o,i,a,s,c){u=!1,l=null,function(e,t,n,r,o,i,a,u,l){va...
function m (line 1) | function m(){if(p)for(var e in h){var t=h[e],n=p.indexOf(e);if(-1<n||a("...
function v (line 1) | function v(e,t,n){b[e]&&a("100",e),b[e]=t,w[e]=t.eventTypes[n].dependenc...
function E (line 1) | function E(e,t,n){var r=e.type||"unknown-event";e.currentTarget=k(n),fun...
function C (line 1) | function C(e,t){return null==t&&a("30"),null==e?t:Array.isArray(e)?Array...
function P (line 1) | function P(e,t,n){Array.isArray(e)?e.forEach(t,n):e&&t.call(n,e)}
function T (line 1) | function T(e){if(e){var t=e._dispatchListeners,n=e._dispatchInstances;if...
function M (line 1) | function M(e,t){var n=e.stateNode;if(!n)return null;var r=x(n);if(!r)ret...
function R (line 1) | function R(e){if(null!==e&&(O=C(O,e)),e=O,O=null,e&&(P(e,T),O&&a("95"),s...
function A (line 1) | function A(e){if(e[N])return e[N];for(;!e[N];){if(!e.parentNode)return n...
function z (line 1) | function z(e){return!(e=e[N])||5!==e.tag&&6!==e.tag?null:e}
function L (line 1) | function L(e){if(5===e.tag||6===e.tag)return e.stateNode;a("33")}
function I (line 1) | function I(e){return e[D]||null}
function F (line 1) | function F(e){do{e=e.return}while(e&&5!==e.tag);return e||null}
function U (line 1) | function U(e,t,n){(t=M(e,n.dispatchConfig.phasedRegistrationNames[t]))&&...
function W (line 1) | function W(e){if(e&&e.dispatchConfig.phasedRegistrationNames){for(var t=...
function B (line 1) | function B(e,t,n){e&&n&&n.dispatchConfig.registrationName&&(t=M(e,n.disp...
function H (line 1) | function H(e){e&&e.dispatchConfig.registrationName&&B(e._targetInst,null...
function V (line 1) | function V(e){P(e,W)}
function X (line 1) | function X(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n["We...
function Q (line 1) | function Q(e){if(G[e])return G[e];if(!q[e])return e;var t,n=q[e];for(t i...
function ie (line 1) | function ie(){if(oe)return oe;var e,t,n=re,r=n.length,o="value"in ne?ne....
function ae (line 1) | function ae(){return!0}
function ue (line 1) | function ue(){return!1}
function le (line 1) | function le(e,t,n,r){for(var o in this.dispatchConfig=e,this._targetInst...
function se (line 1) | function se(e,t,n,r){if(this.eventPool.length){var o=this.eventPool.pop(...
function ce (line 1) | function ce(e){e instanceof this||a("279"),e.destructor(),10>this.eventP...
function fe (line 1) | function fe(e){e.eventPool=[],e.getPooled=se,e.release=ce}
function t (line 1) | function t(){}
function n (line 1) | function n(){return r.apply(this,arguments)}
function Se (line 1) | function Se(e,t){switch(e){case"keyup":return-1!==he.indexOf(t.keyCode);...
function ke (line 1) | function ke(e){return"object"===typeof(e=e.detail)&&"data"in e?e.data:null}
function _e (line 1) | function _e(e){if(e=S(e)){"function"!==typeof Pe&&a("280");var t=x(e.sta...
function Me (line 1) | function Me(e){Oe?Te?Te.push(e):Te=[e]:Oe=e}
function Re (line 1) | function Re(){if(Oe){var e=Oe,t=Te;if(Te=Oe=null,_e(e),t)for(e=0;e<t.len...
function je (line 1) | function je(e,t){return e(t)}
function Ne (line 1) | function Ne(e,t,n){return e(t,n)}
function De (line 1) | function De(){}
function ze (line 1) | function ze(e,t){if(Ae)return e(t);Ae=!0;try{return je(e,t)}finally{Ae=!...
function Ie (line 1) | function Ie(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return"inpu...
function Fe (line 1) | function Fe(e){return(e=e.target||e.srcElement||window).correspondingUse...
function Ue (line 1) | function Ue(e){if(!Y)return!1;var t=(e="on"+e)in document;return t||((t=...
function We (line 1) | function We(e){var t=e.type;return(e=e.nodeName)&&"input"===e.toLowerCas...
function Be (line 1) | function Be(e){e._valueTracker||(e._valueTracker=function(e){var t=We(e)...
function He (line 1) | function He(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n...
function at (line 1) | function at(e){return null===e||"object"!==typeof e?null:"function"===ty...
function ut (line 1) | function ut(e){if(null==e)return null;if("function"===typeof e)return e....
function lt (line 1) | function lt(e){var t="";do{e:switch(e.tag){case 3:case 4:case 6:case 7:c...
function pt (line 1) | function pt(e,t,n,r,o){this.acceptsBooleans=2===t||3===t||4===t,this.att...
function vt (line 1) | function vt(e){return e[1].toUpperCase()}
function yt (line 1) | function yt(e,t,n,r){var o=ht.hasOwnProperty(t)?ht[t]:null;(null!==o?0==...
function gt (line 1) | function gt(e){switch(typeof e){case"boolean":case"number":case"object":...
function bt (line 1) | function bt(e,t){var n=t.checked;return o({},t,{defaultChecked:void 0,de...
function wt (line 1) | function wt(e,t){var n=null==t.defaultValue?"":t.defaultValue,r=null!=t....
function xt (line 1) | function xt(e,t){null!=(t=t.checked)&&yt(e,"checked",t,!1)}
function St (line 1) | function St(e,t){xt(e,t);var n=gt(t.value),r=t.type;if(null!=n)"number"=...
function kt (line 1) | function kt(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defau...
function Et (line 1) | function Et(e,t,n){"number"===t&&e.ownerDocument.activeElement===e||(nul...
function Pt (line 1) | function Pt(e,t,n){return(e=le.getPooled(Ct.change,e,t,n)).type="change"...
function _t (line 1) | function _t(e){R(e)}
function Mt (line 1) | function Mt(e){if(He(L(e)))return e}
function Rt (line 1) | function Rt(e,t){if("change"===e)return t}
function Nt (line 1) | function Nt(){Ot&&(Ot.detachEvent("onpropertychange",Dt),Tt=Ot=null)}
function Dt (line 1) | function Dt(e){"value"===e.propertyName&&Mt(Tt)&&ze(_t,e=Pt(Tt,e,Fe(e)))}
function At (line 1) | function At(e,t,n){"focus"===e?(Nt(),Tt=n,(Ot=t).attachEvent("onproperty...
function zt (line 1) | function zt(e){if("selectionchange"===e||"keyup"===e||"keydown"===e)retu...
function Lt (line 1) | function Lt(e,t){if("click"===e)return Mt(t)}
function It (line 1) | function It(e,t){if("input"===e||"change"===e)return Mt(t)}
function Bt (line 1) | function Bt(e){var t=this.nativeEvent;return t.getModifierState?t.getMod...
function Ht (line 1) | function Ht(){return Bt}
function $t (line 1) | function $t(e,t){return e===t&&(0!==e||1/e===1/t)||e!==e&&t!==t}
function en (line 1) | function en(e,t){if($t(e,t))return!0;if("object"!==typeof e||null===e||"...
function tn (line 1) | function tn(e){var t=e;if(e.alternate)for(;t.return;)t=t.return;else{if(...
function nn (line 1) | function nn(e){2!==tn(e)&&a("188")}
function rn (line 1) | function rn(e){if(!(e=function(e){var t=e.alternate;if(!t)return 3===(t=...
function ln (line 1) | function ln(e){var t=e.keyCode;return"charCode"in e?0===(e=e.charCode)&&...
function bn (line 1) | function bn(e,t){var n=e[0],r="on"+((e=e[1])[0].toUpperCase()+e.slice(1)...
function kn (line 1) | function kn(e){var t=e.targetInst,n=t;do{if(!n){e.ancestors.push(n);brea...
function Cn (line 1) | function Cn(e,t){if(!t)return null;var n=(xn(e)?On:Tn).bind(null,e);t.ad...
function Pn (line 1) | function Pn(e,t){if(!t)return null;var n=(xn(e)?On:Tn).bind(null,e);t.ad...
function On (line 1) | function On(e,t){Ne(Tn,e,t)}
function Tn (line 1) | function Tn(e,t){if(En){var n=Fe(t);if(null===(n=A(n))||"number"!==typeo...
function jn (line 1) | function jn(e){return Object.prototype.hasOwnProperty.call(e,Rn)||(e[Rn]...
function Nn (line 1) | function Nn(e){if("undefined"===typeof(e=e||("undefined"!==typeof docume...
function Dn (line 1) | function Dn(e){for(;e&&e.firstChild;)e=e.firstChild;return e}
function An (line 1) | function An(e,t){var n,r=Dn(e);for(e=0;r;){if(3===r.nodeType){if(n=e+r.t...
function zn (line 1) | function zn(){for(var e=window,t=Nn();t instanceof e.HTMLIFrameElement;)...
function Ln (line 1) | function Ln(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(...
function In (line 1) | function In(e){var t=zn(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n...
function Yn (line 1) | function Yn(e,t){var n=t.window===t?t.document:9===t.nodeType?t:t.ownerD...
function qn (line 1) | function qn(e,t){return e=o({children:void 0},t),(t=function(e){var t=""...
function Gn (line 1) | function Gn(e,t,n,r){if(e=e.options,t){t={};for(var o=0;o<n.length;o++)t...
function Kn (line 1) | function Kn(e,t){return null!=t.dangerouslySetInnerHTML&&a("91"),o({},t,...
function Qn (line 1) | function Qn(e,t){var n=t.value;null==n&&(n=t.defaultValue,null!=(t=t.chi...
function Jn (line 1) | function Jn(e,t){var n=gt(t.value),r=gt(t.defaultValue);null!=n&&((n=""+...
function $n (line 1) | function $n(e){var t=e.textContent;t===e._wrapperState.initialValue&&(e....
function er (line 1) | function er(e){switch(e){case"svg":return"http://www.w3.org/2000/svg";ca...
function tr (line 1) | function tr(e,t){return null==e||"http://www.w3.org/1999/xhtml"===e?er(t...
function ir (line 1) | function ir(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&3===n.n...
function lr (line 1) | function lr(e,t,n){return null==t||"boolean"===typeof t||""===t?"":n||"n...
function sr (line 1) | function sr(e,t){for(var n in e=e.style,t)if(t.hasOwnProperty(n)){var r=...
function fr (line 1) | function fr(e,t){t&&(cr[e]&&(null!=t.children||null!=t.dangerouslySetInn...
function dr (line 1) | function dr(e,t){if(-1===e.indexOf("-"))return"string"===typeof t.is;swi...
function pr (line 1) | function pr(e,t){var n=jn(e=9===e.nodeType||11===e.nodeType?e:e.ownerDoc...
function hr (line 1) | function hr(){}
function yr (line 1) | function yr(e,t){switch(e){case"button":case"input":case"select":case"te...
function gr (line 1) | function gr(e,t){return"textarea"===e||"option"===e||"noscript"===e||"st...
function kr (line 1) | function kr(e){for(e=e.nextSibling;e&&1!==e.nodeType&&3!==e.nodeType;)e=...
function Er (line 1) | function Er(e){for(e=e.firstChild;e&&1!==e.nodeType&&3!==e.nodeType;)e=e...
function Or (line 1) | function Or(e){0>Pr||(e.current=Cr[Pr],Cr[Pr]=null,Pr--)}
function Tr (line 1) | function Tr(e,t){Cr[++Pr]=e.current,e.current=t}
function Nr (line 1) | function Nr(e,t){var n=e.type.contextTypes;if(!n)return _r;var r=e.state...
function Dr (line 1) | function Dr(e){return null!==(e=e.childContextTypes)&&void 0!==e}
function Ar (line 1) | function Ar(e){Or(Rr),Or(Mr)}
function zr (line 1) | function zr(e){Or(Rr),Or(Mr)}
function Lr (line 1) | function Lr(e,t,n){Mr.current!==_r&&a("168"),Tr(Mr,t),Tr(Rr,n)}
function Ir (line 1) | function Ir(e,t,n){var r=e.stateNode;if(e=t.childContextTypes,"function"...
function Fr (line 1) | function Fr(e){var t=e.stateNode;return t=t&&t.__reactInternalMemoizedMe...
function Ur (line 1) | function Ur(e,t,n){var r=e.stateNode;r||a("169"),n?(t=Ir(e,t,jr),r.__rea...
function Hr (line 1) | function Hr(e){return function(t){try{return e(t)}catch(n){}}}
function Vr (line 1) | function Vr(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this....
function Yr (line 1) | function Yr(e,t,n,r){return new Vr(e,t,n,r)}
function Xr (line 1) | function Xr(e){return!(!(e=e.prototype)||!e.isReactComponent)}
function qr (line 1) | function qr(e,t){var n=e.alternate;return null===n?((n=Yr(e.tag,t,e.key,...
function Gr (line 1) | function Gr(e,t,n,r,o,i){var u=2;if(r=e,"function"===typeof e)Xr(e)&&(u=...
function Kr (line 1) | function Kr(e,t,n,r){return(e=Yr(7,e,r,t)).expirationTime=n,e}
function Qr (line 1) | function Qr(e,t,n,r){return e=Yr(8,e,r,t),t=0===(1&t)?Qe:et,e.elementTyp...
function Jr (line 1) | function Jr(e,t,n){return(e=Yr(6,e,null,t)).expirationTime=n,e}
function $r (line 1) | function $r(e,t,n){return(t=Yr(4,null!==e.children?e.children:[],e.key,t...
function Zr (line 1) | function Zr(e,t){e.didError=!1;var n=e.earliestPendingTime;0===n?e.earli...
function eo (line 1) | function eo(e,t){e.didError=!1,e.latestPingedTime>=t&&(e.latestPingedTim...
function to (line 1) | function to(e,t){var n=e.earliestPendingTime;return n>t&&(t=n),(e=e.earl...
function no (line 1) | function no(e,t){var n=t.earliestSuspendedTime,r=t.latestSuspendedTime,o...
function ro (line 1) | function ro(e,t){if(e&&e.defaultProps)for(var n in t=o({},t),e=e.default...
function io (line 1) | function io(e,t,n,r){n=null===(n=n(r,t=e.memoizedState))||void 0===n?t:o...
function uo (line 1) | function uo(e,t,n,r,o,i,a){return"function"===typeof(e=e.stateNode).shou...
function lo (line 1) | function lo(e,t,n){var r=!1,o=_r,i=t.contextType;return"object"===typeof...
function so (line 1) | function so(e,t,n,r){e=t.state,"function"===typeof t.componentWillReceiv...
function co (line 1) | function co(e,t,n,r){var o=e.stateNode;o.props=n,o.state=e.memoizedState...
function po (line 1) | function po(e,t,n){if(null!==(e=n.ref)&&"function"!==typeof e&&"object"!...
function ho (line 1) | function ho(e,t){"textarea"!==e.type&&a("31","[object Object]"===Object....
function mo (line 1) | function mo(e){function t(t,n){if(e){var r=t.lastEffect;null!==r?(r.next...
function So (line 1) | function So(e){return e===go&&a("174"),e}
function ko (line 1) | function ko(e,t){Tr(xo,t),Tr(wo,e),Tr(bo,go);var n=t.nodeType;switch(n){...
function Eo (line 1) | function Eo(e){Or(bo),Or(wo),Or(xo)}
function Co (line 1) | function Co(e){So(xo.current);var t=So(bo.current),n=tr(t,e.type);t!==n&...
function Po (line 1) | function Po(e){wo.current===e&&(Or(bo),Or(wo))}
function Ko (line 1) | function Ko(){a("321")}
function Qo (line 1) | function Qo(e,t){if(null===t)return!1;for(var n=0;n<t.length&&n<e.length...
function Jo (line 1) | function Jo(e,t,n,r,o,i){if(zo=i,Lo=t,Fo=null!==e?e.memoizedState:null,A...
function $o (line 1) | function $o(){Ao.current=si,zo=0,Bo=Wo=Uo=Fo=Io=Lo=null,Ho=0,Vo=null,Yo=...
function Zo (line 1) | function Zo(){var e={memoizedState:null,baseState:null,queue:null,baseUp...
function ei (line 1) | function ei(){if(null!==Bo)Bo=(Wo=Bo).next,Fo=null!==(Io=Fo)?Io.next:nul...
function ti (line 1) | function ti(e,t){return"function"===typeof t?t(e):t}
function ni (line 1) | function ni(e){var t=ei(),n=t.queue;if(null===n&&a("311"),n.lastRendered...
function ri (line 1) | function ri(e,t,n,r){return e={tag:e,create:t,destroy:n,deps:r,next:null...
function oi (line 1) | function oi(e,t,n,r){var o=Zo();Yo|=e,o.memoizedState=ri(t,n,void 0,void...
function ii (line 1) | function ii(e,t,n,r){var o=ei();r=void 0===r?null:r;var i=void 0;if(null...
function ai (line 1) | function ai(e,t){return"function"===typeof t?(e=e(),t(e),function(){t(nu...
function ui (line 1) | function ui(){}
function li (line 1) | function li(e,t,n){25>Go||a("301");var r=e.alternate;if(e===Lo||null!==r...
function mi (line 1) | function mi(e,t){var n=Yr(5,null,null,0);n.elementType="DELETED",n.type=...
function vi (line 1) | function vi(e,t){switch(e.tag){case 5:var n=e.type;return null!==(t=1!==...
function yi (line 1) | function yi(e){if(hi){var t=pi;if(t){var n=t;if(!vi(e,t)){if(!(t=kr(n))|...
function gi (line 1) | function gi(e){for(e=e.return;null!==e&&5!==e.tag&&3!==e.tag&&18!==e.tag...
function bi (line 1) | function bi(e){if(e!==di)return!1;if(!hi)return gi(e),hi=!0,!1;var t=e.t...
function wi (line 1) | function wi(){pi=di=null,hi=!1}
function ki (line 1) | function ki(e,t,n,r){t.child=null===e?yo(t,null,n,r):vo(t,e.child,n,r)}
function Ei (line 1) | function Ei(e,t,n,r,o){n=n.render;var i=t.ref;return Wi(t,o),r=Jo(e,t,n,...
function Ci (line 1) | function Ci(e,t,n,r,o,i){if(null===e){var a=n.type;return"function"!==ty...
function Pi (line 1) | function Pi(e,t,n,r,o,i){return null!==e&&en(e.memoizedProps,r)&&e.ref==...
function Oi (line 1) | function Oi(e,t){var n=t.ref;(null===e&&null!==n||null!==e&&e.ref!==n)&&...
function Ti (line 1) | function Ti(e,t,n,r,o){var i=Dr(n)?jr:Mr.current;return i=Nr(t,i),Wi(t,o...
function _i (line 1) | function _i(e,t,n,r,o){if(Dr(n)){var i=!0;Fr(t)}else i=!1;if(Wi(t,o),nul...
function Mi (line 1) | function Mi(e,t,n,r,o,i){Oi(e,t);var a=0!==(64&t.effectTag);if(!r&&!a)re...
function Ri (line 1) | function Ri(e){var t=e.stateNode;t.pendingContext?Lr(0,t.pendingContext,...
function ji (line 1) | function ji(e,t,n){var r=t.mode,o=t.pendingProps,i=t.memoizedState;if(0=...
function Ni (line 1) | function Ni(e,t,n){if(null!==e&&(t.contextDependencies=e.contextDependen...
function Di (line 1) | function Di(e,t,n){var r=t.expirationTime;if(null!==e){if(e.memoizedProp...
function Fi (line 1) | function Fi(e,t){var n=e.type._context;Tr(Ai,n._currentValue),n._current...
function Ui (line 1) | function Ui(e){var t=Ai.current;Or(Ai),e.type._context._currentValue=t}
function Wi (line 1) | function Wi(e,t){zi=e,Ii=Li=null;var n=e.contextDependencies;null!==n&&n...
function Bi (line 1) | function Bi(e,t){return Ii!==e&&!1!==t&&0!==t&&("number"===typeof t&&107...
function Gi (line 1) | function Gi(e){return{baseState:e,firstUpdate:null,lastUpdate:null,first...
function Ki (line 1) | function Ki(e){return{baseState:e.baseState,firstUpdate:e.firstUpdate,la...
function Qi (line 1) | function Qi(e){return{expirationTime:e,tag:Hi,payload:null,callback:null...
function Ji (line 1) | function Ji(e,t){null===e.lastUpdate?e.firstUpdate=e.lastUpdate=t:(e.las...
function $i (line 1) | function $i(e,t){var n=e.alternate;if(null===n){var r=e.updateQueue,o=nu...
function Zi (line 1) | function Zi(e,t){var n=e.updateQueue;null===(n=null===n?e.updateQueue=Gi...
function ea (line 1) | function ea(e,t){var n=e.alternate;return null!==n&&t===n.updateQueue&&(...
function ta (line 1) | function ta(e,t,n,r,i,a){switch(n.tag){case Vi:return"function"===typeof...
function na (line 1) | function na(e,t,n,r,o){qi=!1;for(var i=(t=ea(e,t)).baseState,a=null,u=0,...
function ra (line 1) | function ra(e,t,n){null!==t.firstCapturedUpdate&&(null!==t.lastUpdate&&(...
function oa (line 1) | function oa(e,t){for(;null!==e;){var n=e.callback;if(null!==n){e.callbac...
function ia (line 1) | function ia(e,t){return{value:e,source:t,stack:lt(t)}}
function aa (line 1) | function aa(e){e.effectTag|=4}
function da (line 1) | function da(e,t){var n=t.source,r=t.stack;null===r&&null!==n&&(r=lt(n)),...
function pa (line 1) | function pa(e){var t=e.ref;if(null!==t)if("function"===typeof t)try{t(nu...
function ha (line 1) | function ha(e,t,n){if(null!==(n=null!==(n=n.updateQueue)?n.lastEffect:nu...
function ma (line 1) | function ma(e){switch("function"===typeof Br&&Br(e),e.tag){case 0:case 1...
function va (line 1) | function va(e){return 5===e.tag||3===e.tag||4===e.tag}
function ya (line 1) | function ya(e){e:{for(var t=e.return;null!==t;){if(va(t)){var n=t;break ...
function ga (line 1) | function ga(e){for(var t=e,n=!1,r=void 0,o=void 0;;){if(!n){n=t.return;e...
function ba (line 1) | function ba(e,t){switch(t.tag){case 0:case 11:case 14:case 15:ha(_o,Mo,t...
function xa (line 1) | function xa(e,t,n){(n=Qi(n)).tag=Xi,n.payload={element:null};var r=t.val...
function Sa (line 1) | function Sa(e,t,n){(n=Qi(n)).tag=Xi;var r=e.type.getDerivedStateFromErro...
function ka (line 1) | function ka(e){switch(e.tag){case 1:Dr(e.type)&&Ar();var t=e.effectTag;r...
function Fa (line 1) | function Fa(){if(null!==Ta)for(var e=Ta.return;null!==e;){var t=e;switch...
function Ua (line 1) | function Ua(){for(;null!==Na;){var e=Na.effectTag;if(16&e&&ir(Na.stateNo...
function Wa (line 1) | function Wa(){for(;null!==Na;){if(256&Na.effectTag)e:{var e=Na.alternate...
function Ba (line 1) | function Ba(e,t){for(;null!==Na;){var n=Na.effectTag;if(36&n){var r=Na.a...
function Ha (line 1) | function Ha(){null!==za&&Sr(za),null!==La&&La()}
function Va (line 1) | function Va(e,t){Da=Oa=!0,e.current===t&&a("177");var n=e.pendingCommitE...
function Ya (line 1) | function Ya(e){for(;;){var t=e.alternate,n=e.return,r=e.sibling;if(0===(...
function Xa (line 1) | function Xa(e){var t=Di(e.alternate,e,Ma);return e.memoizedProps=e.pendi...
function qa (line 1) | function qa(e,t){Oa&&a("243"),Ha(),Oa=!0;var n=Ea.current;Ea.current=si;...
function Ga (line 1) | function Ga(e,t){for(var n=e.return;null!==n;){switch(n.tag){case 1:var ...
function Ka (line 1) | function Ka(e,t){var n=i.unstable_getCurrentPriorityLevel(),r=void 0;if(...
function Qa (line 1) | function Qa(e,t,n){var r=e.pingCache;null!==r&&r.delete(t),null!==_a&&Ma...
function Ja (line 1) | function Ja(e,t){e.expirationTime<t&&(e.expirationTime=t);var n=e.altern...
function $a (line 1) | function $a(e,t){null!==(e=Ja(e,t))&&(!Oa&&0!==Ma&&t>Ma&&Fa(),Zr(e,t),Oa...
function Za (line 1) | function Za(e,t,n,r,o){return i.unstable_runWithPriority(i.unstable_Imme...
function bu (line 1) | function bu(){hu=1073741822-((i.unstable_now()-pu)/10|0)}
function wu (line 1) | function wu(e,t){if(0!==nu){if(t<nu)return;null!==ru&&i.unstable_cancelC...
function xu (line 1) | function xu(e,t,n,r,o){e.expirationTime=r,0!==o||Pu()?0<o&&(e.timeoutHan...
function Su (line 1) | function Su(){return ou?mu:(Eu(),0!==au&&1!==au||(bu(),mu=hu),mu)}
function ku (line 1) | function ku(e,t){null===e.nextScheduledRoot?(e.expirationTime=t,null===t...
function Eu (line 1) | function Eu(){var e=0,t=null;if(null!==tu)for(var n=tu,r=eu;null!==r;){v...
function Pu (line 1) | function Pu(){return!!Cu||!!i.unstable_shouldYield()&&(Cu=!0)}
function Ou (line 1) | function Ou(){try{if(!Pu()&&null!==eu){bu();var e=eu;do{var t=e.expirati...
function Tu (line 1) | function Tu(e,t){if(Eu(),t)for(bu(),mu=hu;null!==iu&&0!==au&&e<=au&&!(Cu...
function _u (line 1) | function _u(e,t){ou&&a("253"),iu=e,au=t,Mu(e,t,!1),Tu(1073741823,!1)}
function Mu (line 1) | function Mu(e,t,n){if(ou&&a("245"),ou=!0,n){var r=e.finishedWork;null!==...
function Ru (line 1) | function Ru(e,t,n){var r=e.firstBatch;if(null!==r&&r._expirationTime>=n&...
function ju (line 1) | function ju(e){null===iu&&a("246"),iu.expirationTime=0,lu||(lu=!0,su=e)}
function Nu (line 1) | function Nu(e,t){var n=cu;cu=!0;try{return e(t)}finally{(cu=n)||ou||Tu(1...
function Du (line 1) | function Du(e,t){if(cu&&!fu){fu=!0;try{return e(t)}finally{fu=!1}}return...
function Au (line 1) | function Au(e,t,n){cu||ou||0===uu||(Tu(uu,!1),uu=0);var r=cu;cu=!0;try{r...
function zu (line 1) | function zu(e,t,n,r,o){var i=t.current;e:if(n){t:{2===tn(n=n._reactInter...
function Lu (line 1) | function Lu(e,t,n,r){var o=t.current;return zu(e,t,n,o=Ka(Su(),o),r)}
function Iu (line 1) | function Iu(e){if(!(e=e.current).child)return null;switch(e.child.tag){c...
function Fu (line 1) | function Fu(e){var t=1073741822-25*(1+((1073741822-Su()+500)/25|0));t>=P...
function Uu (line 1) | function Uu(){this._callbacks=null,this._didCommit=!1,this._onCommit=thi...
function Wu (line 1) | function Wu(e,t,n){e={current:t=Yr(3,null,null,t?3:0),containerInfo:e,pe...
function Bu (line 1) | function Bu(e){return!(!e||1!==e.nodeType&&9!==e.nodeType&&11!==e.nodeTy...
function Hu (line 1) | function Hu(e,t,n,r,o){var i=n._reactRootContainer;if(i){if("function"==...
function Vu (line 1) | function Vu(e,t){var n=2<arguments.length&&void 0!==arguments[2]?argumen...
function s (line 1) | function s(){if(!u){var e=n.expirationTime;l?k():l=!0,S(d,e)}}
function c (line 1) | function c(){var e=n,t=n.next;if(n===t)n=null;else{var r=n.previous;n=r....
function f (line 1) | function f(){if(-1===i&&null!==n&&1===n.priorityLevel){u=!0;try{do{c()}w...
function d (line 1) | function d(e){u=!0;var o=r;r=e;try{if(e)for(;null!==n;){var i=t.unstable...
function w (line 1) | function w(e){p=g(function(t){y(h),e(t)}),h=v(function(){b(p),e(t.unstab...
function u (line 1) | function u(e){var t=new i(e),n=o(i.prototype.request,t);return r.extend(...
function n (line 1) | function n(e){return!!e.constructor&&"function"===typeof e.constructor.i...
function u (line 1) | function u(e){this.defaults=e,this.interceptors={request:new i,response:...
function i (line 1) | function i(){throw new Error("setTimeout has not been defined")}
function a (line 1) | function a(){throw new Error("clearTimeout has not been defined")}
function u (line 1) | function u(e){if(n===setTimeout)return setTimeout(e,0);if((n===i||!n)&&s...
function d (line 1) | function d(){c&&l&&(c=!1,l.length?s=l.concat(s):f=-1,s.length&&p())}
function p (line 1) | function p(){if(!c){var e=u(d);c=!0;for(var t=s.length;t;){for(l=s,s=[];...
function h (line 1) | function h(e,t){this.fun=e,this.array=t}
function m (line 1) | function m(){}
function o (line 1) | function o(e){return encodeURIComponent(e).replace(/%40/gi,"@").replace(...
function o (line 1) | function o(e){var r=e;return t&&(n.setAttribute("href",r),r=n.href),n.se...
function o (line 1) | function o(){this.message="String contains an invalid character"}
function o (line 1) | function o(){this.handlers=[]}
function s (line 1) | function s(e){e.cancelToken&&e.cancelToken.throwIfRequested()}
function o (line 1) | function o(e){if("function"!==typeof e)throw new TypeError("executor mus...
function n (line 1) | function n(e,t){return e(t={exports:{}},t.exports),t.exports}
function r (line 1) | function r(e){return function(){return e}}
function s (line 1) | function s(e){this.message=e,this.stack=""}
function c (line 1) | function c(e){var n={},r=0;function i(i,a,c,f,d,h,m){if(f=f||o,h=h||c,m!...
function f (line 1) | function f(e){return c(function(t,n,r,o,i,a){var u=t[n];if(m(u)!==e){var...
function h (line 1) | function h(t){switch(typeof t){case"number":case"string":case"undefined"...
function m (line 1) | function m(e){var t=typeof e;return Array.isArray(e)?"array":e instanceo...
function v (line 1) | function v(e){if("undefined"===typeof e||null===e)return""+e;var t=m(e);...
function y (line 1) | function y(e){var t=v(e);switch(t){case"array":case"object":return"an "+...
function n (line 1) | function n(){for(var e=[],r=0;r<arguments.length;r++){var o=arguments[r]...
function S (line 1) | function S(e,t){for(var n=0,r=e.length;n<r;n++)if(t.apply(t,[e[n],n,e]))...
function k (line 1) | function k(e){return"function"===typeof e||"[object Function]"===Object....
function E (line 1) | function E(e){return"number"===typeof e&&!isNaN(e)}
function C (line 1) | function C(e){return parseInt(e,10)}
function P (line 1) | function P(e,t,n){if(e[t])return new Error("Invalid prop "+t+" passed to...
function T (line 1) | function T(e,t){return t?""+t+function(e){for(var t="",n=!0,r=0;r<e.leng...
function e (line 1) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function I (line 1) | function I(e,t){return L||(L=S(["matches","webkitMatchesSelector","mozMa...
function F (line 1) | function F(e,t,n){var r=e;do{if(I(r,t))return!0;if(r===n)return!1;r=r.pa...
function U (line 1) | function U(e,t,n){e&&(e.attachEvent?e.attachEvent("on"+t,n):e.addEventLi...
function W (line 1) | function W(e,t,n){e&&(e.detachEvent?e.detachEvent("on"+t,n):e.removeEven...
function B (line 1) | function B(e){var t=e.clientHeight,n=e.ownerDocument.defaultView.getComp...
function H (line 1) | function H(e){var t=e.clientWidth,n=e.ownerDocument.defaultView.getCompu...
function V (line 1) | function V(e){var t=e.clientHeight,n=e.ownerDocument.defaultView.getComp...
function Y (line 1) | function Y(e){var t=e.clientWidth,n=e.ownerDocument.defaultView.getCompu...
function X (line 1) | function X(e){if(e){var t,n,r=e.getElementById("react-draggable-style-el...
function q (line 1) | function q(e){try{e&&e.body&&(t=e.body,n="react-draggable-transparent-se...
function G (line 1) | function G(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0...
function K (line 1) | function K(e){return"both"===e.props.axis||"x"===e.props.axis}
function Q (line 1) | function Q(e){return"both"===e.props.axis||"y"===e.props.axis}
function J (line 1) | function J(e,t,n){var r="number"===typeof t?function(e,t){return e.targe...
function $ (line 1) | function $(e,t,n){var r=e.state,o=!E(r.lastX),i=ee(e);return o?{node:i,d...
function Z (line 1) | function Z(e,t){var n=e.props.scale;return{node:t.node,x:e.state.x+t.del...
function ee (line 1) | function ee(t){var n=e.findDOMNode(t);if(!n)throw new Error("<DraggableC...
function r (line 1) | function r(){var t,n,o;M(this,r);for(var i=arguments.length,a=Array(i),u...
function r (line 1) | function r(e){M(this,r);var t=A(this,(r.__proto__||Object.getPrototypeOf...
function t (line 1) | function t(){var e,n;(0,a.default)(this,t);for(var r=arguments.length,o=...
function n (line 1) | function n(t,r){return e.exports=n=Object.setPrototypeOf||function(e,t){...
function o (line 1) | function o(){}
function i (line 1) | function i(){}
function e (line 1) | function e(e,t,n,o,i,a){if(a!==r){var u=new Error("Calling PropTypes val...
function t (line 1) | function t(){return e}
function y (line 1) | function y(e){if("object"===typeof e&&null!==e){var t=e.$$typeof;switch(...
function g (line 1) | function g(e){return y(e)===d}
function e (line 1) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function e (line 1) | function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Ca...
function e (line 1) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function g (line 1) | function g(e){return e&&e.__esModule?e:{default:e}}
function e (line 1) | function e(t){!function(e,t){if(!(e instanceof t))throw new TypeError("C...
function e (line 1) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function e (line 1) | function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Ca...
function l (line 1) | function l(e){return e&&e.__esModule?e:{default:e}}
function e (line 1) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function e (line 1) | function e(t,n,r){!function(e,t){if(!(e instanceof t))throw new TypeErro...
function e (line 1) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function e (line 1) | function e(t,n,r){for(var i in function(e,t){if(!(e instanceof t))throw ...
function e (line 1) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function e (line 1) | function e(t,n,r){for(var i in function(e,t){if(!(e instanceof t))throw ...
function e (line 1) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function e (line 1) | function e(t,n,r){!function(e,t){if(!(e instanceof t))throw new TypeErro...
function e (line 1) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function e (line 1) | function e(t,n,r){!function(e,t){if(!(e instanceof t))throw new TypeErro...
function a (line 1) | function a(e){return e&&e.__esModule?e:{default:e}}
function a (line 1) | function a(e){return e&&e.__esModule?e:{default:e}}
function e (line 1) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function l (line 1) | function l(e){return e&&e.__esModule?e:{default:e}}
function c (line 1) | function c(e,t){try{return e.style.getPropertyValue(t)}catch(n){return""}}
function f (line 1) | function f(e,t,n){try{var r=n;if(Array.isArray(n)&&(r=(0,u.default)(n,!0...
function d (line 1) | function d(e,t){try{e.style.removeProperty(t)}catch(n){(0,o.default)(!1,...
function v (line 1) | function v(e,t){return e.selectorText=t,e.selectorText===t}
function b (line 1) | function b(e){var t=i.default.registry;if(t.length>0){var n=function(e,t...
function e (line 1) | function e(t){!function(e,t){if(!(e instanceof t))throw new TypeError("C...
function e (line 1) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function e (line 1) | function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Ca...
function e (line 1) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function a (line 1) | function a(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a ...
function e (line 1) | function e(t,n,o){for(var u in a(this,e),this.type="global",this.key=t,t...
function e (line 1) | function e(t,n,o){a(this,e),this.name=t,this.options=o;var i=t.substr(l....
function d (line 1) | function d(e,t){for(var n=e.split(f),r="",o=0;o<n.length;o++)r+=t+" "+n[...
function e (line 1) | function e(e){return function(t,n){var r=e.getRule(n);return r?r.selecto...
function n (line 1) | function n(e,n){for(var r=n.split(u),o=e.split(u),i="",a=0;a<r.length;a+...
function o (line 1) | function o(e,t,n){if(n)return r({},n,{index:n.index+1});var o=e.options....
function a (line 1) | function a(e){var t={};for(var n in e)t[(0,i.default)(n)]=e[n];return e....
function a (line 1) | function a(e){return"-"+e.toLowerCase()}
function a (line 1) | function a(e){var t=/(-[a-z])/g,n=function(e){return e[1].toUpperCase()}...
function l (line 1) | function l(e,t,n){if(!t)return t;var o=t,i="undefined"===typeof t?"undef...
function a (line 1) | function a(e){return e&&e.__esModule?e:{default:e}}
function a (line 1) | function a(e){return e&&e.__esModule?e:{default:e}}
function o (line 1) | function o(e,t){return t?t.toUpperCase():""}
function i (line 1) | function i(e){return e&&e.__esModule?e:{default:e}}
function e (line 1) | function e(e,t){return e.length-t.length}
function o (line 1) | function o(e){return!0===r(e)&&"[object Object]"===Object.prototype.toSt...
function f (line 1) | function f(e){var t="number"===typeof n[e]?n[e]:e;return"@media (min-wid...
function d (line 1) | function d(e,t){var r=a.indexOf(t)+1;return r===a.length?f(e):"@media (m...
function P (line 1) | function P(e){var t=(0,d.getContrastRatio)(e,h.text.primary)>=S?h.text.p...
function O (line 1) | function O(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[...
function m (line 1) | function m(e,t,n,r){e[t]||(e.hasOwnProperty(n)?e[t]=e[n]:"light"===t?e.l...
function l (line 1) | function l(e){return Math.round(1e5*e)/1e5}
function a (line 1) | function a(){return["".concat(arguments.length<=0?void 0:arguments[0],"p...
function i (line 1) | function i(e){return String(e).replace(o,"-")}
function a (line 1) | function a(e,t){return t}
function E (line 1) | function E(e){return!!e.children&&e.children.props.hasOwnProperty("in")}
function t (line 1) | function t(e){var n;return(0,u.default)(this,t),(n=(0,s.default)(this,(0...
function t (line 1) | function t(){return(0,o.default)(this,t),(0,a.default)(this,(0,u.default...
function t (line 1) | function t(){var e,n;(0,o.default)(this,t);for(var r=arguments.length,i=...
function u (line 1) | function u(e){return e&&"body"===e.tagName.toLowerCase()}
function d (line 1) | function d(e){var t=e.classes,n=e.className,r=e.invisible,s=e.open,f=e.t...
function t (line 1) | function t(){var e,n;(0,a.default)(this,t);for(var r=arguments.length,o=...
function n (line 1) | function n(e,t){var r;return(0,u.default)(this,n),(r=(0,s.default)(this,...
function f (line 1) | function f(e){var t=e.classes,n=e.className,r=e.component,s=e.square,c=e...
function d (line 1) | function d(e){var t=e.disableUnderline,n=e.classes,r=(0,a.default)(e,["d...
function t (line 1) | function t(e){var n;return(0,u.default)(this,t),(n=(0,l.default)(this,(0...
function t (line 1) | function t(e){var n;return(0,a.default)(this,t),(n=(0,l.default)(this,(0...
function n (line 1) | function n(e,t,n){var r,o,i,a,u;function l(){var s=Date.now()-a;s<t&&s>=...
function r (line 1) | function r(e){return e&&"object"===typeof e&&"default"in e?e.default:e}
function m (line 1) | function m(e){return f({},h,e)}
function v (line 1) | function v(e,t,n){var r=[e,t];return r.push(p?n:n.capture),r}
function y (line 1) | function y(e,t,n,r){e.addEventListener.apply(e,v(t,n,r))}
function g (line 1) | function g(e,t,n,r){e.removeEventListener.apply(e,v(t,n,r))}
function t (line 1) | function t(){return o(this,t),a(this,u(t).apply(this,arguments))}
function r (line 1) | function r(e){return null!=e&&!(Array.isArray(e)&&0===e.length)}
function h (line 1) | function h(e){var t,n=e.children,r=e.classes,s=e.className,c=e.color,p=e...
function t (line 1) | function t(){var e,n;(0,u.default)(this,t);for(var r=arguments.length,o=...
function t (line 1) | function t(){var e,n;(0,o.default)(this,t);for(var r=arguments.length,i=...
function t (line 1) | function t(){var e,n;(0,u.default)(this,t);for(var r=arguments.length,o=...
function u (line 1) | function u(e){return e&&e.__esModule?e:{default:e}}
function l (line 1) | function l(){return(l=Object.assign||function(e){for(var t=1;t<arguments...
function s (line 1) | function s(e){if(void 0===e)throw new ReferenceError("this hasn't been i...
function r (line 1) | function r(t,n){var r,o=(r=e.call(this,t,n)||this).handleExited.bind(s(s...
function o (line 1) | function o(e,t){var n=Object.create(null);return e&&r.Children.map(e,fun...
function i (line 1) | function i(e,t){function n(n){return n in t?t[n]:e[n]}e=e||{},t=t||{};va...
function a (line 1) | function a(e,t,n){return null!=n[t]?n[t]:e.props[t]}
function t (line 1) | function t(){var e,n;(0,u.default)(this,t);for(var r=arguments.length,o=...
function r (line 1) | function r(){return t.apply(this,arguments)||this}
function o (line 1) | function o(e,t){return e===t?0!==e||0!==t||1/e===1/t:e!==e&&t!==t}
function d (line 1) | function d(e){var t,n=e.children,r=e.classes,s=e.className,f=e.color,d=e...
function v (line 1) | function v(e){if("rtl"!==("undefined"!==typeof window&&document.body.get...
function t (line 1) | function t(e){var n;return(0,a.default)(this,t),(n=(0,l.default)(this,(0...
function u (line 1) | function u(e){return e&&"[object Function]"==={}.toString.call(e)}
function l (line 1) | function l(e,t){if(1!==e.nodeType)return[];var n=e.ownerDocument.default...
function s (line 1) | function s(e){return"HTML"===e.nodeName?e:e.parentNode||e.host}
function c (line 1) | function c(e){if(!e)return document.body;switch(e.nodeName){case"HTML":c...
function p (line 1) | function p(e){return 11===e?f:10===e?d:f||d}
function h (line 1) | function h(e){if(!e)return document.documentElement;for(var t=p(10)?docu...
function m (line 1) | function m(e){return null!==e.parentNode?m(e.parentNode):e}
function v (line 1) | function v(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.docu...
function y (line 1) | function y(e){var t="top"===(arguments.length>1&&void 0!==arguments[1]?a...
function g (line 1) | function g(e,t){var n="x"===t?"Left":"Top",r="Left"===n?"Right":"Bottom"...
function b (line 1) | function b(e,t,n,r){return Math.max(t["offset"+e],t["scroll"+e],n["clien...
function w (line 1) | function w(e){var t=e.body,n=e.documentElement,r=p(10)&&getComputedStyle...
function e (line 1) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function C (line 1) | function C(e){return E({},e,{right:e.left+e.width,bottom:e.top+e.height})}
function P (line 1) | function P(e){var t={};try{if(p(10)){t=e.getBoundingClientRect();var n=y...
function O (line 1) | function O(e,t){var n=arguments.length>2&&void 0!==arguments[2]&&argumen...
function T (line 1) | function T(e){if(!e||!e.parentElement||p())return document.documentEleme...
function _ (line 1) | function _(e,t,n,r){var o=arguments.length>4&&void 0!==arguments[4]&&arg...
function M (line 1) | function M(e,t,n,r,o){var i=arguments.length>5&&void 0!==arguments[5]?ar...
function R (line 1) | function R(e,t,n){var r=arguments.length>3&&void 0!==arguments[3]?argume...
function j (line 1) | function j(e){var t=e.ownerDocument.defaultView.getComputedStyle(e),n=pa...
function N (line 1) | function N(e){var t={left:"right",right:"left",bottom:"top",top:"bottom"...
function D (line 1) | function D(e,t,n){n=n.split("-")[0];var r=j(e),o={width:r.width,height:r...
function A (line 1) | function A(e,t){return Array.prototype.find?e.find(t):e.filter(t)[0]}
function z (line 1) | function z(e,t,n){return(void 0===n?e:e.slice(0,function(e,t,n){if(Array...
function L (line 1) | function L(e,t){return e.some(function(e){var n=e.name;return e.enabled&...
function I (line 1) | function I(e){for(var t=[!1,"ms","Webkit","Moz","O"],n=e.charAt(0).toUpp...
function F (line 1) | function F(e){var t=e.ownerDocument;return t?t.defaultView:window}
function U (line 1) | function U(e,t,n,r){n.updateBound=r,F(e).addEventListener("resize",n.upd...
function W (line 1) | function W(){var e,t;this.state.eventsEnabled&&(cancelAnimationFrame(thi...
function B (line 1) | function B(e){return""!==e&&!isNaN(parseFloat(e))&&isFinite(e)}
function H (line 1) | function H(e,t){Object.keys(t).forEach(function(n){var r="";-1!==["width...
function Y (line 1) | function Y(e,t,n){var r=A(e,function(e){return e.name===t}),o=!!r&&e.som...
function G (line 1) | function G(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments...
function Q (line 1) | function Q(e,t,n,r){var o=[0,0],i=-1!==["right","left"].indexOf(r),a=e.s...
function e (line 1) | function e(t,n){var r=this,o=arguments.length>2&&void 0!==arguments[2]?a...
function u (line 1) | function u(e,t){return function(e){if(Array.isArray(e))return e}(e)||fun...
function c (line 1) | function c(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enume...
function f (line 1) | function f(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[...
function m (line 1) | function m(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a ...
function v (line 1) | function v(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function y (line 1) | function y(e,t,n){return t&&v(e.prototype,t),n&&v(e,n),e}
function g (line 1) | function g(e){return(g="function"===typeof Symbol&&"symbol"===typeof Sym...
function b (line 1) | function b(e){return(b="function"===typeof Symbol&&"symbol"===g(Symbol.i...
function w (line 1) | function w(e,t){return!t||"object"!==b(t)&&"function"!==typeof t?functio...
function x (line 1) | function x(e){return(x=Object.setPrototypeOf?Object.getPrototypeOf:funct...
function S (line 1) | function S(e,t){return(S=Object.setPrototypeOf||function(e,t){return e._...
function k (line 1) | function k(e,t){if("function"!==typeof t&&null!==t)throw new TypeError("...
function t (line 1) | function t(e){var n;return m(this,t),(n=w(this,x(t).call(this,e))).state...
function j (line 1) | function j(e,t){var n=null;t.forEach(function(e,t){n=e,setTimeout(functi...
function N (line 1) | function N(e){console.debug("getHistoryMessage "+e);var t="".concat(h.ch...
function t (line 1) | function t(e){var n;return m(this,t),(n=w(this,x(t).call(this,e))).ROW_N...
function e (line 1) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function t (line 1) | function t(e){F(this,t);var n=B(this,(t.__proto__||Object.getPrototypeOf...
function t (line 1) | function t(t){var n=e.call(this,t)||this;return n.isResizing=!1,n.state=...
function n (line 1) | function n(){this.constructor=e}
function me (line 1) | function me(e){return o.a.createElement(he.a,Object.assign({className:"s...
function ve (line 1) | function ve(){var e=u(Object(r.useState)(!1),2),t=e[0],n=e[1],i=u(Object...
function Le (line 1) | function Le(e){return function(e){if(Array.isArray(e)){for(var t=0,n=new...
function Ze (line 1) | function Ze(e,t,n){var r=[];r=n?[].concat(Le(t),[e]):t.filter(function(t...
function it (line 1) | function it(e){var t=0;return Object.values(e).forEach(function(e){e.mes...
FILE: extension/build/popup_menu/js/main.js
function t (line 1) | function t(n){if(c[n])return c[n].exports;var l=c[n]={i:n,l:!1,exports:{...
function l (line 1) | function l(){for(var e=[],c=0;c<arguments.length;c++){var n=arguments[c]...
function s (line 1) | function s(c){e&&Object({NODE_ENV:"production",PUBLIC_URL:""})||console....
function v (line 1) | function v(e){return"object"===typeof e&&"string"===typeof e.name&&"stri...
function f (line 1) | function f(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0...
function e (line 1) | function e(){a()(this,e),this.collection={}}
function d (line 1) | function d(e,c,t){return t?h.createElement(e.tag,l()({key:c},f(e.attrs),...
function z (line 1) | function z(e){return Object(u.generate)(e)[0]}
function p (line 1) | function p(e,c){switch(c){case"fill":return e+"-fill";case"outline":retu...
function o (line 1) | function o(e,c,t){e.addEventListener(c,t,!1)}
function i (line 1) | function i(e,c,t){e.removeEventListener(c,t,!1)}
function c (line 1) | function c(c,t){for(var n in c)if(c.hasOwnProperty(n)){var l=c[n];for(va...
function n (line 1) | function n(){var e=this.constructor.getDerivedStateFromProps(this.props,...
function l (line 1) | function l(e){this.setState(function(c){var t=this.constructor.getDerive...
function r (line 1) | function r(e,c){try{var t=this.props,n=this.state;this.props=e,this.stat...
function a (line 1) | function a(e){var c=e.prototype;if(!c||!c.isReactComponent)throw new Err...
function e (line 1) | function e(e,c){for(var t=0;t<c.length;t++){var n=c[t];n.enumerable=n.en...
function i (line 1) | function i(e){for(var c=[],t=1;t<arguments.length;t++)c[t-1]=arguments[t...
function u (line 1) | function u(e,c,t){return{name:e,theme:c,icon:t}}
function a (line 1) | function a(e){return e&&e.__esModule?e:{default:e}}
function r (line 1) | function r(){throw new Error("setTimeout has not been defined")}
function a (line 1) | function a(){throw new Error("clearTimeout has not been defined")}
function o (line 1) | function o(e){if(t===setTimeout)return setTimeout(e,0);if((t===r||!t)&&s...
function v (line 1) | function v(){h&&i&&(h=!1,i.length?u=i.concat(u):s=-1,u.length&&f())}
function f (line 1) | function f(){if(!h){var e=o(v);h=!0;for(var c=u.length;c;){for(i=u,u=[];...
function m (line 1) | function m(e,c){this.fun=e,this.array=c}
function d (line 1) | function d(){}
function a (line 1) | function a(e){return e&&e.__esModule?e:{default:e}}
function r (line 1) | function r(e){return e&&e.__esModule?e:{default:e}}
function M (line 1) | function M(e){for(var c=arguments.length-1,t="https://reactjs.org/docs/e...
function g (line 1) | function g(e,c,t){this.props=e,this.context=c,this.refs=H,this.updater=t...
function V (line 1) | function V(){}
function b (line 1) | function b(e,c,t){this.props=e,this.context=c,this.refs=H,this.updater=t...
function k (line 1) | function k(e,c,t){var n=void 0,l={},a=null,o=null;if(null!=c)for(n in vo...
function x (line 1) | function x(e){return"object"===typeof e&&null!==e&&e.$$typeof===r}
function E (line 1) | function E(e,c,t,n){if(_.length){var l=_.pop();return l.result=e,l.keyPr...
function A (line 1) | function A(e){e.result=null,e.keyPrefix=null,e.func=null,e.context=null,...
function P (line 1) | function P(e,c,t){return null==e?0:function e(c,t,n,l){var o=typeof c;"u...
function F (line 1) | function F(e,c){return"object"===typeof e&&null!==e&&null!=e.key?functio...
function N (line 1) | function N(e,c){e.func.call(e.context,c,e.count++)}
function I (line 1) | function I(e,c,t){var n=e.result,l=e.keyPrefix;e=e.func.call(e.context,c...
function j (line 1) | function j(e,c,t,n,l){var r="";null!=t&&(r=(""+t).replace(O,"$&/")+"/"),...
function D (line 1) | function D(){var e=L.current;return null===e&&M("307"),e}
function a (line 1) | function a(e){for(var c=arguments.length-1,t="https://reactjs.org/docs/e...
function v (line 1) | function v(e,c,t,n,l,r,a,u,h){o=!1,i=null,function(e,c,t,n,l,r,a,o,i){va...
function d (line 1) | function d(){if(f)for(var e in m){var c=m[e],t=f.indexOf(e);if(-1<t||a("...
function z (line 1) | function z(e,c,t){y[e]&&a("100",e),y[e]=c,H[e]=c.eventTypes[t].dependenc...
function C (line 1) | function C(e,c,t){var n=e.type||"unknown-event";e.currentTarget=b(t),fun...
function L (line 1) | function L(e,c){return null==c&&a("30"),null==e?c:Array.isArray(e)?Array...
function w (line 1) | function w(e,c,t){Array.isArray(e)?e.forEach(c,t):e&&c.call(t,e)}
function S (line 1) | function S(e){if(e){var c=e._dispatchListeners,t=e._dispatchInstances;if...
function x (line 1) | function x(e,c){var t=e.stateNode;if(!t)return null;var n=g(t);if(!n)ret...
function O (line 1) | function O(e){if(null!==e&&(T=L(T,e)),e=T,T=null,e&&(w(e,S),T&&a("95"),u...
function P (line 1) | function P(e){if(e[E])return e[E];for(;!e[E];){if(!e.parentNode)return n...
function F (line 1) | function F(e){return!(e=e[E])||5!==e.tag&&6!==e.tag?null:e}
function N (line 1) | function N(e){if(5===e.tag||6===e.tag)return e.stateNode;a("33")}
function I (line 1) | function I(e){return e[A]||null}
function j (line 1) | function j(e){do{e=e.return}while(e&&5!==e.tag);return e||null}
function D (line 1) | function D(e,c,t){(c=x(e,t.dispatchConfig.phasedRegistrationNames[c]))&&...
function R (line 1) | function R(e){if(e&&e.dispatchConfig.phasedRegistrationNames){for(var c=...
function U (line 1) | function U(e,c,t){e&&t&&t.dispatchConfig.registrationName&&(c=x(e,t.disp...
function q (line 1) | function q(e){e&&e.dispatchConfig.registrationName&&U(e._targetInst,null...
function B (line 1) | function B(e){w(e,R)}
function Q (line 1) | function Q(e,c){var t={};return t[e.toLowerCase()]=c.toLowerCase(),t["We...
function K (line 1) | function K(e){if(G[e])return G[e];if(!Y[e])return e;var c,t=Y[e];for(c i...
function re (line 1) | function re(){if(le)return le;var e,c,t=ne,n=t.length,l="value"in te?te....
function ae (line 1) | function ae(){return!0}
function oe (line 1) | function oe(){return!1}
function ie (line 1) | function ie(e,c,t,n){for(var l in this.dispatchConfig=e,this._targetInst...
function ue (line 1) | function ue(e,c,t,n){if(this.eventPool.length){var l=this.eventPool.pop(...
function he (line 1) | function he(e){e instanceof this||a("279"),e.destructor(),10>this.eventP...
function se (line 1) | function se(e){e.eventPool=[],e.getPooled=ue,e.release=he}
function c (line 1) | function c(){}
function t (line 1) | function t(){return n.apply(this,arguments)}
function Ve (line 1) | function Ve(e,c){switch(e){case"keyup":return-1!==me.indexOf(c.keyCode);...
function be (line 1) | function be(e){return"object"===typeof(e=e.detail)&&"data"in e?e.data:null}
function ke (line 1) | function ke(e){if(e=V(e)){"function"!==typeof we&&a("280");var c=g(e.sta...
function xe (line 1) | function xe(e){Te?Se?Se.push(e):Se=[e]:Te=e}
function Oe (line 1) | function Oe(){if(Te){var e=Te,c=Se;if(Se=Te=null,ke(e),c)for(e=0;e<c.len...
function _e (line 1) | function _e(e,c){return e(c)}
function Ee (line 1) | function Ee(e,c,t){return e(c,t)}
function Ae (line 1) | function Ae(){}
function Fe (line 1) | function Fe(e,c){if(Pe)return e(c);Pe=!0;try{return _e(e,c)}finally{Pe=!...
function Ie (line 1) | function Ie(e){var c=e&&e.nodeName&&e.nodeName.toLowerCase();return"inpu...
function je (line 1) | function je(e){return(e=e.target||e.srcElement||window).correspondingUse...
function De (line 1) | function De(e){if(!W)return!1;var c=(e="on"+e)in document;return c||((c=...
function Re (line 1) | function Re(e){var c=e.type;return(e=e.nodeName)&&"input"===e.toLowerCas...
function Ue (line 1) | function Ue(e){e._valueTracker||(e._valueTracker=function(e){var c=Re(e)...
function qe (line 1) | function qe(e){if(!e)return!1;var c=e._valueTracker;if(!c)return!0;var t...
function ac (line 1) | function ac(e){return null===e||"object"!==typeof e?null:"function"===ty...
function oc (line 1) | function oc(e){if(null==e)return null;if("function"===typeof e)return e....
function ic (line 1) | function ic(e){var c="";do{e:switch(e.tag){case 3:case 4:case 6:case 7:c...
function fc (line 1) | function fc(e,c,t,n,l){this.acceptsBooleans=2===c||3===c||4===c,this.att...
function zc (line 1) | function zc(e){return e[1].toUpperCase()}
function pc (line 1) | function pc(e,c,t,n){var l=mc.hasOwnProperty(c)?mc[c]:null;(null!==l?0==...
function Mc (line 1) | function Mc(e){switch(typeof e){case"boolean":case"number":case"object":...
function yc (line 1) | function yc(e,c){var t=c.checked;return l({},c,{defaultChecked:void 0,de...
function Hc (line 1) | function Hc(e,c){var t=null==c.defaultValue?"":c.defaultValue,n=null!=c....
function gc (line 1) | function gc(e,c){null!=(c=c.checked)&&pc(e,"checked",c,!1)}
function Vc (line 1) | function Vc(e,c){gc(e,c);var t=Mc(c.value),n=c.type;if(null!=t)"number"=...
function bc (line 1) | function bc(e,c,t){if(c.hasOwnProperty("value")||c.hasOwnProperty("defau...
function Cc (line 1) | function Cc(e,c,t){"number"===c&&e.ownerDocument.activeElement===e||(nul...
function wc (line 1) | function wc(e,c,t){return(e=ie.getPooled(Lc.change,e,c,t)).type="change"...
function kc (line 1) | function kc(e){O(e)}
function xc (line 1) | function xc(e){if(qe(N(e)))return e}
function Oc (line 1) | function Oc(e,c){if("change"===e)return c}
function Ec (line 1) | function Ec(){Tc&&(Tc.detachEvent("onpropertychange",Ac),Sc=Tc=null)}
function Ac (line 1) | function Ac(e){"value"===e.propertyName&&xc(Sc)&&Fe(kc,e=wc(Sc,e,je(e)))}
function Pc (line 1) | function Pc(e,c,t){"focus"===e?(Ec(),Sc=t,(Tc=c).attachEvent("onproperty...
function Fc (line 1) | function Fc(e){if("selectionchange"===e||"keyup"===e||"keydown"===e)retu...
function Nc (line 1) | function Nc(e,c){if("click"===e)return xc(c)}
function Ic (line 1) | function Ic(e,c){if("input"===e||"change"===e)return xc(c)}
function Uc (line 1) | function Uc(e){var c=this.nativeEvent;return c.getModifierState?c.getMod...
function qc (line 1) | function qc(){return Uc}
function Jc (line 1) | function Jc(e,c){return e===c&&(0!==e||1/e===1/c)||e!==e&&c!==c}
function et (line 1) | function et(e,c){if(Jc(e,c))return!0;if("object"!==typeof e||null===e||"...
function ct (line 1) | function ct(e){var c=e;if(e.alternate)for(;c.return;)c=c.return;else{if(...
function tt (line 1) | function tt(e){2!==ct(e)&&a("188")}
function nt (line 1) | function nt(e){if(!(e=function(e){var c=e.alternate;if(!c)return 3===(c=...
function ot (line 1) | function ot(e){var c=e.keyCode;return"charCode"in e?0===(e=e.charCode)&&...
function Mt (line 1) | function Mt(e,c){var t=e[0],n="on"+((e=e[1])[0].toUpperCase()+e.slice(1)...
function Vt (line 1) | function Vt(e){var c=e.targetInst,t=c;do{if(!t){e.ancestors.push(t);brea...
function Ct (line 1) | function Ct(e,c){if(!c)return null;var t=(Ht(e)?wt:Tt).bind(null,e);c.ad...
function Lt (line 1) | function Lt(e,c){if(!c)return null;var t=(Ht(e)?wt:Tt).bind(null,e);c.ad...
function wt (line 1) | function wt(e,c){Ee(Tt,e,c)}
function Tt (line 1) | function Tt(e,c){if(bt){var t=je(c);if(null===(t=P(t))||"number"!==typeo...
function Ot (line 1) | function Ot(e){return Object.prototype.hasOwnProperty.call(e,xt)||(e[xt]...
function _t (line 1) | function _t(e){if("undefined"===typeof(e=e||("undefined"!==typeof docume...
function Et (line 1) | function Et(e){for(;e&&e.firstChild;)e=e.firstChild;return e}
function At (line 1) | function At(e,c){var t,n=Et(e);for(e=0;n;){if(3===n.nodeType){if(t=e+n.t...
function Pt (line 1) | function Pt(){for(var e=window,c=_t();c instanceof e.HTMLIFrameElement;)...
function Ft (line 1) | function Ft(e){var c=e&&e.nodeName&&e.nodeName.toLowerCase();return c&&(...
function qt (line 1) | function qt(e,c){var t=c.window===c?c.document:9===c.nodeType?c:c.ownerD...
function Wt (line 1) | function Wt(e,c){return e=l({children:void 0},c),(c=function(e){var c=""...
function Qt (line 1) | function Qt(e,c,t,n){if(e=e.options,c){c={};for(var l=0;l<t.length;l++)c...
function Yt (line 1) | function Yt(e,c){return null!=c.dangerouslySetInnerHTML&&a("91"),l({},c,...
function Gt (line 1) | function Gt(e,c){var t=c.value;null==t&&(t=c.defaultValue,null!=(c=c.chi...
function $t (line 1) | function $t(e,c){var t=Mc(c.value),n=Mc(c.defaultValue);null!=t&&((t=""+...
function Kt (line 1) | function Kt(e){var c=e.textContent;c===e._wrapperState.initialValue&&(e....
function Jt (line 1) | function Jt(e){switch(e){case"svg":return"http://www.w3.org/2000/svg";ca...
function Xt (line 1) | function Xt(e,c){return null==e||"http://www.w3.org/1999/xhtml"===e?Jt(c...
function nn (line 1) | function nn(e,c){if(c){var t=e.firstChild;if(t&&t===e.lastChild&&3===t.n...
function an (line 1) | function an(e,c,t){return null==c||"boolean"===typeof c||""===c?"":t||"n...
function on (line 1) | function on(e,c){for(var t in e=e.style,c)if(c.hasOwnProperty(t)){var n=...
function hn (line 1) | function hn(e,c){c&&(un[e]&&(null!=c.children||null!=c.dangerouslySetInn...
function sn (line 1) | function sn(e,c){if(-1===e.indexOf("-"))return"string"===typeof c.is;swi...
function vn (line 1) | function vn(e,c){var t=Ot(e=9===e.nodeType||11===e.nodeType?e:e.ownerDoc...
function fn (line 1) | function fn(){}
function zn (line 1) | function zn(e,c){switch(e){case"button":case"input":case"select":case"te...
function pn (line 1) | function pn(e,c){return"textarea"===e||"option"===e||"noscript"===e||"st...
function Vn (line 1) | function Vn(e){for(e=e.nextSibling;e&&1!==e.nodeType&&3!==e.nodeType;)e=...
function bn (line 1) | function bn(e){for(e=e.firstChild;e&&1!==e.nodeType&&3!==e.nodeType;)e=e...
function wn (line 1) | function wn(e){0>Ln||(e.current=Cn[Ln],Cn[Ln]=null,Ln--)}
function Tn (line 1) | function Tn(e,c){Cn[++Ln]=e.current,e.current=c}
function _n (line 1) | function _n(e,c){var t=e.type.contextTypes;if(!t)return Sn;var n=e.state...
function En (line 1) | function En(e){return null!==(e=e.childContextTypes)&&void 0!==e}
function An (line 1) | function An(e){wn(xn),wn(kn)}
function Pn (line 1) | function Pn(e){wn(xn),wn(kn)}
function Fn (line 1) | function Fn(e,c,t){kn.current!==Sn&&a("168"),Tn(kn,c),Tn(xn,t)}
function Nn (line 1) | function Nn(e,c,t){var n=e.stateNode;if(e=c.childContextTypes,"function"...
function In (line 1) | function In(e){var c=e.stateNode;return c=c&&c.__reactInternalMemoizedMe...
function jn (line 1) | function jn(e,c,t){var n=e.stateNode;n||a("169"),t?(c=Nn(e,c,On),n.__rea...
function Un (line 1) | function Un(e){return function(c){try{return e(c)}catch(t){}}}
function qn (line 1) | function qn(e,c,t,n){this.tag=e,this.key=t,this.sibling=this.child=this....
function Bn (line 1) | function Bn(e,c,t,n){return new qn(e,c,t,n)}
function Wn (line 1) | function Wn(e){return!(!(e=e.prototype)||!e.isReactComponent)}
function Qn (line 1) | function Qn(e,c){var t=e.alternate;return null===t?((t=Bn(e.tag,c,e.key,...
function Yn (line 1) | function Yn(e,c,t,n,l,r){var o=2;if(n=e,"function"===typeof e)Wn(e)&&(o=...
function Gn (line 1) | function Gn(e,c,t,n){return(e=Bn(7,e,n,c)).expirationTime=t,e}
function $n (line 1) | function $n(e,c,t,n){return e=Bn(8,e,n,c),c=0===(1&c)?Ke:ec,e.elementTyp...
function Kn (line 1) | function Kn(e,c,t){return(e=Bn(6,e,null,c)).expirationTime=t,e}
function Zn (line 1) | function Zn(e,c,t){return(c=Bn(4,null!==e.children?e.children:[],e.key,c...
function Jn (line 1) | function Jn(e,c){e.didError=!1;var t=e.earliestPendingTime;0===t?e.earli...
function Xn (line 1) | function Xn(e,c){e.didError=!1,e.latestPingedTime>=c&&(e.latestPingedTim...
function el (line 1) | function el(e,c){var t=e.earliestPendingTime;return t>c&&(c=t),(e=e.earl...
function cl (line 1) | function cl(e,c){var t=c.earliestSuspendedTime,n=c.latestSuspendedTime,l...
function tl (line 1) | function tl(e,c){if(e&&e.defaultProps)for(var t in c=l({},c),e=e.default...
function ll (line 1) | function ll(e,c,t,n){t=null===(t=t(n,c=e.memoizedState))||void 0===t?c:l...
function al (line 1) | function al(e,c,t,n,l,r,a){return"function"===typeof(e=e.stateNode).shou...
function ol (line 1) | function ol(e,c,t){var n=!1,l=Sn,r=c.contextType;return"object"===typeof...
function il (line 1) | function il(e,c,t,n){e=c.state,"function"===typeof c.componentWillReceiv...
function ul (line 1) | function ul(e,c,t,n){var l=e.stateNode;l.props=t,l.state=e.memoizedState...
function sl (line 1) | function sl(e,c,t){if(null!==(e=t.ref)&&"function"!==typeof e&&"object"!...
function vl (line 1) | function vl(e,c){"textarea"!==e.type&&a("31","[object Object]"===Object....
function fl (line 1) | function fl(e){function c(c,t){if(e){var n=c.lastEffect;null!==n?(n.next...
function Hl (line 1) | function Hl(e){return e===zl&&a("174"),e}
function gl (line 1) | function gl(e,c){Tn(yl,c),Tn(Ml,e),Tn(pl,zl);var t=c.nodeType;switch(t){...
function Vl (line 1) | function Vl(e){wn(pl),wn(Ml),wn(yl)}
function bl (line 1) | function bl(e){Hl(yl.current);var c=Hl(pl.current),t=Xt(c,e.type);c!==t&...
function Cl (line 1) | function Cl(e){Ml.current===e&&(wn(pl),wn(Ml))}
function Yl (line 1) | function Yl(){a("307")}
function Gl (line 1) | function Gl(e,c){if(null===c)return!1;for(var t=0;t<c.length&&t<e.length...
function $l (line 1) | function $l(e,c,t,n,l,r){if(Al=r,Pl=c,Nl=null!==e?e.memoizedState:null,E...
function Kl (line 1) | function Kl(){El.current=or,Al=0,Dl=jl=Il=Nl=Fl=Pl=null,Rl=0,Ul=null,ql=...
function Zl (line 1) | function Zl(){var e={memoizedState:null,baseState:null,queue:null,baseUp...
function Jl (line 1) | function Jl(){if(null!==Dl)Dl=(jl=Dl).next,Nl=null!==(Fl=Nl)?Fl.next:nul...
function Xl (line 1) | function Xl(e,c){return"function"===typeof c?c(e):c}
function er (line 1) | function er(e){var c=Jl(),t=c.queue;if(null===t&&a("311"),0<Ql){var n=t....
function cr (line 1) | function cr(e,c,t,n){return e={tag:e,create:c,destroy:t,deps:n,next:null...
function tr (line 1) | function tr(e,c,t,n){var l=Zl();ql|=e,l.memoizedState=cr(c,t,void 0,void...
function nr (line 1) | function nr(e,c,t,n){var l=Jl();n=void 0===n?null:n;var r=void 0;if(null...
function lr (line 1) | function lr(e,c){return"function"===typeof c?(e=e(),c(e),function(){c(nu...
function rr (line 1) | function rr(){}
function ar (line 1) | function ar(e,c,t){25>Ql||a("301");var n=e.alternate;if(e===Pl||null!==n...
function fr (line 1) | function fr(e,c){var t=Bn(5,null,null,0);t.elementType="DELETED",t.type=...
function mr (line 1) | function mr(e,c){switch(e.tag){case 5:var t=e.type;return null!==(c=1!==...
function dr (line 1) | function dr(e){if(vr){var c=sr;if(c){var t=c;if(!mr(e,c)){if(!(c=Vn(t))|...
function zr (line 1) | function zr(e){for(e=e.return;null!==e&&5!==e.tag&&3!==e.tag;)e=e.return...
function pr (line 1) | function pr(e){if(e!==hr)return!1;if(!vr)return zr(e),vr=!0,!1;var c=e.t...
function Mr (line 1) | function Mr(){sr=hr=null,vr=!1}
function gr (line 1) | function gr(e,c,t,n){c.child=null===e?dl(c,null,t,n):ml(c,e.child,t,n)}
function Vr (line 1) | function Vr(e,c,t,n,l){t=t.render;var r=c.ref;return jr(c,l),n=$l(e,c,t,...
function br (line 1) | function br(e,c,t,n,l,r){if(null===e){var a=t.type;return"function"!==ty...
function Cr (line 1) | function Cr(e,c,t,n,l,r){return null!==e&&et(e.memoizedProps,n)&&e.ref==...
function Lr (line 1) | function Lr(e,c){var t=c.ref;(null===e&&null!==t||null!==e&&e.ref!==t)&&...
function wr (line 1) | function wr(e,c,t,n,l){var r=En(t)?On:kn.current;return r=_n(c,r),jr(c,l...
function Tr (line 1) | function Tr(e,c,t,n,l){if(En(t)){var r=!0;In(c)}else r=!1;if(jr(c,l),nul...
function Sr (line 1) | function Sr(e,c,t,n,l,r){Lr(e,c);var a=0!==(64&c.effectTag);if(!n&&!a)re...
function kr (line 1) | function kr(e){var c=e.stateNode;c.pendingContext?Fn(0,c.pendingContext,...
function xr (line 1) | function xr(e,c,t){var n=c.mode,l=c.pendingProps,r=c.memoizedState;if(0=...
function Or (line 1) | function Or(e,c,t){if(null!==e&&(c.contextDependencies=e.contextDependen...
function _r (line 1) | function _r(e,c,t){var n=c.expirationTime;if(null!==e){if(e.memoizedProp...
function Nr (line 1) | function Nr(e,c){var t=e.type._context;Tn(Er,t._currentValue),t._current...
function Ir (line 1) | function Ir(e){var c=Er.current;wn(Er),e.type._context._currentValue=c}
function jr (line 1) | function jr(e,c){Ar=e,Fr=Pr=null;var t=e.contextDependencies;null!==t&&t...
function Dr (line 1) | function Dr(e,c){return Fr!==e&&!1!==c&&0!==c&&("number"===typeof c&&107...
function Qr (line 1) | function Qr(e){return{baseState:e,firstUpdate:null,lastUpdate:null,first...
function Yr (line 1) | function Yr(e){return{baseState:e.baseState,firstUpdate:e.firstUpdate,la...
function Gr (line 1) | function Gr(e){return{expirationTime:e,tag:Rr,payload:null,callback:null...
function $r (line 1) | function $r(e,c){null===e.lastUpdate?e.firstUpdate=e.lastUpdate=c:(e.las...
function Kr (line 1) | function Kr(e,c){var t=e.alternate;if(null===t){var n=e.updateQueue,l=nu...
function Zr (line 1) | function Zr(e,c){var t=e.updateQueue;null===(t=null===t?e.updateQueue=Qr...
function Jr (line 1) | function Jr(e,c){var t=e.alternate;return null!==t&&c===t.updateQueue&&(...
function Xr (line 1) | function Xr(e,c,t,n,r,a){switch(t.tag){case Ur:return"function"===typeof...
function ea (line 1) | function ea(e,c,t,n,l){Wr=!1;for(var r=(c=Jr(e,c)).baseState,a=null,o=0,...
function ca (line 1) | function ca(e,c,t){null!==c.firstCapturedUpdate&&(null!==c.lastUpdate&&(...
function ta (line 1) | function ta(e,c){for(;null!==e;){var t=e.callback;if(null!==t){e.callbac...
function na (line 1) | function na(e,c){return{value:e,source:c,stack:ic(c)}}
function la (line 1) | function la(e){e.effectTag|=4}
function ha (line 1) | function ha(e,c){var t=c.source,n=c.stack;null===n&&null!==t&&(n=ic(t)),...
function sa (line 1) | function sa(e){var c=e.ref;if(null!==c)if("function"===typeof c)try{c(nu...
function va (line 1) | function va(e,c,t){if(null!==(t=null!==(t=t.updateQueue)?t.lastEffect:nu...
function fa (line 1) | function fa(e){switch("function"===typeof Rn&&Rn(e),e.tag){case 0:case 1...
function ma (line 1) | function ma(e){return 5===e.tag||3===e.tag||4===e.tag}
function da (line 1) | function da(e){e:{for(var c=e.return;null!==c;){if(ma(c)){var t=c;break ...
function za (line 1) | function za(e){for(var c=e,t=!1,n=void 0,l=void 0;;){if(!t){t=c.return;e...
function pa (line 1) | function pa(e,c){switch(c.tag){case 0:case 11:case 14:case 15:va(Tl,Sl,c...
function ya (line 1) | function ya(e,c,t){(t=Gr(t)).tag=Br,t.payload={element:null};var n=c.val...
function Ha (line 1) | function Ha(e,c,t){(t=Gr(t)).tag=Br;var n=e.type.getDerivedStateFromErro...
function ga (line 1) | function ga(e){switch(e.tag){case 1:En(e.type)&&An();var c=e.effectTag;r...
function Ia (line 1) | function Ia(){if(null!==Ta)for(var e=Ta.return;null!==e;){var c=e;switch...
function ja (line 1) | function ja(){null!==Pa&&gn(Pa),null!==Fa&&Fa()}
function Da (line 1) | function Da(e){for(;;){var c=e.alternate,t=e.return,n=e.sibling;if(0===(...
function Ra (line 1) | function Ra(e){var c=_r(e.alternate,e,ka);return e.memoizedProps=e.pendi...
function Ua (line 1) | function Ua(e,c){wa&&a("243"),ja(),wa=!0;var t=Va.current;Va.current=or;...
function qa (line 1) | function qa(e,c){for(var t=e.return;null!==t;){switch(t.tag){case 1:var ...
function Ba (line 1) | function Ba(e,c){return 0!==La?e=La:wa?e=Ea?1073741823:ka:1&c.mode?(e=oo...
function Wa (line 1) | function Wa(e,c,t){var n=e.pingCache;null!==n&&n.delete(c),null!==Sa&&ka...
function Qa (line 1) | function Qa(e,c){e.expirationTime<c&&(e.expirationTime=c);var t=e.altern...
function Ya (line 1) | function Ya(e,c){null!==(e=Qa(e,c))&&(!wa&&0!==ka&&c>ka&&Ia(),Jn(e,c),wa...
function Ga (line 1) | function Ga(e,c,t,n,l){var r=La;La=1073741823;try{return e(c,t,n,l)}fina...
function zo (line 1) | function zo(){ho=1073741822-((r.unstable_now()-uo)/10|0)}
function po (line 1) | function po(e,c){if(0!==Za){if(c<Za)return;null!==Ja&&r.unstable_cancelC...
function Mo (line 1) | function Mo(e,c,t,n,l){e.expirationTime=n,0!==l||bo()?0<l&&(e.timeoutHan...
function yo (line 1) | function yo(){return Xa?so:(go(),0!==co&&1!==co||(zo(),so=ho),so)}
function Ho (line 1) | function Ho(e,c){null===e.nextScheduledRoot?(e.expirationTime=c,null===K...
function go (line 1) | function go(){var e=0,c=null;if(null!==Ka)for(var t=Ka,n=$a;null!==n;){v...
function bo (line 1) | function bo(){return!!Vo||!!r.unstable_shouldYield()&&(Vo=!0)}
function Co (line 1) | function Co(){try{if(!bo()&&null!==$a){zo();var e=$a;do{var c=e.expirati...
function Lo (line 1) | function Lo(e,c){if(go(),c)for(zo(),so=ho;null!==eo&&0!==co&&e<=co&&!(Vo...
function wo (line 1) | function wo(e,c){Xa&&a("253"),eo=e,co=c,To(e,c,!1),Lo(1073741823,!1)}
function To (line 1) | function To(e,c,t){if(Xa&&a("245"),Xa=!0,t){var n=e.finishedWork;null!==...
function So (line 1) | function So(e,c,t){var n=e.firstBatch;if(null!==n&&n._expirationTime>=t&...
function ko (line 1) | function ko(e){null===eo&&a("246"),eo.expirationTime=0,no||(no=!0,lo=e)}
function xo (line 1) | function xo(e,c){var t=ro;ro=!0;try{return e(c)}finally{(ro=t)||Xa||Lo(1...
function Oo (line 1) | function Oo(e,c){if(ro&&!ao){ao=!0;try{return e(c)}finally{ao=!1}}return...
function _o (line 1) | function _o(e,c,t){if(oo)return e(c,t);ro||Xa||0===to||(Lo(to,!1),to=0);...
function Eo (line 1) | function Eo(e,c,t,n,l){var r=c.current;e:if(t){c:{2===ct(t=t._reactInter...
function Ao (line 1) | function Ao(e,c,t,n){var l=c.current;return Eo(e,c,t,l=Ba(yo(),l),n)}
function Po (line 1) | function Po(e){if(!(e=e.current).child)return null;switch(e.child.tag){c...
function Fo (line 1) | function Fo(e){var c=1073741822-25*(1+((1073741822-yo()+500)/25|0));c>=C...
function No (line 1) | function No(){this._callbacks=null,this._didCommit=!1,this._onCommit=thi...
function Io (line 1) | function Io(e,c,t){e={current:c=Bn(3,null,null,c?3:0),containerInfo:e,pe...
function jo (line 1) | function jo(e){return!(!e||1!==e.nodeType&&9!==e.nodeType&&11!==e.nodeTy...
function Do (line 1) | function Do(e,c,t,n,l){var r=t._reactRootContainer;if(r){if("function"==...
function Ro (line 1) | function Ro(e,c){var t=2<arguments.length&&void 0!==arguments[2]?argumen...
function u (line 1) | function u(){if(!o){var e=t.expirationTime;i?b():i=!0,V(v,e)}}
function h (line 1) | function h(){var e=t,c=t.next;if(t===c)t=null;else{var n=t.previous;t=n....
function s (line 1) | function s(){if(-1===r&&null!==t&&1===t.priorityLevel){o=!0;try{do{h()}w...
function v (line 1) | function v(e){o=!0;var l=n;n=e;try{if(e)for(;null!==t;){var r=c.unstable...
function H (line 1) | function H(e){f=M(function(c){p(m),e(c)}),m=z(function(){y(f),e(c.unstab...
function l (line 1) | function l(){}
function e (line 1) | function e(e,c,t,l,r,a){if(a!==n){var o=new Error("Calling PropTypes val...
function c (line 1) | function c(){return e}
function i (line 1) | function i(){return(i=Object.assign||function(e){for(var c=1;c<arguments...
function u (line 1) | function u(e,c){if(null==e)return{};var t,n,l=function(e,c){if(null==e)r...
function h (line 1) | function h(e,c){for(var t=0;t<c.length;t++){var n=c[t];n.enumerable=n.en...
function s (line 1) | function s(e){return(s=Object.setPrototypeOf?Object.getPrototypeOf:funct...
function v (line 1) | function v(e,c){return(v=Object.setPrototypeOf||function(e,c){return e._...
function f (line 1) | function f(e){if(void 0===e)throw new ReferenceError("this hasn't been i...
function m (line 1) | function m(e,c,t){return c in e?Object.defineProperty(e,c,{value:t,enume...
function c (line 1) | function c(e){var t,n,l;!function(e,c){if(!(e instanceof c))throw new Ty...
function f (line 1) | function f(e,c,t){var n=void 0;return(n=Math.round(e.h)>=60&&Math.round(...
function m (line 1) | function m(e,c,t){if(0===e.h&&0===e.s)return e.s;var n=void 0;return(n=t...
function d (line 1) | function d(e,c,t){return t?Math.round(100*e.v)+u*c:Math.round(100*e.v)-h*c}
function v (line 1) | function v(e,c){if(c=c||{},(e=e||"")instanceof v)return e;if(!(this inst...
function f (line 1) | function f(e,c,t){e=E(e,255),c=E(c,255),t=E(t,255);var n,l,r=h(e,c,t),a=...
function m (line 1) | function m(e,c,t){e=E(e,255),c=E(c,255),t=E(t,255);var n,l,r=h(e,c,t),a=...
function d (line 1) | function d(e,c,t,n){var l=[F(i(e).toString(16)),F(i(c).toString(16)),F(i...
function z (line 1) | function z(e,c,t,n){return[F(I(n)),F(i(e).toString(16)),F(i(c).toString(...
function p (line 1) | function p(e,c){c=0===c?0:c||10;var t=v(e).toHsl();return t.s-=c/100,t.s...
function M (line 1) | function M(e,c){c=0===c?0:c||10;var t=v(e).toHsl();return t.s+=c/100,t.s...
function y (line 1) | function y(e){return v(e).desaturate(100)}
function H (line 1) | function H(e,c){c=0===c?0:c||10;var t=v(e).toHsl();return t.l+=c/100,t.l...
function g (line 1) | function g(e,c){c=0===c?0:c||10;var t=v(e).toRgb();return t.r=h(0,u(255,...
function V (line 1) | function V(e,c){c=0===c?0:c||10;var t=v(e).toHsl();return t.l-=c/100,t.l...
function b (line 1) | function b(e,c){var t=v(e).toHsl(),n=(t.h+c)%360;return t.h=n<0?360+n:n,...
function C (line 1) | function C(e){var c=v(e).toHsl();return c.h=(c.h+180)%360,v(c)}
function L (line 1) | function L(e){var c=v(e).toHsl(),t=c.h;return[v(e),v({h:(t+120)%360,s:c....
function w (line 1) | function w(e){var c=v(e).toHsl(),t=c.h;return[v(e),v({h:(t+90)%360,s:c.s...
function T (line 1) | function T(e){var c=v(e).toHsl(),t=c.h;return[v(e),v({h:(t+72)%360,s:c.s...
function S (line 1) | function S(e,c,t){c=c||6,t=t||30;var n=v(e).toHsl(),l=360/t,r=[v(e)];for...
function k (line 1) | function k(e,c){c=c||6;for(var t=v(e).toHsv(),n=t.h,l=t.s,r=t.v,a=[],o=1...
function _ (line 1) | function _(e){return e=parseFloat(e),(isNaN(e)||e<0||e>1)&&(e=1),e}
function E (line 1) | function E(e,c){(function(e){return"string"==typeof e&&-1!=e.indexOf("."...
function A (line 1) | function A(e){return u(1,h(0,e))}
function P (line 1) | function P(e){return parseInt(e,16)}
function F (line 1) | function F(e){return 1==e.length?"0"+e:""+e}
function N (line 1) | function N(e){return e<=1&&(e=100*e+"%"),e}
function I (line 1) | function I(e){return l.round(255*parseFloat(e)).toString(16)}
function j (line 1) | function j(e){return P(e)/255}
function R (line 1) | function R(e){return!!D.CSS_UNIT.exec(e)}
function a (line 1) | function a(e){return e&&e.__esModule?e:{default:e}}
function o (line 1) | function o(e,c){if(!(e instanceof c))throw new TypeError("Cannot call a ...
function i (line 1) | function i(e,c){if(!e)throw new ReferenceError("this hasn't been initial...
function u (line 1) | function u(e,c){if("function"!==typeof c&&null!==c)throw new TypeError("...
function t (line 1) | function t(){var c,n;o(this,t);for(var l=arguments.length,r=Array(l),a=0...
function t (line 1) | function t(){var e,n;o(this,t);for(var l=arguments.length,r=Array(l),a=0...
function n (line 1) | function n(e){return function(){return e}}
function o (line 1) | function o(e,c,t){return c in e?Object.defineProperty(e,c,{value:t,enume...
function i (line 1) | function i(e,c){for(var t=0;t<c.length;t++){var n=c[t];n.enumerable=n.en...
function u (line 1) | function u(e){return(u="function"===typeof Symbol&&"symbol"===typeof Sym...
function h (line 1) | function h(e){return(h="function"===typeof Symbol&&"symbol"===u(Symbol.i...
function s (line 1) | function s(e,c){return!c||"object"!==h(c)&&"function"!==typeof c?functio...
function v (line 1) | function v(e){return(v=Object.setPrototypeOf?Object.getPrototypeOf:funct...
function f (line 1) | function f(e,c){return(f=Object.setPrototypeOf||function(e,c){return e._...
function T (line 1) | function T(e){var c=arguments.length>1&&void 0!==arguments[1]?arguments[...
function O (line 1) | function O(){return(O=Object.assign||function(e){for(var c=1;c<arguments...
function A (line 1) | function A(e){return(A="function"===typeof Symbol&&"symbol"===typeof Sym...
function P (line 1) | function P(){return(P=Object.assign||function(e){for(var c=1;c<arguments...
function F (line 1) | function F(e,c){for(var t=0;t<c.length;t++){var n=c[t];n.enumerable=n.en...
function N (line 1) | function N(e,c){return!c||"object"!==A(c)&&"function"!==typeof c?functio...
function I (line 1) | function I(e){return(I=Object.setPrototypeOf?Object.getPrototypeOf:funct...
function j (line 1) | function j(e,c){return(j=Object.setPrototypeOf||function(e,c){return e._...
function c (line 1) | function c(){return function(e,c){if(!(e instanceof c))throw new TypeErr...
function R (line 1) | function R(){return(R=Object.assign||function(e){for(var c=1;c<arguments...
function $ (line 1) | function $(e){return($="function"===typeof Symbol&&"symbol"===typeof Sym...
function K (line 1) | function K(e,c){for(var t=0;t<c.length;t++){var n=c[t];n.enumerable=n.en...
function Z (line 1) | function Z(e){return(Z=Object.setPrototypeOf?Object.getPrototypeOf:funct...
function J (line 1) | function J(e,c){return(J=Object.setPrototypeOf||function(e,c){return e._...
function X (line 1) | function X(e){if(void 0===e)throw new ReferenceError("this hasn't been i...
function ee (line 1) | function ee(e){return!e||null===e.offsetParent}
function c (line 1) | function c(){var e,t,n;return function(e,c){if(!(e instanceof c))throw n...
function c (line 1) | function c(){return ie()(this,c),ve()(this,(c.__proto__||Object.getProto...
function ye (line 1) | function ye(){return(ye=Object.assign||function(e){for(var c=1;c<argumen...
function Te (line 1) | function Te(e,c,t){return c in e?Object.defineProperty(e,c,{value:t,enum...
function _e (line 1) | function _e(e){return Me.setTwoToneColors({primaryColor:e})}
function Ee (line 1) | function Ee(){return(Ee=Object.assign||function(e){for(var c=1;c<argumen...
function Ae (line 1) | function Ae(e,c,t){return c in e?Object.defineProperty(e,c,{value:t,enum...
function Re (line 1) | function Re(e){return(Re="function"===typeof Symbol&&"symbol"===typeof S...
function Ue (line 1) | function Ue(){return(Ue=Object.assign||function(e){for(var c=1;c<argumen...
function qe (line 1) | function qe(e,c,t){return c in e?Object.defineProperty(e,c,{value:t,enum...
function Be (line 1) | function Be(e,c){for(var t=0;t<c.length;t++){var n=c[t];n.enumerable=n.e...
function We (line 1) | function We(e,c){return!c||"object"!==Re(c)&&"function"!==typeof c?funct...
function Qe (line 1) | function Qe(e){return(Qe=Object.setPrototypeOf?Object.getPrototypeOf:fun...
function Ye (line 1) | function Ye(e,c){return(Ye=Object.setPrototypeOf||function(e,c){return e...
function c (line 1) | function c(){var e;return function(e,c){if(!(e instanceof c))throw new T...
function Ze (line 1) | function Ze(e){return(Ze="function"===typeof Symbol&&"symbol"===typeof S...
function Je (line 1) | function Je(){return(Je=Object.assign||function(e){for(var c=1;c<argumen...
function Xe (line 1) | function Xe(e,c,t){return c in e?Object.defineProperty(e,c,{value:t,enum...
function ec (line 1) | function ec(e,c){for(var t=0;t<c.length;t++){var n=c[t];n.enumerable=n.e...
function cc (line 1) | function cc(e,c){return!c||"object"!==Ze(c)&&"function"!==typeof c?funct...
function tc (line 1) | function tc(e){return(tc=Object.setPrototypeOf?Object.getPrototypeOf:fun...
function nc (line 1) | function nc(e,c){return(nc=Object.setPrototypeOf||function(e,c){return e...
function c (line 1) | function c(e){var t;return function(e,c){if(!(e instanceof c))throw new ...
function vc (line 1) | function vc(){return(vc=Object.assign||function(e){for(var c=1;c<argumen...
function c (line 1) | function c(e){var t;return function(e,c){if(!(e instanceof c))throw new ...
FILE: extension/build/popup_old.js
function renderWhitelist (line 48) | function renderWhitelist() {
function getWhitelist (line 83) | function getWhitelist() {
function msgChatboxFrame (line 97) | function msgChatboxFrame(msg, callback) {
function showHideChatbox (line 115) | function showHideChatbox() {
function showHideDanmu (line 132) | function showHideDanmu(type, val) {
function checkChatboxStatus (line 141) | function checkChatboxStatus() {
function toggleWhitelistOptions (line 169) | function toggleWhitelistOptions (enable) {
function saveConfig (line 302) | function saveConfig(key, value) {
function extractHostname (line 311) | function extractHostname(url) {
function extractRootDomain (line 331) | function extractRootDomain(url) {
FILE: extension/popup/config/env.js
constant NODE_ENV (line 10) | const NODE_ENV = process.env.NODE_ENV;
constant REACT_APP (line 61) | const REACT_APP = /^REACT_APP_/i;
function getClientEnvironment (line 63) | function getClientEnvironment(publicUrl) {
FILE: extension/popup/config/jest/cssTransform.js
method process (line 7) | process() {
method getCacheKey (line 10) | getCacheKey() {
FILE: extension/popup/config/jest/fileTransform.js
method process (line 9) | process(src, filename) {
FILE: extension/popup/config/paths.js
function ensureSlash (line 14) | function ensureSlash(inputPath, needsSlash) {
function getServedPath (line 34) | function getServedPath(appPackageJson) {
FILE: extension/popup/config/webpackDevServer.config.js
method before (line 85) | before(app, server) {
FILE: extension/popup/scripts/build.js
constant WARN_AFTER_BUNDLE_GZIP_SIZE (line 37) | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
constant WARN_AFTER_CHUNK_GZIP_SIZE (line 38) | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
function build (line 127) | function build(previousFileSizes) {
function copyPublicFolder (line 187) | function copyPublicFolder() {
FILE: extension/popup/scripts/start.js
constant DEFAULT_PORT (line 44) | const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
constant HOST (line 45) | const HOST = process.env.HOST || '0.0.0.0';
FILE: extension/popup/scripts/test.js
function isInGitRepository (line 23) | function isInGitRepository() {
function isInMercurialRepository (line 32) | function isInMercurialRepository() {
FILE: extension/popup/src/App.js
constant AUTO_CONNECT (line 8) | let AUTO_CONNECT = "Auto Connect"
constant AUTO_OPEN_CHATBOX (line 9) | let AUTO_OPEN_CHATBOX = "Auto Open Chatbox"
constant SHOW_CHAT_ICON (line 10) | let SHOW_CHAT_ICON = "Show Chat Shortcut"
constant ENABLE_LIVE_CHAT_DANMU_STR (line 11) | let ENABLE_LIVE_CHAT_DANMU_STR = "Enable Live Chat Danmu"
constant ENABLE_VIDEO_DANMU_STR (line 12) | let ENABLE_VIDEO_DANMU_STR = "Enable Youtube Danmu"
constant OPEN_STR (line 13) | let OPEN_STR = "Open Chatbox"
constant CLOSE_STR (line 14) | let CLOSE_STR = "Close Chatbox"
constant AVATAR_STR (line 15) | let AVATAR_STR = "Show Online Avatars"
class App (line 28) | class App extends React.Component {
method constructor (line 29) | constructor(props) {
method componentDidMount (line 44) | componentDidMount() {
method render (line 143) | render() {
FILE: extension/popup/src/serviceWorker.js
function register (line 23) | function register(config) {
function registerValidSW (line 57) | function registerValidSW(swUrl, config) {
function checkValidServiceWorker (line 101) | function checkValidServiceWorker(swUrl, config) {
function unregister (line 129) | function unregister() {
FILE: inject-script/.storybook/config.js
function loadStories (line 5) | function loadStories() {
FILE: inject-script/config/env.js
constant NODE_ENV (line 10) | const NODE_ENV = process.env.NODE_ENV;
constant REACT_APP (line 61) | const REACT_APP = /^REACT_APP_/i;
function getClientEnvironment (line 63) | function getClientEnvironment(publicUrl) {
FILE: inject-script/config/jest/cssTransform.js
method process (line 7) | process() {
method getCacheKey (line 10) | getCacheKey() {
FILE: inject-script/config/jest/fileTransform.js
method process (line 9) | process(src, filename) {
FILE: inject-script/config/paths.js
function ensureSlash (line 14) | function ensureSlash(inputPath, needsSlash) {
function getServedPath (line 34) | function getServedPath(appPackageJson) {
FILE: inject-script/config/webpackDevServer.config.js
method before (line 85) | before(app, server) {
FILE: inject-script/scripts/build.js
constant WARN_AFTER_BUNDLE_GZIP_SIZE (line 37) | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
constant WARN_AFTER_CHUNK_GZIP_SIZE (line 38) | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
function build (line 127) | function build(previousFileSizes) {
function copyPublicFolder (line 187) | function copyPublicFolder() {
FILE: inject-script/scripts/start.js
constant DEFAULT_PORT (line 44) | const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
constant HOST (line 45) | const HOST = process.env.HOST || '0.0.0.0';
FILE: inject-script/scripts/test.js
function isInGitRepository (line 23) | function isInGitRepository() {
function isInMercurialRepository (line 32) | function isInMercurialRepository() {
FILE: inject-script/src/containers/App/App.js
function getMessageOffset (line 19) | function getMessageOffset(conversations) {
function App (line 30) | function App(props) {
FILE: inject-script/src/containers/ChatDanmu/AnimationDanmu.js
function queueHistoryMessages (line 17) | function queueHistoryMessages(roomId, msgs) {
function getHistoryMessage (line 30) | function getHistoryMessage(roomId) {
class AnimationDanmu (line 72) | class AnimationDanmu extends Component {
method constructor (line 80) | constructor(props) {
method componentDidMount (line 186) | componentDidMount() {
method render (line 218) | render() {
FILE: inject-script/src/containers/ChatDanmu/Danmu.js
class Danmu (line 3) | class Danmu extends Component {
method constructor (line 5) | constructor(props) {
method componentDidMount (line 10) | componentDidMount() {
method render (line 45) | render() {
FILE: inject-script/src/containers/ChatIcon/ChatIcon.js
constant SHOW_CHAT_ICON_BY_DEFAULT (line 9) | const SHOW_CHAT_ICON_BY_DEFAULT = true
function ChatIcon (line 12) | function ChatIcon({ userCount }) {
FILE: inject-script/src/containers/ChatboxIframe/ChatboxIframe.js
function keepCheckingLocation (line 31) | function keepCheckingLocation() {
function ChatboxIframe (line 51) | function ChatboxIframe({ blacklist }) {
FILE: inject-script/src/containers/ImageModal/ImageModal.js
function SimpleDialog (line 5) | function SimpleDialog(props) {
function SimpleDialogDemo (line 14) | function SimpleDialogDemo() {
FILE: inject-script/src/containers/Room/Room.js
constant MSG_TIMEOUT (line 10) | const MSG_TIMEOUT = 7 * 1000
function Room (line 12) | function Room({ blacklist, isBlacklisted }) {
FILE: inject-script/src/containers/User/User.js
function toggleBlock (line 9) | function toggleBlock(user, blacklist, block) {
function User (line 22) | function User({ user, blacklist, blacklisted }) {
FILE: inject-script/src/serviceWorker.js
function register (line 23) | function register(config) {
function registerValidSW (line 57) | function registerValidSW(swUrl, config) {
function checkValidServiceWorker (line 101) | function checkValidServiceWorker(swUrl, config) {
function unregister (line 129) | function unregister() {
FILE: inject-script/src/services/room/room.js
constant MSG_TIMEOUT (line 12) | const MSG_TIMEOUT = 7 * 1000
FILE: inject-script/stories/index.stories.js
function sendDanmu (line 9) | function sendDanmu (msg, key) {
Condensed preview — 231 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,076K chars).
[
{
"path": ".gitignore",
"chars": 111,
"preview": "\n.DS_Store\nclient/extension/build/content-static/*\nclient/extension/build.zip\nclient/.vscode/launch.json\n*.zip\n"
},
{
"path": ".prettierrc",
"chars": 107,
"preview": "{\n\t\"trailingComma\": \"none\",\n\t\"arrowParens\": \"avoid\",\n\t\"semi\": false,\n\t\"useTabs\": true,\n\t\"printWidth\": 80\n}\n"
},
{
"path": "README.md",
"chars": 2205,
"preview": "#### [Read English Version](https://github.com/Same-Page/client/blob/master/README_EN.md)\n\n# 一叶\n\n《一叶》是一款[浏览器插件](https://"
},
{
"path": "README_EN.md",
"chars": 3919,
"preview": "# Same Page\n\nSame Page (previously called Chat Anywhere) is a [Chrome extension](https://chrome.google.com/webstore/deta"
},
{
"path": "chatbox/.gitignore",
"chars": 310,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
},
{
"path": "chatbox/.prettierrc",
"chars": 36,
"preview": "{\n \"tabWidth\": 2,\n \"semi\": false\n}"
},
{
"path": "chatbox/.storybook/addons.js",
"chars": 86,
"preview": "import '@storybook/addon-actions/register';\nimport '@storybook/addon-links/register';\n"
},
{
"path": "chatbox/.storybook/config.js",
"chars": 136,
"preview": "import { configure } from '@storybook/react';\n\nfunction loadStories() {\n require('../src/stories');\n}\n\nconfigure(loadSt"
},
{
"path": "chatbox/README.md",
"chars": 2867,
"preview": "This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).\n\n## Available Scrip"
},
{
"path": "chatbox/package.json",
"chars": 1755,
"preview": "{\n \"name\": \"same-page\",\n \"homepage\": \"./\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"dependencies\": {\n \"@material"
},
{
"path": "chatbox/public/index.html",
"chars": 1598,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <link rel=\"shortcut icon\" href=\"%PUBLIC_URL%/"
},
{
"path": "chatbox/public/manifest.json",
"chars": 306,
"preview": "{\n \"short_name\": \"React App\",\n \"name\": \"Create React App Sample\",\n \"icons\": [\n {\n \"src\": \"favicon.ico\",\n "
},
{
"path": "chatbox/src/.eslintrc.json",
"chars": 162,
"preview": "{\n \"extends\": \"react-app\",\n \"plugins\": [\"react-hooks\"],\n \"rules\": {\n \"react-hooks/rules-of-hooks\": \"error\",\n \"r"
},
{
"path": "chatbox/src/App.js",
"chars": 8204,
"preview": "import axios from \"axios\"\nimport moment from \"moment\"\n\nimport { IntlProvider, createIntl } from \"react-intl\"\nimport msg_"
},
{
"path": "chatbox/src/components/Emoji/Emoji.css",
"chars": 479,
"preview": ".emoji-mart-preview {\n display: none;\n}\n.emoji-mart {\n max-width: 100%;\n position: fixed;\n bottom: 39px;\n left: 0px"
},
{
"path": "chatbox/src/components/Emoji/Emoji.js",
"chars": 2266,
"preview": "import \"./Emoji.css\"\nimport \"emoji-mart/css/emoji-mart.css\"\n\nimport React from \"react\"\nimport { createIntl } from \"react"
},
{
"path": "chatbox/src/components/Emoji/index.js",
"chars": 34,
"preview": "export { default } from \"./Emoji\"\n"
},
{
"path": "chatbox/src/components/Iframe/Iframe.css",
"chars": 142,
"preview": ".sp-iframe-modal .ant-modal-content {\n height: 80%;\n position: fixed;\n\n /* width: 80%; */\n top: 7% !important;\n lef"
},
{
"path": "chatbox/src/components/Iframe/Iframe.js",
"chars": 639,
"preview": "import \"./Iframe.css\"\n\nimport React from \"react\"\nimport { Modal } from \"antd\"\n\nfunction Iframe({ title, url, show, setSh"
},
{
"path": "chatbox/src/components/Iframe/index.js",
"chars": 35,
"preview": "export { default } from \"./Iframe\"\n"
},
{
"path": "chatbox/src/components/InputWithPicker/InputWithPicker.css",
"chars": 608,
"preview": ".sp-input-with-picker .ant-input-group-addon {\n border-radius: unset;\n}\n.sp-input-with-picker .ant-input-group-addon:la"
},
{
"path": "chatbox/src/components/InputWithPicker/InputWithPicker.js",
"chars": 3838,
"preview": "import \"./InputWithPicker.css\"\n\nimport { useIntl } from \"react-intl\"\n\nimport React, { useState, useRef, useEffect } from"
},
{
"path": "chatbox/src/components/InputWithPicker/index.js",
"chars": 44,
"preview": "export { default } from \"./InputWithPicker\"\n"
},
{
"path": "chatbox/src/components/MusicPlayer/MusicPlayer.css",
"chars": 574,
"preview": ".wmp-title {\n /* text-align: center; */\n /* margin: 20px; */\n}\n/* .wmp-container {\n min-height: 200px;\n} */\n/* .wmp-c"
},
{
"path": "chatbox/src/components/MusicPlayer/MusicPlayer.js",
"chars": 3071,
"preview": "import \"video.js/dist/video-js.css\"\nimport \"./MusicPlayer.css\"\nimport React from \"react\"\nimport videojs from \"video.js\"\n"
},
{
"path": "chatbox/src/components/MusicPlayer/index.js",
"chars": 40,
"preview": "export { default } from \"./MusicPlayer\"\n"
},
{
"path": "chatbox/src/components/OutsideClickDetector.js",
"chars": 1090,
"preview": "import React from \"react\"\n\nclass OutsideAlerter extends React.Component {\n constructor(props) {\n super(props)\n\n t"
},
{
"path": "chatbox/src/config/index.js",
"chars": 0,
"preview": ""
},
{
"path": "chatbox/src/config/logger.js",
"chars": 152,
"preview": "const spDebug = str => {\n if (window.spConfig && window.spConfig.debug) {\n console.debug(str)\n }\n}\nwindow.spDebug ="
},
{
"path": "chatbox/src/config/urls.js",
"chars": 412,
"preview": "const urls = {\n // debugMsgSrc: \"http://localhost:9009\",\n // dbAPI: \"https://api-v2.yiyechat.com\",\n // dbAPI: \"http:/"
},
{
"path": "chatbox/src/containers/Account/Account.js",
"chars": 3910,
"preview": "import React, { useState, useEffect } from \"react\"\nimport { Button } from \"antd\"\nimport { connect } from \"react-redux\"\n\n"
},
{
"path": "chatbox/src/containers/Account/AvatarUploader/AvatarUploader.css",
"chars": 2292,
"preview": ".fileContainer {\n background: none;\n box-shadow: none;\n position: unset;\n border-radius: unset;\n padding: unset;\n "
},
{
"path": "chatbox/src/containers/Account/AvatarUploader/AvatarUploader.js",
"chars": 1590,
"preview": "import React, { forwardRef } from \"react\"\nimport ImageUploader from \"react-images-upload\"\nimport \"./AvatarUploader.css\"\n"
},
{
"path": "chatbox/src/containers/Account/AvatarUploader/index.js",
"chars": 43,
"preview": "export { default } from \"./AvatarUploader\"\n"
},
{
"path": "chatbox/src/containers/Account/Blacklist.js",
"chars": 921,
"preview": "import React, { useEffect, useState, useRef } from \"react\"\nimport { useIntl } from \"react-intl\"\nimport { Avatar, Icon, R"
},
{
"path": "chatbox/src/containers/Account/EditProfile.js",
"chars": 4842,
"preview": "import React from \"react\"\n\nimport { Form, Input, Button, message } from \"antd\"\n\nimport AvatarUploader from \"./AvatarUplo"
},
{
"path": "chatbox/src/containers/Account/Follow/Follow.css",
"chars": 209,
"preview": ".sp-follow-tab .ant-avatar {\n margin-right: 10px;\n}\n.sp-follow-row {\n padding: 10px;\n cursor: pointer;\n /* border-bo"
},
{
"path": "chatbox/src/containers/Account/Follow/Follow.js",
"chars": 4715,
"preview": "import \"./Follow.css\"\nimport axios from \"axios\"\n\nimport React, { useEffect, useState, useRef } from \"react\"\nimport { use"
},
{
"path": "chatbox/src/containers/Account/Follow/event.js",
"chars": 285,
"preview": "// If user follow/unfollow anyone, it will call\n// follow method, the Follow.js component can 'subscribe'\n// to it by pr"
},
{
"path": "chatbox/src/containers/Account/Follow/index.js",
"chars": 35,
"preview": "export { default } from \"./Follow\"\n"
},
{
"path": "chatbox/src/containers/Account/Login.js",
"chars": 5044,
"preview": "import React from \"react\"\nimport { Form, Icon, Input, Button, message } from \"antd\"\nimport { injectIntl } from \"react-in"
},
{
"path": "chatbox/src/containers/Account/Profile/Profile.css",
"chars": 124,
"preview": ".sp-follow-stats {\n cursor: pointer;\n}\n.sp-follow-stats:hover {\n color: rgb(0, 153, 255);\n text-decoration: underline"
},
{
"path": "chatbox/src/containers/Account/Profile/Profile.js",
"chars": 4725,
"preview": "import \"./Profile.css\"\nimport { useIntl } from \"react-intl\"\n\nimport React, { useState } from \"react\"\nimport { Avatar, Bu"
},
{
"path": "chatbox/src/containers/Account/Profile/index.js",
"chars": 36,
"preview": "export { default } from \"./Profile\"\n"
},
{
"path": "chatbox/src/containers/Account/ResetPassword.js",
"chars": 4076,
"preview": "import React from \"react\"\nimport { Form, Input, Button, message } from \"antd\"\nimport { injectIntl } from \"react-intl\"\n\ni"
},
{
"path": "chatbox/src/containers/Account/index.js",
"chars": 36,
"preview": "export { default } from \"./Account\"\n"
},
{
"path": "chatbox/src/containers/Chat/Body.css",
"chars": 735,
"preview": ".room-show-media-false {\n display: none;\n}\n.sp-message-detail-html {\n height: 100%;\n width: 100%;\n overflow-y: auto;"
},
{
"path": "chatbox/src/containers/Chat/Body.js",
"chars": 9350,
"preview": "import \"./Body.css\"\n\nimport React, { useEffect, useRef, useState } from \"react\"\nimport moment from \"moment\"\n// import { "
},
{
"path": "chatbox/src/containers/Chat/Chat.js",
"chars": 3811,
"preview": "import React, { useState, useEffect } from \"react\"\n// import { message } from \"antd\"\nimport { connect } from \"react-redu"
},
{
"path": "chatbox/src/containers/Chat/Footer/Footer.css",
"chars": 67,
"preview": ".sp-chat-bottom {\n position: fixed;\n bottom: 0;\n width: 100%;\n}\n"
},
{
"path": "chatbox/src/containers/Chat/Footer/Footer.js",
"chars": 6066,
"preview": "import \"./Footer.css\"\n\nimport React, { useState, useEffect } from \"react\"\nimport { message, Button, Modal, Tooltip } fro"
},
{
"path": "chatbox/src/containers/Chat/Footer/index.js",
"chars": 35,
"preview": "export { default } from \"./Footer\"\n"
},
{
"path": "chatbox/src/containers/Chat/Header/Header.css",
"chars": 765,
"preview": ".sp-toggle-online {\n float: left;\n display: none;\n}\n.sp-toggle-page-site-chat {\n /* z-index: 0; */\n /* position: rel"
},
{
"path": "chatbox/src/containers/Chat/Header/Header.js",
"chars": 5702,
"preview": "import \"./Header.css\"\n\n// import { FormattedMessage, useIntl } from \"react-intl\"\nimport { useIntl } from \"react-intl\"\nim"
},
{
"path": "chatbox/src/containers/Chat/Header/Invite.js",
"chars": 465,
"preview": "import React, { useState } from \"react\"\nimport { Popover, Button } from \"antd\"\n\nfunction Users(props) {\n // const users"
},
{
"path": "chatbox/src/containers/Chat/Header/RoomHeader.js",
"chars": 5706,
"preview": "import React, { useState, useEffect } from \"react\"\nimport { Button, Icon, message } from \"antd\"\nimport { connect } from "
},
{
"path": "chatbox/src/containers/Chat/Header/RoomInfo.js",
"chars": 6431,
"preview": "import React, { useState, useEffect } from \"react\"\nimport { Modal, Switch, Avatar, Button } from \"antd\"\nimport { useIntl"
},
{
"path": "chatbox/src/containers/Chat/Header/Users.js",
"chars": 1603,
"preview": "import React from \"react\"\nimport { connect } from \"react-redux\"\nimport { useIntl } from \"react-intl\"\n\nimport { Avatar } "
},
{
"path": "chatbox/src/containers/Chat/Header/index.js",
"chars": 35,
"preview": "export { default } from \"./Header\"\n"
},
{
"path": "chatbox/src/containers/Chat/Message/Body.css",
"chars": 1337,
"preview": ".sp-message-body {\n max-width: calc(100% - 50px);\n margin-top: 5px;\n display: inline-block;\n}\n\n.sp-message-body.file,"
},
{
"path": "chatbox/src/containers/Chat/Message/Body.js",
"chars": 6627,
"preview": "import \"./Body.css\"\n\nimport React, { useState } from \"react\"\nimport { Popover, Button, Icon } from \"antd\"\nimport { useIn"
},
{
"path": "chatbox/src/containers/Chat/Message/Message.css",
"chars": 205,
"preview": ".sp-message-username {\n font-size: smaller;\n vertical-align: middle;\n margin-left: 5px;\n max-width: 60px;\n text-ove"
},
{
"path": "chatbox/src/containers/Chat/Message/Message.js",
"chars": 1985,
"preview": "import \"./Message.css\"\n\nimport React from \"react\"\n\nimport MessageBody from \"./Body\"\nimport AvatarWithHoverCard from \"con"
},
{
"path": "chatbox/src/containers/Chat/Message/index.js",
"chars": 36,
"preview": "export { default } from \"./Message\"\n"
},
{
"path": "chatbox/src/containers/Chat/ResizableMedia.js",
"chars": 2898,
"preview": "import React, { useEffect, useRef, useState } from \"react\"\n\nimport { Resizable } from \"re-resizable\"\n\nimport MusicPlayer"
},
{
"path": "chatbox/src/containers/Chat/View.js",
"chars": 3019,
"preview": "import React, { useState, useRef, useEffect } from \"react\"\n// import { connect } from \"react-redux\"\nimport { useIntl } f"
},
{
"path": "chatbox/src/containers/Chat/index.js",
"chars": 33,
"preview": "export { default } from \"./Chat\"\n"
},
{
"path": "chatbox/src/containers/Comment/Body.js",
"chars": 484,
"preview": "import React from \"react\"\n\nimport Message from \"./Message\"\n\nfunction CommentBody(props) {\n const data = props.data || ["
},
{
"path": "chatbox/src/containers/Comment/Comment.css",
"chars": 224,
"preview": ".sp-comment-footer {\n position: fixed;\n bottom: 0;\n width: 100%;\n background: #eceff1;\n}\n\n.sp-comment-footer textare"
},
{
"path": "chatbox/src/containers/Comment/Comment.js",
"chars": 6391,
"preview": "import \"./Comment.css\"\n\nimport React from \"react\"\nimport { Button, Icon, Input } from \"antd\"\n\nimport axios from \"axios\"\n"
},
{
"path": "chatbox/src/containers/Comment/Header.js",
"chars": 539,
"preview": "import React from \"react\"\nimport { Select } from \"antd\"\nconst Option = Select.Option\n\nfunction CommentHeader(props) {\n "
},
{
"path": "chatbox/src/containers/Comment/Message/Message.css",
"chars": 702,
"preview": ".sp-comment-message-username {\n font-size: smaller;\n font-weight: bold;\n}\n\n.sp-comment-message-avatar {\n vertical-ali"
},
{
"path": "chatbox/src/containers/Comment/Message/Message.js",
"chars": 2080,
"preview": "import \"./Message.css\"\n\nimport React, { useState } from \"react\"\nimport { Icon } from \"antd\"\nimport AvatarWithHoverCard f"
},
{
"path": "chatbox/src/containers/Comment/Message/index.js",
"chars": 36,
"preview": "export { default } from \"./Message\"\n"
},
{
"path": "chatbox/src/containers/Comment/index.js",
"chars": 36,
"preview": "export { default } from \"./Comment\"\n"
},
{
"path": "chatbox/src/containers/Home/Comments/Comment.css",
"chars": 514,
"preview": ".sp-comment-url {\n color: #1890ff;\n cursor: pointer;\n}\n.sp-home-comment {\n white-space: nowrap;\n overflow: hidden;\n "
},
{
"path": "chatbox/src/containers/Home/Comments/Comments.js",
"chars": 1628,
"preview": "import \"./Comment.css\"\n\nimport React, { useState, useEffect } from \"react\"\nimport { Icon } from \"antd\"\n// import moment "
},
{
"path": "chatbox/src/containers/Home/Comments/index.js",
"chars": 37,
"preview": "export { default } from \"./Comments\"\n"
},
{
"path": "chatbox/src/containers/Home/CreateRoom.js",
"chars": 3948,
"preview": "import React from \"react\"\n\nimport { Form, Input, Button, message } from \"antd\"\nimport { createRoom } from \"services/room"
},
{
"path": "chatbox/src/containers/Home/Danmus/Danmus.js",
"chars": 1154,
"preview": "import React, { useState, useEffect } from \"react\"\nimport { Icon } from \"antd\"\n\nimport { getLatestDanmus } from \"service"
},
{
"path": "chatbox/src/containers/Home/Danmus/index.js",
"chars": 35,
"preview": "export { default } from \"./Danmus\"\n"
},
{
"path": "chatbox/src/containers/Home/Discover.js",
"chars": 6943,
"preview": "import \"./Home.css\"\nimport { useIntl } from \"react-intl\"\nimport React, { useState, useEffect } from \"react\"\n\nimport { Ic"
},
{
"path": "chatbox/src/containers/Home/Home.css",
"chars": 1576,
"preview": ".sp-hot-chatrooms-wrapper {\n display: -webkit-flex; /* Safari */\n -webkit-flex-wrap: wrap; /* Safari 6.1+ */\n display"
},
{
"path": "chatbox/src/containers/Home/Home.js",
"chars": 2279,
"preview": "import \"./Home.css\"\nimport React, { useState, useEffect } from \"react\"\n\nimport { Collapse, Modal, Button } from \"antd\"\n\n"
},
{
"path": "chatbox/src/containers/Home/Rooms/Room.css",
"chars": 432,
"preview": ".sp-home-chatroom {\n /* width: 80px;\n height: 70px;\n line-height: 70px;\n background: #72c1ff;\n margin: 5px;\n color"
},
{
"path": "chatbox/src/containers/Home/Rooms/Rooms.js",
"chars": 1397,
"preview": "import \"./Room.css\"\n\nimport React from \"react\"\nimport { Icon } from \"antd\"\nimport { connect } from \"react-redux\"\n\nimport"
},
{
"path": "chatbox/src/containers/Home/Rooms/index.js",
"chars": 34,
"preview": "export { default } from \"./Rooms\"\n"
},
{
"path": "chatbox/src/containers/Home/RoomsWrapper.js",
"chars": 2047,
"preview": "import \"./Home.css\"\nimport { useIntl } from \"react-intl\"\nimport React, { useState, useEffect } from \"react\"\nimport { Ico"
},
{
"path": "chatbox/src/containers/Home/Users/Users.css",
"chars": 144,
"preview": ".sp-home-users {\n margin: 10px;\n}\n/* .sp-home-users:hover {\n background: lightgray;\n} */\n.sp-home-users .sp-username {"
},
{
"path": "chatbox/src/containers/Home/Users/Users.js",
"chars": 901,
"preview": "import \"./Users.css\"\n\nimport React, { useState, useEffect } from \"react\"\nimport { Icon } from \"antd\"\n\nimport { getLatest"
},
{
"path": "chatbox/src/containers/Home/Users/index.js",
"chars": 34,
"preview": "export { default } from \"./Users\"\n"
},
{
"path": "chatbox/src/containers/Home/VideoRoom/VideoRoom.js",
"chars": 4481,
"preview": "import React, { useState, useEffect, useRef } from \"react\"\nimport { Button, Radio } from \"antd\"\n\n// import MusicPlayer f"
},
{
"path": "chatbox/src/containers/Home/VideoRoom/index.js",
"chars": 38,
"preview": "export { default } from \"./VideoRoom\"\n"
},
{
"path": "chatbox/src/containers/Home/index.js",
"chars": 33,
"preview": "export { default } from \"./Home\"\n"
},
{
"path": "chatbox/src/containers/Inbox/Conversation/Conversation.css",
"chars": 181,
"preview": ".sp-inbox-conversation .sp-conversation-username {\n color: black;\n cursor: pointer;\n padding: 5px;\n}\n.sp-inbox-conver"
},
{
"path": "chatbox/src/containers/Inbox/Conversation/Conversation.js",
"chars": 4618,
"preview": "import \"./Conversation.css\"\n\nimport React, { useState, useRef, useEffect } from \"react\"\nimport { Button } from \"antd\"\nim"
},
{
"path": "chatbox/src/containers/Inbox/Conversation/index.js",
"chars": 41,
"preview": "export { default } from \"./Conversation\"\n"
},
{
"path": "chatbox/src/containers/Inbox/Inbox.css",
"chars": 788,
"preview": ".sp-inbox-row {\n padding-left: 5px;\n padding-right: 5px;\n display: flex;\n align-items: center; /* vertically align a"
},
{
"path": "chatbox/src/containers/Inbox/Inbox.js",
"chars": 11521,
"preview": "import \"./Inbox.css\"\nimport React, { useEffect, useState, useRef } from \"react\"\nimport { Avatar, Icon, Radio, Button, me"
},
{
"path": "chatbox/src/containers/Inbox/index.js",
"chars": 34,
"preview": "export { default } from \"./Inbox\"\n"
},
{
"path": "chatbox/src/containers/Music/MusicTab.js",
"chars": 3202,
"preview": "import React, { useState } from \"react\"\nimport { Button, Radio, Modal } from \"antd\"\n\nimport MusicPlayer from \"components"
},
{
"path": "chatbox/src/containers/Music/Playlist/Playlist.css",
"chars": 331,
"preview": ".sp-playlist-item {\n padding: 10px;\n color: lightgray;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ell"
},
{
"path": "chatbox/src/containers/Music/Playlist/Playlist.js",
"chars": 2579,
"preview": "import \"./Playlist.css\"\nimport React, { useState, useEffect } from \"react\"\nimport moment from \"moment\"\n\nimport AvatarWit"
},
{
"path": "chatbox/src/containers/Music/Playlist/index.js",
"chars": 37,
"preview": "export { default } from \"./Playlist\"\n"
},
{
"path": "chatbox/src/containers/Music/index.js",
"chars": 37,
"preview": "export { default } from \"./MusicTab\"\n"
},
{
"path": "chatbox/src/containers/OtherProfile/AvatarWithHoverCard.js",
"chars": 2499,
"preview": "import React, { useState } from \"react\"\nimport { Avatar } from \"antd\"\n\nimport { connect } from \"react-redux\"\n\nimport Pro"
},
{
"path": "chatbox/src/containers/OtherProfile/OtherProfile.js",
"chars": 1829,
"preview": "import React, { useState } from \"react\"\nimport { Button } from \"antd\"\n// import { connect } from \"react-redux\"\n\nimport P"
},
{
"path": "chatbox/src/containers/OtherProfile/ProfileBody/ProfileBody.js",
"chars": 9336,
"preview": "import React, { useState } from \"react\"\nimport { Button, Avatar, Icon, Row, Col, message } from \"antd\"\nimport { useIntl "
},
{
"path": "chatbox/src/containers/OtherProfile/ProfileBody/index.js",
"chars": 40,
"preview": "export { default } from \"./ProfileBody\"\n"
},
{
"path": "chatbox/src/containers/OtherProfile/ProfileCard.css",
"chars": 110,
"preview": ".ant-card-meta-title {\n /* color: white; */\n}\n.ant-card-bordered {\n border: 1px solid rgb(187, 187, 187);\n}\n"
},
{
"path": "chatbox/src/containers/OtherProfile/ProfileCard.js",
"chars": 4268,
"preview": "import \"./ProfileCard.css\"\n\nimport React from \"react\"\nimport { useIntl } from \"react-intl\"\nimport { Button, Avatar, Card"
},
{
"path": "chatbox/src/containers/OtherProfile/ProfileMeta/ProfileMeta.js",
"chars": 2768,
"preview": "import React, { useState, useEffect } from \"react\"\nimport { connect } from \"react-redux\"\n\nimport { getUser } from \"servi"
},
{
"path": "chatbox/src/containers/OtherProfile/ProfileMeta/index.js",
"chars": 40,
"preview": "export { default } from \"./ProfileMeta\"\n"
},
{
"path": "chatbox/src/containers/OtherProfile/index.js",
"chars": 41,
"preview": "export { default } from \"./OtherProfile\"\n"
},
{
"path": "chatbox/src/containers/Tab/Tab.css",
"chars": 3861,
"preview": "body {\n /* background: #eceff1; */\n /* background-color: rgb(246, 249, 252); */\n}\nbody ::-webkit-scrollbar {\n width: "
},
{
"path": "chatbox/src/containers/Tab/Tab.js",
"chars": 5020,
"preview": "import \"antd/dist/antd.css\"\nimport \"./Tab.css\"\nimport { useIntl } from \"react-intl\"\nimport React, { useState, useEffect "
},
{
"path": "chatbox/src/containers/Tab/index.js",
"chars": 32,
"preview": "export { default } from \"./Tab\"\n"
},
{
"path": "chatbox/src/context/preference-context.js",
"chars": 109,
"preview": "import React from \"react\"\n\nconst PreferenceContext = React.createContext()\n\nexport default PreferenceContext\n"
},
{
"path": "chatbox/src/i18n/en.json",
"chars": 2472,
"preview": "{\n \"sp\": \"Same Page\",\n\n \"possessive\": \"'s\",\n\n \"site\": \"Site\",\n \"page\": \"Page\",\n\n \"yes\": \"OK\",\n \"save\": \"Save\",\n \""
},
{
"path": "chatbox/src/i18n/zh.json",
"chars": 1938,
"preview": "{\n \"sp\": \"一叶\",\n\n \"possessive\": \"的\",\n \"site\": \"网站\",\n \"page\": \"网页\",\n\n \"yes\": \"确认\",\n \"save\": \"保存\",\n \"sending\": \"发送中。"
},
{
"path": "chatbox/src/index.css",
"chars": 400,
"preview": "body {\n margin: 0;\n padding: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\",\n "
},
{
"path": "chatbox/src/index.js",
"chars": 568,
"preview": "import React from \"react\"\nimport ReactDOM from \"react-dom\"\n\nimport { Provider } from \"react-redux\"\nimport store from \"./"
},
{
"path": "chatbox/src/redux/actions/chat/index.js",
"chars": 669,
"preview": "export const changeChatView = view => ({\n type: \"CHANGE_CHAT_VIEW\",\n payload: view\n})\nexport const setChatModes = mode"
},
{
"path": "chatbox/src/redux/actions/index.js",
"chars": 433,
"preview": "export const changeTab = view => ({\n type: \"CHANGE_TAB\",\n payload: view\n})\n\nexport const viewOtherUser = user => ({\n "
},
{
"path": "chatbox/src/redux/reducers/index.js",
"chars": 3591,
"preview": "import axios from \"axios\"\n\n// import socketManager from \"socket/socket\"\nimport storageManager from \"utils/storage\"\nimpor"
},
{
"path": "chatbox/src/redux/store/index.js",
"chars": 163,
"preview": "import { createStore } from \"redux\"\nimport rootReducer from \"../reducers/index\"\nconst store = createStore(rootReducer)\nw"
},
{
"path": "chatbox/src/serviceWorker.js",
"chars": 4951,
"preview": "// This optional code is used to register a service worker.\n// register() is not called by default.\n\n// This lets the ap"
},
{
"path": "chatbox/src/services/account.js",
"chars": 711,
"preview": "import axios from \"axios\"\n\nimport urls from \"config/urls\"\n\nexport const getAccount = () => {\n return axios.get(`${urls."
},
{
"path": "chatbox/src/services/comment.js",
"chars": 161,
"preview": "import axios from \"axios\"\n\nimport urls from \"config/urls\"\n\nexport const getLatestComments = () => {\n return axios.get(`"
},
{
"path": "chatbox/src/services/danmu.js",
"chars": 157,
"preview": "import axios from \"axios\"\n\nimport urls from \"config/urls\"\n\nexport const getLatestDanmus = () => {\n return axios.get(`${"
},
{
"path": "chatbox/src/services/follow.js",
"chars": 190,
"preview": "import axios from \"axios\"\n\nimport urls from \"config/urls\"\n\nexport const followUser = id => {\n const payload = {\n id:"
},
{
"path": "chatbox/src/services/index.js",
"chars": 85,
"preview": "import axios from \"axios\"\n\nexport const getData = url => {\n return axios.get(url)\n}\n"
},
{
"path": "chatbox/src/services/message.js",
"chars": 729,
"preview": "import axios from \"axios\"\n\nimport urls from \"config/urls\"\n\nexport const getMessages = offset => {\n const params = {\n "
},
{
"path": "chatbox/src/services/room.js",
"chars": 451,
"preview": "import axios from \"axios\"\n\nimport urls from \"config/urls\"\n\nexport const getRooms = params => {\n return axios.get(\"https"
},
{
"path": "chatbox/src/services/user.js",
"chars": 997,
"preview": "import axios from \"axios\"\n\nimport urls from \"config/urls\"\n\nexport const getUser = userId => {\n return axios.get(`${urls"
},
{
"path": "chatbox/src/socket/index.js",
"chars": 35,
"preview": "export { default } from \"./socket\"\n"
},
{
"path": "chatbox/src/socket/socket.js",
"chars": 5468,
"preview": "// import Rooms from \"../containers/Home/Rooms\"\n// import storageManager from \"utils/storage\"\nimport store from \"redux/s"
},
{
"path": "chatbox/src/stories/IframeWithSrcInput.js",
"chars": 699,
"preview": "import React, { useState } from \"react\"\nimport { Button, Input } from \"antd\"\n\nconst Search = Input.Search\n\nfunction Ifra"
},
{
"path": "chatbox/src/stories/data/chats.js",
"chars": 1858,
"preview": "const chats = [\n {\n userId: 1,\n username: \"David\",\n avatarSrc:\n \"https://zos.alipayobjects.com/rmsportal/"
},
{
"path": "chatbox/src/stories/data/comments.js",
"chars": 10738,
"preview": "const comments = [\n {\n id: 455,\n content: \"少逛知乎多种树\",\n user_id: \"f82a9c8b-9b7e-8cc0-04b0-e2a726df9049\",\n cre"
},
{
"path": "chatbox/src/stories/iframe.css",
"chars": 372,
"preview": ".sp-chatbox-iframe {\n /* don't remove anything, this is fighting other sites' styling*/\n position: fixed;\n left: 20;\n"
},
{
"path": "chatbox/src/stories/index.js",
"chars": 1385,
"preview": "import \"antd/dist/antd.css\"\nimport \"./iframe.css\"\n\nimport { Button } from \"antd\"\nimport React from \"react\"\nimport { stor"
},
{
"path": "chatbox/src/utils/pageTitle.js",
"chars": 132,
"preview": "let _title = null\n\nexport const getPageTitle = () => {\n return _title\n}\n\nexport const setPageTitle = title => {\n _titl"
},
{
"path": "chatbox/src/utils/storage.js",
"chars": 2719,
"preview": "const storage = {\n pushToParentWindow: () => {\n // push everything in storage to parent window\n // useful when pa"
},
{
"path": "chatbox/src/utils/url.js",
"chars": 356,
"preview": "let _url = window.location.search.substring(1)\n\nexport const setUrl = url => {\n _url = url\n}\n\nexport const getUrl = () "
},
{
"path": "extension/build/_locales/en/messages.json",
"chars": 305,
"preview": "{\n\t\"appName\": {\n\t\t\"message\": \"Same Page\",\n\t\t\"description\": \"The title of the application, displayed in the web store.\"\n\t"
},
{
"path": "extension/build/_locales/zh_CN/messages.json",
"chars": 115,
"preview": "{\n \"appName\": {\n \"message\": \"一叶\"\n },\n \"appDesc\": {\n \"message\": \"和浏览相同网页的人实时聊天,发弹幕!\"\n }\n}\n"
},
{
"path": "extension/build/background.js",
"chars": 1338,
"preview": "chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {\n if (request.clearIcon) {\n chro"
},
{
"path": "extension/build/content-static/css/main.css",
"chars": 2870,
"preview": ".sp-danmu-wrapper{display:flex;position:fixed;cursor:pointer;z-index:2147483647}.sp-danmu-avatar{border-radius:100%;widt"
},
{
"path": "extension/build/content-static/js/main.js",
"chars": 409310,
"preview": "!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.expo"
},
{
"path": "extension/build/manifest.json",
"chars": 833,
"preview": "{\n\t\"name\": \"__MSG_appName__\",\n\t\"short_name\": \"__MSG_appName__\",\n\t\"description\": \"__MSG_appDesc__\",\n\t\"default_locale\": \"e"
},
{
"path": "extension/build/popup.css",
"chars": 2519,
"preview": "#msg {\n\tdisplay:none;\n\tfloat: right;\n}\n\n#qtime-title {\n\tfont-size: 17px;\n\ttext-decoration: none;\n\tcolor: black;\n\tmargin:"
},
{
"path": "extension/build/popup.html",
"chars": 869,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<link rel=\"shortcut icon\" href=\"/favicon.ico\" />\n\t"
},
{
"path": "extension/build/popup_menu/css/main.css",
"chars": 440533,
"preview": "/*!\n * \n * antd v3.13.2\n * \n * Copyright 2015-present, Alipay, Inc.\n * All rights reserved.\n * \n */body,html{width"
},
{
"path": "extension/build/popup_menu/js/main.js",
"chars": 689762,
"preview": "!function(e){var c={};function t(n){if(c[n])return c[n].exports;var l=c[n]={i:n,l:!1,exports:{}};return e[n].call(l.expo"
},
{
"path": "extension/build/popup_old.js",
"chars": 12211,
"preview": "var whitelist = {};\nvar pageURL = null;\nvar configDataFromStorage = {};\nvar chatboxOpenState = false;\n// visible strings"
},
{
"path": "extension/popup/.gitignore",
"chars": 310,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
},
{
"path": "extension/popup/README.md",
"chars": 92,
"preview": "This is another create-react-app\n\nThis folder is for the popup menu of the chrome extension\n"
},
{
"path": "extension/popup/config/env.js",
"chars": 3487,
"preview": "'use strict';\n\nconst fs = require('fs');\nconst path = require('path');\nconst paths = require('./paths');\n\n// Make sure t"
},
{
"path": "extension/popup/config/jest/cssTransform.js",
"chars": 314,
"preview": "'use strict';\n\n// This is a custom Jest transformer turning style imports into empty objects.\n// http://facebook.github."
},
{
"path": "extension/popup/config/jest/fileTransform.js",
"chars": 755,
"preview": "'use strict';\n\nconst path = require('path');\n\n// This is a custom Jest transformer turning file imports into filenames.\n"
},
{
"path": "extension/popup/config/paths.js",
"chars": 2688,
"preview": "'use strict';\n\nconst path = require('path');\nconst fs = require('fs');\nconst url = require('url');\n\n// Make sure any sym"
},
{
"path": "extension/popup/config/webpack.config.js",
"chars": 26569,
"preview": "'use strict';\n\nconst fs = require('fs');\nconst path = require('path');\nconst webpack = require('webpack');\nconst resolve"
},
{
"path": "extension/popup/config/webpackDevServer.config.js",
"chars": 5646,
"preview": "'use strict';\n\nconst errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');\nconst evalSourceMapMidd"
},
{
"path": "extension/popup/package.json",
"chars": 3034,
"preview": "{\n\t\"name\": \"danmu-react\",\n\t\"version\": \"0.1.0\",\n\t\"private\": true,\n\t\"dependencies\": {\n\t\t\"@babel/core\": \"7.1.6\",\n\t\t\"@svgr/w"
},
{
"path": "extension/popup/public/index.html",
"chars": 1686,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<link rel=\"shortcut icon\" href=\"%PUBLIC_URL%/favic"
},
{
"path": "extension/popup/public/manifest.json",
"chars": 306,
"preview": "{\n \"short_name\": \"React App\",\n \"name\": \"Create React App Sample\",\n \"icons\": [\n {\n \"src\": \"favicon.ico\",\n "
},
{
"path": "extension/popup/scripts/build.js",
"chars": 6046,
"preview": "'use strict';\n\n// Do this as the first thing so that any code reading it knows the right env.\nprocess.env.BABEL_ENV = 'p"
},
{
"path": "extension/popup/scripts/start.js",
"chars": 3841,
"preview": "'use strict';\n\n// Do this as the first thing so that any code reading it knows the right env.\nprocess.env.BABEL_ENV = 'd"
},
{
"path": "extension/popup/scripts/test.js",
"chars": 1365,
"preview": "'use strict';\n\n// Do this as the first thing so that any code reading it knows the right env.\nprocess.env.BABEL_ENV = 't"
},
{
"path": "extension/popup/src/App.js",
"chars": 5675,
"preview": "/*global chrome*/\n\nimport React from \"react\"\nimport { Switch, Button } from \"antd\"\nimport \"antd/dist/antd.css\"\nimport \"."
},
{
"path": "extension/popup/src/App.test.js",
"chars": 248,
"preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport App from './App';\n\nit('renders without crashing', ()"
},
{
"path": "extension/popup/src/index.css",
"chars": 961,
"preview": "body {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\",\n \"Ubuntu\", \"Can"
},
{
"path": "extension/popup/src/index.js",
"chars": 429,
"preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport App from './App';\nimport * as serviceWorker from './"
},
{
"path": "extension/popup/src/serviceWorker.js",
"chars": 4948,
"preview": "// This optional code is used to register a service worker.\n// register() is not called by default.\n\n// This lets the ap"
},
{
"path": "inject-script/.gitignore",
"chars": 328,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
},
{
"path": "inject-script/.storybook/addons.js",
"chars": 127,
"preview": "import '@storybook/addon-actions/register';\nimport '@storybook/addon-knobs/register';\nimport '@storybook/addon-notes/reg"
},
{
"path": "inject-script/.storybook/config.js",
"chars": 280,
"preview": "import { configure } from '@storybook/react';\n\n// automatically import all files ending in *.stories.js\nconst req = requ"
},
{
"path": "inject-script/.storybook/webpack_empty.config.js",
"chars": 574,
"preview": "// you can use this file to add your custom webpack plugins, loaders and anything you like.\n// This is just the basic wa"
},
{
"path": "inject-script/README.md",
"chars": 2867,
"preview": "This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).\n\n## Available Scrip"
},
{
"path": "inject-script/config/env.js",
"chars": 3487,
"preview": "'use strict';\n\nconst fs = require('fs');\nconst path = require('path');\nconst paths = require('./paths');\n\n// Make sure t"
},
{
"path": "inject-script/config/jest/cssTransform.js",
"chars": 314,
"preview": "'use strict';\n\n// This is a custom Jest transformer turning style imports into empty objects.\n// http://facebook.github."
},
{
"path": "inject-script/config/jest/fileTransform.js",
"chars": 755,
"preview": "'use strict';\n\nconst path = require('path');\n\n// This is a custom Jest transformer turning file imports into filenames.\n"
},
{
"path": "inject-script/config/paths.js",
"chars": 2688,
"preview": "'use strict';\n\nconst path = require('path');\nconst fs = require('fs');\nconst url = require('url');\n\n// Make sure any sym"
},
{
"path": "inject-script/config/webpack.config.js",
"chars": 26946,
"preview": "'use strict';\n\nconst fs = require('fs');\nconst path = require('path');\nconst webpack = require('webpack');\nconst resolve"
},
{
"path": "inject-script/config/webpackDevServer.config.js",
"chars": 5646,
"preview": "'use strict';\n\nconst errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');\nconst evalSourceMapMidd"
},
{
"path": "inject-script/package.json",
"chars": 4369,
"preview": "{\n\t\"name\": \"danmu-react-inject\",\n\t\"version\": \"0.1.0\",\n\t\"homepage\": \"./\",\n\t\"private\": true,\n\t\"dependencies\": {\n\t\t\"@babel/"
},
{
"path": "inject-script/public/index.html",
"chars": 1738,
"preview": "<!DOCTYPE html>\n<html style=\"font-size: 10px;\" lang=\"en\">\n\t<head>\n\t\t<script>\n\t\t\twindow.spConfig = {\n\t\t\t\tdefaultTab: \"inb"
},
{
"path": "inject-script/public/manifest.json",
"chars": 306,
"preview": "{\n \"short_name\": \"React App\",\n \"name\": \"Create React App Sample\",\n \"icons\": [\n {\n \"src\": \"favicon.ico\",\n "
},
{
"path": "inject-script/scripts/build.js",
"chars": 6062,
"preview": "'use strict';\n\n// Do this as the first thing so that any code reading it knows the right env.\nprocess.env.BABEL_ENV = 'p"
},
{
"path": "inject-script/scripts/start.js",
"chars": 3857,
"preview": "'use strict';\n\n// Do this as the first thing so that any code reading it knows the right env.\nprocess.env.BABEL_ENV = 'd"
},
{
"path": "inject-script/scripts/test.js",
"chars": 1586,
"preview": "'use strict';\n\n// Do this as the first thing so that any code reading it knows the right env.\nprocess.env.BABEL_ENV = 't"
},
{
"path": "inject-script/src/config/iframe.js",
"chars": 406,
"preview": "import spConfig from \"config\"\n\n// TODO: chrome extension popup.html should be able to set this\nexport const createIframe"
},
{
"path": "inject-script/src/config/index.js",
"chars": 998,
"preview": "const defaultConfig = {\n\ttabList: [\"discover\", \"chat\", \"inbox\", \"profile\", \"close\"],\n\tdefaultTab: \"chat\",\n\tchatModes: [\""
},
{
"path": "inject-script/src/config/logger.js",
"chars": 152,
"preview": "const spDebug = str => {\n\tif (window.spConfig && window.spConfig.debug) {\n\t\tconsole.debug(str);\n\t}\n};\nwindow.spDebug = s"
},
{
"path": "inject-script/src/config/urls.js",
"chars": 202,
"preview": "import spConfig from \"./index.js\"\n\nconst apiUrl = spConfig.apiUrl\nconst socketUrl = spConfig.socketUrl\nconst stickersUrl"
},
{
"path": "inject-script/src/containers/App/App.js",
"chars": 4039,
"preview": "import spDebug from \"config/logger\"\nimport spConfig from \"config\"\nimport React, { useEffect, useState } from \"react\"\n// "
},
{
"path": "inject-script/src/containers/App/App.test.js",
"chars": 248,
"preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport App from './App';\n\nit('renders without crashing', ()"
},
{
"path": "inject-script/src/containers/App/index.js",
"chars": 32,
"preview": "export { default } from \"./App\"\n"
},
{
"path": "inject-script/src/containers/ChatDanmu/AnimationDanmu.css",
"chars": 1079,
"preview": ".sp-danmu-wrapper {\n\tdisplay: flex;\n\tposition: fixed;\n\tcursor: pointer;\n\tz-index: 2147483647;\n}\n.sp-danmu-avatar {\n\t/* d"
}
]
// ... and 31 more files (download for full content)
About this extraction
This page contains the full source code of the Same-Page/front-and-back GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 231 files (1.9 MB), approximately 818.5k tokens, and a symbol index with 1153 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.