Browse Source

功能适配与界面调整若干

pull/6/head
hole-thu 5 years ago
parent
commit
99f99d39f7
  1. 2
      public/index.html
  2. BIN
      public/static/favicon/180.png
  3. BIN
      public/static/favicon/192.png
  4. BIN
      public/static/favicon/512.png
  5. 37
      src/Common.js
  6. 15
      src/Flows.css
  7. 47
      src/Flows.js
  8. 5
      src/Message.js
  9. 6
      src/UserAction.css
  10. 38
      src/UserAction.js
  11. 22
      src/flows_api.js
  12. 5
      src/infrastructure/functions.js

2
public/index.html

@ -26,6 +26,6 @@
<title>新T树洞</title> <title>新T树洞</title>
</head> </head>
<body> <body>
<div id="root">开启javascript,或刷新重试</div> <div id="root">请开启javascript,或稍等片刻</div>
</body> </body>
</html> </html>

BIN
public/static/favicon/180.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 67 KiB

BIN
public/static/favicon/192.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 67 KiB

BIN
public/static/favicon/512.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 79 KiB

37
src/Common.js

@ -16,7 +16,7 @@ import renderMd from './Markdown';
export { format_time, Time, TitleLine }; export { format_time, Time, TitleLine };
export const API_BASE = '/_api/v1/'; export const API_BASE = '/_api/v1';
// https://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex // https://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
function escape_regex(string) { function escape_regex(string) {
@ -164,6 +164,7 @@ export class HighlightedMarkdown extends Component {
['url', URL_RE], ['url', URL_RE],
['pid', PID_RE], ['pid', PID_RE],
['nickname', NICKNAME_RE], ['nickname', NICKNAME_RE],
//TODO: tag
]); ]);
return ( return (
@ -217,30 +218,15 @@ export class HighlightedMarkdown extends Component {
}, },
]; ];
const parser = new HtmlToReact.Parser(); const parser = new HtmlToReact.Parser();
if (props.author && props.text.match(/^(?:#+ |>|```|\t|\s*-|\s*\d+\.)/)) { let rawMd = props.text;
const renderedMarkdown = renderMd(props.text); const renderedMarkdown = renderMd(rawMd);
return ( return (
<> parser.parseWithInstructions(
{props.author} renderedMarkdown,
{parser.parseWithInstructions( (node) => node.type !== 'script',
renderedMarkdown, processInstructions,
(node) => node.type !== 'script', ) || null
processInstructions, );
) || ''}
</>
);
} else {
let rawMd = props.text;
if (props.author) rawMd = props.author + ' ' + rawMd;
const renderedMarkdown = renderMd(rawMd);
return (
parser.parseWithInstructions(
renderedMarkdown,
(node) => node.type !== 'script',
processInstructions,
) || null
);
}
} }
} }
@ -314,6 +300,7 @@ export class SafeTextarea extends Component {
onChange={this.on_change_bound} onChange={this.on_change_bound}
value={this.state.text} value={this.state.text}
onKeyDown={this.on_keydown_bound} onKeyDown={this.on_keydown_bound}
maxLength="4096"
/> />
); );
} }

15
src/Flows.css

@ -254,7 +254,7 @@
color: white; color: white;
} }
.box-header-tag { .box-header-cw {
color: white; color: white;
background-color: #00c; background-color: #00c;
font-weight: bold; font-weight: bold;
@ -263,7 +263,16 @@
padding: 0 .25em; padding: 0 .25em;
} }
.root-dark-mode .box-header-tag { .box-header-name {
color: white;
background-color: #3338;
font-weight: bold;
border-radius: 5px;
margin-right: .5em;
padding: .1em .5em;
}
.root-dark-mode .box-header-cw {
background-color: #00a; background-color: #00a;
} }
@ -282,4 +291,4 @@
float: right; float: right;
padding: 0 .5em; padding: 0 .5em;
opacity: .4; opacity: .4;
} }

47
src/Flows.js

@ -30,19 +30,6 @@ const CLICKABLE_TAGS = { a: true, audio: true };
const PREVIEW_REPLY_COUNT = 10; 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 = []; const QUOTE_BLACKLIST = [];
const FOLD_TAGS = [
'性相关',
'政治相关',
'性话题',
'政治话题',
'折叠',
'NSFW',
'刷屏',
'真实性可疑',
'用户举报较多',
'举报较多',
'重复内容',
];
window.LATEST_POST_ID = parseInt(localStorage['_LATEST_POST_ID'], 10) || 0; window.LATEST_POST_ID = parseInt(localStorage['_LATEST_POST_ID'], 10) || 0;
@ -104,11 +91,8 @@ class Reply extends PureComponent {
} }
render() { render() {
const replyContent = this.props.info.text; const author = this.props.info.name,
const splitIdx = replyContent.indexOf(']'); replyText = this.props.info.text;
const author = replyContent.substr(0, splitIdx + 1),
replyText = replyContent.substr(splitIdx + 2);
return ( return (
<div <div
className={'flow-reply box'} className={'flow-reply box'}
@ -122,7 +106,6 @@ class Reply extends PureComponent {
} }
> >
<div className="box-header"> <div className="box-header">
<code className="box-id">#{this.props.info.cid}</code>
{!!this.props.do_filter_name && ( {!!this.props.do_filter_name && (
<span <span
className="reply-header-badge clickable" className="reply-header-badge clickable"
@ -134,10 +117,12 @@ class Reply extends PureComponent {
</span> </span>
)} )}
&nbsp; &nbsp;
{this.props.info.tag !== null && ( {(
<span className="box-header-tag">{this.props.info.tag}</span> <span className="box-header-name">{this.props.info.name}</span>
)} )}
<Time stamp={this.props.info.timestamp} short={false} /> <Time stamp={this.props.info.timestamp} short={false} />
&nbsp;
<code className="box-id">{'$' + this.props.info.cid}</code>
</div> </div>
<div className="box-content"> <div className="box-content">
<HighlightedMarkdown <HighlightedMarkdown
@ -161,7 +146,7 @@ class FlowItem extends PureComponent {
event.preventDefault(); event.preventDefault();
copy( copy(
`${event.target.href}${ `${event.target.href}${
this.props.info.tag ? ' 【' + this.props.info.tag + '】' : '' this.props.info.cw ? ' 【' + this.props.info.cw + '】' : ''
}\n` + }\n` +
`${this.props.info.text}${ `${this.props.info.text}${
this.props.info.type === 'image' this.props.info.type === 'image'
@ -174,7 +159,7 @@ class FlowItem extends PureComponent {
this.props.info.likenum this.props.info.likenum
}关注 ${this.props.info.reply}回复\n` + }关注 ${this.props.info.reply}回复\n` +
this.props.replies this.props.replies
.map((r) => (r.tag ? '【' + r.tag + '】' : '') + r.text) .map((r) => (r.cw ? '【' + r.cw + '】' : '') + r.text)
.join('\n'), .join('\n'),
); );
} }
@ -237,8 +222,8 @@ class FlowItem extends PureComponent {
</a> </a>
</code> </code>
&nbsp; &nbsp;
{props.info.tag !== null && props.info.tag !== '折叠' && ( {props.info.cw !== null && (
<span className="box-header-tag">{props.info.tag}</span> <span className="box-header-cw">{props.info.cw}</span>
)} )}
<Time stamp={props.info.timestamp} short={!props.img_clickable} /> <Time stamp={props.info.timestamp} short={!props.img_clickable} />
</div> </div>
@ -634,8 +619,7 @@ class FlowSidebar extends PureComponent {
class FlowItemRow extends PureComponent { class FlowItemRow extends PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this.needFold = this.needFold = props.info.cw &&
FOLD_TAGS.indexOf(props.info.tag) > -1 &&
(props.search_param === '热榜' || !props.search_param) && (props.search_param === '热榜' || !props.search_param) &&
window.config.fold && window.config.fold &&
props.mode !== 'attention' && props.mode !== 'attention_finished'; props.mode !== 'attention' && props.mode !== 'attention_finished';
@ -861,8 +845,8 @@ class FlowItemRow extends PureComponent {
)} )}
<code className="box-id">#{this.props.info.pid}</code> <code className="box-id">#{this.props.info.pid}</code>
&nbsp; &nbsp;
{this.props.info.tag !== null && this.props.info.tag !== '折叠' && ( {this.props.info.cw !== null && (
<span className="box-header-tag">{this.props.info.tag}</span> <span className="box-header-cw">{this.props.info.cw}</span>
)} )}
<Time stamp={this.props.info.timestamp} short={true} /> <Time stamp={this.props.info.timestamp} short={true} />
<span className="box-header-badge"> <span className="box-header-badge">
@ -1038,10 +1022,11 @@ export class Flow extends PureComponent {
load_page(page) { load_page(page) {
const failed = (err) => { const failed = (err) => {
console.error(err); console.error(err);
console.log(err.to_string);
this.setState((prev, props) => ({ this.setState((prev, props) => ({
loaded_pages: prev.loaded_pages - 1, loaded_pages: prev.loaded_pages - 1,
loading_status: 'failed', loading_status: 'failed',
error_msg: '' + err, error_msg: prev.loaded_pages>1 ? '找不到更多了' : '' + err,
})); }));
}; };
@ -1219,7 +1204,7 @@ export class Flow extends PureComponent {
&nbsp;Loading... &nbsp;Loading...
</span> </span>
) : ( ) : (
'© thuhole' '🄯 2020 copyleft: hole_thu'
) )
} }
/> />

5
src/Message.js

@ -22,9 +22,8 @@ export class MessageViewer extends PureComponent {
}, },
() => { () => {
fetch( fetch(
'/api/v1/system_msg?user_token=' + '/_api/v1/system_msg?user_token=' +
encodeURIComponent(this.props.token) + encodeURIComponent(this.props.token)
API_VERSION_PARAM(),
) )
.then(get_json) .then(get_json)
.then((json) => { .then((json) => {

6
src/UserAction.css

@ -109,3 +109,9 @@
.life-info-error a { .life-info-error a {
--var-link-color: hsl(25,100%,45%); --var-link-color: hsl(25,100%,45%);
} }
.spoiler-input {
width: 100%;
margin-bottom: 5px;
padding: 2px;
}

38
src/UserAction.js

@ -171,6 +171,8 @@ export class LoginForm extends Component {
当您发送的内容违规时我们将用系统消息提示您 当您发送的内容违规时我们将用系统消息提示您
</p> </p>
<p> <p>
<small>{token.value}</small>
<br/>
<a onClick={this.copy_token.bind(this, token.value)}> <a onClick={this.copy_token.bind(this, token.value)}>
复制 User Token 复制 User Token
</a> </a>
@ -263,7 +265,7 @@ export class ReplyForm extends Component {
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( fetch(
API_BASE + '/api.php?action=docomment' + token_param(this.props.token), API_BASE + '/docomment' + token_param(this.props.token),
{ {
method: 'POST', method: 'POST',
headers: { headers: {
@ -355,6 +357,7 @@ export class PostForm extends Component {
super(props); super(props);
this.state = { this.state = {
text: '', text: '',
cw: '',
loading_status: 'done', loading_status: 'done',
img_tip: null, img_tip: null,
preview: false, preview: false,
@ -362,6 +365,7 @@ export class PostForm extends Component {
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_cw_change_bound = this.on_cw_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();
} }
@ -370,6 +374,12 @@ export class PostForm extends Component {
if (this.area_ref.current) this.area_ref.current.focus(); if (this.area_ref.current) this.area_ref.current.focus();
} }
on_cw_change(event) {
this.setState({
cw: event.target.value,
});
}
on_change(value) { on_change(value) {
this.setState({ this.setState({
text: value, text: value,
@ -378,12 +388,13 @@ export class PostForm extends Component {
do_post(text, img) { do_post(text, img) {
let data = new URLSearchParams(); let data = new URLSearchParams();
data.append('cw', this.state.cw);
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) data.append('data', img); if (img) data.append('data', img);
fetch(API_BASE + '/api.php?action=dopost' + token_param(this.props.token), { fetch(API_BASE + '/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',
@ -632,12 +643,23 @@ export class PostForm extends Component {
/> />
</div> </div>
) : ( ) : (
<SafeTextarea <>
ref={this.area_ref} <input
id="new_post" type="text"
on_change={this.on_change_bound} placeholder="折叠警告(留空表示不折叠)"
on_submit={this.on_submit.bind(this)} value={this.state.cw}
/> id="post_cw"
className="spoiler-input"
onChange={this.on_cw_change_bound}
maxLength="32"
/>
<SafeTextarea
ref={this.area_ref}
id="new_post"
on_change={this.on_change_bound}
on_submit={this.on_submit.bind(this)}
/>
</>
)} )}
<p> <p>
<small> <small>

22
src/flows_api.js

@ -1,9 +1,9 @@
import { get_json, API_VERSION_PARAM } from './infrastructure/functions'; import { get_json} from './infrastructure/functions';
import { API_BASE } from './Common'; import { API_BASE } from './Common';
import { cache } from './cache'; import { cache } from './cache';
export function token_param(token) { export function token_param(token) {
return API_VERSION_PARAM() + (token ? '&user_token=' + token : ''); return token ? '?user_token=' + token : '?notoken';
} }
export { get_json }; export { get_json };
@ -34,7 +34,7 @@ export const API = {
load_replies: async (pid, token, color_picker, cache_version) => { load_replies: async (pid, token, color_picker, cache_version) => {
pid = parseInt(pid); pid = parseInt(pid);
let response = await fetch( let response = await fetch(
API_BASE + '/api.php?action=getcomment&pid=' + pid + token_param(token), API_BASE + '/getcomment' + token_param(token) + '&pid=' + pid ,
); );
let json = await handle_response(response); let json = await handle_response(response);
// Why delete then put ?? // Why delete then put ??
@ -61,7 +61,7 @@ export const API = {
data.append('pid', pid); data.append('pid', pid);
data.append('switch', attention ? '1' : '0'); data.append('switch', attention ? '1' : '0');
let response = await fetch( let response = await fetch(
API_BASE + '/api.php?action=attention' + token_param(token), API_BASE + '/attention' + token_param(token),
{ {
method: 'POST', method: 'POST',
headers: { headers: {
@ -81,7 +81,7 @@ export const API = {
data.append('pid', pid); data.append('pid', pid);
data.append('reason', reason); data.append('reason', reason);
let response = await fetch( let response = await fetch(
API_BASE + '/api.php?action=report' + token_param(token), API_BASE + '/report' + token_param(token),
{ {
method: 'POST', method: 'POST',
headers: { headers: {
@ -95,7 +95,7 @@ export const API = {
get_list: async (page, token) => { get_list: async (page, token) => {
let response = await fetch( let response = await fetch(
API_BASE + '/api.php?action=getlist' + '&p=' + page + token_param(token), API_BASE + '/getlist' + token_param(token) + '&p=' + page,
); );
return handle_response(response); return handle_response(response);
}, },
@ -103,28 +103,28 @@ export const API = {
get_search: async (page, keyword, token) => { get_search: async (page, keyword, token) => {
let response = await fetch( let response = await fetch(
API_BASE + API_BASE +
'/api.php?action=search' + '/search' +
token_param(token) +
'&pagesize=' + '&pagesize=' +
SEARCH_PAGESIZE + SEARCH_PAGESIZE +
'&page=' + '&page=' +
page + page +
'&keywords=' + '&keywords=' +
encodeURIComponent(keyword) + encodeURIComponent(keyword)
token_param(token),
); );
return handle_response(response); return handle_response(response);
}, },
get_single: async (pid, token) => { get_single: async (pid, token) => {
let response = await fetch( let response = await fetch(
API_BASE + '/api.php?action=getone' + '&pid=' + pid + token_param(token), API_BASE + '/getone' + token_param(token) + '&pid=' + pid,
); );
return handle_response(response); return handle_response(response);
}, },
get_attention: async (token) => { get_attention: async (token) => {
let response = await fetch( let response = await fetch(
API_BASE + '/api.php?action=getattention' + token_param(token), API_BASE + '/getattention' + token_param(token),
); );
return handle_response(response); return handle_response(response);
}, },

5
src/infrastructure/functions.js

@ -1,5 +1,5 @@
export function get_json(res) { export function get_json(res) {
if(!res.ok) throw Error(`网络错误 ${res.status} ${res.statusText}`); if(!res.ok) throw Error(`错误 ${res.status} ${res.statusText}`);
return ( return (
res res
.text() .text()
@ -30,6 +30,3 @@ export function listen_darkmode(override) { // override: true/false/undefined
}); });
} }
export function API_VERSION_PARAM() {
return '&PKUHelperAPI=3.0&jsapiver='+encodeURIComponent((process.env.REACT_APP_BUILD_INFO||'null')+'-'+(Math.floor(+new Date()/7200000)*2));
}
Loading…
Cancel
Save