diff --git a/package.json b/package.json index 99baa98..2dc2759 100644 --- a/package.json +++ b/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", diff --git a/src/Flows.css b/src/Flows.css index 1e53ef6..036d959 100644 --- a/src/Flows.css +++ b/src/Flows.css @@ -198,6 +198,10 @@ overflow-y: hidden; } +.box-poll.disabled { + pointer-events: none; +} + .box-id { color: #666666; } diff --git a/src/Flows.js b/src/Flows.js index 94221a6..01f5d9d 100644 --- a/src/Flows.js +++ b/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' && }*/} + { info.poll && ( +
+ {})} + customStyles={{'theme': 'cyan'}} + noStorage={true} + vote={info.poll.vote} + /> +
+ )} {!!(attention && info.variant.latest_reply) && (

最新回复{' '} @@ -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)} /> ); diff --git a/src/UserAction.css b/src/UserAction.css index e7c49a4..53d2aea 100644 --- a/src/UserAction.css +++ b/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; +} diff --git a/src/UserAction.js b/src/UserAction.js index 9679a8b..fa04dea 100644 --- a/src/UserAction.js +++ b/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 (

- {/* - - */} - - {this.state.preview ? ( + {preview ? ( )} - {this.state.loading_status !== 'done' ? ( + {loading_status !== 'done' ? ( ) : ( + + {has_poll && ( +
+
投票选项
+ {poll_options.map( (option, idx) => ( + this.on_poll_option_change_bound(e, idx)} + maxLength="32" + /> + ))} +
+ )} +


请遵守 @@ -716,7 +755,7 @@ export class PostForm extends Component {

- 插入图片请使用图片外链,Markdown格式 ![](图片链接), 支持动图,支持多图。推荐的图床: + 插入图片请使用图片外链,Markdown格式 ![](图片链接), 支持动图,支持多图。推荐的图床: 路过图床 diff --git a/src/flows_api.js b/src/flows_api.js index a68ba25..765b1a7 100644 --- a/src/flows_api.js +++ b/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); + }, }; diff --git a/yarn.lock b/yarn.lock index 0e97252..204721a 100644 --- a/yarn.lock +++ b/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"