|
|
@ -1,24 +1,35 @@ |
|
|
|
import React, {Component, PureComponent} from 'react'; |
|
|
|
import React, { Component, PureComponent } from 'react'; |
|
|
|
import {API_BASE, SafeTextarea, PromotionBar, HighlightedMarkdown} from './Common'; |
|
|
|
import { |
|
|
|
import {MessageViewer} from './Message'; |
|
|
|
API_BASE, |
|
|
|
import {LoginPopup} from './infrastructure/widgets'; |
|
|
|
SafeTextarea, |
|
|
|
import {ColorPicker} from './color_picker'; |
|
|
|
PromotionBar, |
|
|
|
import {ConfigUI} from './Config'; |
|
|
|
HighlightedMarkdown, |
|
|
|
|
|
|
|
} from './Common'; |
|
|
|
|
|
|
|
import { MessageViewer } from './Message'; |
|
|
|
|
|
|
|
import { LoginPopup } from './infrastructure/widgets'; |
|
|
|
|
|
|
|
import { ColorPicker } from './color_picker'; |
|
|
|
|
|
|
|
import { ConfigUI } from './Config'; |
|
|
|
import fixOrientation from 'fix-orientation'; |
|
|
|
import fixOrientation from 'fix-orientation'; |
|
|
|
import copy from 'copy-to-clipboard'; |
|
|
|
import copy from 'copy-to-clipboard'; |
|
|
|
import {cache} from './cache'; |
|
|
|
import { cache } from './cache'; |
|
|
|
import {API_VERSION_PARAM, THUHOLE_API_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'; |
|
|
|
import './UserAction.css'; |
|
|
|
|
|
|
|
|
|
|
|
const BASE64_RATE=4/3; |
|
|
|
const BASE64_RATE = 4 / 3; |
|
|
|
const MAX_IMG_DIAM=8000; |
|
|
|
const MAX_IMG_DIAM = 8000; |
|
|
|
const MAX_IMG_PX=5000000; |
|
|
|
const MAX_IMG_PX = 5000000; |
|
|
|
const MAX_IMG_FILESIZE=450000*BASE64_RATE; |
|
|
|
const MAX_IMG_FILESIZE = 450000 * BASE64_RATE; |
|
|
|
|
|
|
|
|
|
|
|
export const TokenCtx=React.createContext({ |
|
|
|
export const TokenCtx = React.createContext({ |
|
|
|
value: null, |
|
|
|
value: null, |
|
|
|
set_value: ()=>{}, |
|
|
|
set_value: () => {}, |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// class LifeInfoBox extends Component {
|
|
|
|
// class LifeInfoBox extends Component {
|
|
|
@ -201,73 +212,107 @@ export function InfoSidebar(props) { |
|
|
|
<PromotionBar /> |
|
|
|
<PromotionBar /> |
|
|
|
<LoginForm show_sidebar={props.show_sidebar} /> |
|
|
|
<LoginForm show_sidebar={props.show_sidebar} /> |
|
|
|
<div className="box list-menu"> |
|
|
|
<div className="box list-menu"> |
|
|
|
<a onClick={()=>{props.show_sidebar( |
|
|
|
<a |
|
|
|
'设置', |
|
|
|
onClick={() => { |
|
|
|
<ConfigUI /> |
|
|
|
props.show_sidebar('设置', <ConfigUI />); |
|
|
|
)}}> |
|
|
|
}} |
|
|
|
<span className="icon icon-settings" /><label>设置</label> |
|
|
|
> |
|
|
|
|
|
|
|
<span className="icon icon-settings" /> |
|
|
|
|
|
|
|
<label>设置</label> |
|
|
|
</a> |
|
|
|
</a> |
|
|
|
|
|
|
|
|
|
|
|
<a href="https://thuhole.com/policy.html" target="_blank"> |
|
|
|
<a href="https://thuhole.com/policy.html" target="_blank"> |
|
|
|
<span className="icon icon-textfile" /><label>树洞规范(试行)</label> |
|
|
|
<span className="icon icon-textfile" /> |
|
|
|
|
|
|
|
<label>树洞规范(试行)</label> |
|
|
|
</a> |
|
|
|
</a> |
|
|
|
|
|
|
|
|
|
|
|
<a href="https://github.com/thuhole/thuhole-go-backend/issues" target="_blank"> |
|
|
|
<a |
|
|
|
<span className="icon icon-github" /><label>意见反馈</label> |
|
|
|
href="https://github.com/thuhole/thuhole-go-backend/issues" |
|
|
|
|
|
|
|
target="_blank" |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
<span className="icon icon-github" /> |
|
|
|
|
|
|
|
<label>意见反馈</label> |
|
|
|
</a> |
|
|
|
</a> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div className="box help-desc-box"> |
|
|
|
<div className="box help-desc-box"> |
|
|
|
<p> |
|
|
|
<p> |
|
|
|
<a onClick={()=>{ |
|
|
|
<a |
|
|
|
if('serviceWorker' in navigator) { |
|
|
|
onClick={() => { |
|
|
|
navigator.serviceWorker.getRegistrations() |
|
|
|
if ('serviceWorker' in navigator) { |
|
|
|
.then((registrations)=>{ |
|
|
|
navigator.serviceWorker |
|
|
|
for(let registration of registrations) { |
|
|
|
.getRegistrations() |
|
|
|
console.log('unregister',registration); |
|
|
|
.then((registrations) => { |
|
|
|
|
|
|
|
for (let registration of registrations) { |
|
|
|
|
|
|
|
console.log('unregister', registration); |
|
|
|
registration.unregister(); |
|
|
|
registration.unregister(); |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
cache().clear(); |
|
|
|
cache().clear(); |
|
|
|
setTimeout(()=>{ |
|
|
|
setTimeout(() => { |
|
|
|
window.location.reload(true); |
|
|
|
window.location.reload(true); |
|
|
|
},200); |
|
|
|
}, 200); |
|
|
|
}}>强制检查更新</a> |
|
|
|
}} |
|
|
|
(当前版本:【{process.env.REACT_APP_BUILD_INFO||'---'} {process.env.NODE_ENV}】 会自动在后台检查更新并在下次访问时更新) |
|
|
|
> |
|
|
|
|
|
|
|
强制检查更新 |
|
|
|
|
|
|
|
</a> |
|
|
|
|
|
|
|
(当前版本:【{process.env.REACT_APP_BUILD_INFO || '---'}{' '} |
|
|
|
|
|
|
|
{process.env.NODE_ENV}】 会自动在后台检查更新并在下次访问时更新) |
|
|
|
</p> |
|
|
|
</p> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div className="box help-desc-box"> |
|
|
|
<div className="box help-desc-box"> |
|
|
|
<p> |
|
|
|
<p>联系我们:thuhole at protonmail dot com</p> |
|
|
|
联系我们:thuhole at protonmail dot com |
|
|
|
|
|
|
|
</p> |
|
|
|
|
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div className="box help-desc-box"> |
|
|
|
<div className="box help-desc-box"> |
|
|
|
<p> |
|
|
|
<p> |
|
|
|
T大树洞 网页版 by @thuhole, |
|
|
|
T大树洞 网页版 by @thuhole, 基于 |
|
|
|
基于 |
|
|
|
<a |
|
|
|
<a href="https://www.gnu.org/licenses/gpl-3.0.zh-cn.html" target="_blank">GPLv3</a> |
|
|
|
href="https://www.gnu.org/licenses/gpl-3.0.zh-cn.html" |
|
|
|
协议在 <a href="https://github.com/thuhole/webhole" target="_blank">GitHub</a> 开源 |
|
|
|
target="_blank" |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
GPLv3 |
|
|
|
|
|
|
|
</a> |
|
|
|
|
|
|
|
协议在{' '} |
|
|
|
|
|
|
|
<a href="https://github.com/thuhole/webhole" target="_blank"> |
|
|
|
|
|
|
|
GitHub |
|
|
|
|
|
|
|
</a>{' '} |
|
|
|
|
|
|
|
开源 |
|
|
|
</p> |
|
|
|
</p> |
|
|
|
<p> |
|
|
|
<p> |
|
|
|
T大树洞 网页版的诞生离不开 |
|
|
|
T大树洞 网页版的诞生离不开 |
|
|
|
<a href="https://github.com/pkuhelper-web/webhole" target="_blank" rel="noopener">P大树洞网页版 by @xmcp</a> |
|
|
|
<a |
|
|
|
|
|
|
|
href="https://github.com/pkuhelper-web/webhole" |
|
|
|
|
|
|
|
target="_blank" |
|
|
|
|
|
|
|
rel="noopener" |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
P大树洞网页版 by @xmcp |
|
|
|
|
|
|
|
</a> |
|
|
|
、 |
|
|
|
、 |
|
|
|
<a href="https://reactjs.org/" target="_blank" rel="noopener">React</a> |
|
|
|
<a href="https://reactjs.org/" target="_blank" rel="noopener"> |
|
|
|
|
|
|
|
React |
|
|
|
|
|
|
|
</a> |
|
|
|
、 |
|
|
|
、 |
|
|
|
<a href="https://icomoon.io/#icons" target="_blank" rel="noopener">IcoMoon</a> |
|
|
|
<a href="https://icomoon.io/#icons" target="_blank" rel="noopener"> |
|
|
|
|
|
|
|
IcoMoon |
|
|
|
|
|
|
|
</a> |
|
|
|
等开源项目 |
|
|
|
等开源项目 |
|
|
|
</p> |
|
|
|
</p> |
|
|
|
<p> |
|
|
|
<p> |
|
|
|
This program is free software: you can redistribute it and/or modify |
|
|
|
This program is free software: you can redistribute it and/or modify |
|
|
|
it under the terms of the GNU General Public License as published by |
|
|
|
it under the terms of the GNU General Public License as published by |
|
|
|
the Free Software Foundation, either version 3 of the License, or |
|
|
|
the Free Software Foundation, either version 3 of the License, or (at |
|
|
|
(at your option) any later version. |
|
|
|
your option) any later version. |
|
|
|
</p> |
|
|
|
</p> |
|
|
|
<p> |
|
|
|
<p> |
|
|
|
This program is distributed in the hope that it will be useful, |
|
|
|
This program is distributed in the hope that it will be useful, but |
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
|
<a href="https://www.gnu.org/licenses/gpl-3.0.zh-cn.html" target="_blank">GNU General Public License</a> |
|
|
|
<a |
|
|
|
|
|
|
|
href="https://www.gnu.org/licenses/gpl-3.0.zh-cn.html" |
|
|
|
|
|
|
|
target="_blank" |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
GNU General Public License |
|
|
|
|
|
|
|
</a> |
|
|
|
for more details. |
|
|
|
for more details. |
|
|
|
</p> |
|
|
|
</p> |
|
|
|
</div> |
|
|
|
</div> |
|
|
@ -278,19 +323,27 @@ export function InfoSidebar(props) { |
|
|
|
class ResetUsertokenWidget extends Component { |
|
|
|
class ResetUsertokenWidget extends Component { |
|
|
|
constructor(props) { |
|
|
|
constructor(props) { |
|
|
|
super(props); |
|
|
|
super(props); |
|
|
|
this.state={ |
|
|
|
this.state = { |
|
|
|
loading_status: 'done', |
|
|
|
loading_status: 'done', |
|
|
|
}; |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
do_reset() { |
|
|
|
do_reset() { |
|
|
|
if(window.confirm('您正在重置 UserToken!\n您的账号将会在【所有设备】上注销,您需要手动重新登录!')) { |
|
|
|
if ( |
|
|
|
let uid=window.prompt('您正在重置 UserToken!\n请输入您的学号以确认身份:'); |
|
|
|
window.confirm( |
|
|
|
if(uid) |
|
|
|
'您正在重置 UserToken!\n您的账号将会在【所有设备】上注销,您需要手动重新登录!', |
|
|
|
this.setState({ |
|
|
|
) |
|
|
|
|
|
|
|
) { |
|
|
|
|
|
|
|
let uid = window.prompt( |
|
|
|
|
|
|
|
'您正在重置 UserToken!\n请输入您的学号以确认身份:', |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
if (uid) |
|
|
|
|
|
|
|
this.setState( |
|
|
|
|
|
|
|
{ |
|
|
|
loading_status: 'loading', |
|
|
|
loading_status: 'loading', |
|
|
|
},()=>{ |
|
|
|
}, |
|
|
|
fetch(THUHOLE_API_ROOT+'api_xmcp/hole/reset_usertoken', { |
|
|
|
() => { |
|
|
|
|
|
|
|
fetch(THUHOLE_API_ROOT + 'api_xmcp/hole/reset_usertoken', { |
|
|
|
method: 'post', |
|
|
|
method: 'post', |
|
|
|
headers: { |
|
|
|
headers: { |
|
|
|
'Content-Type': 'application/json', |
|
|
|
'Content-Type': 'application/json', |
|
|
@ -301,53 +354,61 @@ class ResetUsertokenWidget extends Component { |
|
|
|
}), |
|
|
|
}), |
|
|
|
}) |
|
|
|
}) |
|
|
|
.then(get_json) |
|
|
|
.then(get_json) |
|
|
|
.then((json)=>{ |
|
|
|
.then((json) => { |
|
|
|
if(json.error) |
|
|
|
if (json.error) throw new Error(json.error); |
|
|
|
throw new Error(json.error); |
|
|
|
else alert('重置成功!您需要在所有设备上重新登录。'); |
|
|
|
else |
|
|
|
|
|
|
|
alert('重置成功!您需要在所有设备上重新登录。'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.setState({ |
|
|
|
this.setState({ |
|
|
|
loading_status: 'done', |
|
|
|
loading_status: 'done', |
|
|
|
}); |
|
|
|
}); |
|
|
|
}) |
|
|
|
}) |
|
|
|
.catch((e)=>{ |
|
|
|
.catch((e) => { |
|
|
|
alert('重置失败:'+e); |
|
|
|
alert('重置失败:' + e); |
|
|
|
this.setState({ |
|
|
|
this.setState({ |
|
|
|
loading_status: 'done', |
|
|
|
loading_status: 'done', |
|
|
|
}); |
|
|
|
}); |
|
|
|
}) |
|
|
|
|
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
render() { |
|
|
|
render() { |
|
|
|
if(this.state.loading_status==='done') |
|
|
|
if (this.state.loading_status === 'done') |
|
|
|
return (<a onClick={this.do_reset.bind(this)}>重置</a>); |
|
|
|
return <a onClick={this.do_reset.bind(this)}>重置</a>; |
|
|
|
else if(this.state.loading_status==='loading') |
|
|
|
else if (this.state.loading_status === 'loading') |
|
|
|
return (<a><span className="icon icon-loading" /></a>); |
|
|
|
return ( |
|
|
|
|
|
|
|
<a> |
|
|
|
|
|
|
|
<span className="icon icon-loading" /> |
|
|
|
|
|
|
|
</a> |
|
|
|
|
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export class LoginForm extends Component { |
|
|
|
export class LoginForm extends Component { |
|
|
|
copy_token(token) { |
|
|
|
copy_token(token) { |
|
|
|
if(copy(token)) |
|
|
|
if (copy(token)) alert('复制成功!\n请一定不要泄露哦'); |
|
|
|
alert('复制成功!\n请一定不要泄露哦'); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
render() { |
|
|
|
render() { |
|
|
|
return ( |
|
|
|
return ( |
|
|
|
<TokenCtx.Consumer>{(token)=> |
|
|
|
<TokenCtx.Consumer> |
|
|
|
|
|
|
|
{(token) => ( |
|
|
|
<div> |
|
|
|
<div> |
|
|
|
{/*{!!token.value &&*/} |
|
|
|
{/*{!!token.value &&*/} |
|
|
|
{/* <LifeInfoBox token={token.value} set_token={token.set_value} />*/} |
|
|
|
{/* <LifeInfoBox token={token.value} set_token={token.set_value} />*/} |
|
|
|
{/*}*/} |
|
|
|
{/*}*/} |
|
|
|
<div className="login-form box"> |
|
|
|
<div className="login-form box"> |
|
|
|
{token.value ? |
|
|
|
{token.value ? ( |
|
|
|
<div> |
|
|
|
<div> |
|
|
|
<p> |
|
|
|
<p> |
|
|
|
<b>您已登录。</b> |
|
|
|
<b>您已登录。</b> |
|
|
|
<button type="button" onClick={()=>{token.set_value(null);}}> |
|
|
|
<button |
|
|
|
|
|
|
|
type="button" |
|
|
|
|
|
|
|
onClick={() => { |
|
|
|
|
|
|
|
token.set_value(null); |
|
|
|
|
|
|
|
}} |
|
|
|
|
|
|
|
> |
|
|
|
<span className="icon icon-logout" /> 注销 |
|
|
|
<span className="icon icon-logout" /> 注销 |
|
|
|
</button> |
|
|
|
</button> |
|
|
|
<br /> |
|
|
|
<br /> |
|
|
@ -357,18 +418,32 @@ export class LoginForm extends Component { |
|
|
|
{/*T大树洞将会单向加密(i.e. 哈希散列)您的邮箱后再存入数据库,因此您的发帖具有较强的匿名性。具体可见我们的<a href="https://github.com/thuhole/thuhole-go-backend/blob/76f56e6b75257b59e552b6bdba77e114151fcad1/src/db.go#L184">后端开源代码</a>。*/} |
|
|
|
{/*T大树洞将会单向加密(i.e. 哈希散列)您的邮箱后再存入数据库,因此您的发帖具有较强的匿名性。具体可见我们的<a href="https://github.com/thuhole/thuhole-go-backend/blob/76f56e6b75257b59e552b6bdba77e114151fcad1/src/db.go#L184">后端开源代码</a>。*/} |
|
|
|
{/*</p>*/} |
|
|
|
{/*</p>*/} |
|
|
|
<p> |
|
|
|
<p> |
|
|
|
<a onClick={()=>{this.props.show_sidebar( |
|
|
|
<a |
|
|
|
|
|
|
|
onClick={() => { |
|
|
|
|
|
|
|
this.props.show_sidebar( |
|
|
|
'系统消息', |
|
|
|
'系统消息', |
|
|
|
<MessageViewer token={token.value} /> |
|
|
|
<MessageViewer token={token.value} />, |
|
|
|
)}}>查看系统消息</a><br /> |
|
|
|
); |
|
|
|
|
|
|
|
}} |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
查看系统消息 |
|
|
|
|
|
|
|
</a> |
|
|
|
|
|
|
|
<br /> |
|
|
|
当您发送的内容违规时,我们将用系统消息提示您 |
|
|
|
当您发送的内容违规时,我们将用系统消息提示您 |
|
|
|
</p> |
|
|
|
</p> |
|
|
|
<p> |
|
|
|
<p> |
|
|
|
<a onClick={this.copy_token.bind(this,token.value)}>复制 User Token</a><br /> |
|
|
|
<a onClick={this.copy_token.bind(this, token.value)}> |
|
|
|
复制 User Token 可以在新设备登录,切勿告知他人。若怀疑被盗号请重新邮箱验证码登录以重置Token。{/*,若怀疑被盗号请尽快 <ResetUsertokenWidget token={token.value} />*/} |
|
|
|
复制 User Token |
|
|
|
|
|
|
|
</a> |
|
|
|
|
|
|
|
<br /> |
|
|
|
|
|
|
|
复制 User Token |
|
|
|
|
|
|
|
可以在新设备登录,切勿告知他人。若怀疑被盗号请重新邮箱验证码登录以重置Token。 |
|
|
|
|
|
|
|
{/*,若怀疑被盗号请尽快 <ResetUsertokenWidget token={token.value} />*/} |
|
|
|
</p> |
|
|
|
</p> |
|
|
|
</div> : |
|
|
|
</div> |
|
|
|
<LoginPopup token_callback={token.set_value}>{(do_popup)=>( |
|
|
|
) : ( |
|
|
|
|
|
|
|
<LoginPopup token_callback={token.set_value}> |
|
|
|
|
|
|
|
{(do_popup) => ( |
|
|
|
<div> |
|
|
|
<div> |
|
|
|
<p> |
|
|
|
<p> |
|
|
|
<button type="button" onClick={do_popup}> |
|
|
|
<button type="button" onClick={do_popup}> |
|
|
@ -376,46 +451,61 @@ export class LoginForm extends Component { |
|
|
|
登录 |
|
|
|
登录 |
|
|
|
</button> |
|
|
|
</button> |
|
|
|
</p> |
|
|
|
</p> |
|
|
|
<p><small> |
|
|
|
<p> |
|
|
|
T大树洞 面向T大学生,通过T大邮箱验证您的身份并提供服务。 |
|
|
|
<small> |
|
|
|
</small></p> |
|
|
|
T大树洞 |
|
|
|
|
|
|
|
面向T大学生,通过T大邮箱验证您的身份并提供服务。 |
|
|
|
|
|
|
|
</small> |
|
|
|
|
|
|
|
</p> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
)}</LoginPopup> |
|
|
|
)} |
|
|
|
} |
|
|
|
</LoginPopup> |
|
|
|
|
|
|
|
)} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
}</TokenCtx.Consumer> |
|
|
|
)} |
|
|
|
) |
|
|
|
</TokenCtx.Consumer> |
|
|
|
|
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export class ReplyForm extends Component { |
|
|
|
export class ReplyForm extends Component { |
|
|
|
constructor(props) { |
|
|
|
constructor(props) { |
|
|
|
super(props); |
|
|
|
super(props); |
|
|
|
this.state={ |
|
|
|
this.state = { |
|
|
|
text: '', |
|
|
|
text: '', |
|
|
|
loading_status: 'done', |
|
|
|
loading_status: 'done', |
|
|
|
preview: false, |
|
|
|
preview: false, |
|
|
|
}; |
|
|
|
}; |
|
|
|
this.on_change_bound=this.on_change.bind(this); |
|
|
|
this.on_change_bound = this.on_change.bind(this); |
|
|
|
this.area_ref=this.props.area_ref||React.createRef(); |
|
|
|
this.area_ref = this.props.area_ref || React.createRef(); |
|
|
|
this.global_keypress_handler_bound=this.global_keypress_handler.bind(this); |
|
|
|
this.global_keypress_handler_bound = this.global_keypress_handler.bind( |
|
|
|
this.color_picker=new ColorPicker(); |
|
|
|
this, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
this.color_picker = new ColorPicker(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
global_keypress_handler(e) { |
|
|
|
global_keypress_handler(e) { |
|
|
|
if(e.code==='Enter' && !e.ctrlKey && !e.altKey && ['input','textarea'].indexOf(e.target.tagName.toLowerCase())===-1) { |
|
|
|
if ( |
|
|
|
if(this.area_ref.current) { |
|
|
|
e.code === 'Enter' && |
|
|
|
|
|
|
|
!e.ctrlKey && |
|
|
|
|
|
|
|
!e.altKey && |
|
|
|
|
|
|
|
['input', 'textarea'].indexOf(e.target.tagName.toLowerCase()) === -1 |
|
|
|
|
|
|
|
) { |
|
|
|
|
|
|
|
if (this.area_ref.current) { |
|
|
|
e.preventDefault(); |
|
|
|
e.preventDefault(); |
|
|
|
this.area_ref.current.focus(); |
|
|
|
this.area_ref.current.focus(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
componentDidMount() { |
|
|
|
componentDidMount() { |
|
|
|
document.addEventListener('keypress',this.global_keypress_handler_bound); |
|
|
|
document.addEventListener('keypress', this.global_keypress_handler_bound); |
|
|
|
} |
|
|
|
} |
|
|
|
componentWillUnmount() { |
|
|
|
componentWillUnmount() { |
|
|
|
document.removeEventListener('keypress',this.global_keypress_handler_bound); |
|
|
|
document.removeEventListener( |
|
|
|
|
|
|
|
'keypress', |
|
|
|
|
|
|
|
this.global_keypress_handler_bound, |
|
|
|
|
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
on_change(value) { |
|
|
|
on_change(value) { |
|
|
@ -425,28 +515,30 @@ export class ReplyForm extends Component { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
on_submit(event) { |
|
|
|
on_submit(event) { |
|
|
|
if(event) event.preventDefault(); |
|
|
|
if (event) event.preventDefault(); |
|
|
|
if(this.state.loading_status==='loading') |
|
|
|
if (this.state.loading_status === 'loading') return; |
|
|
|
return; |
|
|
|
|
|
|
|
this.setState({ |
|
|
|
this.setState({ |
|
|
|
loading_status: 'loading', |
|
|
|
loading_status: 'loading', |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
let data=new URLSearchParams(); |
|
|
|
let data = new URLSearchParams(); |
|
|
|
data.append('pid',this.props.pid); |
|
|
|
data.append('pid', this.props.pid); |
|
|
|
data.append('text',this.state.text); |
|
|
|
data.append('text', this.state.text); |
|
|
|
data.append('user_token',this.props.token); |
|
|
|
data.append('user_token', this.props.token); |
|
|
|
fetch(API_BASE+'/api.php?action=docomment'+token_param(this.props.token), { |
|
|
|
fetch( |
|
|
|
|
|
|
|
API_BASE + '/api.php?action=docomment' + token_param(this.props.token), |
|
|
|
|
|
|
|
{ |
|
|
|
method: 'POST', |
|
|
|
method: 'POST', |
|
|
|
headers: { |
|
|
|
headers: { |
|
|
|
'Content-Type': 'application/x-www-form-urlencoded', |
|
|
|
'Content-Type': 'application/x-www-form-urlencoded', |
|
|
|
}, |
|
|
|
}, |
|
|
|
body: data, |
|
|
|
body: data, |
|
|
|
}) |
|
|
|
}, |
|
|
|
|
|
|
|
) |
|
|
|
.then(get_json) |
|
|
|
.then(get_json) |
|
|
|
.then((json)=>{ |
|
|
|
.then((json) => { |
|
|
|
if(json.code!==0) { |
|
|
|
if (json.code !== 0) { |
|
|
|
if(json.msg) alert(json.msg); |
|
|
|
if (json.msg) alert(json.msg); |
|
|
|
throw new Error(JSON.stringify(json)); |
|
|
|
throw new Error(JSON.stringify(json)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -458,7 +550,7 @@ export class ReplyForm extends Component { |
|
|
|
this.area_ref.current.clear(); |
|
|
|
this.area_ref.current.clear(); |
|
|
|
this.props.on_complete(); |
|
|
|
this.props.on_complete(); |
|
|
|
}) |
|
|
|
}) |
|
|
|
.catch((e)=>{ |
|
|
|
.catch((e) => { |
|
|
|
console.error(e); |
|
|
|
console.error(e); |
|
|
|
alert('回复失败'); |
|
|
|
alert('回复失败'); |
|
|
|
this.setState({ |
|
|
|
this.setState({ |
|
|
@ -469,55 +561,76 @@ export class ReplyForm extends Component { |
|
|
|
|
|
|
|
|
|
|
|
toggle_preview() { |
|
|
|
toggle_preview() { |
|
|
|
this.setState({ |
|
|
|
this.setState({ |
|
|
|
preview: !this.state.preview |
|
|
|
preview: !this.state.preview, |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
render() { |
|
|
|
render() { |
|
|
|
return ( |
|
|
|
return ( |
|
|
|
<form onSubmit={this.on_submit.bind(this)} className={'reply-form box'+(this.state.text?' reply-sticky':'')}> |
|
|
|
<form |
|
|
|
{ |
|
|
|
onSubmit={this.on_submit.bind(this)} |
|
|
|
this.state.preview ?
|
|
|
|
className={'reply-form box' + (this.state.text ? ' reply-sticky' : '')} |
|
|
|
<div className='reply-preview'> |
|
|
|
> |
|
|
|
<HighlightedMarkdown text={this.state.text} color_picker={this.color_picker} show_pid={()=>{}} /> |
|
|
|
{this.state.preview ? ( |
|
|
|
</div> : |
|
|
|
<div className="reply-preview"> |
|
|
|
<SafeTextarea ref={this.area_ref} id={this.props.pid} on_change={this.on_change_bound} on_submit={this.on_submit.bind(this)} /> |
|
|
|
<HighlightedMarkdown |
|
|
|
} |
|
|
|
text={this.state.text} |
|
|
|
<button type='button' onClick={()=>{this.toggle_preview()}}> |
|
|
|
color_picker={this.color_picker} |
|
|
|
{this.state.preview? <span className="icon icon-eye-blocked" />: <span className="icon icon-eye" />} |
|
|
|
show_pid={() => {}} |
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
) : ( |
|
|
|
|
|
|
|
<SafeTextarea |
|
|
|
|
|
|
|
ref={this.area_ref} |
|
|
|
|
|
|
|
id={this.props.pid} |
|
|
|
|
|
|
|
on_change={this.on_change_bound} |
|
|
|
|
|
|
|
on_submit={this.on_submit.bind(this)} |
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
)} |
|
|
|
|
|
|
|
<button |
|
|
|
|
|
|
|
type="button" |
|
|
|
|
|
|
|
onClick={() => { |
|
|
|
|
|
|
|
this.toggle_preview(); |
|
|
|
|
|
|
|
}} |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
{this.state.preview ? ( |
|
|
|
|
|
|
|
<span className="icon icon-eye-blocked" /> |
|
|
|
|
|
|
|
) : ( |
|
|
|
|
|
|
|
<span className="icon icon-eye" /> |
|
|
|
|
|
|
|
)} |
|
|
|
</button> |
|
|
|
</button> |
|
|
|
{this.state.loading_status==='loading' ? |
|
|
|
{this.state.loading_status === 'loading' ? ( |
|
|
|
<button disabled="disabled"> |
|
|
|
<button disabled="disabled"> |
|
|
|
<span className="icon icon-loading" /> |
|
|
|
<span className="icon icon-loading" /> |
|
|
|
</button> : |
|
|
|
</button> |
|
|
|
|
|
|
|
) : ( |
|
|
|
<button type="submit"> |
|
|
|
<button type="submit"> |
|
|
|
<span className="icon icon-send" /> |
|
|
|
<span className="icon icon-send" /> |
|
|
|
</button> |
|
|
|
</button> |
|
|
|
} |
|
|
|
)} |
|
|
|
</form> |
|
|
|
</form> |
|
|
|
) |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export class PostForm extends Component { |
|
|
|
export class PostForm extends Component { |
|
|
|
constructor(props) { |
|
|
|
constructor(props) { |
|
|
|
super(props); |
|
|
|
super(props); |
|
|
|
this.state={ |
|
|
|
this.state = { |
|
|
|
text: '', |
|
|
|
text: '', |
|
|
|
loading_status: 'done', |
|
|
|
loading_status: 'done', |
|
|
|
img_tip: null, |
|
|
|
img_tip: null, |
|
|
|
preview: false, |
|
|
|
preview: false, |
|
|
|
}; |
|
|
|
}; |
|
|
|
this.img_ref=React.createRef(); |
|
|
|
this.img_ref = React.createRef(); |
|
|
|
this.area_ref=React.createRef(); |
|
|
|
this.area_ref = React.createRef(); |
|
|
|
this.on_change_bound=this.on_change.bind(this); |
|
|
|
this.on_change_bound = this.on_change.bind(this); |
|
|
|
this.on_img_change_bound=this.on_img_change.bind(this); |
|
|
|
this.on_img_change_bound = this.on_img_change.bind(this); |
|
|
|
this.color_picker=new ColorPicker(); |
|
|
|
this.color_picker = new ColorPicker(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
componentDidMount() { |
|
|
|
componentDidMount() { |
|
|
|
if(this.area_ref.current) |
|
|
|
if (this.area_ref.current) this.area_ref.current.focus(); |
|
|
|
this.area_ref.current.focus(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
on_change(value) { |
|
|
|
on_change(value) { |
|
|
@ -526,15 +639,14 @@ export class PostForm extends Component { |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
do_post(text,img) { |
|
|
|
do_post(text, img) { |
|
|
|
let data=new URLSearchParams(); |
|
|
|
let data = new URLSearchParams(); |
|
|
|
data.append('text',this.state.text); |
|
|
|
data.append('text', this.state.text); |
|
|
|
data.append('type',img ? 'image' : 'text'); |
|
|
|
data.append('type', img ? 'image' : 'text'); |
|
|
|
data.append('user_token',this.props.token); |
|
|
|
data.append('user_token', this.props.token); |
|
|
|
if(img) |
|
|
|
if (img) data.append('data', img); |
|
|
|
data.append('data',img); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fetch(API_BASE+'/api.php?action=dopost'+token_param(this.props.token), { |
|
|
|
fetch(API_BASE + '/api.php?action=dopost' + token_param(this.props.token), { |
|
|
|
method: 'POST', |
|
|
|
method: 'POST', |
|
|
|
headers: { |
|
|
|
headers: { |
|
|
|
'Content-Type': 'application/x-www-form-urlencoded', |
|
|
|
'Content-Type': 'application/x-www-form-urlencoded', |
|
|
@ -542,9 +654,9 @@ export class PostForm extends Component { |
|
|
|
body: data, |
|
|
|
body: data, |
|
|
|
}) |
|
|
|
}) |
|
|
|
.then(get_json) |
|
|
|
.then(get_json) |
|
|
|
.then((json)=>{ |
|
|
|
.then((json) => { |
|
|
|
if(json.code!==0) { |
|
|
|
if (json.code !== 0) { |
|
|
|
if(json.msg) alert(json.msg); |
|
|
|
if (json.msg) alert(json.msg); |
|
|
|
throw new Error(JSON.stringify(json)); |
|
|
|
throw new Error(JSON.stringify(json)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -556,7 +668,7 @@ export class PostForm extends Component { |
|
|
|
this.area_ref.current.clear(); |
|
|
|
this.area_ref.current.clear(); |
|
|
|
this.props.on_complete(); |
|
|
|
this.props.on_complete(); |
|
|
|
}) |
|
|
|
}) |
|
|
|
.catch((e)=>{ |
|
|
|
.catch((e) => { |
|
|
|
console.error(e); |
|
|
|
console.error(e); |
|
|
|
alert('发表失败'); |
|
|
|
alert('发表失败'); |
|
|
|
this.setState({ |
|
|
|
this.setState({ |
|
|
@ -566,59 +678,66 @@ export class PostForm extends Component { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
proc_img(file) { |
|
|
|
proc_img(file) { |
|
|
|
return new Promise((resolve,reject)=>{ |
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
function return_url(url) { |
|
|
|
function return_url(url) { |
|
|
|
const idx=url.indexOf(';base64,'); |
|
|
|
const idx = url.indexOf(';base64,'); |
|
|
|
if(idx===-1) |
|
|
|
if (idx === -1) throw new Error('img not base64 encoded'); |
|
|
|
throw new Error('img not base64 encoded'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return url.substr(idx+8); |
|
|
|
return url.substr(idx + 8); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
let reader=new FileReader(); |
|
|
|
let reader = new FileReader(); |
|
|
|
function on_got_img(url) { |
|
|
|
function on_got_img(url) { |
|
|
|
const image = new Image(); |
|
|
|
const image = new Image(); |
|
|
|
image.onload=(()=>{ |
|
|
|
image.onload = () => { |
|
|
|
let width=image.width; |
|
|
|
let width = image.width; |
|
|
|
let height=image.height; |
|
|
|
let height = image.height; |
|
|
|
let compressed=false; |
|
|
|
let compressed = false; |
|
|
|
|
|
|
|
|
|
|
|
if(width>MAX_IMG_DIAM) { |
|
|
|
if (width > MAX_IMG_DIAM) { |
|
|
|
height=height*MAX_IMG_DIAM/width; |
|
|
|
height = (height * MAX_IMG_DIAM) / width; |
|
|
|
width=MAX_IMG_DIAM; |
|
|
|
width = MAX_IMG_DIAM; |
|
|
|
compressed=true; |
|
|
|
compressed = true; |
|
|
|
} |
|
|
|
} |
|
|
|
if(height>MAX_IMG_DIAM) { |
|
|
|
if (height > MAX_IMG_DIAM) { |
|
|
|
width=width*MAX_IMG_DIAM/height; |
|
|
|
width = (width * MAX_IMG_DIAM) / height; |
|
|
|
height=MAX_IMG_DIAM; |
|
|
|
height = MAX_IMG_DIAM; |
|
|
|
compressed=true; |
|
|
|
compressed = true; |
|
|
|
} |
|
|
|
} |
|
|
|
if(height*width>MAX_IMG_PX) { |
|
|
|
if (height * width > MAX_IMG_PX) { |
|
|
|
let rate=Math.sqrt(height*width/MAX_IMG_PX); |
|
|
|
let rate = Math.sqrt((height * width) / MAX_IMG_PX); |
|
|
|
height/=rate; |
|
|
|
height /= rate; |
|
|
|
width/=rate; |
|
|
|
width /= rate; |
|
|
|
compressed=true; |
|
|
|
compressed = true; |
|
|
|
} |
|
|
|
} |
|
|
|
console.log('chosen img size',width,height); |
|
|
|
console.log('chosen img size', width, height); |
|
|
|
|
|
|
|
|
|
|
|
let canvas=document.createElement('canvas'); |
|
|
|
let canvas = document.createElement('canvas'); |
|
|
|
let ctx=canvas.getContext('2d'); |
|
|
|
let ctx = canvas.getContext('2d'); |
|
|
|
canvas.width=width; |
|
|
|
canvas.width = width; |
|
|
|
canvas.height=height; |
|
|
|
canvas.height = height; |
|
|
|
ctx.drawImage(image,0,0,width,height); |
|
|
|
ctx.drawImage(image, 0, 0, width, height); |
|
|
|
|
|
|
|
|
|
|
|
let quality_l=.1,quality_r=.9,quality,new_url; |
|
|
|
let quality_l = 0.1, |
|
|
|
while(quality_r-quality_l>=.03) { |
|
|
|
quality_r = 0.9, |
|
|
|
quality=(quality_r+quality_l)/2; |
|
|
|
quality, |
|
|
|
new_url=canvas.toDataURL('image/jpeg',quality); |
|
|
|
new_url; |
|
|
|
console.log(quality_l,quality_r,'trying quality',quality,'size',new_url.length); |
|
|
|
while (quality_r - quality_l >= 0.03) { |
|
|
|
if(new_url.length<=MAX_IMG_FILESIZE) |
|
|
|
quality = (quality_r + quality_l) / 2; |
|
|
|
quality_l=quality; |
|
|
|
new_url = canvas.toDataURL('image/jpeg', quality); |
|
|
|
else |
|
|
|
console.log( |
|
|
|
quality_r=quality; |
|
|
|
quality_l, |
|
|
|
|
|
|
|
quality_r, |
|
|
|
|
|
|
|
'trying quality', |
|
|
|
|
|
|
|
quality, |
|
|
|
|
|
|
|
'size', |
|
|
|
|
|
|
|
new_url.length, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
if (new_url.length <= MAX_IMG_FILESIZE) quality_l = quality; |
|
|
|
|
|
|
|
else quality_r = quality; |
|
|
|
} |
|
|
|
} |
|
|
|
if(quality_l>=.101) { |
|
|
|
if (quality_l >= 0.101) { |
|
|
|
console.log('chosen img quality',quality); |
|
|
|
console.log('chosen img quality', quality); |
|
|
|
resolve({ |
|
|
|
resolve({ |
|
|
|
img: return_url(new_url), |
|
|
|
img: return_url(new_url), |
|
|
|
quality: quality, |
|
|
|
quality: quality, |
|
|
@ -629,11 +748,11 @@ export class PostForm extends Component { |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
reject('图片过大,无法上传'); |
|
|
|
reject('图片过大,无法上传'); |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
}; |
|
|
|
image.src=url; |
|
|
|
image.src = url; |
|
|
|
} |
|
|
|
} |
|
|
|
reader.onload=(event)=>{ |
|
|
|
reader.onload = (event) => { |
|
|
|
fixOrientation(event.target.result,{},(fixed_dataurl)=>{ |
|
|
|
fixOrientation(event.target.result, {}, (fixed_dataurl) => { |
|
|
|
on_got_img(fixed_dataurl); |
|
|
|
on_got_img(fixed_dataurl); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}; |
|
|
|
}; |
|
|
@ -642,23 +761,31 @@ export class PostForm extends Component { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
on_img_change() { |
|
|
|
on_img_change() { |
|
|
|
if(this.img_ref.current && this.img_ref.current.files.length) |
|
|
|
if (this.img_ref.current && this.img_ref.current.files.length) |
|
|
|
this.setState({ |
|
|
|
this.setState( |
|
|
|
img_tip: '(正在处理图片……)' |
|
|
|
{ |
|
|
|
},()=>{ |
|
|
|
img_tip: '(正在处理图片……)', |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
() => { |
|
|
|
this.proc_img(this.img_ref.current.files[0]) |
|
|
|
this.proc_img(this.img_ref.current.files[0]) |
|
|
|
.then((d)=>{ |
|
|
|
.then((d) => { |
|
|
|
this.setState({ |
|
|
|
this.setState({ |
|
|
|
img_tip: `(${d.compressed?'压缩到':'尺寸'} ${d.width}*${d.height} / `+ |
|
|
|
img_tip: |
|
|
|
`质量 ${Math.floor(d.quality*100)}% / ${Math.floor(d.img.length/BASE64_RATE/1000)}KB)`, |
|
|
|
`(${d.compressed ? '压缩到' : '尺寸'} ${d.width}*${ |
|
|
|
|
|
|
|
d.height |
|
|
|
|
|
|
|
} / ` +
|
|
|
|
|
|
|
|
`质量 ${Math.floor(d.quality * 100)}% / ${Math.floor( |
|
|
|
|
|
|
|
d.img.length / BASE64_RATE / 1000, |
|
|
|
|
|
|
|
)}KB)`,
|
|
|
|
}); |
|
|
|
}); |
|
|
|
}) |
|
|
|
}) |
|
|
|
.catch((e)=>{ |
|
|
|
.catch((e) => { |
|
|
|
this.setState({ |
|
|
|
this.setState({ |
|
|
|
img_tip: `图片无效:${e}`, |
|
|
|
img_tip: `图片无效:${e}`, |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}, |
|
|
|
|
|
|
|
); |
|
|
|
else |
|
|
|
else |
|
|
|
this.setState({ |
|
|
|
this.setState({ |
|
|
|
img_tip: null, |
|
|
|
img_tip: null, |
|
|
@ -666,34 +793,33 @@ export class PostForm extends Component { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
on_submit(event) { |
|
|
|
on_submit(event) { |
|
|
|
if(event) event.preventDefault(); |
|
|
|
if (event) event.preventDefault(); |
|
|
|
if(this.state.loading_status==='loading') |
|
|
|
if (this.state.loading_status === 'loading') return; |
|
|
|
return; |
|
|
|
if (this.img_ref.current.files.length) { |
|
|
|
if(this.img_ref.current.files.length) { |
|
|
|
|
|
|
|
this.setState({ |
|
|
|
this.setState({ |
|
|
|
loading_status: 'processing', |
|
|
|
loading_status: 'processing', |
|
|
|
}); |
|
|
|
}); |
|
|
|
this.proc_img(this.img_ref.current.files[0]) |
|
|
|
this.proc_img(this.img_ref.current.files[0]) |
|
|
|
.then((d)=>{ |
|
|
|
.then((d) => { |
|
|
|
this.setState({ |
|
|
|
this.setState({ |
|
|
|
loading_status: 'loading', |
|
|
|
loading_status: 'loading', |
|
|
|
}); |
|
|
|
}); |
|
|
|
this.do_post(this.state.text,d.img); |
|
|
|
this.do_post(this.state.text, d.img); |
|
|
|
}) |
|
|
|
}) |
|
|
|
.catch((e)=>{ |
|
|
|
.catch((e) => { |
|
|
|
alert(e); |
|
|
|
alert(e); |
|
|
|
}); |
|
|
|
}); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
this.setState({ |
|
|
|
this.setState({ |
|
|
|
loading_status: 'loading', |
|
|
|
loading_status: 'loading', |
|
|
|
}); |
|
|
|
}); |
|
|
|
this.do_post(this.state.text,null); |
|
|
|
this.do_post(this.state.text, null); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
toggle_preview() { |
|
|
|
toggle_preview() { |
|
|
|
this.setState({ |
|
|
|
this.setState({ |
|
|
|
preview: !this.state.preview |
|
|
|
preview: !this.state.preview, |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -703,52 +829,89 @@ export class PostForm extends Component { |
|
|
|
<div className="post-form-bar"> |
|
|
|
<div className="post-form-bar"> |
|
|
|
<label> |
|
|
|
<label> |
|
|
|
图片 |
|
|
|
图片 |
|
|
|
<input ref={this.img_ref} type="file" accept="image/*" disabled={this.state.loading_status!=='done'} |
|
|
|
<input |
|
|
|
|
|
|
|
ref={this.img_ref} |
|
|
|
|
|
|
|
type="file" |
|
|
|
|
|
|
|
accept="image/*" |
|
|
|
|
|
|
|
disabled={this.state.loading_status !== 'done'} |
|
|
|
onChange={this.on_img_change_bound} |
|
|
|
onChange={this.on_img_change_bound} |
|
|
|
/> |
|
|
|
/> |
|
|
|
</label> |
|
|
|
</label> |
|
|
|
|
|
|
|
|
|
|
|
{ |
|
|
|
{this.state.preview ? ( |
|
|
|
this.state.preview ? |
|
|
|
<button |
|
|
|
<button type='button' onClick={()=>{this.toggle_preview()}}> |
|
|
|
type="button" |
|
|
|
|
|
|
|
onClick={() => { |
|
|
|
|
|
|
|
this.toggle_preview(); |
|
|
|
|
|
|
|
}} |
|
|
|
|
|
|
|
> |
|
|
|
<span className="icon icon-eye-blocked" /> |
|
|
|
<span className="icon icon-eye-blocked" /> |
|
|
|
编辑 |
|
|
|
编辑 |
|
|
|
</button> : |
|
|
|
</button> |
|
|
|
<button type='button' onClick={()=>{this.toggle_preview()}}> |
|
|
|
) : ( |
|
|
|
|
|
|
|
<button |
|
|
|
|
|
|
|
type="button" |
|
|
|
|
|
|
|
onClick={() => { |
|
|
|
|
|
|
|
this.toggle_preview(); |
|
|
|
|
|
|
|
}} |
|
|
|
|
|
|
|
> |
|
|
|
<span className="icon icon-eye" /> |
|
|
|
<span className="icon icon-eye" /> |
|
|
|
预览 |
|
|
|
预览 |
|
|
|
</button> |
|
|
|
</button> |
|
|
|
} |
|
|
|
)} |
|
|
|
|
|
|
|
|
|
|
|
{ |
|
|
|
{this.state.loading_status !== 'done' ? ( |
|
|
|
this.state.loading_status!=='done' ? |
|
|
|
|
|
|
|
<button disabled="disabled"> |
|
|
|
<button disabled="disabled"> |
|
|
|
<span className="icon icon-loading" /> |
|
|
|
<span className="icon icon-loading" /> |
|
|
|
{this.state.loading_status==='processing' ? '处理' : '上传'} |
|
|
|
|
|
|
|
</button> : |
|
|
|
{this.state.loading_status === 'processing' ? '处理' : '上传'} |
|
|
|
|
|
|
|
</button> |
|
|
|
|
|
|
|
) : ( |
|
|
|
<button type="submit"> |
|
|
|
<button type="submit"> |
|
|
|
<span className="icon icon-send" /> |
|
|
|
<span className="icon icon-send" /> |
|
|
|
发表 |
|
|
|
发表 |
|
|
|
</button> |
|
|
|
</button> |
|
|
|
} |
|
|
|
)} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
{!!this.state.img_tip && |
|
|
|
{!!this.state.img_tip && ( |
|
|
|
<p className="post-form-img-tip"> |
|
|
|
<p className="post-form-img-tip"> |
|
|
|
<a onClick={()=>{this.img_ref.current.value=""; this.on_img_change();}}>删除图片</a> |
|
|
|
<a |
|
|
|
|
|
|
|
onClick={() => { |
|
|
|
|
|
|
|
this.img_ref.current.value = ''; |
|
|
|
|
|
|
|
this.on_img_change(); |
|
|
|
|
|
|
|
}} |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
删除图片 |
|
|
|
|
|
|
|
</a> |
|
|
|
{this.state.img_tip} |
|
|
|
{this.state.img_tip} |
|
|
|
</p> |
|
|
|
</p> |
|
|
|
} |
|
|
|
)} |
|
|
|
{ |
|
|
|
{this.state.preview ? ( |
|
|
|
this.state.preview ?
|
|
|
|
<div className="post-preview"> |
|
|
|
<div className='post-preview'> |
|
|
|
<HighlightedMarkdown |
|
|
|
<HighlightedMarkdown text={this.state.text} color_picker={this.color_picker} show_pid={()=>{}} /> |
|
|
|
text={this.state.text} |
|
|
|
</div> : |
|
|
|
color_picker={this.color_picker} |
|
|
|
<SafeTextarea ref={this.area_ref} id="new_post" on_change={this.on_change_bound} on_submit={this.on_submit.bind(this)} /> |
|
|
|
show_pid={() => {}} |
|
|
|
} |
|
|
|
/> |
|
|
|
<p><small> |
|
|
|
</div> |
|
|
|
请遵守<a href="https://thuhole.com/policy.html" target="_blank">树洞管理规范(试行)</a>,文明发言 |
|
|
|
) : ( |
|
|
|
</small></p> |
|
|
|
<SafeTextarea |
|
|
|
|
|
|
|
ref={this.area_ref} |
|
|
|
|
|
|
|
id="new_post" |
|
|
|
|
|
|
|
on_change={this.on_change_bound} |
|
|
|
|
|
|
|
on_submit={this.on_submit.bind(this)} |
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
)} |
|
|
|
|
|
|
|
<p> |
|
|
|
|
|
|
|
<small> |
|
|
|
|
|
|
|
请遵守 |
|
|
|
|
|
|
|
<a href="https://thuhole.com/policy.html" target="_blank"> |
|
|
|
|
|
|
|
树洞管理规范(试行) |
|
|
|
|
|
|
|
</a> |
|
|
|
|
|
|
|
,文明发言 |
|
|
|
|
|
|
|
</small> |
|
|
|
|
|
|
|
</p> |
|
|
|
</form> |
|
|
|
</form> |
|
|
|
) |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |