diff --git a/CNAME b/CNAME index a7ea9dc..d8132a1 100644 --- a/CNAME +++ b/CNAME @@ -1 +1 @@ -tst.thuhole.tech \ No newline at end of file +tst.thuhole.com \ No newline at end of file diff --git a/asset-manifest.json b/asset-manifest.json index c2bd92a..4a1f9f6 100644 --- a/asset-manifest.json +++ b/asset-manifest.json @@ -1,14 +1,14 @@ { "files": { "main.css": "//cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/static/css/main.e52316e2.chunk.css", - "main.js": "//cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/static/js/main.62bb5ac5.chunk.js", - "main.js.map": "//cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/static/js/main.62bb5ac5.chunk.js.map", + "main.js": "//cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/static/js/main.82fe48f3.chunk.js", + "main.js.map": "//cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/static/js/main.82fe48f3.chunk.js.map", "runtime~main.js": "//cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/static/js/runtime~main.a9c024a1.js", "runtime~main.js.map": "//cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/static/js/runtime~main.a9c024a1.js.map", "static/js/2.688ae6b0.chunk.js": "//cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/static/js/2.688ae6b0.chunk.js", "static/js/2.688ae6b0.chunk.js.map": "//cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/static/js/2.688ae6b0.chunk.js.map", "index.html": "//cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/index.html", - "precache-manifest.2a28e16a27d65299f04df889a07240f9.js": "//cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/precache-manifest.2a28e16a27d65299f04df889a07240f9.js", + "precache-manifest.873b0492f2d03cbcde637a67255e023a.js": "//cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/precache-manifest.873b0492f2d03cbcde637a67255e023a.js", "service-worker.js": "//cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/service-worker.js", "static/css/main.e52316e2.chunk.css.map": "//cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/static/css/main.e52316e2.chunk.css.map" } diff --git a/index.html b/index.html index f9e6deb..90858ec 100644 --- a/index.html +++ b/index.html @@ -1 +1 @@ -
\n {props.text}\n
\n )\n}\n\nexport function GlobalTitle(props) {\n return (\n{props.text}
\n\n// {!!dropdown_cur_app ?\n// app_elem((()=>{\n// let [id,title,_url,icon_normal,icon_hover,_new_tab]=dropdown_cur_app;\n// return [id,title+'▾',null,icon_normal,icon_hover,false];\n// })(),true) :\n// app_elem(['-placeholder-elem','更多▾',null,appicon_dropdown,appicon_dropdown_rev,false],true)\n// }\n//
\n// {this.state.apps.dropdown.map((app)=>{\n// let ref=React.createRef();\n// return (\n//{\n// if(!e.target.closest('a') && ref.current)\n// ref.current.click();\n// }}>\n// {app_elem(app,true,ref)}\n//
\n// );\n// })}\n//\n 接收验证码来登录 T大树洞\n
\n\n \n \n {/*this.do_sendcode('sms')}>*/}\n {/* 短信 */}\n {/**/}\n {/*/*/}\n this.do_sendcode('mail')}>\n 发送邮件 \n \n \n
\n\n \n \n
\n\n 从其他设备导入登录状态\n
\n\n \n \n
\n\n \n
\n\n {this.props.parts.map((part,idx)=>{\n let [rule,p]=part;\n return (\n {\n rule==='url_pid' ? /## :\n rule==='url' ? {p} :\n rule==='pid' ? {e.preventDefault(); this.props.show_pid(p.substring(1));}}>{p} :\n rule==='nickname' ? {p} :\n rule==='search' ? {p} :\n p\n }\n );\n })}\n
\n )\n }\n}\n\nwindow.TEXTAREA_BACKUP={};\n\nexport class SafeTextarea extends Component {\n constructor(props) {\n super(props);\n this.state={\n text: '',\n };\n this.on_change_bound=this.on_change.bind(this);\n this.on_keydown_bound=this.on_keydown.bind(this);\n this.clear=this.clear.bind(this);\n this.area_ref=React.createRef();\n this.change_callback=props.on_change||(()=>{});\n this.submit_callback=props.on_submit||(()=>{});\n }\n\n componentDidMount() {\n this.setState({\n text: window.TEXTAREA_BACKUP[this.props.id]||''\n },()=>{\n this.change_callback(this.state.text);\n });\n }\n\n componentWillUnmount() {\n window.TEXTAREA_BACKUP[this.props.id]=this.state.text;\n this.change_callback(this.state.text);\n }\n\n on_change(event) {\n this.setState({\n text: event.target.value,\n });\n this.change_callback(event.target.value);\n }\n on_keydown(event) {\n if(event.key==='Enter' && event.ctrlKey && !event.altKey) {\n event.preventDefault();\n this.submit_callback();\n }\n }\n\n clear() {\n this.setState({\n text: '',\n });\n }\n set(text) {\n this.change_callback(text);\n this.setState({\n text: text,\n });\n }\n get() {\n return this.state.text;\n }\n focus() {\n this.area_ref.current.focus();\n }\n\n render() {\n return (\n \n )\n }\n}\n\nlet pwa_prompt_event=null;\nwindow.addEventListener('beforeinstallprompt', (e) => {\n console.log('pwa: received before install prompt');\n pwa_prompt_event=e;\n});\n\nexport function PromotionBar(props) {\n let is_ios=/iPhone|iPad|iPod/i.test(window.navigator.userAgent);\n let is_installed=(window.matchMedia('(display-mode: standalone)').matches) || (window.navigator.standalone);\n\n if(is_installed)\n return null;\n\n if(is_ios)\n // noinspection JSConstructorReturnsPrimitive\n return !navigator.standalone ? (\n 正在下载……
);\n else if(this.state.state==='decoding')\n return (正在解码……
);\n else if(this.state.state==='loaded')\n return ();\n }\n}","import React, {Component, PureComponent} from 'react';\n\nimport './Config.css';\n\nconst BUILTIN_IMGS={\n 'static/bg/gbp.jpg': '寻觅繁星(默认)',\n 'static/bg/eriri.jpg': '平成著名画师',\n 'static/bg/yurucamp.jpg': '露营天下第一',\n 'static/bg/minecraft.jpg': '麦恩·库拉夫特',\n 'static/bg/cyberpunk.jpg': '赛博城市',\n 'static/bg/sif.jpg': '梦开始的地方',\n};\n\nconst DEFAULT_CONFIG={\n background_img: 'static/bg/gbp.jpg',\n background_color: '#113366',\n pressure: false,\n easter_egg: true,\n color_scheme: 'default',\n};\n\nexport function load_config() {\n let config=Object.assign({},DEFAULT_CONFIG);\n let loaded_config;\n try {\n loaded_config=JSON.parse(localStorage['hole_config']||'{}');\n } catch(e) {\n alert('设置加载失败,将重置为默认设置!\\n'+e);\n delete localStorage['hole_config'];\n loaded_config={};\n }\n\n // unrecognized configs are removed\n Object.keys(loaded_config).forEach((key)=>{\n if(config[key]!==undefined)\n config[key]=loaded_config[key];\n });\n\n console.log('config loaded',config);\n window.config=config;\n}\nexport function save_config() {\n localStorage['hole_config']=JSON.stringify(window.config);\n load_config();\n}\n\nexport function bgimg_style(img,color) {\n if(img===undefined) img=window.config.background_img;\n if(color===undefined) color=window.config.background_color;\n return {\n background: 'transparent center center',\n backgroundImage: img===null ? 'unset' : 'url(\"'+encodeURI(img)+'\")',\n backgroundColor: color,\n backgroundSize: 'cover',\n };\n}\n\nclass ConfigBackground extends PureComponent {\n constructor(props) {\n super(props);\n this.state={\n img: window.config.background_img,\n color: window.config.background_color,\n };\n }\n\n save_changes() {\n this.props.callback({\n background_img: this.state.img,\n background_color: this.state.color,\n });\n }\n\n on_select(e) {\n let value=e.target.value;\n this.setState({\n img: value==='##other' ? '' :\n value==='##color' ? null : value,\n },this.save_changes.bind(this));\n }\n on_change_img(e) {\n this.setState({\n img: e.target.value,\n },this.save_changes.bind(this));\n }\n on_change_color(e) {\n this.setState({\n color: e.target.value,\n },this.save_changes.bind(this));\n }\n\n render() {\n let img_select= this.state.img===null ? '##color' :\n Object.keys(BUILTIN_IMGS).indexOf(this.state.img)===-1 ? '##other' : this.state.img;\n return (\n\n 背景图片:\n \n \n {img_select==='##other' &&\n \n }\n {img_select==='##color' &&\n \n }\n
\n \n\n 夜间模式:\n \n #color_scheme\n
\n\n 选择浅色或深色模式,深色模式下将会调暗图片亮度\n
\n\n \n
\n\n {this.props.description}\n
\n这些功能仍在测试,可能不稳定(全部重置)
\n修改设置后 {window.location.reload()}}>刷新页面 方可生效
\n加载中……
);\n else if(this.state.loading_status==='failed')\n return ();\n else if(this.state.loading_status==='done')\n return this.state.msg.map((msg)=>(\n{msg.content}\n
\n T大树洞 网页版 by @thuhole,\n 基于 \n GPLv3\n 协议在 GitHub 开源\n
\n\n T大树洞 网页版的诞生离不开 \n P大树洞\n 、\n React\n 、\n IcoMoon\n 等开源项目\n
\n\n {\n if('serviceWorker' in navigator) {\n navigator.serviceWorker.getRegistrations()\n .then((registrations)=>{\n for(let registration of registrations) {\n console.log('unregister',registration);\n registration.unregister();\n }\n });\n }\n cache().clear();\n setTimeout(()=>{\n window.location.reload(true);\n },200);\n }}>强制检查更新\n (当前版本:【{process.env.REACT_APP_BUILD_INFO||'---'} {process.env.NODE_ENV}】 会自动在后台检查更新并在下次访问时更新)\n
\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n
\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the \n GNU General Public License\n for more details.\n
\n\n 您已登录。\n \n
\n
*/}\n {/*根据计算中心要求,访问授权三个月内有效,过期需重新登录。*/}\n {/*T大树洞将会单向加密(i.e. 哈希散列)您的邮箱后再存入数据库,因此您的发帖具有较强的匿名性。具体可见我们的后端开源代码。*/}\n {/*
*/}\n {/**/}\n {/* {this.props.show_sidebar(*/}\n {/* '系统消息',*/}\n {/*
*/}\n {/* /!*当您发送的内容违规时,我们将用系统消息提示您*!/*/}\n {/*
\n 复制 User Token
\n User Token 用于迁移登录状态,切勿告知他人{/*,若怀疑被盗号请尽快
\n \n
\n\n T大树洞 面向清华大学学生,通过清华邮箱验证您的身份并提供服务。\n
\nload_single_meta(show_sidebar,token)(pid,true)}>重新加载
\n{''+e}
\n#{this.props.info.cid}
\n {!!this.props.do_filter_name &&\n {this.props.do_filter_name(this.props.info.name);}}>\n \n \n }\n \n {this.props.info.tag!==null &&\n \n {this.props.info.tag}\n \n }\n \n #{props.info.pid}
\n \n {props.info.tag!==null &&\n \n {props.info.tag}\n \n }\n \n \n {props.img_clickable ?\n :\n
\n }\n
最新回复
\n }\n加载中……
);\n\n let show_pid=load_single_meta(this.props.show_sidebar,this.props.token);\n\n let replies_to_show=this.state.filter_name ? this.state.replies.filter((r)=>r.name===this.state.filter_name) : this.state.replies.slice();\n if(this.state.rev) replies_to_show.reverse();\n\n // key for lazyload elem\n let view_mode_key=(this.state.rev ? 'y-' : 'n-')+(this.state.filter_name||'null');\n\n let replies_cnt={[DZ_NAME]:1};\n replies_to_show.forEach((r)=>{\n if(replies_cnt[r.name]===undefined)\n replies_cnt[r.name]=0;\n replies_cnt[r.name]++;\n });\n\n // hide main thread when filtered\n let main_thread_elem=(this.state.filter_name && this.state.filter_name!==DZ_NAME) ? null : (\n\n {this.set_filter_name(null)}}>还原\n 当前只看 \n
回复加载失败
\n{this.state.error_msg}
\n{this.state.reply_error}
\n{this.state.error_msg}
\n{this.load_page(this.state.loaded_pages+1)}}>重新加载
\n{this.state.error_msg}
\n\n props.show_sidebar(\n 'T大树洞',\n
\n
\n {props.text}\n
\n )\n}\n\nexport function GlobalTitle(props) {\n return (\n{props.text}
\n\n// {!!dropdown_cur_app ?\n// app_elem((()=>{\n// let [id,title,_url,icon_normal,icon_hover,_new_tab]=dropdown_cur_app;\n// return [id,title+'▾',null,icon_normal,icon_hover,false];\n// })(),true) :\n// app_elem(['-placeholder-elem','更多▾',null,appicon_dropdown,appicon_dropdown_rev,false],true)\n// }\n//
\n// {this.state.apps.dropdown.map((app)=>{\n// let ref=React.createRef();\n// return (\n//{\n// if(!e.target.closest('a') && ref.current)\n// ref.current.click();\n// }}>\n// {app_elem(app,true,ref)}\n//
\n// );\n// })}\n//\n 接收验证码来登录 T大树洞\n
\n\n \n \n {/*this.do_sendcode('sms')}>*/}\n {/* 短信 */}\n {/**/}\n {/*/*/}\n this.do_sendcode('mail')}>\n 发送邮件 \n \n \n
\n\n \n \n
\n\n 从其他设备导入登录状态\n
\n\n \n \n
\n\n \n
\n\n {this.props.parts.map((part,idx)=>{\n let [rule,p]=part;\n return (\n {\n rule==='url_pid' ? /## :\n rule==='url' ? {p} :\n rule==='pid' ? {e.preventDefault(); this.props.show_pid(p.substring(1));}}>{p} :\n rule==='nickname' ? {p} :\n rule==='search' ? {p} :\n p\n }\n );\n })}\n
\n )\n }\n}\n\nwindow.TEXTAREA_BACKUP={};\n\nexport class SafeTextarea extends Component {\n constructor(props) {\n super(props);\n this.state={\n text: '',\n };\n this.on_change_bound=this.on_change.bind(this);\n this.on_keydown_bound=this.on_keydown.bind(this);\n this.clear=this.clear.bind(this);\n this.area_ref=React.createRef();\n this.change_callback=props.on_change||(()=>{});\n this.submit_callback=props.on_submit||(()=>{});\n }\n\n componentDidMount() {\n this.setState({\n text: window.TEXTAREA_BACKUP[this.props.id]||''\n },()=>{\n this.change_callback(this.state.text);\n });\n }\n\n componentWillUnmount() {\n window.TEXTAREA_BACKUP[this.props.id]=this.state.text;\n this.change_callback(this.state.text);\n }\n\n on_change(event) {\n this.setState({\n text: event.target.value,\n });\n this.change_callback(event.target.value);\n }\n on_keydown(event) {\n if(event.key==='Enter' && event.ctrlKey && !event.altKey) {\n event.preventDefault();\n this.submit_callback();\n }\n }\n\n clear() {\n this.setState({\n text: '',\n });\n }\n set(text) {\n this.change_callback(text);\n this.setState({\n text: text,\n });\n }\n get() {\n return this.state.text;\n }\n focus() {\n this.area_ref.current.focus();\n }\n\n render() {\n return (\n \n )\n }\n}\n\nlet pwa_prompt_event=null;\nwindow.addEventListener('beforeinstallprompt', (e) => {\n console.log('pwa: received before install prompt');\n pwa_prompt_event=e;\n});\n\nexport function PromotionBar(props) {\n let is_ios=/iPhone|iPad|iPod/i.test(window.navigator.userAgent);\n let is_installed=(window.matchMedia('(display-mode: standalone)').matches) || (window.navigator.standalone);\n\n if(is_installed)\n return null;\n\n if(is_ios)\n // noinspection JSConstructorReturnsPrimitive\n return !navigator.standalone ? (\n 正在下载……
);\n else if(this.state.state==='decoding')\n return (正在解码……
);\n else if(this.state.state==='loaded')\n return ();\n }\n}","import React, {Component, PureComponent} from 'react';\n\nimport './Config.css';\n\nconst BUILTIN_IMGS={\n 'static/bg/gbp.jpg': '寻觅繁星(默认)',\n 'static/bg/eriri.jpg': '平成著名画师',\n 'static/bg/yurucamp.jpg': '露营天下第一',\n 'static/bg/minecraft.jpg': '麦恩·库拉夫特',\n 'static/bg/cyberpunk.jpg': '赛博城市',\n 'static/bg/sif.jpg': '梦开始的地方',\n};\n\nconst DEFAULT_CONFIG={\n background_img: 'static/bg/gbp.jpg',\n background_color: '#113366',\n pressure: false,\n easter_egg: true,\n color_scheme: 'default',\n};\n\nexport function load_config() {\n let config=Object.assign({},DEFAULT_CONFIG);\n let loaded_config;\n try {\n loaded_config=JSON.parse(localStorage['hole_config']||'{}');\n } catch(e) {\n alert('设置加载失败,将重置为默认设置!\\n'+e);\n delete localStorage['hole_config'];\n loaded_config={};\n }\n\n // unrecognized configs are removed\n Object.keys(loaded_config).forEach((key)=>{\n if(config[key]!==undefined)\n config[key]=loaded_config[key];\n });\n\n console.log('config loaded',config);\n window.config=config;\n}\nexport function save_config() {\n localStorage['hole_config']=JSON.stringify(window.config);\n load_config();\n}\n\nexport function bgimg_style(img,color) {\n if(img===undefined) img=window.config.background_img;\n if(color===undefined) color=window.config.background_color;\n return {\n background: 'transparent center center',\n backgroundImage: img===null ? 'unset' : 'url(\"'+encodeURI(img)+'\")',\n backgroundColor: color,\n backgroundSize: 'cover',\n };\n}\n\nclass ConfigBackground extends PureComponent {\n constructor(props) {\n super(props);\n this.state={\n img: window.config.background_img,\n color: window.config.background_color,\n };\n }\n\n save_changes() {\n this.props.callback({\n background_img: this.state.img,\n background_color: this.state.color,\n });\n }\n\n on_select(e) {\n let value=e.target.value;\n this.setState({\n img: value==='##other' ? '' :\n value==='##color' ? null : value,\n },this.save_changes.bind(this));\n }\n on_change_img(e) {\n this.setState({\n img: e.target.value,\n },this.save_changes.bind(this));\n }\n on_change_color(e) {\n this.setState({\n color: e.target.value,\n },this.save_changes.bind(this));\n }\n\n render() {\n let img_select= this.state.img===null ? '##color' :\n Object.keys(BUILTIN_IMGS).indexOf(this.state.img)===-1 ? '##other' : this.state.img;\n return (\n\n 背景图片:\n \n \n {img_select==='##other' &&\n \n }\n {img_select==='##color' &&\n \n }\n
\n \n\n 夜间模式:\n \n #color_scheme\n
\n\n 选择浅色或深色模式,深色模式下将会调暗图片亮度\n
\n\n \n
\n\n {this.props.description}\n
\n这些功能仍在测试,可能不稳定(全部重置)
\n修改设置后 {window.location.reload()}}>刷新页面 方可生效
\n加载中……
);\n else if(this.state.loading_status==='failed')\n return ();\n else if(this.state.loading_status==='done')\n return this.state.msg.map((msg)=>(\n{msg.content}\n
\n T大树洞 网页版 by @thuhole,\n 基于 \n GPLv3\n 协议在 GitHub 开源\n
\n\n T大树洞 网页版的诞生离不开 \n P大树洞\n 、\n React\n 、\n IcoMoon\n 等开源项目\n
\n\n {\n if('serviceWorker' in navigator) {\n navigator.serviceWorker.getRegistrations()\n .then((registrations)=>{\n for(let registration of registrations) {\n console.log('unregister',registration);\n registration.unregister();\n }\n });\n }\n cache().clear();\n setTimeout(()=>{\n window.location.reload(true);\n },200);\n }}>强制检查更新\n (当前版本:【{process.env.REACT_APP_BUILD_INFO||'---'} {process.env.NODE_ENV}】 会自动在后台检查更新并在下次访问时更新)\n
\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n
\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the \n GNU General Public License\n for more details.\n
\n\n 您已登录。\n \n
\n
*/}\n {/*根据计算中心要求,访问授权三个月内有效,过期需重新登录。*/}\n {/*T大树洞将会单向加密(i.e. 哈希散列)您的邮箱后再存入数据库,因此您的发帖具有较强的匿名性。具体可见我们的后端开源代码。*/}\n {/*
*/}\n {/**/}\n {/* {this.props.show_sidebar(*/}\n {/* '系统消息',*/}\n {/*
*/}\n {/* /!*当您发送的内容违规时,我们将用系统消息提示您*!/*/}\n {/*
\n 复制 User Token
\n User Token 用于迁移登录状态,切勿告知他人{/*,若怀疑被盗号请尽快
\n \n
\n\n T大树洞 面向清华大学学生,通过清华邮箱验证您的身份并提供服务。\n
\nload_single_meta(show_sidebar,token)(pid,true)}>重新加载
\n{''+e}
\n#{this.props.info.cid}
\n {!!this.props.do_filter_name &&\n {this.props.do_filter_name(this.props.info.name);}}>\n \n \n }\n \n {this.props.info.tag!==null &&\n \n {this.props.info.tag}\n \n }\n \n #{props.info.pid}
\n \n {props.info.tag!==null &&\n \n {props.info.tag}\n \n }\n \n \n {props.img_clickable ?\n :\n
\n }\n
最新回复
\n }\n加载中……
);\n\n let show_pid=load_single_meta(this.props.show_sidebar,this.props.token);\n\n let replies_to_show=this.state.filter_name ? this.state.replies.filter((r)=>r.name===this.state.filter_name) : this.state.replies.slice();\n if(this.state.rev) replies_to_show.reverse();\n\n // key for lazyload elem\n let view_mode_key=(this.state.rev ? 'y-' : 'n-')+(this.state.filter_name||'null');\n\n let replies_cnt={[DZ_NAME]:1};\n replies_to_show.forEach((r)=>{\n if(replies_cnt[r.name]===undefined)\n replies_cnt[r.name]=0;\n replies_cnt[r.name]++;\n });\n\n // hide main thread when filtered\n let main_thread_elem=(this.state.filter_name && this.state.filter_name!==DZ_NAME) ? null : (\n\n {this.set_filter_name(null)}}>还原\n 当前只看 \n
回复加载失败
\n{this.state.error_msg}
\n{this.state.reply_error}
\n{this.state.error_msg}
\n{this.load_page(this.state.loaded_pages+1)}}>重新加载
\n{this.state.error_msg}
\n\n props.show_sidebar(\n 'T大树洞',\n
\n