xmcp 7 years ago
parent
commit
fdb88eef2e
  1. 9
      src/Common.js
  2. 12
      src/Flows.css
  3. 66
      src/Flows.js
  4. 2
      src/Sidebar.css
  5. 52
      src/UserAction.js
  6. 2
      src/index.css

9
src/Common.js

@ -18,14 +18,17 @@ function pad2(x) {
return x<10 ? '0'+x : ''+x; return x<10 ? '0'+x : ''+x;
} }
export function format_time(time) {
return `${time.getMonth()+1}-${pad2(time.getDate())} ${time.getHours()}:${pad2(time.getMinutes())}:${pad2(time.getSeconds())}`;
}
export function Time(props) { export function Time(props) {
const time=new Date(props.stamp*1000); const time=new Date(props.stamp*1000);
return ( return (
<span> <span>
<TimeAgo date={time} formatter={chinese_format} /> <TimeAgo date={time} formatter={chinese_format} />
&nbsp; &nbsp;
{time.getMonth()+1}-{time.getDate()}&nbsp; {format_time(time)}
{time.getHours()}:{pad2(time.getMinutes())}
</span> </span>
); );
} }
@ -102,7 +105,7 @@ export function PromotionBar(props) {
return is_ios ? ( return is_ios ? (
<div className="box promotion-bar"> <div className="box promotion-bar">
<span className="icon icon-about" />&nbsp; <span className="icon icon-about" />&nbsp;
Safari 将本网站 <b>添加到主屏幕</b> Safari 将本网站 <b>添加到主屏幕</b>
</div> </div>
) : null; ) : null;
} }

12
src/Flows.css

@ -113,6 +113,10 @@
} }
} }
.box-header {
font-size: small;
}
.flow-item-row p.img { .flow-item-row p.img {
text-align: center; text-align: center;
} }
@ -129,10 +133,6 @@
margin: 0 .5em; margin: 0 .5em;
} }
.box-id {
opacity: .6;
}
.flow-item-dot { .flow-item-dot {
position: relative; position: relative;
top: calc(-.5em - 5px); top: calc(-.5em - 5px);
@ -143,4 +143,8 @@
border-radius: 50%; border-radius: 50%;
background-color: orange; background-color: orange;
box-shadow: 0 0 5px rgba(0,0,0,.4); box-shadow: 0 0 5px rgba(0,0,0,.4);
}
.box-content {
margin: .5em 0;
} }

66
src/Flows.js

@ -1,7 +1,7 @@
import React, {Component, PureComponent} from 'react'; import React, {Component, PureComponent} from 'react';
import copy from 'copy-to-clipboard'; import copy from 'copy-to-clipboard';
import {ColorPicker} from './color_picker'; import {ColorPicker} from './color_picker';
import {Time, TitleLine, HighlightedText} from './Common'; import {format_time, 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'; import {AudioWidget} from './AudioWidget';
@ -38,6 +38,7 @@ function load_single_meta(show_sidebar,token) {
<FlowSidebar <FlowSidebar
info={single.data} replies={replies.data} attention={replies.attention} info={single.data} replies={replies.data} attention={replies.attention}
token={token} show_sidebar={show_sidebar} color_picker={color_picker} token={token} show_sidebar={show_sidebar} color_picker={color_picker}
deletion_detect={localStorage['DELETION_DETECT']==='on'}
/> />
) )
}) })
@ -62,7 +63,9 @@ function Reply(props) {
<code className="box-id">#{props.info.cid}</code>&nbsp; <code className="box-id">#{props.info.cid}</code>&nbsp;
<Time stamp={props.info.timestamp} /> <Time stamp={props.info.timestamp} />
</div> </div>
<HighlightedText text={props.info.text} color_picker={props.color_picker} show_pid={props.show_pid} /> <div className="box-content">
<HighlightedText text={props.info.text} color_picker={props.color_picker} show_pid={props.show_pid} />
</div>
</div> </div>
); );
} }
@ -70,7 +73,12 @@ function Reply(props) {
function FlowItem(props) { function FlowItem(props) {
function copy_link(event) { function copy_link(event) {
event.preventDefault(); event.preventDefault();
copy(event.target.href); copy(
`${event.target.href}\n`+
`${format_time(new Date(props.info.timestamp*1000))} ${props.info.likenum}${props.info.reply}回复)\n`+
`${props.info.text}${props.info.type==='image'?' [图片]':props.info.type==='audio'?' [语音]':''}\n`+
props.replies.map((r)=>(r.text)).join('\n')
);
} }
return ( return (
@ -93,16 +101,18 @@ function FlowItem(props) {
&nbsp; &nbsp;
<Time stamp={props.info.timestamp} /> <Time stamp={props.info.timestamp} />
</div> </div>
<HighlightedText text={props.info.text} color_picker={props.color_picker} show_pid={props.show_pid} /> <div className="box-content">
{props.info.type==='image' && <HighlightedText text={props.info.text} color_picker={props.color_picker} show_pid={props.show_pid} />
<p className="img"> {props.info.type==='image' &&
{props.img_clickable ? <p className="img">
<a href={IMAGE_BASE+props.info.url} target="_blank"><img src={IMAGE_BASE+props.info.url} /></a> : {props.img_clickable ?
<img src={IMAGE_BASE+props.info.url} /> <a href={IMAGE_BASE+props.info.url} target="_blank"><img src={IMAGE_BASE+props.info.url} /></a> :
} <img src={IMAGE_BASE+props.info.url} />
</p> }
} </p>
{props.info.type==='audio' && <AudioWidget src={AUDIO_BASE+props.info.url} />} }
{props.info.type==='audio' && <AudioWidget src={AUDIO_BASE+props.info.url} />}
</div>
</div> </div>
); );
} }
@ -129,9 +139,6 @@ class FlowSidebar extends PureComponent {
.then((json)=>{ .then((json)=>{
this.setState((prev,props)=>({ this.setState((prev,props)=>({
replies: json.data, replies: json.data,
info: Object.assign({}, prev.info, {
reply: ''+json.data.length,
}),
attention: !!json.attention, attention: !!json.attention,
loading_status: 'done', loading_status: 'done',
}), ()=>{ }), ()=>{
@ -213,20 +220,20 @@ class FlowSidebar extends PureComponent {
return ( return (
<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">
{star_brush && {(star_brush && !!this.props.token) &&
<span> <span>
<a onClick={this.star_brush.bind(this)}>-</a> <a onClick={this.star_brush.bind(this)}>-</a>
&nbsp;/&nbsp; &nbsp;/&nbsp;
</span> </span>
} }
{this.props.token && {!!this.props.token &&
<span> <span>
<a onClick={this.report.bind(this)}>举报</a> <a onClick={this.report.bind(this)}>举报</a>
&nbsp;/&nbsp; &nbsp;/&nbsp;
</span> </span>
} }
<a onClick={this.load_replies.bind(this)}>刷新回复</a> <a onClick={this.load_replies.bind(this)}>刷新回复</a>
{this.props.token && {!!this.props.token &&
<span> <span>
&nbsp;/&nbsp; &nbsp;/&nbsp;
<a onClick={()=>{ <a onClick={()=>{
@ -241,14 +248,20 @@ class FlowSidebar extends PureComponent {
} }
</div> </div>
<FlowItem info={this.state.info} attention={this.state.attention} img_clickable={true} <FlowItem info={this.state.info} attention={this.state.attention} img_clickable={true}
color_picker={this.color_picker} show_pid={this.show_pid} /> color_picker={this.color_picker} show_pid={this.show_pid} replies={this.state.replies} />
{(this.props.deletion_detect && parseInt(this.state.info.reply)!==this.state.replies.length) &&
<div className="box box-tip flow-item box-danger">
{parseInt(this.state.info.reply)-this.state.replies.length} 条回复被删除
</div>
}
{this.state.replies.map((reply)=>( {this.state.replies.map((reply)=>(
<LazyLoad key={reply.cid} offset={500} height="5em" overflow={true} once={true}> <LazyLoad key={reply.cid} offset={500} height="5em" overflow={true} once={true}>
<Reply info={reply} color_picker={this.color_picker} show_pid={this.show_pid} /> <Reply info={reply} color_picker={this.color_picker} show_pid={this.show_pid} />
</LazyLoad> </LazyLoad>
))} ))}
{this.props.token && {!!this.props.token ?
<ReplyForm pid={this.state.info.pid} token={this.props.token} on_complete={this.load_replies.bind(this)} /> <ReplyForm pid={this.state.info.pid} token={this.props.token} on_complete={this.load_replies.bind(this)} /> :
<div className="box box-tip flow-item">登录后可以回复树洞</div>
} }
</div> </div>
) )
@ -283,9 +296,6 @@ class FlowItemRow extends PureComponent {
.then((json)=>{ .then((json)=>{
this.setState((prev,props)=>({ this.setState((prev,props)=>({
replies: json.data, replies: json.data,
info: Object.assign({}, prev.info, {
reply: ''+json.data.length,
}),
attention: !!json.attention, attention: !!json.attention,
reply_status: 'done', reply_status: 'done',
}),callback); }),callback);
@ -305,6 +315,7 @@ class FlowItemRow extends PureComponent {
<FlowSidebar <FlowSidebar
info={this.state.info} replies={this.state.replies} attention={this.state.attention} sync_state={this.setState.bind(this)} info={this.state.info} replies={this.state.replies} attention={this.state.attention} sync_state={this.setState.bind(this)}
token={this.props.token} show_sidebar={this.props.show_sidebar} color_picker={this.color_picker} token={this.props.token} show_sidebar={this.props.show_sidebar} color_picker={this.color_picker}
deletion_detect={this.props.deletion_detect}
/> />
); );
} }
@ -316,7 +327,7 @@ class FlowItemRow extends PureComponent {
this.show_sidebar(); this.show_sidebar();
}}> }}>
<FlowItem info={this.state.info} attention={this.state.attention} img_clickable={false} <FlowItem info={this.state.info} attention={this.state.attention} img_clickable={false}
color_picker={this.color_picker} show_pid={this.show_pid} /> color_picker={this.color_picker} show_pid={this.show_pid} replies={this.state.replies} />
<div className="flow-reply-row"> <div className="flow-reply-row">
{this.state.reply_status==='loading' && <div className="box box-tip">加载中</div>} {this.state.reply_status==='loading' && <div className="box box-tip">加载中</div>}
{this.state.reply_status==='failed' && {this.state.reply_status==='failed' &&
@ -349,7 +360,8 @@ function FlowChunk(props) {
</div> </div>
</div> </div>
} }
<FlowItemRow info={info} show_sidebar={props.show_sidebar} token={token} /> <FlowItemRow info={info} show_sidebar={props.show_sidebar} token={token}
deletion_detect={props.deletion_detect} />
</div> </div>
</LazyLoad> </LazyLoad>
))} ))}

2
src/Sidebar.css

@ -24,7 +24,7 @@
height: 100%; height: 100%;
width: calc(100% - 700px); width: calc(100% - 700px);
padding: 1em; padding: 1em;
background-color: rgba(255,255,255,.8); background-color: rgba(255,255,255,.7);
z-index: 21; z-index: 21;
overflow-y: auto; overflow-y: auto;
} }

52
src/UserAction.js

@ -102,32 +102,34 @@ export class LoginForm extends Component {
请勿泄露 Token它代表您的登录状态与您的账户唯一对应且泄露后无法重置 请勿泄露 Token它代表您的登录状态与您的账户唯一对应且泄露后无法重置
</p> </p>
</div> : </div> :
<p>登录后可以使用关注回复等功能</p> <div>
<p>登录后可以使用关注回复等功能</p>
<p>
<label>
 学号&nbsp;
<input ref={this.username_ref} type="tel" />
</label>
<button type="button" disabled={this.state.loading_status==='loading'}
onClick={(e)=>this.do_sendcode()}>
发送验证码
</button>
</p>
<p>
<label>
验证码&nbsp;
<input ref={this.password_ref} type="tel" />
</label>
<button type="button" disabled={this.state.loading_status==='loading'}
onClick={(e)=>this.do_login(token.set_value)}>
登录
</button>
</p>
<p>
登录请求会被发送到北大统一验证接口和 PKU Helper 服务器 <br />
我们不会记录或使用您的登录信息
</p>
</div>
} }
<p>
<label>
 学号&nbsp;
<input ref={this.username_ref} type="tel" />
</label>
<button type="button" disabled={this.state.loading_status==='loading'}
onClick={(e)=>this.do_sendcode()}>
发送验证码
</button>
</p>
<p>
<label>
验证码&nbsp;
<input ref={this.password_ref} type="tel" />
</label>
<button type="button" disabled={this.state.loading_status==='loading'}
onClick={(e)=>this.do_login(token.set_value)}>
登录
</button>
</p>
<p>
登录请求会被发送到北大统一验证接口和 PKU Helper 服务器 <br />
我们不会记录或使用您的登录信息
</p>
</div> </div>
}</TokenCtx.Consumer> }</TokenCtx.Consumer>
) )

2
src/index.css

@ -20,7 +20,7 @@ body, textarea, pre {
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
} }
p { p, pre {
margin: 0; margin: 0;
} }

Loading…
Cancel
Save