支持投票
This commit is contained in:
@@ -17,7 +17,8 @@
|
||||
"react-scripts": "^3.4.1",
|
||||
"react-timeago": "^4.4.0",
|
||||
"typescript": "^4.0.2",
|
||||
"workbox-sw": "^5.1.3"
|
||||
"workbox-sw": "^5.1.3",
|
||||
"react-polls": "^1.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
||||
@@ -198,6 +198,10 @@
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.box-poll.disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.box-id {
|
||||
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 { cache } from './cache';
|
||||
import { save_attentions } from './Attention'
|
||||
import Poll from 'react-polls';
|
||||
|
||||
/*
|
||||
const IMAGE_BASE = 'https://thimg.yecdn.com/';
|
||||
@@ -159,7 +160,7 @@ class FlowItem extends PureComponent {
|
||||
this.state = {
|
||||
hot_score: props.info.hot_score || 0,
|
||||
cw: props.info.cw || '',
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
copy_link(event) {
|
||||
@@ -199,7 +200,8 @@ class FlowItem extends PureComponent {
|
||||
render() {
|
||||
const {
|
||||
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;
|
||||
const { cw, hot_score } = this.state;
|
||||
return (
|
||||
@@ -348,6 +350,19 @@ class FlowItem extends PureComponent {
|
||||
)}
|
||||
{/*{info.type==='audio' && <AudioWidget src={AUDIO_BASE+info.url} />}*/}
|
||||
</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) && (
|
||||
<p className="box-footer">
|
||||
最新回复{' '}
|
||||
@@ -458,21 +473,16 @@ class FlowSidebar extends PureComponent {
|
||||
}
|
||||
|
||||
toggle_attention() {
|
||||
this.setState({
|
||||
loading_status: 'loading',
|
||||
});
|
||||
const prev_info = this.state.info;
|
||||
const pid = prev_info.pid;
|
||||
API.set_attention(pid, !this.state.attention, this.props.token)
|
||||
.then((json) => {
|
||||
this.setState({
|
||||
loading_status: 'done',
|
||||
attention: json.attention,
|
||||
info: Object.assign({}, prev_info, {
|
||||
likenum: ''+json.likenum,
|
||||
}),
|
||||
});
|
||||
console.log(json);
|
||||
|
||||
let saved_attentions = window.saved_attentions;
|
||||
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() {
|
||||
let reason = prompt(`举报 #${this.state.info.pid} 的理由:`);
|
||||
if (reason !== null) {
|
||||
@@ -543,7 +575,6 @@ class FlowSidebar extends PureComponent {
|
||||
|
||||
make_do_delete(token, on_complete=null) {
|
||||
const do_delete = (type, id) => {
|
||||
console.log('del', type, id, token);
|
||||
let note = prompt(`将删除${type}=${id}, 备注:`, '(无)');
|
||||
if (note !== null) {
|
||||
API.del(type, id, note, token)
|
||||
@@ -562,7 +593,6 @@ class FlowSidebar extends PureComponent {
|
||||
|
||||
make_do_edit_cw(token) {
|
||||
const do_edit_cw = (cw, id) => {
|
||||
console.log('edit cw', cw);
|
||||
API.update_cw(cw, id, token)
|
||||
.then((json) => {
|
||||
alert('已更新\n刷新列表显示新版本');
|
||||
@@ -577,7 +607,6 @@ class FlowSidebar extends PureComponent {
|
||||
|
||||
make_do_edit_score(token) {
|
||||
const do_edit_score = (score, id) => {
|
||||
console.log('edit score', score);
|
||||
API.update_score(score, id, token)
|
||||
.then((json) => {
|
||||
console.log('已更新');
|
||||
@@ -636,6 +665,7 @@ class FlowSidebar extends PureComponent {
|
||||
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_score={this.make_do_edit_score(this.props.token)}
|
||||
do_vote={this.do_vote.bind(this)}
|
||||
/>
|
||||
</ClickHandler>
|
||||
);
|
||||
|
||||
@@ -116,3 +116,16 @@
|
||||
margin-bottom: 5px;
|
||||
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((json) => {
|
||||
if (json.code !== 0) {
|
||||
if (json.msg) alert(json.msg);
|
||||
throw new Error(JSON.stringify(json));
|
||||
throw new Error(json.msg);
|
||||
}
|
||||
|
||||
let saved_attentions = window.saved_attentions;
|
||||
@@ -376,12 +375,15 @@ export class PostForm extends Component {
|
||||
loading_status: 'done',
|
||||
img_tip: null,
|
||||
preview: false,
|
||||
has_poll: !!window.POLL_BACKUP,
|
||||
poll_options: JSON.parse(window.POLL_BACKUP || '[""]'),
|
||||
};
|
||||
this.img_ref = React.createRef();
|
||||
this.area_ref = React.createRef();
|
||||
this.on_change_bound = this.on_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_poll_option_change_bound = this.on_poll_option_change.bind(this);
|
||||
this.on_img_change_bound = this.on_img_change.bind(this);
|
||||
this.color_picker = new ColorPicker();
|
||||
}
|
||||
@@ -391,8 +393,10 @@ export class PostForm extends Component {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.CW_BACKUP = this.state.cw;
|
||||
window.AS_BACKUP = this.state.allow_search;
|
||||
const { cw, allow_search, has_poll, poll_options } = this.state;
|
||||
window.CW_BACKUP = cw;
|
||||
window.AS_BACKUP = allow_search;
|
||||
window.POLL_BACKUP = has_poll ? JSON.stringify(poll_options) : null;
|
||||
}
|
||||
|
||||
on_allow_search_change(event) {
|
||||
@@ -413,13 +417,21 @@ export class PostForm extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
do_post(text, img) {
|
||||
do_post() {
|
||||
let data = new URLSearchParams();
|
||||
const { cw, text, allow_search, has_poll, poll_options } = this.state;
|
||||
data.append('cw', this.state.cw);
|
||||
data.append('text', this.state.text);
|
||||
data.append('allow_search', this.state.allow_search ? '1' : '');
|
||||
data.append('type', img ? 'image' : 'text');
|
||||
if (img) data.append('data', img);
|
||||
//data.append('type', img ? 'image' : 'text');
|
||||
//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', {
|
||||
method: 'POST',
|
||||
@@ -432,8 +444,7 @@ export class PostForm extends Component {
|
||||
.then(get_json)
|
||||
.then((json) => {
|
||||
if (json.code !== 0) {
|
||||
if (json.msg) alert(json.msg);
|
||||
throw new Error(JSON.stringify(json));
|
||||
throw new Error(json.msg);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
@@ -444,6 +455,7 @@ export class PostForm extends Component {
|
||||
this.area_ref.current.clear();
|
||||
this.props.on_complete();
|
||||
window.CW_BACKUP = '';
|
||||
window.POLL_BACKUP = null;
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
@@ -593,7 +605,7 @@ export class PostForm extends Component {
|
||||
this.setState({
|
||||
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() {
|
||||
const { has_poll, poll_options, preview, loading_status } = this.state;
|
||||
return (
|
||||
<form onSubmit={this.on_submit.bind(this)} className="post-form box">
|
||||
<div className="post-form-bar">
|
||||
{/*
|
||||
<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 ? (
|
||||
{preview ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
@@ -642,11 +656,11 @@ export class PostForm extends Component {
|
||||
</button>
|
||||
)}
|
||||
|
||||
{this.state.loading_status !== 'done' ? (
|
||||
{loading_status !== 'done' ? (
|
||||
<button disabled="disabled">
|
||||
<span className="icon icon-loading" />
|
||||
|
||||
{this.state.loading_status === 'processing' ? '处理' : '上传'}
|
||||
{loading_status === 'processing' ? '处理' : '上传'}
|
||||
</button>
|
||||
) : (
|
||||
<button type="submit">
|
||||
@@ -678,7 +692,7 @@ export class PostForm extends Component {
|
||||
{this.state.img_tip}
|
||||
</p>
|
||||
)}
|
||||
{this.state.preview ? (
|
||||
{preview ? (
|
||||
<div className="post-preview">
|
||||
<HighlightedMarkdown
|
||||
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>
|
||||
<small>
|
||||
请遵守
|
||||
|
||||
@@ -201,4 +201,23 @@ export const API = {
|
||||
);
|
||||
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"
|
||||
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:
|
||||
version "3.2.4"
|
||||
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"
|
||||
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:
|
||||
version "3.4.3"
|
||||
resolved "https://registry.npm.taobao.org/react-scripts/download/react-scripts-3.4.3.tgz#21de5eb93de41ee92cd0b85b0e1298d0bb2e6c51"
|
||||
|
||||
Reference in New Issue
Block a user