支持投票
This commit is contained in:
@@ -17,7 +17,8 @@
|
|||||||
"react-scripts": "^3.4.1",
|
"react-scripts": "^3.4.1",
|
||||||
"react-timeago": "^4.4.0",
|
"react-timeago": "^4.4.0",
|
||||||
"typescript": "^4.0.2",
|
"typescript": "^4.0.2",
|
||||||
"workbox-sw": "^5.1.3"
|
"workbox-sw": "^5.1.3",
|
||||||
|
"react-polls": "^1.2.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
|
|||||||
@@ -198,6 +198,10 @@
|
|||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.box-poll.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.box-id {
|
.box-id {
|
||||||
color: #666666;
|
color: #666666;
|
||||||
}
|
}
|
||||||
|
|||||||
50
src/Flows.js
50
src/Flows.js
@@ -24,6 +24,7 @@ import { TokenCtx, ReplyForm } from './UserAction';
|
|||||||
import { API } from './flows_api';
|
import { API } from './flows_api';
|
||||||
import { cache } from './cache';
|
import { cache } from './cache';
|
||||||
import { save_attentions } from './Attention'
|
import { save_attentions } from './Attention'
|
||||||
|
import Poll from 'react-polls';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
const IMAGE_BASE = 'https://thimg.yecdn.com/';
|
const IMAGE_BASE = 'https://thimg.yecdn.com/';
|
||||||
@@ -159,7 +160,7 @@ class FlowItem extends PureComponent {
|
|||||||
this.state = {
|
this.state = {
|
||||||
hot_score: props.info.hot_score || 0,
|
hot_score: props.info.hot_score || 0,
|
||||||
cw: props.info.cw || '',
|
cw: props.info.cw || '',
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
copy_link(event) {
|
copy_link(event) {
|
||||||
@@ -199,7 +200,8 @@ class FlowItem extends PureComponent {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
info, is_quote, cached, attention, can_del, do_filter_name, do_delete,
|
info, is_quote, cached, attention, can_del, do_filter_name, do_delete,
|
||||||
do_edit_cw, do_edit_score, timestamp, img_clickable, color_picker, show_pid
|
do_edit_cw, do_edit_score, timestamp, img_clickable, color_picker,
|
||||||
|
show_pid, do_vote
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { cw, hot_score } = this.state;
|
const { cw, hot_score } = this.state;
|
||||||
return (
|
return (
|
||||||
@@ -348,6 +350,19 @@ class FlowItem extends PureComponent {
|
|||||||
)}
|
)}
|
||||||
{/*{info.type==='audio' && <AudioWidget src={AUDIO_BASE+info.url} />}*/}
|
{/*{info.type==='audio' && <AudioWidget src={AUDIO_BASE+info.url} />}*/}
|
||||||
</div>
|
</div>
|
||||||
|
{ info.poll && (
|
||||||
|
<div className={!do_vote ? "box-poll disabled" : "box-poll"}>
|
||||||
|
<Poll
|
||||||
|
key={+new Date()}
|
||||||
|
question={""}
|
||||||
|
answers={info.poll.answers}
|
||||||
|
onVote={do_vote || (() => {})}
|
||||||
|
customStyles={{'theme': 'cyan'}}
|
||||||
|
noStorage={true}
|
||||||
|
vote={info.poll.vote}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{!!(attention && info.variant.latest_reply) && (
|
{!!(attention && info.variant.latest_reply) && (
|
||||||
<p className="box-footer">
|
<p className="box-footer">
|
||||||
最新回复{' '}
|
最新回复{' '}
|
||||||
@@ -458,21 +473,16 @@ class FlowSidebar extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggle_attention() {
|
toggle_attention() {
|
||||||
this.setState({
|
|
||||||
loading_status: 'loading',
|
|
||||||
});
|
|
||||||
const prev_info = this.state.info;
|
const prev_info = this.state.info;
|
||||||
const pid = prev_info.pid;
|
const pid = prev_info.pid;
|
||||||
API.set_attention(pid, !this.state.attention, this.props.token)
|
API.set_attention(pid, !this.state.attention, this.props.token)
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading_status: 'done',
|
|
||||||
attention: json.attention,
|
attention: json.attention,
|
||||||
info: Object.assign({}, prev_info, {
|
info: Object.assign({}, prev_info, {
|
||||||
likenum: ''+json.likenum,
|
likenum: ''+json.likenum,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
console.log(json);
|
|
||||||
|
|
||||||
let saved_attentions = window.saved_attentions;
|
let saved_attentions = window.saved_attentions;
|
||||||
if (json.attention && !saved_attentions.includes(pid)) {
|
if (json.attention && !saved_attentions.includes(pid)) {
|
||||||
@@ -500,6 +510,28 @@ class FlowSidebar extends PureComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
do_vote(vote) {
|
||||||
|
this.setState({
|
||||||
|
loading_status: 'loading',
|
||||||
|
error_msg: null,
|
||||||
|
});
|
||||||
|
API.add_vote(vote, this.state.info.pid, this.props.token)
|
||||||
|
.then((json) => {
|
||||||
|
if (json.code !== 0) return;
|
||||||
|
this.setState(
|
||||||
|
(prev, props) => ({
|
||||||
|
info: Object.assign({}, prev.info, { poll: json.data }),
|
||||||
|
loading_status: 'done',
|
||||||
|
}),
|
||||||
|
() => {
|
||||||
|
this.syncState({
|
||||||
|
info: this.state.info,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
report() {
|
report() {
|
||||||
let reason = prompt(`举报 #${this.state.info.pid} 的理由:`);
|
let reason = prompt(`举报 #${this.state.info.pid} 的理由:`);
|
||||||
if (reason !== null) {
|
if (reason !== null) {
|
||||||
@@ -543,7 +575,6 @@ class FlowSidebar extends PureComponent {
|
|||||||
|
|
||||||
make_do_delete(token, on_complete=null) {
|
make_do_delete(token, on_complete=null) {
|
||||||
const do_delete = (type, id) => {
|
const do_delete = (type, id) => {
|
||||||
console.log('del', type, id, token);
|
|
||||||
let note = prompt(`将删除${type}=${id}, 备注:`, '(无)');
|
let note = prompt(`将删除${type}=${id}, 备注:`, '(无)');
|
||||||
if (note !== null) {
|
if (note !== null) {
|
||||||
API.del(type, id, note, token)
|
API.del(type, id, note, token)
|
||||||
@@ -562,7 +593,6 @@ class FlowSidebar extends PureComponent {
|
|||||||
|
|
||||||
make_do_edit_cw(token) {
|
make_do_edit_cw(token) {
|
||||||
const do_edit_cw = (cw, id) => {
|
const do_edit_cw = (cw, id) => {
|
||||||
console.log('edit cw', cw);
|
|
||||||
API.update_cw(cw, id, token)
|
API.update_cw(cw, id, token)
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
alert('已更新\n刷新列表显示新版本');
|
alert('已更新\n刷新列表显示新版本');
|
||||||
@@ -577,7 +607,6 @@ class FlowSidebar extends PureComponent {
|
|||||||
|
|
||||||
make_do_edit_score(token) {
|
make_do_edit_score(token) {
|
||||||
const do_edit_score = (score, id) => {
|
const do_edit_score = (score, id) => {
|
||||||
console.log('edit score', score);
|
|
||||||
API.update_score(score, id, token)
|
API.update_score(score, id, token)
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
console.log('已更新');
|
console.log('已更新');
|
||||||
@@ -636,6 +665,7 @@ class FlowSidebar extends PureComponent {
|
|||||||
do_delete={this.make_do_delete(this.props.token, ()=>{window.location.reload();})}
|
do_delete={this.make_do_delete(this.props.token, ()=>{window.location.reload();})}
|
||||||
do_edit_cw={this.make_do_edit_cw(this.props.token)}
|
do_edit_cw={this.make_do_edit_cw(this.props.token)}
|
||||||
do_edit_score={this.make_do_edit_score(this.props.token)}
|
do_edit_score={this.make_do_edit_score(this.props.token)}
|
||||||
|
do_vote={this.do_vote.bind(this)}
|
||||||
/>
|
/>
|
||||||
</ClickHandler>
|
</ClickHandler>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -116,3 +116,16 @@
|
|||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.post-form-poll-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-form-poll-options input {
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-form-poll-options h6 {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -284,8 +284,7 @@ export class ReplyForm extends Component {
|
|||||||
.then(get_json)
|
.then(get_json)
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
if (json.code !== 0) {
|
if (json.code !== 0) {
|
||||||
if (json.msg) alert(json.msg);
|
throw new Error(json.msg);
|
||||||
throw new Error(JSON.stringify(json));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let saved_attentions = window.saved_attentions;
|
let saved_attentions = window.saved_attentions;
|
||||||
@@ -376,12 +375,15 @@ export class PostForm extends Component {
|
|||||||
loading_status: 'done',
|
loading_status: 'done',
|
||||||
img_tip: null,
|
img_tip: null,
|
||||||
preview: false,
|
preview: false,
|
||||||
|
has_poll: !!window.POLL_BACKUP,
|
||||||
|
poll_options: JSON.parse(window.POLL_BACKUP || '[""]'),
|
||||||
};
|
};
|
||||||
this.img_ref = React.createRef();
|
this.img_ref = React.createRef();
|
||||||
this.area_ref = React.createRef();
|
this.area_ref = React.createRef();
|
||||||
this.on_change_bound = this.on_change.bind(this);
|
this.on_change_bound = this.on_change.bind(this);
|
||||||
this.on_allow_search_change_bound = this.on_allow_search_change.bind(this);
|
this.on_allow_search_change_bound = this.on_allow_search_change.bind(this);
|
||||||
this.on_cw_change_bound = this.on_cw_change.bind(this);
|
this.on_cw_change_bound = this.on_cw_change.bind(this);
|
||||||
|
this.on_poll_option_change_bound = this.on_poll_option_change.bind(this);
|
||||||
this.on_img_change_bound = this.on_img_change.bind(this);
|
this.on_img_change_bound = this.on_img_change.bind(this);
|
||||||
this.color_picker = new ColorPicker();
|
this.color_picker = new ColorPicker();
|
||||||
}
|
}
|
||||||
@@ -391,8 +393,10 @@ export class PostForm extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
window.CW_BACKUP = this.state.cw;
|
const { cw, allow_search, has_poll, poll_options } = this.state;
|
||||||
window.AS_BACKUP = this.state.allow_search;
|
window.CW_BACKUP = cw;
|
||||||
|
window.AS_BACKUP = allow_search;
|
||||||
|
window.POLL_BACKUP = has_poll ? JSON.stringify(poll_options) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
on_allow_search_change(event) {
|
on_allow_search_change(event) {
|
||||||
@@ -413,13 +417,21 @@ export class PostForm extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
do_post(text, img) {
|
do_post() {
|
||||||
let data = new URLSearchParams();
|
let data = new URLSearchParams();
|
||||||
|
const { cw, text, allow_search, has_poll, poll_options } = this.state;
|
||||||
data.append('cw', this.state.cw);
|
data.append('cw', this.state.cw);
|
||||||
data.append('text', this.state.text);
|
data.append('text', this.state.text);
|
||||||
data.append('allow_search', this.state.allow_search ? '1' : '');
|
data.append('allow_search', this.state.allow_search ? '1' : '');
|
||||||
data.append('type', img ? 'image' : 'text');
|
//data.append('type', img ? 'image' : 'text');
|
||||||
if (img) data.append('data', img);
|
//if (img) data.append('data', img);
|
||||||
|
data.append('type', 'text')
|
||||||
|
if (has_poll) {
|
||||||
|
poll_options.forEach((opt) => {
|
||||||
|
if (opt)
|
||||||
|
data.append('poll_options', opt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fetch(API_BASE + '/dopost', {
|
fetch(API_BASE + '/dopost', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -432,8 +444,7 @@ export class PostForm extends Component {
|
|||||||
.then(get_json)
|
.then(get_json)
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
if (json.code !== 0) {
|
if (json.code !== 0) {
|
||||||
if (json.msg) alert(json.msg);
|
throw new Error(json.msg);
|
||||||
throw new Error(JSON.stringify(json));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -444,6 +455,7 @@ export class PostForm extends Component {
|
|||||||
this.area_ref.current.clear();
|
this.area_ref.current.clear();
|
||||||
this.props.on_complete();
|
this.props.on_complete();
|
||||||
window.CW_BACKUP = '';
|
window.CW_BACKUP = '';
|
||||||
|
window.POLL_BACKUP = null;
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@@ -593,7 +605,7 @@ export class PostForm extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
loading_status: 'loading',
|
loading_status: 'loading',
|
||||||
});
|
});
|
||||||
this.do_post(this.state.text, null);
|
this.do_post();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -603,24 +615,26 @@ export class PostForm extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
on_poll_option_change(event, idx) {
|
||||||
|
let poll_options = this.state.poll_options;
|
||||||
|
let text = event.target.value;
|
||||||
|
poll_options[idx] = text;
|
||||||
|
if (!text && poll_options.length > 1) {
|
||||||
|
poll_options.splice(idx, 1)
|
||||||
|
}
|
||||||
|
if (poll_options[poll_options.length - 1] && poll_options.length < 8) {
|
||||||
|
poll_options.push('')
|
||||||
|
}
|
||||||
|
this.setState({ poll_options: poll_options });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { has_poll, poll_options, preview, loading_status } = this.state;
|
||||||
return (
|
return (
|
||||||
<form onSubmit={this.on_submit.bind(this)} className="post-form box">
|
<form onSubmit={this.on_submit.bind(this)} className="post-form box">
|
||||||
<div className="post-form-bar">
|
<div className="post-form-bar">
|
||||||
{/*
|
{preview ? (
|
||||||
<label>
|
|
||||||
图片
|
|
||||||
<input
|
|
||||||
ref={this.img_ref}
|
|
||||||
type="file"
|
|
||||||
accept="image/*"
|
|
||||||
disabled={this.state.loading_status !== 'done'}
|
|
||||||
onChange={this.on_img_change_bound}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
*/}
|
|
||||||
|
|
||||||
{this.state.preview ? (
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -642,11 +656,11 @@ export class PostForm extends Component {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.state.loading_status !== 'done' ? (
|
{loading_status !== 'done' ? (
|
||||||
<button disabled="disabled">
|
<button disabled="disabled">
|
||||||
<span className="icon icon-loading" />
|
<span className="icon icon-loading" />
|
||||||
|
|
||||||
{this.state.loading_status === 'processing' ? '处理' : '上传'}
|
{loading_status === 'processing' ? '处理' : '上传'}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button type="submit">
|
<button type="submit">
|
||||||
@@ -678,7 +692,7 @@ export class PostForm extends Component {
|
|||||||
{this.state.img_tip}
|
{this.state.img_tip}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{this.state.preview ? (
|
{preview ? (
|
||||||
<div className="post-preview">
|
<div className="post-preview">
|
||||||
<HighlightedMarkdown
|
<HighlightedMarkdown
|
||||||
text={this.state.text}
|
text={this.state.text}
|
||||||
@@ -705,6 +719,31 @@ export class PostForm extends Component {
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
this.setState({ has_poll: !has_poll });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{has_poll ? '取消' : '添加'}投票
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{has_poll && (
|
||||||
|
<div className="post-form-poll-options">
|
||||||
|
<h6>投票选项</h6>
|
||||||
|
{poll_options.map( (option, idx) => (
|
||||||
|
<input
|
||||||
|
key={idx}
|
||||||
|
type="text"
|
||||||
|
value={option}
|
||||||
|
placeholder="输入以添加选项..."
|
||||||
|
onChange={(e) => this.on_poll_option_change_bound(e, idx)}
|
||||||
|
maxLength="32"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<br /><br /><br />
|
||||||
<p>
|
<p>
|
||||||
<small>
|
<small>
|
||||||
请遵守
|
请遵守
|
||||||
|
|||||||
@@ -201,4 +201,23 @@ export const API = {
|
|||||||
);
|
);
|
||||||
return handle_response(response);
|
return handle_response(response);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
add_vote: async (vote, pid, token) => {
|
||||||
|
let data = new URLSearchParams([
|
||||||
|
['vote', vote],
|
||||||
|
['pid', pid]
|
||||||
|
]);
|
||||||
|
let response = await fetch(
|
||||||
|
API_BASE + '/vote',
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
'User-Token': token,
|
||||||
|
},
|
||||||
|
body: data,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return handle_response(response, true);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
12
yarn.lock
12
yarn.lock
@@ -1874,6 +1874,11 @@ alphanum-sort@^1.0.0:
|
|||||||
resolved "https://registry.npm.taobao.org/alphanum-sort/download/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
|
resolved "https://registry.npm.taobao.org/alphanum-sort/download/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
|
||||||
integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=
|
integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=
|
||||||
|
|
||||||
|
animate.css@^3.7.0:
|
||||||
|
version "3.7.2"
|
||||||
|
resolved "https://registry.npm.taobao.org/animate.css/download/animate.css-3.7.2.tgz#e73e0d50e92cb1cfef1597d9b38a9481020e08ea"
|
||||||
|
integrity sha1-5z4NUOkssc/vFZfZs4qUgQIOCOo=
|
||||||
|
|
||||||
ansi-colors@^3.0.0:
|
ansi-colors@^3.0.0:
|
||||||
version "3.2.4"
|
version "3.2.4"
|
||||||
resolved "https://registry.npm.taobao.org/ansi-colors/download/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
|
resolved "https://registry.npm.taobao.org/ansi-colors/download/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
|
||||||
@@ -8818,6 +8823,13 @@ react-is@^16.8.1, react-is@^16.8.4:
|
|||||||
resolved "https://registry.npm.taobao.org/react-is/download/react-is-16.13.1.tgz?cache=0&sync_timestamp=1598612913326&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-is%2Fdownload%2Freact-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.npm.taobao.org/react-is/download/react-is-16.13.1.tgz?cache=0&sync_timestamp=1598612913326&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-is%2Fdownload%2Freact-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
integrity sha1-eJcppNw23imZ3BVt1sHZwYzqVqQ=
|
integrity sha1-eJcppNw23imZ3BVt1sHZwYzqVqQ=
|
||||||
|
|
||||||
|
react-polls@^1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.npmmirror.com/react-polls/download/react-polls-1.2.0.tgz#4d2e9802efe7e83ee8b37ae3d1368f7bf7869675"
|
||||||
|
integrity sha1-TS6YAu/n6D7os3rj0TaPe/eGlnU=
|
||||||
|
dependencies:
|
||||||
|
animate.css "^3.7.0"
|
||||||
|
|
||||||
react-scripts@^3.4.1:
|
react-scripts@^3.4.1:
|
||||||
version "3.4.3"
|
version "3.4.3"
|
||||||
resolved "https://registry.npm.taobao.org/react-scripts/download/react-scripts-3.4.3.tgz#21de5eb93de41ee92cd0b85b0e1298d0bb2e6c51"
|
resolved "https://registry.npm.taobao.org/react-scripts/download/react-scripts-3.4.3.tgz#21de5eb93de41ee92cd0b85b0e1298d0bb2e6c51"
|
||||||
|
|||||||
Reference in New Issue
Block a user