Browse Source

implement config

dev
xmcp 6 years ago
parent
commit
39e07f24e9
  1. 13
      README.md
  2. 9
      public/index.html
  3. 0
      public/static/bg/eriri.jpg
  4. BIN
      public/static/bg/minecraft.jpg
  5. BIN
      public/static/bg/sif.jpg
  6. BIN
      public/static/bg/yurucamp.jpg
  7. 6
      src/App.js
  8. 2
      src/Common.css
  9. 14
      src/Config.css
  10. 218
      src/Config.js
  11. 7
      src/Flows.css
  12. 4
      src/Flows.js
  13. 2
      src/PressureHelper.js
  14. 23
      src/Title.js
  15. 5
      src/color_picker.js

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`)

9
public/index.html

@ -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;

0
public/static/eriri_bg.jpg → public/static/bg/eriri.jpg

Before

Width:  |  Height:  |  Size: 324 KiB

After

Width:  |  Height:  |  Size: 324 KiB

BIN
public/static/bg/minecraft.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

BIN
public/static/bg/sif.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

BIN
public/static/bg/yurucamp.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

6
src/App.js

@ -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">

2
src/Common.css

@ -41,8 +41,6 @@
left: 0;
width: 100%;
height: 100%;
background: transparent center center;
background-size: cover;
}
.black-outline {

14
src/Config.css

@ -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

@ -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>
&nbsp;
{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>
&nbsp; <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>
新功能建议或问题反馈请在&nbsp;
<a href="https://github.com/xmcp/ashole/issues" target="_blank">GitHub <span className="icon icon-github" /></a>
&nbsp;提出
</p>
</div>
</div>
)
}
}

7
src/Flows.css

@ -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 {

4
src/Flows.js

@ -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>

2
src/PressureHelper.js

@ -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) {

23
src/Title.js

@ -2,17 +2,13 @@ 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 />
@ -30,7 +26,6 @@ const HELP_TEXT=(
GNU General Public License for more details.
</p>
</div>
</div>
);
class ControlBar extends PureComponent {
@ -122,6 +117,16 @@ class ControlBar extends PureComponent {
<div>
<PromotionBar />
<LoginForm />
<div className="box list-menu">
<a onClick={()=>{this.props.show_sidebar(
'设置',
<ConfigUI />
)}}>树洞网页版设置</a>
&nbsp;/&nbsp;
<a href="http://pkuhelper.pku.edu.cn/treehole_rules.html" target="_blank">树洞规范</a>
&nbsp;/&nbsp;
<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>

5
src/color_picker.js

@ -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];
}

Loading…
Cancel
Save