forked from newthuhole/hole_thu_frontend
add post form
This commit is contained in:
19
src/Flows.js
19
src/Flows.js
@@ -131,8 +131,13 @@ class FlowItemRow extends PureComponent {
|
||||
})
|
||||
.then((res)=>res.json())
|
||||
.then((json)=>{
|
||||
if(json.code!==0 && (!json.msg || json.msg!=='已经关注过辣'))
|
||||
throw new Error(json);
|
||||
if(json.code!==0) {
|
||||
if(json.msg && json.msg==='已经关注过辣') {}
|
||||
else {
|
||||
if(json.msg) alert(json.msg);
|
||||
throw new Error(json);
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
attention: next_attention,
|
||||
@@ -164,8 +169,8 @@ class FlowItemRow extends PureComponent {
|
||||
this.toggle_attention(this.show_sidebar.bind(this));
|
||||
}}>
|
||||
{this.state.attention ?
|
||||
<span><span className="icon icon-star-ok" />已关注</span> :
|
||||
<span><span className="icon icon-star" />未关注</span>
|
||||
<span><span className="icon icon-star-ok" /> 已关注</span> :
|
||||
<span><span className="icon icon-star" /> 未关注</span>
|
||||
}
|
||||
</a>
|
||||
</span>
|
||||
@@ -382,7 +387,11 @@ export class Flow extends PureComponent {
|
||||
<a onClick={()=>{this.load_page(this.state.loaded_pages+1)}}>重新加载</a>
|
||||
</div>
|
||||
}
|
||||
<TitleLine text={this.state.loading_status==='loading' ? 'Loading...' : '© xmcp'} />
|
||||
<TitleLine text={
|
||||
this.state.loading_status==='loading' ?
|
||||
<span><span className="icon icon-loading" /> Loading...</span> :
|
||||
'© xmcp'
|
||||
} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ export function Sidebar(props) {
|
||||
<a onClick={props.do_close}>×</a>
|
||||
{props.title}
|
||||
</p>
|
||||
<br />
|
||||
{props.content}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
41
src/Title.js
41
src/Title.js
@@ -1,5 +1,5 @@
|
||||
import React, {Component, PureComponent} from 'react';
|
||||
import {LoginForm} from './UserAction';
|
||||
import {LoginForm, PostForm} from './UserAction';
|
||||
import {TokenCtx} from './UserAction';
|
||||
|
||||
import './Title.css';
|
||||
@@ -112,15 +112,30 @@ class ControlBar extends PureComponent {
|
||||
<input value={this.state.search_text} placeholder="搜索 或 #PID"
|
||||
onChange={this.on_change_bound} onKeyPress={this.on_keypress_bound}
|
||||
/>
|
||||
<a className="control-btn" onClick={()=>{this.props.show_sidebar('登录',<LoginForm />)}}>
|
||||
<span className={'icon icon-'+(token ? 'login-ok' : 'login')} />
|
||||
</a>
|
||||
<a className="control-btn" onClick={()=>{this.props.show_sidebar(
|
||||
'关于 P大树洞(非官方) 网页版',
|
||||
HELP_TEXT
|
||||
)}}>
|
||||
<span className="icon icon-help" />
|
||||
<a className="control-btn" onClick={()=>{
|
||||
this.props.show_sidebar(
|
||||
'P大树洞(非官方)网页版',
|
||||
<div>
|
||||
<LoginForm />
|
||||
{HELP_TEXT}
|
||||
</div>
|
||||
)
|
||||
}}>
|
||||
<span className={'icon icon-'+(token ? 'about' : 'login')} />
|
||||
</a>
|
||||
{!!token &&
|
||||
<a className="control-btn" onClick={()=>{
|
||||
this.props.show_sidebar(
|
||||
'发表树洞',
|
||||
<PostForm token={token} on_complete={()=>{
|
||||
this.props.show_sidebar('',null);
|
||||
this.do_refresh();
|
||||
}} />
|
||||
)
|
||||
}}>
|
||||
<span className="icon icon-plus" />
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
)}</TokenCtx.Consumer>
|
||||
)
|
||||
@@ -131,7 +146,13 @@ export function Title(props) {
|
||||
return (
|
||||
<div className="title-bar">
|
||||
<div className="aux-margin">
|
||||
<p className="title centered-line">P大树洞</p>
|
||||
<p className="title centered-line">
|
||||
P大树洞
|
||||
|
||||
<a href="https://github.com/xmcp/ashole" target="_blank">
|
||||
<span className="icon icon-github" />
|
||||
</a>
|
||||
</p>
|
||||
<ControlBar show_sidebar={props.show_sidebar} set_mode={props.set_mode} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
margin: 1em 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-form button {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.reply-form {
|
||||
display: flex;
|
||||
}
|
||||
@@ -17,5 +15,27 @@
|
||||
height: 5em;
|
||||
}
|
||||
.reply-form button {
|
||||
flex: 0 0 50px;
|
||||
flex: 0 0 3em;
|
||||
}
|
||||
|
||||
.post-form-bar {
|
||||
line-height: 2em;
|
||||
display: flex;
|
||||
}
|
||||
.post-form-bar label {
|
||||
flex: 1;
|
||||
}
|
||||
@media screen and (max-width: 600px) {
|
||||
.post-form-bar input[type=file] {
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
.post-form-bar button {
|
||||
flex: 0 0 8em;
|
||||
}
|
||||
.post-form textarea {
|
||||
resize: vertical;
|
||||
width: 100%;
|
||||
min-height: 5em;
|
||||
height: 20em;
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import './UserAction.css';
|
||||
|
||||
import {API_BASE} from './Common';
|
||||
const LOGIN_BASE=window.location.protocol==='https:' ? '/login_proxy' : 'http://www.pkuhelper.com/services/login';
|
||||
const MAX_IMG_PX=1000;
|
||||
const MAX_IMG_FILESIZE=100000;
|
||||
|
||||
export const TokenCtx=React.createContext({
|
||||
value: null,
|
||||
@@ -42,8 +44,10 @@ export class LoginForm extends Component {
|
||||
})
|
||||
.then((res)=>res.json())
|
||||
.then((json)=>{
|
||||
if(json.code!==0)
|
||||
if(json.code!==0) {
|
||||
if(json.msg) alert(json.msg);
|
||||
throw new Error(json);
|
||||
}
|
||||
|
||||
set_token(json.token);
|
||||
alert(`成功以 ${json.name} 的身份登录`);
|
||||
@@ -63,8 +67,8 @@ export class LoginForm extends Component {
|
||||
render() {
|
||||
return (
|
||||
<TokenCtx.Consumer>{(token)=>
|
||||
<div className="login-form">
|
||||
<form onSubmit={(e)=>this.do_login(e,token.set_value)} className="box">
|
||||
<div className="login-form box">
|
||||
<form onSubmit={(e)=>this.do_login(e,token.set_value)}>
|
||||
<p>{token.value ?
|
||||
<span><b>您已登录。</b>Token: <code>{token.value||'(null)'}</code></span> :
|
||||
'登录后可以使用关注、回复等功能'
|
||||
@@ -83,19 +87,23 @@ export class LoginForm extends Component {
|
||||
</p>
|
||||
<p>
|
||||
{this.state.loading_status==='loading' ?
|
||||
<button disabled="disbled">正在登录</button> :
|
||||
<button type="submit">登录</button>
|
||||
<button disabled="disbled">
|
||||
<span className="icon icon-loading" />
|
||||
正在登录
|
||||
</button> :
|
||||
<button type="submit">
|
||||
<span className="icon icon-login" />
|
||||
登录
|
||||
</button>
|
||||
}
|
||||
<button type="button" onClick={()=>{token.set_value(null);}}>退出</button>
|
||||
</p>
|
||||
</form>
|
||||
<div className="box">
|
||||
<ul>
|
||||
<li>我们不会记录您的密码和个人信息</li>
|
||||
<li><b>请勿泄露 Token</b>,它代表您的登录状态,与您的账户唯一对应且泄露后无法重置</li>
|
||||
<li>如果您不愿输入密码,可以直接修改 <code>localStorage['TOKEN']</code></li>
|
||||
</ul>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
}</TokenCtx.Consumer>
|
||||
)
|
||||
@@ -121,7 +129,6 @@ export class ReplyForm extends Component {
|
||||
|
||||
on_submit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if(this.state.loading_status==='loading')
|
||||
return;
|
||||
this.setState({
|
||||
@@ -142,8 +149,10 @@ export class ReplyForm extends Component {
|
||||
})
|
||||
.then((res)=>res.json())
|
||||
.then((json)=>{
|
||||
if(json.code!==0)
|
||||
if(json.code!==0) {
|
||||
if(json.msg) alert(json.msg);
|
||||
throw new Error(json);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
loading_status: 'done',
|
||||
@@ -163,16 +172,175 @@ export class ReplyForm extends Component {
|
||||
|
||||
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 onSubmit={this.on_submit.bind(this)} className="reply-form box">
|
||||
<SafeTextarea ref={this.area_ref} id={this.props.pid} on_change={this.on_change_bound} />
|
||||
{this.state.loading_status==='loading' ?
|
||||
<button disabled="disabled">
|
||||
<span className="icon icon-loading" />
|
||||
</button> :
|
||||
<button type="submit">
|
||||
<span className="icon icon-send" />
|
||||
</button>
|
||||
}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class PostForm extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state={
|
||||
text: '',
|
||||
loading_status: 'done',
|
||||
};
|
||||
this.img_ref=React.createRef();
|
||||
this.area_ref=React.createRef();
|
||||
this.on_change_bound=this.on_change.bind(this);
|
||||
}
|
||||
|
||||
on_change(value) {
|
||||
this.setState({
|
||||
text: value,
|
||||
});
|
||||
}
|
||||
|
||||
do_post(text,img) {
|
||||
let data=new URLSearchParams();
|
||||
data.append('action','dopost');
|
||||
data.append('text',this.state.text);
|
||||
data.append('type',img ? 'image' : 'text');
|
||||
data.append('token',this.props.token);
|
||||
if(img)
|
||||
data.append('data',img);
|
||||
|
||||
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) {
|
||||
if(json.msg) alert(json.msg);
|
||||
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('发表失败');
|
||||
this.setState({
|
||||
loading_status: 'done',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
proc_img(file) { // http://pkuhole.chenpong.com/
|
||||
return new Promise((resolve,reject)=>{
|
||||
function return_url(url) {
|
||||
const idx=url.indexOf(';base64,');
|
||||
if(idx===-1)
|
||||
throw new Error('img not base64 encoded');
|
||||
|
||||
resolve(url.substr(idx+8));
|
||||
}
|
||||
|
||||
let reader=new FileReader();
|
||||
reader.onload=((event)=>{ // check size
|
||||
const url=event.target.result;
|
||||
const image = new Image();
|
||||
image.src=url;
|
||||
|
||||
image.onload=(()=>{
|
||||
let width=image.width;
|
||||
let height=image.height;
|
||||
if(width>MAX_IMG_PX) {
|
||||
height=height*MAX_IMG_PX/width;
|
||||
width=MAX_IMG_PX;
|
||||
}
|
||||
if(height>MAX_IMG_PX) {
|
||||
width=width*MAX_IMG_PX/height;
|
||||
height=MAX_IMG_PX;
|
||||
}
|
||||
|
||||
</form>
|
||||
</div>
|
||||
let canvas=document.createElement('canvas');
|
||||
let ctx=canvas.getContext('2d');
|
||||
canvas.width=width;
|
||||
canvas.height=height;
|
||||
ctx.drawImage(image,0,0,width,height);
|
||||
|
||||
for(let quality=.9;quality>0;quality-=0.1) {
|
||||
const url=canvas.toDataURL('image/jpeg',quality);
|
||||
console.log('quality',quality,'size',url.length);
|
||||
if(url.length<=MAX_IMG_FILESIZE) {
|
||||
console.log('chosen img quality',quality);
|
||||
return return_url(url);
|
||||
}
|
||||
}
|
||||
// else
|
||||
alert('图片过大,无法上传');
|
||||
reject('img too large');
|
||||
});
|
||||
});
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
on_submit(event) {
|
||||
event.preventDefault();
|
||||
if(this.state.loading_status==='loading')
|
||||
return;
|
||||
if(this.img_ref.current.files.length) {
|
||||
this.setState({
|
||||
loading_status: 'processing',
|
||||
});
|
||||
this.proc_img(this.img_ref.current.files[0])
|
||||
.then((img)=>{
|
||||
this.setState({
|
||||
loading_status: 'loading',
|
||||
});
|
||||
this.do_post(this.state.text,img);
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
loading_status: 'loading',
|
||||
});
|
||||
this.do_post(this.state.text,null);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
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/*"
|
||||
{...this.state.loading_status!=='done' ? {disabled: true} : {}} />
|
||||
</label>
|
||||
{this.state.loading_status!=='done' ?
|
||||
<button disabled="disabled">
|
||||
<span className="icon icon-loading" />
|
||||
正在{this.state.loading_status==='processing' ? '处理' : '上传'}
|
||||
</button> :
|
||||
<button type="submit">
|
||||
<span className="icon icon-send" />
|
||||
发表
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<br />
|
||||
<SafeTextarea ref={this.area_ref} id="new_post" on_change={this.on_change_bound} />
|
||||
</form>
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user