add reply form
This commit is contained in:
@@ -9,6 +9,8 @@ import './Common.css';
|
|||||||
|
|
||||||
const chinese_format=buildFormatter(chineseStrings);
|
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 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;
|
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} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -97,11 +97,13 @@
|
|||||||
.flow-item-row p.img {
|
.flow-item-row p.img {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
p.img img {
|
.flow-item-row p.img img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 100vh;
|
|
||||||
box-shadow: 0 1px 5px rgba(0,0,0,.4);
|
box-shadow: 0 1px 5px rgba(0,0,0,.4);
|
||||||
}
|
}
|
||||||
|
.left-container .flow-item-row p.img img {
|
||||||
|
max-height: 80vh;
|
||||||
|
}
|
||||||
|
|
||||||
.box-header-badge {
|
.box-header-badge {
|
||||||
float: right;
|
float: right;
|
||||||
|
|||||||
21
src/Flows.js
21
src/Flows.js
@@ -1,14 +1,14 @@
|
|||||||
import React, {Component, PureComponent} from 'react';
|
import React, {Component, PureComponent} from 'react';
|
||||||
import {ColorPicker} from './color_picker';
|
import {ColorPicker} from './color_picker';
|
||||||
import {Time, TitleLine, HighlightedText} from './Common.js';
|
import {Time, TitleLine, HighlightedText} from './Common';
|
||||||
import './Flows.css';
|
import './Flows.css';
|
||||||
import LazyLoad from 'react-lazyload';
|
import LazyLoad from 'react-lazyload';
|
||||||
import {AudioWidget} from './AudioWidget.js';
|
import {AudioWidget} from './AudioWidget';
|
||||||
import {TokenCtx} from './UserAction';
|
import {TokenCtx, ReplyForm} from './UserAction';
|
||||||
|
|
||||||
|
import {API_BASE} from './Common';
|
||||||
const IMAGE_BASE='http://www.pkuhelper.com/services/pkuhole/images/';
|
const IMAGE_BASE='http://www.pkuhelper.com/services/pkuhole/images/';
|
||||||
const AUDIO_BASE='/audio_proxy/';
|
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 SEARCH_PAGESIZE=50;
|
||||||
const CLICKABLE_TAGS={a: true, audio: true};
|
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() {
|
show_sidebar() {
|
||||||
this.props.show_sidebar(
|
this.props.show_sidebar(
|
||||||
'帖子详情',
|
'帖子详情',
|
||||||
<div className="flow-item-row sidebar-flow-item">
|
<div className="flow-item-row sidebar-flow-item">
|
||||||
<div className="box box-tip">
|
<div className="box box-tip">
|
||||||
<a onClick={()=>{
|
<a onClick={this.reload_sidebar.bind(this)}>刷新回复</a>
|
||||||
this.props.show_sidebar('帖子详情',<p className="box box-tip">加载中……</p>);
|
|
||||||
this.load_replies(this.show_sidebar.bind(this));
|
|
||||||
}}>刷新回复</a>
|
|
||||||
{this.props.token &&
|
{this.props.token &&
|
||||||
<span>
|
<span>
|
||||||
/
|
/
|
||||||
@@ -175,6 +177,9 @@ class FlowItemRow extends PureComponent {
|
|||||||
<Reply info={reply} color_picker={this.color_picker} />
|
<Reply info={reply} color_picker={this.color_picker} />
|
||||||
</LazyLoad>
|
</LazyLoad>
|
||||||
))}
|
))}
|
||||||
|
{this.props.token &&
|
||||||
|
<ReplyForm pid={this.state.info.pid} token={this.props.token} on_complete={this.reload_sidebar.bind(this)} />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const HELP_TEXT=(
|
|||||||
<li>在列表中点击帖子可以展开全部回复</li>
|
<li>在列表中点击帖子可以展开全部回复</li>
|
||||||
<li>在搜索框输入 #472865 等可以查看指定 ID 的树洞</li>
|
<li>在搜索框输入 #472865 等可以查看指定 ID 的树洞</li>
|
||||||
<li>新的帖子会在左上角显示一个圆点</li>
|
<li>新的帖子会在左上角显示一个圆点</li>
|
||||||
<li>登录后可以关注帖子</li>
|
<li>本网站支持 3D Touch,重压屏幕可以快速返回 / 刷新树洞</li>
|
||||||
<li>请注意:使用 HTTPS 访问本站可能会<b>大幅减慢</b>加载速度</li>
|
<li>请注意:使用 HTTPS 访问本站可能会<b>大幅减慢</b>加载速度</li>
|
||||||
<li>自定义背景图片请修改 <code>localStorage['REPLACE_ERIRI_WITH_URL']</code></li>
|
<li>自定义背景图片请修改 <code>localStorage['REPLACE_ERIRI_WITH_URL']</code></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -6,3 +6,16 @@
|
|||||||
.login-form button {
|
.login-form button {
|
||||||
min-width: 100px;
|
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;
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import React, {Component, PureComponent} from 'react';
|
import React, {Component, PureComponent} from 'react';
|
||||||
|
import {SafeTextarea} from './Common';
|
||||||
|
|
||||||
import './UserAction.css';
|
import './UserAction.css';
|
||||||
|
|
||||||
|
import {API_BASE} from './Common';
|
||||||
const LOGIN_BASE=window.location.protocol==='https:' ? '/login_proxy' : 'http://www.pkuhelper.com/services/login';
|
const LOGIN_BASE=window.location.protocol==='https:' ? '/login_proxy' : 'http://www.pkuhelper.com/services/login';
|
||||||
|
|
||||||
export const TokenCtx=React.createContext({
|
export const TokenCtx=React.createContext({
|
||||||
@@ -63,7 +65,10 @@ export class LoginForm extends Component {
|
|||||||
<TokenCtx.Consumer>{(token)=>
|
<TokenCtx.Consumer>{(token)=>
|
||||||
<div className="login-form">
|
<div className="login-form">
|
||||||
<form onSubmit={(e)=>this.do_login(e,token.set_value)} className="box">
|
<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>
|
<p>
|
||||||
<label>
|
<label>
|
||||||
学号:
|
学号:
|
||||||
@@ -96,3 +101,78 @@ export class LoginForm extends Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-family: '微软雅黑', 'Microsoft YaHei', sans-serif;
|
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
@@ -10,6 +9,10 @@ body::-webkit-scrollbar {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body, textarea, pre {
|
||||||
|
font-family: '微软雅黑', 'Microsoft YaHei', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
@@ -26,11 +29,13 @@ a {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input, textarea {
|
||||||
padding: 0 1em;
|
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
padding: 0 1em;
|
||||||
line-height: 2em;
|
line-height: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +45,6 @@ audio {
|
|||||||
|
|
||||||
pre {
|
pre {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
font-family: '微软雅黑', 'Microsoft YaHei', sans-serif;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button, .button {
|
button, .button {
|
||||||
|
|||||||
Reference in New Issue
Block a user