implement config
This commit is contained in:
13
README.md
13
README.md
@@ -24,6 +24,7 @@ React 版 P大树洞,[pkuhelper.pku.edu.cn/hole](http://pkuhelper.pku.edu.cn/h
|
||||
- 显示无限条搜索结果
|
||||
- 智能调整上传图片的质量
|
||||
- 用颜色区分不同人的回复
|
||||
- 自动显示提到的树洞
|
||||
- 突出显示未读树洞
|
||||
- 精确显示发帖时间
|
||||
- 复制树洞链接和全文
|
||||
@@ -36,15 +37,3 @@ React 版 P大树洞,[pkuhelper.pku.edu.cn/hole](http://pkuhelper.pku.edu.cn/h
|
||||
|
||||
- 搜索时筛选有图片、语音的树洞
|
||||
- 发表语音树洞
|
||||
- 关注的树洞有回复时推送提醒
|
||||
|
||||
**附:进行自定义的方法**
|
||||
|
||||
在搜索框中输入类似 `//setflag KEY=value` 的内容(注意大小写、全半角和空格),然后重新打开页面即可生效。
|
||||
|
||||
目前可以自定义的功能包括:
|
||||
|
||||
- 检测瀑布流中被删除的树洞和树洞被删除的评论(`//setflag DELETION_DETECT=on`)
|
||||
- 自定义背景图片(`//setflag REPLACE_ERIRI_WITH_URL=http://...`)
|
||||
- 禁用重压屏幕(3D Touch)或按住 Esc 键返回(`//setflag DISABLE_PRESSURE=on`)
|
||||
- 禁用自动显示引用树洞功能(`//setflag DISABLE_QUOTE=on`)
|
||||
@@ -29,7 +29,14 @@
|
||||
var _czc=_czc||[];
|
||||
_czc.push(["_setAccount","1274501752"]);
|
||||
_czc.push(["_setCustomVar","has_token",localStorage['TOKEN']?'yes':'no',1]);
|
||||
_czc.push(["_setCustomVar","background_image",localStorage['REPLACE_ERIRI_WITH_URL']||'null',0]);
|
||||
try {
|
||||
var config=JSON.parse(localStorage['hole_config']||'{}');
|
||||
for(var key in config)
|
||||
if(config.hasOwnProperty(key))
|
||||
_czc.push(["_setCustomVar","config_"+key,JSON.stringify(config[key]),0]);
|
||||
} catch(e) {
|
||||
console.trace(e);
|
||||
}
|
||||
var cnzz_s_tag = document.createElement('script');
|
||||
cnzz_s_tag.type = 'text/javascript';
|
||||
cnzz_s_tag.async = true;
|
||||
|
||||
|
Before Width: | Height: | Size: 324 KiB After Width: | Height: | Size: 324 KiB |
BIN
public/static/bg/minecraft.jpg
Normal file
BIN
public/static/bg/minecraft.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 316 KiB |
BIN
public/static/bg/sif.jpg
Normal file
BIN
public/static/bg/sif.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 286 KiB |
BIN
public/static/bg/yurucamp.jpg
Normal file
BIN
public/static/bg/yurucamp.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 266 KiB |
@@ -4,6 +4,7 @@ import {Title} from './Title';
|
||||
import {Sidebar} from './Sidebar';
|
||||
import {PressureHelper} from './PressureHelper';
|
||||
import {TokenCtx,ISOP_APPKEY} from './UserAction';
|
||||
import {load_config,bgimg_style} from './Config';
|
||||
|
||||
import ImasuguApp from './imasugu/src/App';
|
||||
|
||||
@@ -37,6 +38,7 @@ function DeprecatedAlert(props) {
|
||||
class App extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
load_config();
|
||||
this.state={
|
||||
sidebar_title: '',
|
||||
sidebar_content: null, // determine status of sidebar
|
||||
@@ -97,9 +99,7 @@ class App extends Component {
|
||||
},
|
||||
}}>
|
||||
<PressureHelper callback={this.on_pressure_bound} />
|
||||
<div className="bg-img" style={{
|
||||
backgroundImage: 'url('+(localStorage['REPLACE_ERIRI_WITH_URL'] || 'static/eriri_bg.jpg')+')'
|
||||
}} />
|
||||
<div className="bg-img" style={bgimg_style()} />
|
||||
<Title show_sidebar={this.show_sidebar_bound} set_mode={this.set_mode_bound} />
|
||||
<TokenCtx.Consumer>{(token)=>(
|
||||
<div className="left-container">
|
||||
|
||||
@@ -41,8 +41,6 @@
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: transparent center center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.black-outline {
|
||||
|
||||
14
src/Config.css
Normal file
14
src/Config.css
Normal file
@@ -0,0 +1,14 @@
|
||||
.config-ui-header {
|
||||
text-align: center;
|
||||
top: 0;
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
.bg-preview {
|
||||
height: 18em;
|
||||
width: 32em;
|
||||
max-height: 60vh;
|
||||
max-width: 100%;
|
||||
margin: .5em auto 1em;
|
||||
box-shadow: 0 1px 5px rgba(0,0,0,.4);
|
||||
}
|
||||
218
src/Config.js
Normal file
218
src/Config.js
Normal file
@@ -0,0 +1,218 @@
|
||||
import React, {Component, PureComponent} from 'react';
|
||||
|
||||
import './Config.css';
|
||||
|
||||
const BUILTIN_IMGS={
|
||||
'static/bg/eriri.jpg': '平成著名画师(默认)',
|
||||
'static/bg/yurucamp.jpg': '露营天下第一',
|
||||
'static/bg/minecraft.jpg': '麦恩·库拉夫特',
|
||||
'static/bg/sif.jpg': '梦开始的地方',
|
||||
};
|
||||
|
||||
const DEFAULT_CONFIG={
|
||||
background_img: 'static/bg/eriri.jpg',
|
||||
background_color: '#112244',
|
||||
pressure: true,
|
||||
quote: true,
|
||||
horizontal_scroll: true,
|
||||
color_picker: true,
|
||||
easter_egg: true,
|
||||
};
|
||||
|
||||
export function load_config() {
|
||||
let config_txt=localStorage['hole_config']||'{}';
|
||||
let config;
|
||||
try {
|
||||
config=JSON.parse(config_txt);
|
||||
} catch(e) {
|
||||
alert('设置加载失败,将重置为默认设置!\n'+e);
|
||||
delete localStorage['hole_config'];
|
||||
config={};
|
||||
}
|
||||
|
||||
Object.keys(DEFAULT_CONFIG).forEach((key)=>{
|
||||
if(config[key]===undefined)
|
||||
config[key]=DEFAULT_CONFIG[key];
|
||||
});
|
||||
|
||||
console.log('config loaded',config);
|
||||
window.config=config;
|
||||
}
|
||||
export function save_config() {
|
||||
localStorage['hole_config']=JSON.stringify(window.config);
|
||||
load_config();
|
||||
}
|
||||
|
||||
export function bgimg_style(img,color) {
|
||||
if(img===undefined) img=window.config.background_img;
|
||||
if(color===undefined) color=window.config.background_color;
|
||||
return {
|
||||
background: 'transparent center center',
|
||||
backgroundImage: img===null ? 'unset' : 'url('+encodeURI(img)+')',
|
||||
backgroundColor: color,
|
||||
backgroundSize: 'cover',
|
||||
};
|
||||
}
|
||||
|
||||
class ConfigBackground extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state={
|
||||
img: window.config.background_img,
|
||||
color: window.config.background_color,
|
||||
};
|
||||
}
|
||||
|
||||
save_changes() {
|
||||
this.props.callback({
|
||||
background_img: this.state.img,
|
||||
background_color: this.state.color,
|
||||
});
|
||||
}
|
||||
|
||||
on_select(e) {
|
||||
let value=e.target.value;
|
||||
this.setState({
|
||||
img: value==='##other' ? '' :
|
||||
value==='##color' ? null : value,
|
||||
},this.save_changes.bind(this));
|
||||
}
|
||||
on_change_img(e) {
|
||||
this.setState({
|
||||
img: e.target.value,
|
||||
},this.save_changes.bind(this));
|
||||
}
|
||||
on_change_color(e) {
|
||||
this.setState({
|
||||
color: e.target.value,
|
||||
},this.save_changes.bind(this));
|
||||
}
|
||||
|
||||
render() {
|
||||
let img_select= this.state.img===null ? '##color' :
|
||||
Object.keys(BUILTIN_IMGS).indexOf(this.state.img)===-1 ? '##other' : this.state.img;
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
<b>背景图片:</b>
|
||||
<select value={img_select} onChange={this.on_select.bind(this)}>
|
||||
{Object.keys(BUILTIN_IMGS).map((key)=>(
|
||||
<option key={key} value={key}>{BUILTIN_IMGS[key]}</option>
|
||||
))}
|
||||
<option value="##other">输入图片网址……</option>
|
||||
<option value="##color">纯色背景……</option>
|
||||
</select>
|
||||
|
||||
{img_select==='##other' &&
|
||||
<input type="url" placeholder="图片网址" value={this.state.img} onChange={this.on_change_img.bind(this)} />
|
||||
}
|
||||
{img_select==='##color' &&
|
||||
<input type="color" value={this.state.color} onChange={this.on_change_color.bind(this)} />
|
||||
}
|
||||
</p>
|
||||
<div className="bg-preview" style={bgimg_style(this.state.img,this.state.color)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ConfigSwitch extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state={
|
||||
switch: window.config[this.props.id],
|
||||
};
|
||||
}
|
||||
|
||||
on_change(e) {
|
||||
let val=e.target.checked;
|
||||
this.setState({
|
||||
switch: val,
|
||||
},()=>{
|
||||
this.props.callback({
|
||||
[this.props.id]: val,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
<label>
|
||||
<input name={'config-'+this.props.id} type="checkbox" checked={this.state.switch} onChange={this.on_change.bind(this)} />
|
||||
<b>{this.props.name}</b>
|
||||
<small>#{this.props.id}</small>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
{this.props.description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class ConfigUI extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.save_changes_bound=this.save_changes.bind(this);
|
||||
}
|
||||
|
||||
save_changes(chg) {
|
||||
console.log(chg);
|
||||
Object.keys(chg).forEach((key)=>{
|
||||
window.config[key]=chg[key];
|
||||
});
|
||||
save_config();
|
||||
}
|
||||
|
||||
reset_settings() {
|
||||
if(window.confirm('重置所有设置?')) {
|
||||
window.config={};
|
||||
save_config();
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div className="box config-ui-header">
|
||||
<p>这些功能仍在测试,可能不稳定(<a onClick={this.reset_settings.bind(this)}>全部重置</a>)</p>
|
||||
<p>我们会收集你的设置,以用于改进产品</p>
|
||||
<p><b>修改设置后 <a onClick={()=>{window.location.reload()}}>刷新页面</a> 方可生效</b></p>
|
||||
</div>
|
||||
<div className="box">
|
||||
<ConfigBackground callback={this.save_changes_bound} />
|
||||
<hr />
|
||||
<ConfigSwitch callback={this.save_changes_bound} id="pressure" name="快速返回"
|
||||
description="短暂按住 Esc 键或重压屏幕(3D Touch)可以快速返回或者刷新树洞"
|
||||
/>
|
||||
<hr />
|
||||
<ConfigSwitch callback={this.save_changes_bound} id="quote" name="自动显示引文"
|
||||
description="当树洞正文提到另一个树洞的编号时,在下方自动显示引文"
|
||||
/>
|
||||
<hr />
|
||||
<ConfigSwitch callback={this.save_changes_bound} id="horizontal_scroll" name="横向滚动"
|
||||
description="在树洞列表里横向滚动浏览回复,如果经常误触可以把它关掉"
|
||||
/>
|
||||
<hr />
|
||||
<ConfigSwitch callback={this.save_changes_bound} id="color_picker" name="回复颜色标记"
|
||||
description="为不同人的回复分配不同颜色"
|
||||
/>
|
||||
<hr />
|
||||
<ConfigSwitch callback={this.save_changes_bound} id="easter_egg" name="允许彩蛋"
|
||||
description="在某些情况下显示彩蛋"
|
||||
/>
|
||||
<hr />
|
||||
<p>
|
||||
新功能建议或问题反馈请在
|
||||
<a href="https://github.com/xmcp/ashole/issues" target="_blank">GitHub <span className="icon icon-github" /></a>
|
||||
提出。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
border-radius: 5px;
|
||||
margin: 1em 0;
|
||||
padding: .5em;
|
||||
box-shadow: 0 5px 10px rgba(0,0,0,.4);
|
||||
box-shadow: 0 3px 8px rgba(0,0,0,.4);
|
||||
}
|
||||
|
||||
.box-tip {
|
||||
@@ -30,6 +30,9 @@
|
||||
padding-left: 18px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.flow-reply-row.config-no-scroll {
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
.flow-reply-row::-webkit-scrollbar {
|
||||
display: none;
|
||||
@@ -177,7 +180,7 @@
|
||||
|
||||
.flow-item-row-quote {
|
||||
opacity: .8;
|
||||
filter: brightness(90%);
|
||||
filter: brightness(95%);
|
||||
}
|
||||
|
||||
.flow-item-quote>.box {
|
||||
|
||||
@@ -438,7 +438,7 @@ class FlowItemRow extends PureComponent {
|
||||
let parts=split_text(this.state.info.text,hl_rules);
|
||||
|
||||
let quote_id=null;
|
||||
if(!this.props.is_quote && localStorage['DISABLE_QUOTE']!=='on')
|
||||
if(!this.props.is_quote && window.config.quote)
|
||||
for(let [mode,content] of parts)
|
||||
if(mode==='pid' && QUOTE_BLACKLIST.indexOf(content)===-1 && parseInt(content)<parseInt(this.state.info.pid))
|
||||
if(quote_id===null)
|
||||
@@ -455,7 +455,7 @@ class FlowItemRow extends PureComponent {
|
||||
}}>
|
||||
<FlowItem parts={parts} info={this.state.info} attention={this.state.attention} img_clickable={false} is_quote={this.props.is_quote}
|
||||
color_picker={this.color_picker} show_pid={show_pid} replies={this.state.replies} />
|
||||
<div className="flow-reply-row">
|
||||
<div className={'flow-reply-row'+(window.config.horizontal_scroll ? '' : ' config-no-scroll')}>
|
||||
{this.state.reply_status==='loading' && <div className="box box-tip">加载中</div>}
|
||||
{this.state.reply_status==='failed' &&
|
||||
<div className="box box-tip"><a onClick={()=>{this.load_replies()}}>重新加载</a></div>
|
||||
|
||||
@@ -37,7 +37,7 @@ export class PressureHelper extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if(localStorage['DISABLE_PRESSURE']!=='on') {
|
||||
if(window.config.pressure) {
|
||||
Pressure.set(document.body, {
|
||||
change: (force)=>{
|
||||
if(!this.state.fired) {
|
||||
|
||||
55
src/Title.js
55
src/Title.js
@@ -2,34 +2,29 @@ import React, {Component, PureComponent} from 'react';
|
||||
import {LoginForm, PostForm} from './UserAction';
|
||||
import {TokenCtx} from './UserAction';
|
||||
import {PromotionBar} from './Common';
|
||||
import {ConfigUI} from './Config';
|
||||
|
||||
import './Title.css';
|
||||
|
||||
const flag_re=/^\/\/setflag ([a-zA-Z0-9_]+)=(.*)$/;
|
||||
|
||||
const HELP_TEXT=(
|
||||
<div>
|
||||
<div className="box list-menu">
|
||||
<p><a href="http://pkuhelper.pku.edu.cn/treehole_rules.html" target="_blank">树洞管理规范</a></p>
|
||||
<p><a href="https://github.com/xmcp/ashole/issues" target="_blank">意见反馈 <span className="icon icon-github" /></a></p>
|
||||
</div>
|
||||
<div className="box">
|
||||
<p className="centered-line">树洞网页版 by @xmcp</p>
|
||||
<br />
|
||||
<p>
|
||||
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
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
</p>
|
||||
</div>
|
||||
<div className="box">
|
||||
<p className="centered-line">树洞网页版 by @xmcp</p>
|
||||
<br />
|
||||
<p>
|
||||
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
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -122,6 +117,16 @@ class ControlBar extends PureComponent {
|
||||
<div>
|
||||
<PromotionBar />
|
||||
<LoginForm />
|
||||
<div className="box list-menu">
|
||||
<a onClick={()=>{this.props.show_sidebar(
|
||||
'设置',
|
||||
<ConfigUI />
|
||||
)}}>树洞网页版设置</a>
|
||||
/
|
||||
<a href="http://pkuhelper.pku.edu.cn/treehole_rules.html" target="_blank">树洞规范</a>
|
||||
/
|
||||
<a href="https://github.com/xmcp/ashole/issues" target="_blank">意见反馈 <span className="icon icon-github" /></a>
|
||||
</div>
|
||||
{HELP_TEXT}
|
||||
</div>
|
||||
)
|
||||
@@ -149,7 +154,7 @@ class ControlBar extends PureComponent {
|
||||
|
||||
export function Title(props) {
|
||||
let date=new Date();
|
||||
let eriri_easteregg=(1+date.getMonth())===3 && date.getDate()===20 && !localStorage['REPLACE_ERIRI_WITH_URL'];
|
||||
let final_exam_egg=(1+date.getMonth())===6 && date.getDate()>=8 && date.getDate()<=21;
|
||||
|
||||
return (
|
||||
<div className="title-bar">
|
||||
@@ -159,8 +164,8 @@ export function Title(props) {
|
||||
P大树洞
|
||||
</p>
|
||||
<p className="title-small">
|
||||
{ eriri_easteregg ?
|
||||
<span style={{backgroundColor: 'yellow'}}>3月20日是看板娘<a href="https://zh.moegirl.org/%E6%B3%BD%E6%9D%91%C2%B7%E6%96%AF%E5%AE%BE%E5%A1%9E%C2%B7%E8%8B%B1%E6%A2%A8%E6%A2%A8" target="_blank">英梨梨</a>的生日</span> :
|
||||
{ final_exam_egg && window.config.easter_egg ?
|
||||
<span style={{backgroundColor: 'yellow'}}>期末加油</span> :
|
||||
"官方网页版"
|
||||
}
|
||||
</p>
|
||||
|
||||
@@ -12,10 +12,13 @@ export class ColorPicker {
|
||||
name=name.toLowerCase();
|
||||
if(name==='洞主')
|
||||
return 'hsl(0,0%,97%)';
|
||||
if(!window.config.color_picker)
|
||||
return 'hsl(0,0%,85%)';
|
||||
|
||||
if(!this.names[name]) {
|
||||
this.current_h+=golden_ratio_conjugate;
|
||||
this.current_h%=1;
|
||||
this.names[name]=`hsl(${this.current_h*360}, 40%, 85%)`;
|
||||
this.names[name]=`hsl(${this.current_h*360}, 40%, 87%)`;
|
||||
}
|
||||
return this.names[name];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user