diff --git a/public/_redirects b/public/_redirects
index 7e17922c..ac0520bf 100644
--- a/public/_redirects
+++ b/public/_redirects
@@ -1,2 +1,3 @@
-/api_proxy/* http://www.pkuhelper.com:10301/services/pkuhole/:splat 200
-/audio_proxy/* http://www.pkuhelper.com:10301/services/pkuhole/audios/:splat 200
\ No newline at end of file
+/api_proxy/* http://www.pkuhelper.com/services/pkuhole/:splat 200
+/audio_proxy/* http://www.pkuhelper.com/services/pkuhole/audios/:splat 200
+/login_proxy/* http://www.pkuhelper.com/services/login/:splat 200
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index 4a9aa3cb..bccb22a1 100644
--- a/public/index.html
+++ b/public/index.html
@@ -6,6 +6,8 @@
+
+
diff --git a/public/static/fonts_1/icomoon.css b/public/static/fonts_1/icomoon.css
new file mode 100644
index 00000000..b391fb6d
--- /dev/null
+++ b/public/static/fonts_1/icomoon.css
@@ -0,0 +1,49 @@
+@font-face {
+ font-family: 'icomoon';
+ src:
+ url('icomoon.ttf?4yzqd4') format('truetype'),
+ url('icomoon.woff?4yzqd4') format('woff'),
+ url('icomoon.svg?4yzqd4#icomoon') format('svg');
+ font-weight: normal;
+ font-style: normal;
+}
+
+.icon {
+ /* use !important to prevent issues with browser extensions that change fonts */
+ /*noinspection CssNoGenericFontName*/
+ font-family: 'icomoon' !important;
+ speak: none;
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ text-transform: none;
+
+ /* Better Font Rendering =========== */
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-reply:before {
+ content: "\e96b";
+}
+.icon-login-ok:before {
+ content: "\e975";
+}
+.icon-login:before {
+ content: "\e98d";
+}
+.icon-attention:before {
+ content: "\e9d3";
+}
+.icon-star:before {
+ content: "\e9d7";
+}
+.icon-star-ok:before {
+ content: "\e9d9";
+}
+.icon-help:before {
+ content: "\ea09";
+}
+.icon-refresh:before {
+ content: "\ea2e";
+}
diff --git a/public/static/fonts_1/icomoon.svg b/public/static/fonts_1/icomoon.svg
new file mode 100644
index 00000000..14934e90
--- /dev/null
+++ b/public/static/fonts_1/icomoon.svg
@@ -0,0 +1,18 @@
+
+
+
\ No newline at end of file
diff --git a/public/static/fonts_1/icomoon.ttf b/public/static/fonts_1/icomoon.ttf
new file mode 100644
index 00000000..af51e902
Binary files /dev/null and b/public/static/fonts_1/icomoon.ttf differ
diff --git a/public/static/fonts_1/icomoon.woff b/public/static/fonts_1/icomoon.woff
new file mode 100644
index 00000000..f43f6ec5
Binary files /dev/null and b/public/static/fonts_1/icomoon.woff differ
diff --git a/src/App.js b/src/App.js
index f6950965..0ffab404 100644
--- a/src/App.js
+++ b/src/App.js
@@ -2,6 +2,7 @@ import React, {Component} from 'react';
import {Flow} from './Flows';
import {Title} from './Title';
import {Sidebar} from './Sidebar';
+import {TokenCtx} from './UserAction';
class App extends Component {
constructor(props) {
@@ -9,9 +10,10 @@ class App extends Component {
this.state={
sidebar_title: null,
sidebar_content: null,
- mode: 'list', // list, single, search
+ mode: 'list', // list, single, search, attention
search_text: null,
flow_render_key: +new Date(),
+ token: localStorage['TOKEN']||null,
};
this.show_sidebar_bound=this.show_sidebar.bind(this);
this.set_mode_bound=this.set_mode.bind(this);
@@ -34,23 +36,35 @@ class App extends Component {
render() {
return (
-
-
-
-
-
-
-
-
{
+ {
+ localStorage['TOKEN']=x||'';
this.setState({
- sidebar_content: null,
+ token: x,
});
- }} content={this.state.sidebar_content} title={this.state.sidebar_title} />
-
+ },
+ }}>
+
+
+
+
+ {(token)=>(
+
+ )}
+
+
+
{
+ this.setState({
+ sidebar_content: null,
+ });
+ }} content={this.state.sidebar_content} title={this.state.sidebar_title} />
+
+
);
}
}
diff --git a/src/Common.css b/src/Common.css
index eac22c50..0f3bb043 100644
--- a/src/Common.css
+++ b/src/Common.css
@@ -15,12 +15,12 @@
}
.centered-line::before {
- right: 0.5em;
+ right: 1em;
margin-left: -50%;
}
.centered-line::after {
- left: 0.5em;
+ left: 1em;
margin-right: -50%;
}
diff --git a/src/Flows.css b/src/Flows.css
index 013a9944..a5d26283 100644
--- a/src/Flows.css
+++ b/src/Flows.css
@@ -112,7 +112,6 @@ p.img img {
}
.box-id {
- font-family: Consolas, Courier, monospace;
opacity: .6;
}
diff --git a/src/Flows.js b/src/Flows.js
index 446dc150..783609c7 100644
--- a/src/Flows.js
+++ b/src/Flows.js
@@ -4,10 +4,11 @@ import {Time, TitleLine, HighlightedText} from './Common.js';
import './Flows.css';
import LazyLoad from 'react-lazyload';
import {AudioWidget} from './AudioWidget.js';
+import {TokenCtx} from './UserAction';
const IMAGE_BASE='http://www.pkuhelper.com/services/pkuhole/images/';
const AUDIO_BASE='/audio_proxy/';
-const API_BASE=window.location.protocol==='https:' ? '/api_proxy' : 'http://www.pkuhelper.com:10301/services/pkuhole';
+const API_BASE=window.location.protocol==='https:' ? '/api_proxy' : 'http://www.pkuhelper.com/services/pkuhole';
const SEARCH_PAGESIZE=50;
const CLICKABLE_TAGS={a: true, audio: true};
@@ -21,7 +22,7 @@ function Reply(props) {
backgroundColor: props.info._display_color,
} : null}>
- #{props.info.cid}
+ #{props.info.cid}
@@ -34,9 +35,19 @@ function FlowItem(props) {
{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}
+ {!!parseInt(props.info.likenum,10) &&
+
+ {props.info.likenum}
+
+
+ }
+ {!!parseInt(props.info.reply,10) &&
+
+ {props.info.reply}
+
+
+ }
+ #{props.info.pid}
@@ -53,6 +64,7 @@ class FlowItemRow extends PureComponent {
replies: [],
reply_status: 'done',
info: props.info,
+ attention: false,
};
this.color_picker=new ColorPicker();
}
@@ -68,11 +80,16 @@ class FlowItemRow extends PureComponent {
this.setState({
reply_status: 'loading',
});
- fetch(API_BASE+'/api.php?action=getcomment&pid='+this.state.info.pid)
+ const token_param=this.props.token ? '&token='+this.props.token : '';
+ fetch(
+ API_BASE+'/api.php?action=getcomment'+
+ '&pid='+this.state.info.pid+
+ token_param
+ )
.then((res)=>res.json())
.then((json)=>{
if(json.code!==0)
- throw new Error(json.code);
+ throw new Error(json);
const replies=json.data
.sort((a,b)=>{
return parseInt(a.timestamp,10)-parseInt(b.timestamp,10);
@@ -86,6 +103,7 @@ class FlowItemRow extends PureComponent {
info: Object.assign({}, prev.info, {
reply: ''+replies.length,
}),
+ attention: !!json.attention,
reply_status: 'done',
}),callback);
})
@@ -106,9 +124,9 @@ class FlowItemRow extends PureComponent {
{
this.props.show_sidebar('帖子详情',加载中……
);
this.load_replies(this.show_sidebar);
- }}>更新回复
+ }}>刷新回复
-
+
{this.state.replies.map((reply)=>(
@@ -125,7 +143,7 @@ class FlowItemRow extends PureComponent {
if(!CLICKABLE_TAGS[event.target.tagName.toLowerCase()])
this.show_sidebar();
}}>
-
+
{this.state.reply_status==='loading' &&
加载中
}
{this.state.reply_status==='failed' &&
@@ -145,14 +163,16 @@ class FlowItemRow extends PureComponent {
function FlowChunk(props) {
return (
-
-
- {props.list.map((info)=>(
-
-
-
- ))}
-
+
{({value: token})=>(
+
+
+ {props.list.map((info)=>(
+
+
+
+ ))}
+
+ )}
);
}
@@ -171,16 +191,30 @@ export class Flow extends PureComponent {
}
load_page(page) {
+ const failed=(err)=>{
+ console.trace(err);
+ this.setState((prev,props)=>({
+ loaded_pages: prev.loaded_pages-1,
+ loading_status: 'failed',
+ }));
+ };
+
+ const token_param=this.props.token ? '&token='+this.props.token : '';
+
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') {
- fetch(API_BASE+'/api.php?action=getlist&p='+page)
+ fetch(
+ API_BASE+'/api.php?action=getlist'+
+ '&p='+page+
+ token_param
+ )
.then((res)=>res.json())
.then((json)=>{
if(json.code!==0)
- throw new Error(json.code);
+ throw new Error(json);
json.data.forEach((x)=>{
if(parseInt(x.pid,10)>(parseInt(localStorage['_LATEST_POST_ID'],10)||0))
localStorage['_LATEST_POST_ID']=x.pid;
@@ -196,23 +230,18 @@ export class Flow extends PureComponent {
loading_status: 'done',
}));
})
- .catch((err)=>{
- console.trace(err);
- this.setState((prev,props)=>({
- loaded_pages: prev.loaded_pages-1,
- loading_status: 'failed',
- }));
- });
+ .catch(failed);
} else if(this.state.mode==='search') {
fetch(
API_BASE+'/api.php?action=search'+
'&pagesize='+SEARCH_PAGESIZE*page+
- '&keywords='+encodeURIComponent(this.state.search_param)
+ '&keywords='+encodeURIComponent(this.state.search_param)+
+ token_param
)
.then((res)=>res.json())
.then((json)=>{
if(json.code!==0)
- throw new Error(json.code);
+ throw new Error(json);
const finished=json.data.length
{
- console.trace(err);
- this.setState((prev,props)=>({
- loaded_pages: prev.loaded_pages-1,
- loading_status: 'failed',
- }));
- });
+ .catch(failed);
} else if(this.state.mode==='single') {
const pid=parseInt(this.state.search_param.substr(1),10);
fetch(
API_BASE+'/api.php?action=getone'+
- '&pid='+pid
+ '&pid='+pid+
+ token_param
)
.then((res)=>res.json())
.then((json)=>{
if(json.code!==0)
- throw new Error(json.code);
+ throw new Error(json);
this.setState({
chunks: [{
title: 'PID = '+pid,
@@ -249,13 +273,26 @@ export class Flow extends PureComponent {
loading_status: 'done',
});
})
- .catch((err)=>{
- console.trace(err);
- this.setState((prev,props)=>({
- loaded_pages: prev.loaded_pages-1,
- loading_status: 'failed',
- }));
- });
+ .catch(failed);
+ } else if(this.state.mode==='attention') {
+ fetch(
+ API_BASE+'/api.php?action=getattention'+
+ token_param
+ )
+ .then((res)=>res.json())
+ .then((json)=>{
+ if(json.code!==0)
+ throw new Error(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;
diff --git a/src/Sidebar.css b/src/Sidebar.css
index e8eca078..9e69f0ed 100644
--- a/src/Sidebar.css
+++ b/src/Sidebar.css
@@ -41,10 +41,11 @@
}
@media screen and (max-width: 600px) {
.sidebar {
- width: calc(100% - 50px);
+ width: calc(100% - 25px);
+ padding: 1em .5em;
}
.sidebar-on .sidebar {
- left: 50px;
+ left: 25px;
}
}
diff --git a/src/Title.css b/src/Title.css
index 804564b5..9a766832 100644
--- a/src/Title.css
+++ b/src/Title.css
@@ -10,13 +10,9 @@
margin-bottom: 1em;
}
-.title-bar a {
- padding: 0 .5em;
-}
-
.title {
- font-size: 2em;
- line-height: 3em;
+ font-size: 1.5em;
+ line-height: 4em;
text-align: center;
}
@@ -26,13 +22,10 @@
line-height: 2em;
}
-.control-bar .refresh-btn {
- flex: 0 0 100px;
- color: black;
- background-color: rgba(255,255,255,.5);
- border-radius: 5px;
+.control-btn {
+ flex: 0 0 2em;
text-align: center;
- border: 1px solid black;
+ color: black;
}
.control-bar input {
flex: auto;
diff --git a/src/Title.js b/src/Title.js
index 9a19646a..a924b94d 100644
--- a/src/Title.js
+++ b/src/Title.js
@@ -1,4 +1,7 @@
import React, {Component, PureComponent} from 'react';
+import {LoginForm} from './UserAction';
+import {TokenCtx} from './UserAction';
+
import './Title.css';
const HELP_TEXT=(
@@ -10,7 +13,7 @@ const HELP_TEXT=(
在搜索框输入 #472865 等可以查看指定 ID 的树洞
新的帖子会在左上角显示一个圆点
请注意:使用 HTTPS 访问本站可能会大幅减慢加载速度
- 自定义背景图片请修改 localStorage['REPLACE_ERIRI_WITH_URL']
+ 自定义背景图片请修改 localStorage['REPLACE_ERIRI_WITH_URL']
使用本网站时,您需要了解并同意:
@@ -50,6 +53,7 @@ class ControlBar extends PureComponent {
this.on_change_bound=this.on_change.bind(this);
this.on_keypress_bound=this.on_keypress.bind(this);
this.do_refresh_bound=this.do_refresh.bind(this);
+ this.do_attention_bound=this.do_attention.bind(this);
}
componentDidMount() {
@@ -69,8 +73,10 @@ class ControlBar extends PureComponent {
}
on_keypress(event) {
- if(event.key==='Enter')
- this.set_mode('search',this.state.search_text||null);
+ if(event.key==='Enter') {
+ const mode=this.state.search_text.startsWith('#') ? 'single' : 'search';
+ this.set_mode(mode,this.state.search_text||null);
+ }
}
do_refresh() {
@@ -81,20 +87,40 @@ class ControlBar extends PureComponent {
this.set_mode('list',null);
}
+ do_attention() {
+ window.scrollTo(0,0);
+ this.setState({
+ search_text: '',
+ });
+ this.set_mode('attention',null);
+ }
+
render() {
return (
-
+ {({value: token})=>(
+
+ )}
)
}
}
diff --git a/src/UserAction.css b/src/UserAction.css
new file mode 100644
index 00000000..09b7e93c
--- /dev/null
+++ b/src/UserAction.css
@@ -0,0 +1,8 @@
+.login-form form p {
+ margin: 1em 0;
+ text-align: center;
+}
+
+.login-form button {
+ min-width: 100px;
+}
\ No newline at end of file
diff --git a/src/UserAction.js b/src/UserAction.js
new file mode 100644
index 00000000..702e11a9
--- /dev/null
+++ b/src/UserAction.js
@@ -0,0 +1,92 @@
+import React, {Component, PureComponent} from 'react';
+
+import './UserAction.css';
+
+const LOGIN_BASE=window.location.protocol==='https:' ? '/login_proxy' : 'http://www.pkuhelper.com/services/login';
+
+export const TokenCtx=React.createContext({
+ value: null,
+ set_value: ()=>{},
+});
+
+export class LoginForm extends Component {
+ constructor(props) {
+ super(props);
+ this.state={
+ loading_status: 'done',
+ };
+
+ this.username_ref=React.createRef();
+ this.password_ref=React.createRef();
+ }
+
+ do_login(event,set_token) {
+ event.preventDefault();
+ this.setState({
+ loading_status: 'loading',
+ });
+ let data=new URLSearchParams();
+ data.append('uid', this.username_ref.current.value);
+ data.append('password', this.password_ref.current.value);
+ fetch(LOGIN_BASE+'/login.php?platform=hole_xmcp_ml', {
+ method: 'POST',
+ body: data,
+ })
+ .then((res)=>res.json())
+ .then((json)=>{
+ if(json.code!==0)
+ throw new Error(json);
+
+ set_token(json.token);
+ alert(`成功以 ${json.name} 的身份登录`);
+ this.setState({
+ loading_status: 'done',
+ });
+ })
+ .catch((e)=>{
+ alert('登录失败');
+ this.setState({
+ loading_status: 'done',
+ });
+ console.trace(e);
+ });
+ }
+
+ render() {
+ return (
+ {(token)=>
+
+
+
+
+ - 我们不会记录您的密码和个人信息。
+ - 请勿泄露 Token,它代表您的登录状态,与您的账户唯一对应且泄露后无法重置。
+ - 如果您不愿输入密码,可以直接修改
localStorage['TOKEN']
+
+
+
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/index.css b/src/index.css
index ccf653ab..97ae8856 100644
--- a/src/index.css
+++ b/src/index.css
@@ -31,6 +31,7 @@ input {
border-radius: 5px;
border: 1px solid black;
outline: none;
+ line-height: 2em;
}
audio {
@@ -40,4 +41,22 @@ audio {
pre {
white-space: pre-wrap;
font-family: '微软雅黑', 'Microsoft YaHei', sans-serif;
+}
+
+button, .button {
+ color: black;
+ background-color: rgba(255,255,255,.5);
+ border-radius: 5px;
+ text-align: center;
+ border: 1px solid black;
+ line-height: 2em;
+ margin: 0 .5em;
+}
+
+button:disabled, .button:disabled {
+ background-color: rgba(128,128,128,.5);
+}
+
+code {
+ font-family: Consolas, Courier, monospace;
}
\ No newline at end of file