import React, {Component, PureComponent} from 'react';
import copy from 'copy-to-clipboard';
import {ColorPicker} from './color_picker';
import {format_time, Time, TitleLine, HighlightedText} from './Common';
import './Flows.css';
import LazyLoad from 'react-lazyload';
import {AudioWidget} from './AudioWidget';
import {TokenCtx, ReplyForm} from './UserAction';
import {API} from './flows_api';
const IMAGE_BASE='http://www.pkuhelper.com:10301/services/pkuhole/images/';
const AUDIO_BASE='http://www.pkuhelper.com:10301/services/pkuhole/audios/';
const SEARCH_PAGESIZE=50;
const CLICKABLE_TAGS={a: true, audio: true};
const PREVIEW_REPLY_COUNT=10;
window.LATEST_POST_ID=parseInt(localStorage['_LATEST_POST_ID'],10)||0;
function load_single_meta(show_sidebar,token) {
return (pid)=>{
const color_picker=new ColorPicker();
show_sidebar(
'帖子详情',
正在加载 #{pid}
);
Promise.all([
API.get_single(pid,token),
API.load_replies(pid,token,color_picker),
])
.then((res)=>{
const [single,replies]=res;
single.data.variant={};
show_sidebar(
'帖子详情',
)
})
.catch((e)=>{
console.error(e);
show_sidebar(
'帖子详情',
);
})
};
}
class Reply extends PureComponent {
constructor(props) {
super(props);
}
render() {
return (
);
}
}
class FlowItem extends PureComponent {
constructor(props) {
super(props);
}
copy_link(event) {
event.preventDefault();
copy(
`${event.target.href}\n`+
`${this.props.info.text}${this.props.info.type==='image'?' [图片]':this.props.info.type==='audio'?' [语音]':''}\n`+
`(${format_time(new Date(this.props.info.timestamp*1000))} ${this.props.info.likenum}关注 ${this.props.info.reply}回复)\n`+
this.props.replies.map((r)=>(r.text)).join('\n')
);
}
render() {
let props=this.props;
return (
{parseInt(props.info.pid,10)>window.LATEST_POST_ID &&
}
{!!parseInt(props.info.likenum,10) &&
{props.info.likenum}
}
{!!parseInt(props.info.reply,10) &&
{props.info.reply}
}
#{props.info.pid}
{props.info.type==='image' &&
{props.img_clickable ?
:
}
}
{props.info.type==='audio' &&
}
);
}
}
class FlowSidebar extends PureComponent {
constructor(props) {
super(props);
this.state={
attention: props.attention,
info: props.info,
replies: props.replies,
loading_status: 'done',
};
this.color_picker=props.color_picker;
this.show_pid=load_single_meta(this.props.show_sidebar,this.props.token);
this.syncState=props.sync_state||(()=>{});
this.reply_ref=React.createRef();
}
set_variant(cid,variant) {
this.setState((prev)=>{
if(cid)
return {
replies: prev.replies.map((reply)=>{
if(reply.cid===cid)
return Object.assign({},reply,{variant: variant});
else
return reply;
}),
};
else
return {
info: Object.assign({},prev.info,{variant: variant}),
}
},function() {
this.syncState({
info: this.state.info,
replies: this.state.replies,
});
});
}
load_replies(update_count=true) {
this.setState({
loading_status: 'loading',
});
API.load_replies(this.state.info.pid,this.props.token,this.color_picker)
.then((json)=>{
this.setState((prev,props)=>({
replies: json.data,
info: update_count ? Object.assign({}, prev.info, {
reply: ''+json.data.length,
}) : prev.info,
attention: !!json.attention,
loading_status: 'done',
}), ()=>{
this.syncState({
replies: this.state.replies,
attention: this.state.attention,
info: this.state.info,
});
});
})
.catch((e)=>{
console.error(e);
this.setState({
replies: [],
loading_status: 'done',
});
});
}
toggle_attention() {
this.setState({
loading_status: 'loading',
});
const next_attention=!this.state.attention;
API.set_attention(this.state.info.pid,next_attention,this.props.token)
.then((json)=>{
this.setState({
loading_status: 'done',
attention: next_attention,
});
this.syncState({
attention: next_attention,
});
})
.catch((e)=>{
this.setState({
loading_status: 'done'
});
alert('设置关注失败');
console.error(e);
});
}
report() {
let reason=prompt(`举报 #${this.state.info.pid} 的理由:`);
if(reason!==null) {
API.report(this.state.info.pid,reason,this.props.token)
.then((json)=>{
alert('举报成功');
})
.catch((e)=>{
alert('举报失败');
console.error(e);
})
}
}
star_brush() {
let count=prompt('Count:');
if(count) {
let reqs=[];
for(let i=parseInt(count);i;i--)
reqs.push(API.set_attention(this.state.info.pid,false,this.props.token));
Promise.all(reqs)
.then(()=>{
alert('Completed!')
})
.catch((e)=>{
alert('Failed!\n\n'+e);
})
}
}
do_reply(name,event) {
if(event.target.tagName.toLowerCase()!=='a') {
let text=this.reply_ref.current.get();
if(/^\s*(Re (洞主|\b[A-Z][a-z]+){0,2}:)?\s*$/.test(text)) // text is nearly empty so we can replace it
this.reply_ref.current.set_and_focus('Re '+name+': ');
}
}
render() {
const star_brush=localStorage['STAR_BRUSH']==='on';
if(this.state.loading_status==='loading')
return (加载中……
);
return (
{this.do_reply('',e);}}>
{this.set_variant(null,variant);}}
/>
{(this.props.deletion_detect && parseInt(this.state.info.reply)>this.state.replies.length) &&
{parseInt(this.state.info.reply)-this.state.replies.length} 条回复被删除
}
{this.state.replies.map((reply)=>(
{this.do_reply(reply.name,e);}}>
{this.set_variant(reply.cid,variant);}}
/>
))}
{!!this.props.token ?
:
登录后可以回复树洞
}
)
}
}
class FlowItemRow extends PureComponent {
constructor(props) {
super(props);
this.state={
replies: [],
reply_status: 'done',
info: props.info,
attention: false,
};
this.state.info.variant={};
this.color_picker=new ColorPicker();
this.show_pid=load_single_meta(this.props.show_sidebar,this.props.token);
}
componentDidMount() {
if(parseInt(this.state.info.reply,10)) {
this.load_replies(null,/*update_count=*/false);
}
}
load_replies(callback,update_count=true) {
console.log('fetching reply',this.state.info.pid);
this.setState({
reply_status: 'loading',
});
API.load_replies(this.state.info.pid,this.props.token,this.color_picker)
.then((json)=>{
this.setState((prev,props)=>({
replies: json.data,
info: update_count ? Object.assign({}, prev.info, {
reply: ''+json.data.length,
}) : prev.info,
attention: !!json.attention,
reply_status: 'done',
}),callback);
})
.catch((e)=>{
console.error(e);
this.setState({
replies: [],
reply_status: 'failed',
},callback);
});
}
show_sidebar() {
this.props.show_sidebar(
'帖子详情',
);
}
render() {
return (
{
if(!CLICKABLE_TAGS[event.target.tagName.toLowerCase()])
this.show_sidebar();
}}>
{this.state.reply_status==='loading' &&
加载中
}
{this.state.reply_status==='failed' &&
}
{this.state.replies.slice(0,PREVIEW_REPLY_COUNT).map((reply)=>(
))}
{this.state.replies.length>PREVIEW_REPLY_COUNT &&
还有 {this.state.replies.length-PREVIEW_REPLY_COUNT} 条
}
);
}
}
function FlowChunk(props) {
return (
{({value: token})=>(
{!!props.title &&
}
{props.list.map((info,ind)=>(
{!!(props.deletion_detect && props.mode==='list' && ind && props.list[ind-1].pid-info.pid>1) &&
{props.list[ind-1].pid-info.pid-1} 条被删除
}
))}
)}
);
}
export class Flow extends PureComponent {
constructor(props) {
super(props);
this.state={
mode: props.mode,
search_param: props.search_text,
loaded_pages: 0,
chunks: {
title: '',
data: [],
},
loading_status: 'done',
};
this.on_scroll_bound=this.on_scroll.bind(this);
window.LATEST_POST_ID=parseInt(localStorage['_LATEST_POST_ID'],10)||0;
}
load_page(page) {
const failed=(err)=>{
console.error(err);
this.setState((prev,props)=>({
loaded_pages: prev.loaded_pages-1,
loading_status: 'failed',
}));
};
if(page>this.state.loaded_pages+1)
throw new Error('bad page');
if(page===this.state.loaded_pages+1) {
console.log('fetching page',page);
if(this.state.mode==='list') {
API.get_list(page,this.props.token)
.then((json)=>{
json.data.forEach((x)=>{
if(parseInt(x.pid,10)>(parseInt(localStorage['_LATEST_POST_ID'],10)||0))
localStorage['_LATEST_POST_ID']=x.pid;
});
this.setState((prev,props)=>({
chunks: {
title: 'News Feed',
data: prev.chunks.data.concat(json.data.filter((x)=>(
prev.chunks.data.length===0 ||
!(prev.chunks.data.slice(-100).some((p)=>p.pid===x.pid))
))),
},
loading_status: 'done',
}));
})
.catch(failed);
} else if(this.state.mode==='search') {
API.get_search(SEARCH_PAGESIZE*page,this.state.search_param,this.props.token)
.then((json)=>{
const finished=json.data.length{
this.setState({
chunks: {
title: 'PID = '+pid,
data: [json.data],
},
mode: 'single_finished',
loading_status: 'done',
});
})
.catch(failed);
} else if(this.state.mode==='attention') {
API.get_attention(this.props.token)
.then((json)=>{
this.setState({
chunks: {
title: 'Attention List',
data: json.data,
},
mode: 'attention_finished',
loading_status: 'done',
});
})
.catch(failed);
} else {
console.log('nothing to load');
return;
}
this.setState((prev,props)=>({
loaded_pages: prev.loaded_pages+1,
loading_status: 'loading',
}));
}
}
on_scroll(event) {
if(event.target===document) {
const avail=document.body.scrollHeight-window.scrollY-window.innerHeight;
if(avail
{this.state.loading_status==='failed' &&
}
Loading... :
'© xmcp'
} />
);
}
}