Browse Source

支持投票

pull/16/head
hole-thu 4 years ago
parent
commit
efeb2e5c05
  1. 3
      package.json
  2. 4
      src/Flows.css
  3. 50
      src/Flows.js
  4. 13
      src/UserAction.css
  5. 97
      src/UserAction.js
  6. 19
      src/flows_api.js
  7. 12
      yarn.lock

3
package.json

@ -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",

4
src/Flows.css

@ -198,6 +198,10 @@
overflow-y: hidden;
}
.box-poll.disabled {
pointer-events: none;
}
.box-id {
color: #666666;
}

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>
);

13
src/UserAction.css

@ -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;
}

97
src/UserAction.js

@ -284,10 +284,9 @@ 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;
if (!saved_attentions.includes(pid)) {
saved_attentions.unshift(pid)
@ -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" />
&nbsp;
{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>
请遵守
@ -716,7 +755,7 @@ export class PostForm extends Component {
</p>
<p>
<small>
插入图片请使用图片外链Markdown格式 ![](图片链接) 支持动图支持多图推荐的图床
插入图片请使用图片外链Markdown格式 ![](图片链接) 支持动图支持多图推荐的图床
<a href="https://imgchr.com/" target="_blank">
路过图床
</a>

19
src/flows_api.js

@ -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

@ -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"

Loading…
Cancel
Save