diff --git a/README.md b/README.md index 09a6014..9dc6f32 100644 --- a/README.md +++ b/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`) \ No newline at end of file diff --git a/public/index.html b/public/index.html index e75933a..215ad9f 100644 --- a/public/index.html +++ b/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; diff --git a/public/static/eriri_bg.jpg b/public/static/bg/eriri.jpg similarity index 100% rename from public/static/eriri_bg.jpg rename to public/static/bg/eriri.jpg diff --git a/public/static/bg/minecraft.jpg b/public/static/bg/minecraft.jpg new file mode 100644 index 0000000..b5ba49a Binary files /dev/null and b/public/static/bg/minecraft.jpg differ diff --git a/public/static/bg/sif.jpg b/public/static/bg/sif.jpg new file mode 100644 index 0000000..2fc0636 Binary files /dev/null and b/public/static/bg/sif.jpg differ diff --git a/public/static/bg/yurucamp.jpg b/public/static/bg/yurucamp.jpg new file mode 100644 index 0000000..faf9e3c Binary files /dev/null and b/public/static/bg/yurucamp.jpg differ diff --git a/src/App.js b/src/App.js index e5f7532..c18da70 100644 --- a/src/App.js +++ b/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 { }, }}> -
+
<TokenCtx.Consumer>{(token)=>( <div className="left-container"> diff --git a/src/Common.css b/src/Common.css index 23a0400..c4e7f5b 100644 --- a/src/Common.css +++ b/src/Common.css @@ -41,8 +41,6 @@ left: 0; width: 100%; height: 100%; - background: transparent center center; - background-size: cover; } .black-outline { diff --git a/src/Config.css b/src/Config.css new file mode 100644 index 0000000..03c1601 --- /dev/null +++ b/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); +} \ No newline at end of file diff --git a/src/Config.js b/src/Config.js new file mode 100644 index 0000000..5e1ea7f --- /dev/null +++ b/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> +   + {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> + ) + } +} \ No newline at end of file diff --git a/src/Flows.css b/src/Flows.css index 2d8b297..10f0ee1 100644 --- a/src/Flows.css +++ b/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 { diff --git a/src/Flows.js b/src/Flows.js index 627a51f..6aa5667 100644 --- a/src/Flows.js +++ b/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> diff --git a/src/PressureHelper.js b/src/PressureHelper.js index 0f57291..92aedcb 100644 --- a/src/PressureHelper.js +++ b/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) { diff --git a/src/Title.js b/src/Title.js index d0ffb58..44c911a 100644 --- a/src/Title.js +++ b/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> diff --git a/src/color_picker.js b/src/color_picker.js index 90f5a15..4900cea 100644 --- a/src/color_picker.js +++ b/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]; }