thuhole update

This commit is contained in:
thuhole
2020-06-18 11:19:33 +08:00
parent 30e3b48464
commit 0166bd8947
18 changed files with 254 additions and 245 deletions

View File

@@ -106,14 +106,14 @@ class App extends Component {
<LoginPopup token_callback={token.set_value}>{(do_popup)=>(
<a onClick={do_popup}>
<span className="icon icon-login" />
&nbsp;登录到 PKU Helper
&nbsp;登录到 T大树洞
</a>
)}</LoginPopup>
</p>
</div>
</div>
}
{this.inpku_flag||token.value ?
{this.inpku_flag||token.value||true ?
<Flow key={this.state.flow_render_key} show_sidebar={this.show_sidebar_bound}
mode={this.state.mode} search_text={this.state.search_text} token={token.value}
/> :

View File

@@ -1,12 +1,12 @@
import React, {Component, PureComponent} from 'react';
import {format_time,Time,TitleLine} from './infrastructure/widgets';
import {PKUHELPER_ROOT} from './flows_api';
import {THUHOLE_API_ROOT} from './flows_api';
import './Common.css';
export {format_time,Time,TitleLine};
export const API_BASE=PKUHELPER_ROOT+'services/pkuhole';
export const API_BASE=THUHOLE_API_ROOT+'services/thuhole';
// https://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
function escape_regex(string) {
@@ -37,9 +37,9 @@ export class HighlightedText extends PureComponent {
let [rule,p]=part;
return (
<span key={idx}>{
rule==='url_pid' ? <span className="url-pid-link" title={p}>/hole/##</span> :
rule==='url_pid' ? <span className="url-pid-link" title={p}>/##</span> :
rule==='url' ? <a href={normalize_url(p)} target="_blank" rel="noopener">{p}</a> :
rule==='pid' ? <a href={'##'+p} onClick={(e)=>{e.preventDefault(); this.props.show_pid(p);}}>{p}</a> :
rule==='pid' ? <a href={'#'+p} onClick={(e)=>{e.preventDefault(); this.props.show_pid(p.substring(1));}}>{p}</a> :
rule==='nickname' ? <ColoredSpan colors={this.props.color_picker.get(p)}>{p}</ColoredSpan> :
rule==='search' ? <span className="search-query-highlight">{p}</span> :
p

View File

@@ -7,6 +7,7 @@ const BUILTIN_IMGS={
'static/bg/eriri.jpg': '平成著名画师',
'static/bg/yurucamp.jpg': '露营天下第一',
'static/bg/minecraft.jpg': '麦恩·库拉夫特',
'static/bg/cyberpunk.jpg': '赛博城市',
'static/bg/sif.jpg': '梦开始的地方',
};
@@ -238,7 +239,7 @@ export class ConfigUI extends PureComponent {
<hr />
<p>
新功能建议或问题反馈请在&nbsp;
<a href="https://github.com/pkuhelper-web/webhole/issues" target="_blank">GitHub <span className="icon icon-github" /></a>
<a href="https://github.com/thuhole/webhole/issues" target="_blank">GitHub <span className="icon icon-github" /></a>
&nbsp;提出
</p>
</div>

View File

@@ -8,14 +8,15 @@ import LazyLoad from './react-lazyload/src';
import {AudioWidget} from './AudioWidget';
import {TokenCtx, ReplyForm} from './UserAction';
import {API, PKUHELPER_ROOT} from './flows_api';
import {API, THUHOLE_API_ROOT} from './flows_api';
const IMAGE_BASE=PKUHELPER_ROOT+'services/pkuhole/images/';
const AUDIO_BASE=PKUHELPER_ROOT+'services/pkuhole/audios/';
const IMAGE_BASE=THUHOLE_API_ROOT+'services/thuhole/images/';
const AUDIO_BASE=THUHOLE_API_ROOT+'services/thuhole/audios/';
const CLICKABLE_TAGS={a: true, audio: true};
const PREVIEW_REPLY_COUNT=10;
const QUOTE_BLACKLIST=['23333','233333','66666','666666','10086','10000','100000','99999','999999','55555','555555'];
// const QUOTE_BLACKLIST=['23333','233333','66666','666666','10086','10000','100000','99999','999999','55555','555555'];
const QUOTE_BLACKLIST=[];
window.LATEST_POST_ID=parseInt(localStorage['_LATEST_POST_ID'],10)||0;
@@ -521,7 +522,8 @@ class FlowItemRow extends PureComponent {
let quote_id=null;
if(!this.props.is_quote)
for(let [mode,content] of parts)
for(let [mode,content] of parts) {
content = content.length > 0 ? content.substring(1) : content
if(mode==='pid' && QUOTE_BLACKLIST.indexOf(content)===-1 && parseInt(content)<parseInt(this.state.info.pid))
if(quote_id===null)
quote_id=parseInt(content);
@@ -529,6 +531,7 @@ class FlowItemRow extends PureComponent {
quote_id=null;
break;
}
}
let res=(
<div className={'flow-item-row flow-item-row-with-prompt'+(this.props.is_quote ? ' flow-item-row-quote' : '')} onClick={(event)=>{
@@ -809,7 +812,7 @@ export class Flow extends PureComponent {
<TitleLine text={
this.state.loading_status==='loading' ?
<span><span className="icon icon-loading" />&nbsp;Loading...</span> :
xmcp'
thuhole'
} />
</div>
);

View File

@@ -1,5 +1,5 @@
import React, {Component, PureComponent} from 'react';
import {PKUHELPER_ROOT, get_json, API_VERSION_PARAM} from './flows_api';
import {THUHOLE_API_ROOT, get_json, API_VERSION_PARAM} from './flows_api';
import {Time} from './Common';
export class MessageViewer extends PureComponent {
@@ -20,7 +20,7 @@ export class MessageViewer extends PureComponent {
this.setState({
loading_status: 'loading',
},()=>{
fetch(PKUHELPER_ROOT+'api_xmcp/hole/system_msg?user_token='+encodeURIComponent(this.props.token)+API_VERSION_PARAM())
fetch(THUHOLE_API_ROOT+'api_xmcp/hole/system_msg?user_token='+encodeURIComponent(this.props.token)+API_VERSION_PARAM())
.then(get_json)
.then((json)=>{
if(json.error)

View File

@@ -1,5 +1,5 @@
import React, {Component, PureComponent} from 'react';
import {AppSwitcher} from './infrastructure/widgets';
// import {AppSwitcher} from './infrastructure/widgets';
import {InfoSidebar, PostForm} from './UserAction';
import {TokenCtx} from './UserAction';
@@ -94,7 +94,7 @@ class ControlBar extends PureComponent {
/>
<a className="no-underline control-btn" onClick={()=>{
this.props.show_sidebar(
'P大树洞',
'T大树洞',
<InfoSidebar show_sidebar={this.props.show_sidebar} />
)
}}>
@@ -124,15 +124,15 @@ class ControlBar extends PureComponent {
export function Title(props) {
return (
<div className="title-bar">
<AppSwitcher appid="hole" />
{/* <AppSwitcher appid="hole" /> */}
<div className="aux-margin">
<div className="title">
<p className="centered-line">
<span onClick={()=>props.show_sidebar(
'P大树洞',
'T大树洞',
<InfoSidebar show_sidebar={props.show_sidebar} />
)}>
P大树洞
T大树洞
</span>
</p>
</div>

View File

@@ -6,7 +6,7 @@ import {ConfigUI} from './Config';
import fixOrientation from 'fix-orientation';
import copy from 'copy-to-clipboard';
import {cache} from './cache';
import {API_VERSION_PARAM, PKUHELPER_ROOT, API, get_json, token_param} from './flows_api';
import {API_VERSION_PARAM, THUHOLE_API_ROOT, API, get_json, token_param} from './flows_api';
import './UserAction.css';
@@ -20,179 +20,179 @@ export const TokenCtx=React.createContext({
set_value: ()=>{},
});
class LifeInfoBox extends Component {
constructor(props) {
super(props);
if(!window._life_info_cache)
window._life_info_cache={};
this.CACHE_TIMEOUT_S=15;
this.state={
today_info: this.cache_get('today_info'),
card_balance: this.cache_get('card_balance'),
net_balance: this.cache_get('net_balance'),
mail_count: this.cache_get('mail_count'),
};
this.INTERNAL_NETWORK_FAILURE='_network_failure';
this.API_NAME={
today_info: 'hole/today_info',
card_balance: 'isop/card_balance',
net_balance: 'isop/net_balance',
mail_count: 'isop/mail_count',
};
}
cache_get(key) {
let cache_item=window._life_info_cache[key];
if(!cache_item || (+new Date())-cache_item[0]>1000*this.CACHE_TIMEOUT_S)
return null;
else
return cache_item[1];
}
cache_set(key,value) {
if(!window._life_info_cache[key] || window._life_info_cache[key][1]!==value)
window._life_info_cache[key]=[+new Date(),value];
}
load(state_key) {
this.setState({
[state_key]: null,
},()=>{
fetch(
PKUHELPER_ROOT+'api_xmcp/'+this.API_NAME[state_key]
+'?user_token='+encodeURIComponent(this.props.token)
+API_VERSION_PARAM()
)
.then(get_json)
.then((json)=>{
//console.log(json);
this.setState({
[state_key]: json,
});
})
.catch((e)=>{
this.setState({
[state_key]: {
errMsg: '网络错误 '+e,
errCode: this.INTERNAL_NETWORK_FAILURE,
success: false,
}
});
})
});
}
componentDidMount() {
['today_info','card_balance','net_balance','mail_count'].forEach((k)=>{
if(!this.state[k])
this.load(k);
});
}
reload_all() {
['today_info','card_balance','net_balance','mail_count'].forEach((k)=>{
this.load(k);
});
}
render_line(state_key,title,value_fn,action,url_fn,do_login) {
let s=this.state[state_key];
if(!s)
return (
<tr>
<td>{title}</td>
<td>加载中</td>
<td />
</tr>
);
else if(!s.success) {
let type='加载失败';
if(s.errCode===this.INTERNAL_NETWORK_FAILURE)
type='网络错误';
else if(['E01','E02','E03'].indexOf(s.errCode)!==-1)
type='授权失效';
let details=JSON.stringify(s);
if(s.errMsg)
details=s.errMsg;
else if(s.error)
details=s.error;
return (
<tr>
<td>{title}</td>
<td className="life-info-error">
<a onClick={()=>alert(details)}>{type}</a>
</td>
<td>
{type==='授权失效' ?
<a onClick={do_login}>
<span className="icon icon-forward" />&nbsp;重新登录
</a> :
<a onClick={()=>this.load(state_key)}>
<span className="icon icon-forward" />&nbsp;重试
</a>
}
</td>
</tr>
)
}
else {
this.cache_set(state_key,s);
return (
<tr>
<td>{title}</td>
<td>{value_fn(s)}</td>
<td>
<a href={url_fn(s)} target="_blank">
<span className="icon icon-forward" />&nbsp;{action}
</a>
</td>
</tr>
);
}
}
render() {
return (
<LoginPopup token_callback={(t)=>{
this.props.set_token(t);
this.reload_all();
}}>{(do_login)=>(
<div className="box">
<table className="life-info-table">
<tbody>
{this.render_line(
'today_info',
'今日',(s)=>s.info,
'校历',(s)=>s.schedule_url,
do_login,
)}
{this.render_line(
'card_balance',
'校园卡',(s)=>`余额¥${s.balance.toFixed(2)}`,
'充值',()=>'https://virtualprod.alipay.com/educate/educatePcRecharge.htm?schoolCode=PKU&schoolName=',
do_login,
)}
{this.render_line(
'net_balance',
'网费',(s)=>`余额¥${s.balance.toFixed(2)}`,
'充值',()=>'https://its.pku.edu.cn/epay.jsp',
do_login,
)}
{this.render_line(
'mail_count',
'邮件',(s)=>`未读 ${s.count}`,
'查看',()=>'https://mail.pku.edu.cn/',
do_login,
)}
</tbody>
</table>
</div>
)}</LoginPopup>
)
}
}
// class LifeInfoBox extends Component {
// constructor(props) {
// super(props);
// if(!window._life_info_cache)
// window._life_info_cache={};
// this.CACHE_TIMEOUT_S=15;
// this.state={
// today_info: this.cache_get('today_info'),
// card_balance: this.cache_get('card_balance'),
// net_balance: this.cache_get('net_balance'),
// mail_count: this.cache_get('mail_count'),
// };
// this.INTERNAL_NETWORK_FAILURE='_network_failure';
// this.API_NAME={
// today_info: 'hole/today_info',
// card_balance: 'isop/card_balance',
// net_balance: 'isop/net_balance',
// mail_count: 'isop/mail_count',
// };
// }
//
// cache_get(key) {
// let cache_item=window._life_info_cache[key];
// if(!cache_item || (+new Date())-cache_item[0]>1000*this.CACHE_TIMEOUT_S)
// return null;
// else
// return cache_item[1];
// }
// cache_set(key,value) {
// if(!window._life_info_cache[key] || window._life_info_cache[key][1]!==value)
// window._life_info_cache[key]=[+new Date(),value];
// }
//
// load(state_key) {
// this.setState({
// [state_key]: null,
// },()=>{
// fetch(
// PKUHELPER_ROOT+'api_xmcp/'+this.API_NAME[state_key]
// +'?user_token='+encodeURIComponent(this.props.token)
// +API_VERSION_PARAM()
// )
// .then(get_json)
// .then((json)=>{
// //console.log(json);
// this.setState({
// [state_key]: json,
// });
// })
// .catch((e)=>{
// this.setState({
// [state_key]: {
// errMsg: '网络错误 '+e,
// errCode: this.INTERNAL_NETWORK_FAILURE,
// success: false,
// }
// });
// })
// });
// }
//
// componentDidMount() {
// ['today_info','card_balance','net_balance','mail_count'].forEach((k)=>{
// if(!this.state[k])
// this.load(k);
// });
// }
//
// reload_all() {
// ['today_info','card_balance','net_balance','mail_count'].forEach((k)=>{
// this.load(k);
// });
// }
//
// render_line(state_key,title,value_fn,action,url_fn,do_login) {
// let s=this.state[state_key];
// if(!s)
// return (
// <tr>
// <td>{title}</td>
// <td>加载中……</td>
// <td />
// </tr>
// );
// else if(!s.success) {
// let type='加载失败';
// if(s.errCode===this.INTERNAL_NETWORK_FAILURE)
// type='网络错误';
// else if(['E01','E02','E03'].indexOf(s.errCode)!==-1)
// type='授权失效';
//
// let details=JSON.stringify(s);
// if(s.errMsg)
// details=s.errMsg;
// else if(s.error)
// details=s.error;
//
// return (
// <tr>
// <td>{title}</td>
// <td className="life-info-error">
// <a onClick={()=>alert(details)}>{type}</a>
// </td>
// <td>
// {type==='授权失效' ?
// <a onClick={do_login}>
// <span className="icon icon-forward" />&nbsp;重新登录
// </a> :
// <a onClick={()=>this.load(state_key)}>
// <span className="icon icon-forward" />&nbsp;重试
// </a>
// }
// </td>
// </tr>
// )
// }
// else {
// this.cache_set(state_key,s);
//
// return (
// <tr>
// <td>{title}</td>
// <td>{value_fn(s)}</td>
// <td>
// <a href={url_fn(s)} target="_blank">
// <span className="icon icon-forward" />&nbsp;{action}
// </a>
// </td>
// </tr>
// );
// }
// }
//
// render() {
// return (
// <LoginPopup token_callback={(t)=>{
// this.props.set_token(t);
// this.reload_all();
// }}>{(do_login)=>(
// <div className="box">
// <table className="life-info-table">
// <tbody>
// {this.render_line(
// 'today_info',
// '今日',(s)=>s.info,
// '校历',(s)=>s.schedule_url,
// do_login,
// )}
// {this.render_line(
// 'card_balance',
// '校园卡',(s)=>`余额¥${s.balance.toFixed(2)}`,
// '充值',()=>'https://virtualprod.alipay.com/educate/educatePcRecharge.htm?schoolCode=PKU&schoolName=',
// do_login,
// )}
// {this.render_line(
// 'net_balance',
// '网费',(s)=>`余额¥${s.balance.toFixed(2)}`,
// '充值',()=>'https://its.pku.edu.cn/epay.jsp',
// do_login,
// )}
// {this.render_line(
// 'mail_count',
// '邮件',(s)=>`未读 ${s.count} 封`,
// '查看',()=>'https://mail.pku.edu.cn/',
// do_login,
// )}
// </tbody>
// </table>
// </div>
// )}</LoginPopup>
// )
// }
// }
export function InfoSidebar(props) {
return (
@@ -207,23 +207,25 @@ export function InfoSidebar(props) {
<span className="icon icon-settings" /><label>网页版树洞设置</label>
</a>
&nbsp;&nbsp;
<a href="http://pkuhelper.pku.edu.cn/treehole_rules.html" target="_blank">
<span className="icon icon-textfile" /><label>树洞规范</label>
</a>
&nbsp;&nbsp;
<a href="https://github.com/pkuhelper-web/webhole/issues" target="_blank">
{/*<a href="http://pkuhelper.pku.edu.cn/treehole_rules.html" target="_blank">*/}
{/* <span className="icon icon-textfile" /><label>树洞规范</label>*/}
{/*</a>*/}
{/*&nbsp;&nbsp;*/}
<a href="https://github.com/thuhole/webhole/issues" target="_blank">
<span className="icon icon-github" /><label>意见反馈</label>
</a>
</div>
<div className="box help-desc-box">
<p>
PKUHelper 网页版树洞 by @xmcp
T大树洞 网页版 by @thuhole
基于&nbsp;
<a href="https://www.gnu.org/licenses/gpl-3.0.zh-cn.html" target="_blank">GPLv3</a>
&nbsp;协议在 <a href="https://github.com/pkuhelper-web/webhole" target="_blank">GitHub</a>
&nbsp;协议在 <a href="https://github.com/thuhole/webhole" target="_blank">GitHub</a>
</p>
<p>
PKUHelper 网页版的诞生离不开&nbsp;
T大树洞 网页版的诞生离不开&nbsp;
<a href="https://github.com/pkuhelper-web/webhole" target="_blank" rel="noopener">P大树洞</a>
<a href="https://reactjs.org/" target="_blank" rel="noopener">React</a>
<a href="https://icomoon.io/#icons" target="_blank" rel="noopener">IcoMoon</a>
@@ -280,7 +282,7 @@ class ResetUsertokenWidget extends Component {
this.setState({
loading_status: 'loading',
},()=>{
fetch(PKUHELPER_ROOT+'api_xmcp/hole/reset_usertoken', {
fetch(THUHOLE_API_ROOT+'api_xmcp/hole/reset_usertoken', {
method: 'post',
headers: {
'Content-Type': 'application/json',
@@ -329,9 +331,9 @@ export class LoginForm extends Component {
return (
<TokenCtx.Consumer>{(token)=>
<div>
{!!token.value &&
<LifeInfoBox token={token.value} set_token={token.set_value} />
}
{/*{!!token.value &&*/}
{/* <LifeInfoBox token={token.value} set_token={token.set_value} />*/}
{/*}*/}
<div className="login-form box">
{token.value ?
<div>
@@ -354,7 +356,7 @@ export class LoginForm extends Component {
</p>
<p>
<a onClick={this.copy_token.bind(this,token.value)}>复制 User Token</a><br />
User Token 用于迁移登录状态切勿告知他人若怀疑被盗号请尽快 <ResetUsertokenWidget token={token.value} />
User Token 用于迁移登录状态切勿告知他人{/*若怀疑被盗号请尽快 <ResetUsertokenWidget token={token.value} />*/}
</p>
</div> :
<LoginPopup token_callback={token.set_value}>{(do_popup)=>(
@@ -366,7 +368,7 @@ export class LoginForm extends Component {
</button>
</p>
<p><small>
PKU Helper 面向北京大学学生通过 ISOP北京大学数据共享开放服务平台验证您的身份并提供服务
T大树洞 面向清华大学学生通过清华邮箱验证您的身份并提供服务
</small></p>
</div>
)}</LoginPopup>

View File

@@ -1,9 +1,9 @@
import {get_json, API_VERSION_PARAM} from './infrastructure/functions';
import {PKUHELPER_ROOT} from './infrastructure/const';
import {THUHOLE_API_ROOT} from './infrastructure/const';
import {API_BASE} from './Common';
import {cache} from './cache';
export {PKUHELPER_ROOT, API_VERSION_PARAM};
export {THUHOLE_API_ROOT, API_VERSION_PARAM};
export function token_param(token) {
return API_VERSION_PARAM()+(token ? ('&user_token='+token) : '');

View File

@@ -1,6 +1,9 @@
// regexp should match the WHOLE segmented part
export const PID_RE=/(^|[^\d\u20e3\ufe0e\ufe0f])([2-9]\d{4,5}|1\d{4,6})(?![\d\u20e3\ufe0e\ufe0f])/g;
export const URL_PID_RE=/((?:https?:\/\/)?pkuhelper\.pku\.edu\.cn\/hole\/?#(?:#|%23)([2-9]\d{4,5}|1\d{4,6}))(?!\d|\u20e3|\ufe0e|\ufe0f)/g;
// export const PID_RE=/(^|[^\d\u20e3\ufe0e\ufe0f])([2-9]\d{4,5}|1\d{4,6})(?![\d\u20e3\ufe0e\ufe0f])/g;
export const PID_RE=/(^|[^\d\u20e3\ufe0e\ufe0f])(#\d{1,7})(?![\d\u20e3\ufe0e\ufe0f])/g;
// TODO: fix this re
// export const URL_PID_RE=/((?:https?:\/\/)?thuhole\.tech\/?#(?:#|%23)([2-9]\d{4,5}|1\d{4,6}))(?!\d|\u20e3|\ufe0e|\ufe0f)/g;
export const URL_PID_RE=/((?:https?:\/\/)?thuhole\.tech\/?#(?:#|%23)(\d{1,7}))(?!\d|\u20e3|\ufe0e|\ufe0f)/g;
export const NICKNAME_RE=/(^|[^A-Za-z])((?:(?:Angry|Baby|Crazy|Diligent|Excited|Fat|Greedy|Hungry|Interesting|Jolly|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(?: \d+)?|洞主)(?![A-Za-z])/gi;
export const URL_RE=/(^|[^.@a-zA-Z0-9_])((?:https?:\/\/)?(?:(?:[\w-]+\.)+[a-zA-Z]{2,3}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?::\d{1,5})?(?:\/[\w~!@#$%^&*()\-_=+[\]{};:,./?|]*)?)(?![a-zA-Z0-9])/gi;