Browse Source

add reply form

dev
xmcp 7 years ago
parent
commit
75a35f5af6
  1. 41
      src/Common.js
  2. 6
      src/Flows.css
  3. 21
      src/Flows.js
  4. 2
      src/Title.js
  5. 13
      src/UserAction.css
  6. 82
      src/UserAction.js
  7. 12
      src/index.css

41
src/Common.js

@ -9,6 +9,8 @@ import './Common.css';
const chinese_format=buildFormatter(chineseStrings);
export const API_BASE=window.location.protocol==='https:' ? '/api_proxy' : 'http://www.pkuhelper.com/services/pkuhole';
const PID_RE=/(^|[^\d])([1-9]\d{4,5})(?!\d)/g;
const NICKNAME_RE=/(^|[^A-Za-z])((?:(?:Angry|Baby|Crazy|Diligent|Excited|Fat|Greedy|Hungry|Interesting|Japanese|Kind|Little|Magic|Naïve|Old|Powerful|Quiet|Rich|Superman|THU|Undefined|Valuable|Wifeless|Xiangbuchulai|Young|Zombie)\s)?(?:Alice|Bob|Carol|Dave|Eve|Francis|Grace|Hans|Isabella|Jason|Kate|Louis|Margaret|Nathan|Olivia|Paul|Queen|Richard|Susan|Thomas|Uma|Vivian|Winnie|Xander|Yasmine|Zach)|You Win|洞主)(?![A-Za-z])/gi;
@ -54,3 +56,42 @@ export class HighlightedText extends PureComponent {
)
}
}
window.TEXTAREA_BACKUP={};
export class SafeTextarea extends Component {
constructor(props) {
super(props);
this.state={
text: window.TEXTAREA_BACKUP[props.id]||'',
};
this.on_change_bound=this.on_change.bind(this);
this.clear=this.clear.bind(this);
this.area_ref=React.createRef();
this.change_callback=props.on_change;
}
componentWillUnmount() {
window.TEXTAREA_BACKUP[this.props.id]=this.state.text;
this.change_callback(this.state.text);
}
on_change(event) {
this.setState({
text: event.target.value,
});
this.change_callback(event.target.value);
}
clear() {
this.setState({
text: '',
});
}
render() {
return (
<textarea ref={this.area_ref} onChange={this.on_change_bound} value={this.state.text} />
)
}
}

6
src/Flows.css

@ -97,11 +97,13 @@
.flow-item-row p.img {
text-align: center;
}
p.img img {
.flow-item-row p.img img {
max-width: 100%;
max-height: 100vh;
box-shadow: 0 1px 5px rgba(0,0,0,.4);
}
.left-container .flow-item-row p.img img {
max-height: 80vh;
}
.box-header-badge {
float: right;

21
src/Flows.js

@ -1,14 +1,14 @@
import React, {Component, PureComponent} from 'react';
import {ColorPicker} from './color_picker';
import {Time, TitleLine, HighlightedText} from './Common.js';
import {Time, TitleLine, HighlightedText} from './Common';
import './Flows.css';
import LazyLoad from 'react-lazyload';
import {AudioWidget} from './AudioWidget.js';
import {TokenCtx} from './UserAction';
import {AudioWidget} from './AudioWidget';
import {TokenCtx, ReplyForm} from './UserAction';
import {API_BASE} from './Common';
const IMAGE_BASE='http://www.pkuhelper.com/services/pkuhole/images/';
const AUDIO_BASE='/audio_proxy/';
const API_BASE=window.location.protocol==='https:' ? '/api_proxy' : 'http://www.pkuhelper.com/services/pkuhole';
const SEARCH_PAGESIZE=50;
const CLICKABLE_TAGS={a: true, audio: true};
@ -145,15 +145,17 @@ class FlowItemRow extends PureComponent {
});
}
reload_sidebar() {
this.props.show_sidebar('帖子详情',<p className="box box-tip">加载中</p>);
this.load_replies(this.show_sidebar.bind(this));
}
show_sidebar() {
this.props.show_sidebar(
'帖子详情',
<div className="flow-item-row sidebar-flow-item">
<div className="box box-tip">
<a onClick={()=>{
this.props.show_sidebar('帖子详情',<p className="box box-tip">加载中</p>);
this.load_replies(this.show_sidebar.bind(this));
}}>刷新回复</a>
<a onClick={this.reload_sidebar.bind(this)}>刷新回复</a>
{this.props.token &&
<span>
&nbsp;/&nbsp;
@ -175,6 +177,9 @@ class FlowItemRow extends PureComponent {
<Reply info={reply} color_picker={this.color_picker} />
</LazyLoad>
))}
{this.props.token &&
<ReplyForm pid={this.state.info.pid} token={this.props.token} on_complete={this.reload_sidebar.bind(this)} />
}
</div>
);
}

2
src/Title.js

@ -12,7 +12,7 @@ const HELP_TEXT=(
<li>在列表中点击帖子可以展开全部回复</li>
<li>在搜索框输入 #472865 等可以查看指定 ID 的树洞</li>
<li>新的帖子会在左上角显示一个圆点</li>
<li>登录后可以关注帖子</li>
<li>本网站支持 3D Touch重压屏幕可以快速返回 / 刷新树洞</li>
<li>请注意使用 HTTPS 访问本站可能会<b>大幅减慢</b></li>
<li>自定义背景图片请修改 <code>localStorage['REPLACE_ERIRI_WITH_URL']</code></li>
</ul>

13
src/UserAction.css

@ -5,4 +5,17 @@
.login-form button {
min-width: 100px;
}
.reply-form {
display: flex;
}
.reply-form textarea {
resize: vertical;
flex: 1;
min-height: 3em;
height: 5em;
}
.reply-form button {
flex: 0 0 50px;
}

82
src/UserAction.js

@ -1,7 +1,9 @@
import React, {Component, PureComponent} from 'react';
import {SafeTextarea} from './Common';
import './UserAction.css';
import {API_BASE} from './Common';
const LOGIN_BASE=window.location.protocol==='https:' ? '/login_proxy' : 'http://www.pkuhelper.com/services/login';
export const TokenCtx=React.createContext({
@ -63,7 +65,10 @@ export class LoginForm extends Component {
<TokenCtx.Consumer>{(token)=>
<div className="login-form">
<form onSubmit={(e)=>this.do_login(e,token.set_value)} className="box">
<p>Token: <code>{token.value||'(null)'}</code></p>
<p>{token.value ?
<span><b>您已登录</b>Token: <code>{token.value||'(null)'}</code></span> :
'登录后可以使用关注、回复等功能'
}</p>
<p>
<label>
学号
@ -95,4 +100,79 @@ export class LoginForm extends Component {
}</TokenCtx.Consumer>
)
}
}
export class ReplyForm extends Component {
constructor(props) {
super(props);
this.state={
text: '',
loading_status: 'done',
};
this.on_change_bound=this.on_change.bind(this);
this.area_ref=React.createRef();
}
on_change(value) {
this.setState({
text: value,
});
}
on_submit(event) {
event.preventDefault();
if(this.state.loading_status==='loading')
return;
this.setState({
loading_status: 'loading',
});
let data=new URLSearchParams();
data.append('action','docomment');
data.append('pid',this.props.pid);
data.append('text',this.state.text);
data.append('token',this.props.token);
fetch(API_BASE+'/api.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: data,
})
.then((res)=>res.json())
.then((json)=>{
if(json.code!==0)
throw new Error(json);
this.setState({
loading_status: 'done',
text: '',
});
this.area_ref.current.clear();
this.props.on_complete();
})
.catch((e)=>{
console.trace(e);
alert('回复失败\n(树洞服务器经常抽风,其实有可能已经回复上了,不妨点“刷新回复”看一看)');
this.setState({
loading_status: 'done',
});
});
}
render() {
return (
<div className="box">
<form onSubmit={this.on_submit.bind(this)} className="reply-form">
<SafeTextarea ref={this.area_ref} id={this.props.pid} on_change={this.on_change_bound} />
{this.state.loading_status==='loading' ?
<button disabled="disabled">正在回复</button> :
<button type="submit">回复</button>
}
</form>
</div>
)
}
}

12
src/index.css

@ -1,7 +1,6 @@
body {
margin: 0;
padding: 0;
font-family: '微软雅黑', 'Microsoft YaHei', sans-serif;
background-size: cover;
overflow-x: hidden;
}
@ -10,6 +9,10 @@ body::-webkit-scrollbar {
display: none;
}
body, textarea, pre {
font-family: '微软雅黑', 'Microsoft YaHei', sans-serif;
}
* {
box-sizing: border-box;
word-wrap: break-word;
@ -26,11 +29,13 @@ a {
cursor: pointer;
}
input {
padding: 0 1em;
input, textarea {
border-radius: 5px;
border: 1px solid black;
outline: none;
}
input {
padding: 0 1em;
line-height: 2em;
}
@ -40,7 +45,6 @@ audio {
pre {
white-space: pre-wrap;
font-family: '微软雅黑', 'Microsoft YaHei', sans-serif;
}
button, .button {

Loading…
Cancel
Save