diff --git a/public/index.html b/public/index.html index bccb22a1..c9f0ae9f 100644 --- a/public/index.html +++ b/public/index.html @@ -6,7 +6,7 @@ - + diff --git a/public/static/fonts_1/icomoon.svg b/public/static/fonts_1/icomoon.svg deleted file mode 100644 index 14934e90..00000000 --- a/public/static/fonts_1/icomoon.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - -Generated by IcoMoon - - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/static/fonts_1/icomoon.ttf b/public/static/fonts_1/icomoon.ttf deleted file mode 100644 index af51e902..00000000 Binary files a/public/static/fonts_1/icomoon.ttf and /dev/null differ diff --git a/public/static/fonts_1/icomoon.woff b/public/static/fonts_1/icomoon.woff deleted file mode 100644 index f43f6ec5..00000000 Binary files a/public/static/fonts_1/icomoon.woff and /dev/null differ diff --git a/public/static/fonts_1/icomoon.css b/public/static/fonts_2/icomoon.css similarity index 66% rename from public/static/fonts_1/icomoon.css rename to public/static/fonts_2/icomoon.css index b391fb6d..ac5caf3a 100644 --- a/public/static/fonts_1/icomoon.css +++ b/public/static/fonts_2/icomoon.css @@ -1,9 +1,9 @@ @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'); + url('icomoon.ttf?y01gys') format('truetype'), + url('icomoon.woff?y01gys') format('woff'), + url('icomoon.svg?y01gys#icomoon') format('svg'); font-weight: normal; font-style: normal; } @@ -17,17 +17,21 @@ font-weight: normal; font-variant: normal; text-transform: none; + line-height: 1; /* Better Font Rendering =========== */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } +.icon-send:before { + content: "\e900"; +} .icon-reply:before { content: "\e96b"; } -.icon-login-ok:before { - content: "\e975"; +.icon-loading:before { + content: "\e979"; } .icon-login:before { content: "\e98d"; @@ -41,9 +45,15 @@ .icon-star-ok:before { content: "\e9d9"; } -.icon-help:before { - content: "\ea09"; +.icon-plus:before { + content: "\ea0a"; +} +.icon-about:before { + content: "\ea0c"; } .icon-refresh:before { content: "\ea2e"; } +.icon-github:before { + content: "\eab0"; +} diff --git a/public/static/fonts_2/icomoon.svg b/public/static/fonts_2/icomoon.svg new file mode 100644 index 00000000..c64966a8 --- /dev/null +++ b/public/static/fonts_2/icomoon.svg @@ -0,0 +1,21 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/fonts_2/icomoon.ttf b/public/static/fonts_2/icomoon.ttf new file mode 100644 index 00000000..132accf5 Binary files /dev/null and b/public/static/fonts_2/icomoon.ttf differ diff --git a/public/static/fonts_2/icomoon.woff b/public/static/fonts_2/icomoon.woff new file mode 100644 index 00000000..25b7f5b2 Binary files /dev/null and b/public/static/fonts_2/icomoon.woff differ diff --git a/src/Flows.js b/src/Flows.js index def2c0b0..d9125329 100644 --- a/src/Flows.js +++ b/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 ? - 已关注 : - 未关注 +  已关注 : +  未关注 } @@ -382,7 +387,11 @@ export class Flow extends PureComponent { {this.load_page(this.state.loaded_pages+1)}}>重新加载 } - +  Loading... : + '© xmcp' + } /> ); } diff --git a/src/Sidebar.js b/src/Sidebar.js index f436df5b..f573aa64 100644 --- a/src/Sidebar.js +++ b/src/Sidebar.js @@ -10,7 +10,6 @@ export function Sidebar(props) { ×  {props.title}

-
{props.content} diff --git a/src/Title.js b/src/Title.js index 671de7f6..c69027cd 100644 --- a/src/Title.js +++ b/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 { - {this.props.show_sidebar('登录',)}}> - - - {this.props.show_sidebar( - '关于 P大树洞(非官方) 网页版', - HELP_TEXT - )}}> - + { + this.props.show_sidebar( + 'P大树洞(非官方)网页版', +
+ + {HELP_TEXT} +
+ ) + }}> +
+ {!!token && + { + this.props.show_sidebar( + '发表树洞', + { + this.props.show_sidebar('',null); + this.do_refresh(); + }} /> + ) + }}> + + + } )} ) @@ -131,7 +146,13 @@ export function Title(props) { return (
-

P大树洞

+

+ P大树洞 +   + + + +

diff --git a/src/UserAction.css b/src/UserAction.css index b594a44b..0a2300a2 100644 --- a/src/UserAction.css +++ b/src/UserAction.css @@ -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; } \ No newline at end of file diff --git a/src/UserAction.js b/src/UserAction.js index 709555fa..f2addb60 100644 --- a/src/UserAction.js +++ b/src/UserAction.js @@ -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 ( {(token)=> -
-
this.do_login(e,token.set_value)} className="box"> +
+ this.do_login(e,token.set_value)}>

{token.value ? 您已登录。Token: {token.value||'(null)'} : '登录后可以使用关注、回复等功能' @@ -83,19 +87,23 @@ export class LoginForm extends Component {

{this.state.loading_status==='loading' ? - : - + : + }

- -
  • 我们不会记录您的密码和个人信息
  • 请勿泄露 Token,它代表您的登录状态,与您的账户唯一对应且泄露后无法重置
  • 如果您不愿输入密码,可以直接修改 localStorage['TOKEN']
-
+
} ) @@ -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 ( -
-
- - {this.state.loading_status==='loading' ? - : - + + + {this.state.loading_status==='loading' ? + : + + } + + ) + } +} + +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; } - -
+ 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 ( +
+
+ + {this.state.loading_status!=='done' ? + : + + } +
+
+ + ) } } \ No newline at end of file