Browse Source

formatting

pull/16/head
hole-thu 3 years ago
parent
commit
528daa84fb
  1. 14
      src/App.js
  2. 4
      src/Attention.js
  3. 63
      src/Common.css
  4. 19
      src/Common.js
  5. 34
      src/Config.css
  6. 15
      src/Config.js
  7. 353
      src/Flows.css
  8. 585
      src/Flows.js
  9. 1
      src/Message.css
  10. 42
      src/Message.js
  11. 16
      src/PressureHelper.css
  12. 232
      src/Sidebar.css
  13. 98
      src/Title.css
  14. 12
      src/Title.js
  15. 111
      src/UserAction.css
  16. 115
      src/UserAction.js
  17. 173
      src/flows_api.js
  18. 59
      src/fonts_7/icomoon.css
  19. 103
      src/index.css
  20. 2
      src/index.js
  21. 120
      src/infrastructure/functions.js
  22. 35
      src/infrastructure/global.css
  23. 366
      src/infrastructure/widgets.css
  24. 274
      src/infrastructure/widgets.js

14
src/App.js

@ -100,9 +100,12 @@ class App extends Component {
} }
componentDidMount() { componentDidMount() {
if (window.location.protocol === 'http:' && if (
window.location.hostname !== '127.0.0.1' && !window.location.hostname.endsWith('localhost')) { window.location.protocol === 'http:' &&
window.location.protocol = 'https:'; // 因为CDN的原因先在前端做下https跳转 window.location.hostname !== '127.0.0.1' &&
!window.location.hostname.endsWith('localhost')
) {
window.location.protocol = 'https:'; // 因为CDN的原因先在前端做下https跳转
return; return;
} }
let arg = window.location.search; let arg = window.location.search;
@ -110,7 +113,10 @@ class App extends Component {
if (arg.startsWith('?token=')) { if (arg.startsWith('?token=')) {
let token = arg.substr(7); let token = arg.substr(7);
if (token.endsWith(encodeURI('_任意自定义后缀'))) { if (token.endsWith(encodeURI('_任意自定义后缀'))) {
let tmp_token_suf = localStorage['TOKEN_SUF'] || prompt('设置一个你专属的临时token后缀吧') || Math.random(); let tmp_token_suf =
localStorage['TOKEN_SUF'] ||
prompt('设置一个你专属的临时token后缀吧') ||
Math.random();
localStorage['TOKEN_SUF'] = tmp_token_suf; localStorage['TOKEN_SUF'] = tmp_token_suf;
token = `${token.split('_')[0]}_${tmp_token_suf}`; token = `${token.split('_')[0]}_${tmp_token_suf}`;
} }

4
src/Attention.js

@ -1,5 +1,7 @@
export function load_attentions() { export function load_attentions() {
window.saved_attentions = JSON.parse(localStorage['saved_attentions'] || '[]'); window.saved_attentions = JSON.parse(
localStorage['saved_attentions'] || '[]',
);
} }
export function save_attentions() { export function save_attentions() {

63
src/Common.css

@ -1,65 +1,64 @@
.clickable { .clickable {
cursor: pointer; cursor: pointer;
} }
.bg-img { .bg-img {
position: fixed; position: fixed;
z-index: -1; z-index: -1;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.root-dark-mode .bg-img { .root-dark-mode .bg-img {
opacity: .65; opacity: 0.65;
} }
.black-outline { .black-outline {
text-shadow: /* also change .flow-item-row-with-prompt:hover::before */ text-shadow: /* also change .flow-item-row-with-prompt:hover::before */ -1px -1px
-1px -1px 0 rgba(0,0,0,.6), 0 rgba(0, 0, 0, 0.6),
0 -1px 0 rgba(0,0,0,.6), 0 -1px 0 rgba(0, 0, 0, 0.6), 1px -1px 0 rgba(0, 0, 0, 0.6),
1px -1px 0 rgba(0,0,0,.6), -1px 1px 0 rgba(0, 0, 0, 0.6), 0 1px 0 rgba(0, 0, 0, 0.6),
-1px 1px 0 rgba(0,0,0,.6), 1px 1px 0 rgba(0, 0, 0, 0.6);
0 1px 0 rgba(0,0,0,.6),
1px 1px 0 rgba(0,0,0,.6);
} }
.search-query-highlight { .search-query-highlight {
border-bottom: 1px solid black; border-bottom: 1px solid black;
font-weight: bold; font-weight: bold;
} }
.root-dark-mode .search-query-highlight { .root-dark-mode .search-query-highlight {
border-bottom: 1px solid white; border-bottom: 1px solid white;
} }
.url-pid-link { .url-pid-link {
opacity: .6; opacity: 0.6;
} }
:root { :root {
--coloredspan-bgcolor-light: white; --coloredspan-bgcolor-light: white;
--coloredspan-bgcolor-dark: black; --coloredspan-bgcolor-dark: black;
} }
.colored-span { .colored-span {
background-color: var(--coloredspan-bgcolor-light); background-color: var(--coloredspan-bgcolor-light);
} }
.root-dark-mode .colored-span { .root-dark-mode .colored-span {
background-color: var(--coloredspan-bgcolor-dark); background-color: var(--coloredspan-bgcolor-dark);
} }
.icon+label { .icon + label {
font-size: .9em; font-size: 0.9em;
vertical-align: .05em; vertical-align: 0.05em;
cursor: inherit; cursor: inherit;
padding: 0 .1rem; padding: 0 0.1rem;
margin-left: .15rem; margin-left: 0.15rem;
} }
.ext-img, .ext-video { .ext-img,
.ext-video {
max-width: 100%; max-width: 100%;
max-height: 2000px; max-height: 2000px;
display: block; display: block;
@ -71,7 +70,7 @@
} }
blockquote { blockquote {
margin-left: 8px; margin-left: 8px;
padding-left: 5px; padding-left: 5px;
border-left: 3px solid #cbcbcb; border-left: 3px solid #cbcbcb;
} }

19
src/Common.js

@ -138,13 +138,18 @@ export class HighlightedMarkdown extends Component {
['url', URL_RE], ['url', URL_RE],
['pid', PID_RE], ['pid', PID_RE],
['nickname', NICKNAME_RE], ['nickname', NICKNAME_RE],
['tag', TAG_RE] ['tag', TAG_RE],
]; ];
if (props.search_param) { if (props.search_param) {
let search_kws = props.search_param.split(' ').filter(s => !!s); let search_kws = props.search_param.split(' ').filter((s) => !!s);
rules.push([ rules.push([
'search', 'search',
new RegExp(`(${search_kws.map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join("|")})`, "g") new RegExp(
`(${search_kws
.map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
.join('|')})`,
'g',
),
]); ]);
} }
const splitted = split_text(originalText, rules); const splitted = split_text(originalText, rules);
@ -170,7 +175,7 @@ export class HighlightedMarkdown extends Component {
<span className="icon icon-new-tab" /> <span className="icon icon-new-tab" />
</a> </a>
{is_video(p) && ( {is_video(p) && (
<video className="ext-video" src={p} controls loop/> <video className="ext-video" src={p} controls loop />
)} )}
</> </>
) : rule === 'pid' ? ( ) : rule === 'pid' ? (
@ -190,11 +195,7 @@ export class HighlightedMarkdown extends Component {
) : rule === 'search' ? ( ) : rule === 'search' ? (
<span className="search-query-highlight">{p}</span> <span className="search-query-highlight">{p}</span>
) : rule === 'tag' ? ( ) : rule === 'tag' ? (
<a <a href={p}>{p}</a>
href={p}
>
{p}
</a>
) : ( ) : (
p p
)} )}

34
src/Config.css

@ -1,31 +1,31 @@
.config-ui-header { .config-ui-header {
text-align: center; text-align: center;
top: 1em; top: 1em;
position: sticky; position: sticky;
} }
.config-description { .config-description {
font-size: 0.75em; font-size: 0.75em;
} }
.config-select { .config-select {
height: 2em; height: 2em;
} }
.config-textarea { .config-textarea {
margin-top: 0.5em; margin-top: 0.5em;
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
min-width: 100%; min-width: 100%;
height: 7em; height: 7em;
min-height: 2em; min-height: 2em;
} }
.bg-preview { .bg-preview {
height: 18em; height: 18em;
width: 32em; width: 32em;
max-height: 60vh; max-height: 60vh;
max-width: 100%; max-width: 100%;
margin: .5em auto 1em; margin: 0.5em auto 1em;
box-shadow: 0 1px 5px rgba(0,0,0,.4); box-shadow: 0 1px 5px rgba(0, 0, 0, 0.4);
} }

15
src/Config.js

@ -5,8 +5,7 @@ import './Config.css';
const BUILTIN_IMGS = { const BUILTIN_IMGS = {
'https://cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/static/bg/gbp.jpg': 'https://cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/static/bg/gbp.jpg':
'怀旧背景(默认)', '怀旧背景(默认)',
'https://www.tsinghua.edu.cn/image/nav-bg.jpg': 'https://www.tsinghua.edu.cn/image/nav-bg.jpg': '清华紫',
'清华紫',
'https://cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/static/bg/gbp.jpg': 'https://cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/static/bg/gbp.jpg':
'寻觅繁星', '寻觅繁星',
'https://cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/static/bg/eriri.jpg': 'https://cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/static/bg/eriri.jpg':
@ -25,7 +24,7 @@ const BUILTIN_IMGS = {
const DEFAULT_CONFIG = { const DEFAULT_CONFIG = {
background_img: background_img:
'//cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/static/bg/gbp.jpg', '//cdn.jsdelivr.net/gh/thuhole/webhole@gh-pages/static/bg/gbp.jpg',
background_color: '#113366', background_color: '#113366',
pressure: false, pressure: false,
easter_egg: true, easter_egg: true,
@ -33,7 +32,7 @@ const DEFAULT_CONFIG = {
no_c_post: false, no_c_post: false,
by_c: false, by_c: false,
block_words_v2: ['#天火', '#桃花石'], block_words_v2: ['#天火', '#桃花石'],
whitelist_cw: [] whitelist_cw: [],
}; };
export function load_config() { export function load_config() {
@ -53,7 +52,9 @@ export function load_config() {
}); });
if (loaded_config['block_words']) { if (loaded_config['block_words']) {
config['block_words_v2'] = loaded_config['block_words'].concat(config['block_words_v2']) config['block_words_v2'] = loaded_config['block_words'].concat(
config['block_words_v2'],
);
} }
console.log('config loaded', config); console.log('config loaded', config);
@ -377,7 +378,9 @@ export class ConfigUI extends PureComponent {
id="whitelist_cw" id="whitelist_cw"
callback={this.save_changes_bound} callback={this.save_changes_bound}
name="展开指定的折叠警告" name="展开指定的折叠警告"
description={'完全匹配的树洞不会被折叠,每行一个豁免词,也可使用一个星号("*")表示豁免所有'} description={
'完全匹配的树洞不会被折叠,每行一个豁免词,也可使用一个星号("*")表示豁免所有'
}
display={(array) => array.join('\n')} display={(array) => array.join('\n')}
sift={(array) => array.filter((v) => v)} sift={(array) => array.filter((v) => v)}
parse={(string) => string.split('\n')} parse={(string) => string.split('\n')}

353
src/Flows.css

@ -1,201 +1,200 @@
:root { :root {
--box-bgcolor-light: hsl(0,0%,97%); --box-bgcolor-light: hsl(0, 0%, 97%);
--box-bgcolor-dark: hsl(0,0%,16%); --box-bgcolor-dark: hsl(0, 0%, 16%);
} }
.box { .box {
background-color: var(--box-bgcolor-light); background-color: var(--box-bgcolor-light);
color: black; color: black;
border-radius: 5px; border-radius: 5px;
margin: 1em 0; margin: 1em 0;
padding: .5em; padding: 0.5em;
box-shadow: 0 2px 5px rgba(0,0,0,.4); box-shadow: 0 2px 5px rgba(0, 0, 0, 0.4);
} }
.root-dark-mode .box { .root-dark-mode .box {
background-color: var(--box-bgcolor-dark); background-color: var(--box-bgcolor-dark);
color: var(--foreground-dark); color: var(--foreground-dark);
box-shadow: 0 0 2px rgba(255,255,255,.25), 0 0 7px rgba(0,0,0,.15); box-shadow: 0 0 2px rgba(255, 255, 255, 0.25), 0 0 7px rgba(0, 0, 0, 0.15);
} }
.box-tip { .box-tip {
min-width: 100px; min-width: 100px;
z-index: 1; z-index: 1;
text-align: center; text-align: center;
} }
.box-danger { .box-danger {
background-color: #e55; background-color: #e55;
color: white; color: white;
text-shadow: 0 0 3px black; text-shadow: 0 0 3px black;
} }
.root-dark-mode .box-danger { .root-dark-mode .box-danger {
background-color: #d44; background-color: #d44;
color: var(--foreground-dark); color: var(--foreground-dark);
} }
.left-container .flow-item { .left-container .flow-item {
display: inline-block; display: inline-block;
width: 600px; width: 600px;
float: left; float: left;
} }
.flow-reply-row { .flow-reply-row {
display: inline-flex; display: inline-flex;
align-items: flex-start; align-items: flex-start;
width: calc(100% - 625px); width: calc(100% - 625px);
margin-left: -25px; margin-left: -25px;
padding-left: 18px; padding-left: 18px;
overflow-x: auto; overflow-x: auto;
} }
.sidebar-flow-item .flow-item pre, .sidebar-flow-item .flow-reply pre { .sidebar-flow-item .flow-item pre,
cursor: text; .sidebar-flow-item .flow-reply pre {
cursor: text;
} }
.flow-reply-row::-webkit-scrollbar { .flow-reply-row::-webkit-scrollbar {
display: none; display: none;
} }
.flow-reply-row { .flow-reply-row {
scrollbar-width: none; scrollbar-width: none;
-ms-overflow-style: none; -ms-overflow-style: none;
} }
.flow-reply-row:empty { .flow-reply-row:empty {
margin: 0 !important; margin: 0 !important;
display: none; display: none;
} }
.flow-item-row::after { .flow-item-row::after {
content: ""; content: '';
display: block; display: block;
clear: both; clear: both;
} }
.left-container .flow-reply { .left-container .flow-reply {
flex: 0 0 300px; flex: 0 0 300px;
max-height: 15em; max-height: 15em;
margin-right: -7px; margin-right: -7px;
overflow-y: hidden; overflow-y: hidden;
} }
.left-container .flow-item { .left-container .flow-item {
margin-left: 50px; margin-left: 50px;
} }
@media screen and (min-width: 1301px) { @media screen and (min-width: 1301px) {
.left-container .flow-item-row-with-prompt:hover::before { .left-container .flow-item-row-with-prompt:hover::before {
content: '>>'; content: '>>';
position: absolute; position: absolute;
left: 10px; left: 10px;
margin-top: 1.5em; margin-top: 1.5em;
color: white; color: white;
text-shadow: /* copied from .black-outline */ text-shadow: /* copied from .black-outline */ -1px -1px 0 rgba(0, 0, 0, 0.6),
-1px -1px 0 rgba(0,0,0,.6), 0 -1px 0 rgba(0, 0, 0, 0.6), 1px -1px 0 rgba(0, 0, 0, 0.6),
0 -1px 0 rgba(0,0,0,.6), -1px 1px 0 rgba(0, 0, 0, 0.6), 0 1px 0 rgba(0, 0, 0, 0.6),
1px -1px 0 rgba(0,0,0,.6), 1px 1px 0 rgba(0, 0, 0, 0.6);
-1px 1px 0 rgba(0,0,0,.6), font-family: 'Consolas', 'Courier', monospace;
0 1px 0 rgba(0,0,0,.6), }
1px 1px 0 rgba(0,0,0,.6);
font-family: 'Consolas', 'Courier', monospace;
}
} }
@media screen and (max-width: 1300px) { @media screen and (max-width: 1300px) {
.left-container .flow-item { .left-container .flow-item {
margin-left: 10px; margin-left: 10px;
} }
.flow-reply-row { .flow-reply-row {
width: calc(100% - 485px); width: calc(100% - 485px);
} }
.left-container .flow-item { .left-container .flow-item {
width: 500px; width: 500px;
} }
.flow-item-row:hover::before { .flow-item-row:hover::before {
display: none; display: none;
} }
} }
@media screen and (max-width: 900px) { @media screen and (max-width: 900px) {
.left-container .flow-item { .left-container .flow-item {
display: block; display: block;
width: calc(100vw - 20px); width: calc(100vw - 20px);
max-width: 500px; max-width: 500px;
float: none; float: none;
} }
.flow-reply-row { .flow-reply-row {
display: flex; display: flex;
width: 100% !important; width: 100% !important;
margin-left: 0; margin-left: 0;
padding-left: 30px; padding-left: 30px;
margin-top: -2.5em; margin-top: -2.5em;
margin-bottom: -1em; margin-bottom: -1em;
} }
} }
.left-container .flow-item-row { .left-container .flow-item-row {
cursor: default; cursor: default;
} }
.box-header, .box-footer { .box-header,
font-size: .8em; .box-footer {
font-size: 0.8em;
} }
.flow-item-row p.img { .flow-item-row p.img {
text-align: center; text-align: center;
margin-top: .5em; margin-top: 0.5em;
} }
.flow-item-row p.img img { .flow-item-row p.img img {
max-width: 100%; max-width: 100%;
box-shadow: 0 1px 5px rgba(0,0,0,.4); box-shadow: 0 1px 5px rgba(0, 0, 0, 0.4);
} }
.left-container .flow-item-row p.img img { .left-container .flow-item-row p.img img {
max-height: 80vh; max-height: 80vh;
} }
.root-dark-mode .flow-item-row p.img img { .root-dark-mode .flow-item-row p.img img {
filter: brightness(85%); filter: brightness(85%);
} }
.box-header-badge { .box-header-badge {
float: right; float: right;
margin: 0 .5em; margin: 0 0.5em;
} }
.flow-item-dot { .flow-item-dot {
position: relative; position: relative;
top: calc(-.5em - 4px); top: calc(-0.5em - 4px);
left: calc(-.5em - 4px); left: calc(-0.5em - 4px);
width: 10px; width: 10px;
height: 10px; height: 10px;
margin-bottom: -10px; margin-bottom: -10px;
border-radius: 50%; border-radius: 50%;
background-color: #ffcc77; background-color: #ffcc77;
box-shadow: 1px 1px 5px rgba(0,0,0,.5); box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.5);
display: none; display: none;
} }
.root-dark-mode .flow-item-dot { .root-dark-mode .flow-item-dot {
background-color: #eebb66; background-color: #eebb66;
} }
.left-container .flow-item-dot { .left-container .flow-item-dot {
display: block; display: block;
} }
.box-content { .box-content {
padding: .5em 0; padding: 0.5em 0;
overflow-x: auto; overflow-x: auto;
} }
.left-container .box-content { .left-container .box-content {
max-height: calc(100vh + 15em); max-height: calc(100vh + 15em);
overflow-y: hidden; overflow-y: hidden;
} }
.box-poll.disabled { .box-poll.disabled {
@ -203,68 +202,68 @@
} }
.box-id { .box-id {
color: #666666; color: #666666;
} }
.root-dark-mode .box-id { .root-dark-mode .box-id {
color: #bbbbbb; color: #bbbbbb;
} }
.box-id a:hover::before { .box-id a:hover::before {
content: "复制全文"; content: '复制全文';
position: relative; position: relative;
width: 5em; width: 5em;
height: 1.3em; height: 1.3em;
line-height: 1.3em; line-height: 1.3em;
margin-bottom: -1.3em; margin-bottom: -1.3em;
border-radius: 3px; border-radius: 3px;
text-align: center; text-align: center;
top: -1.5em; top: -1.5em;
display: block; display: block;
color: white; color: white;
background-color: rgba(0,0,0,.6); background-color: rgba(0, 0, 0, 0.6);
pointer-events: none; pointer-events: none;
} }
.flow-item-row-quote { .flow-item-row-quote {
opacity: .8; opacity: 0.8;
filter: brightness(95%); filter: brightness(95%);
} }
.root-dark-mode .flow-item-row-quote { .root-dark-mode .flow-item-row-quote {
opacity: .7; opacity: 0.7;
filter: unset; filter: unset;
} }
.flow-item-quote>.box { .flow-item-quote > .box {
margin-left: 2.5em; margin-left: 2.5em;
max-height: 15em; max-height: 15em;
overflow-y: hidden; overflow-y: hidden;
} }
.flow-item-quote .flow-item-dot, .flow-item-quote .flow-item-dot,
.flow-item-quote .box-id a:hover::before { .flow-item-quote .box-id a:hover::before {
display: none; display: none;
} }
.quote-tip { .quote-tip {
margin-top: .5em; margin-top: 0.5em;
margin-bottom: -10em; /* so that it will not block reply bar */ margin-bottom: -10em; /* so that it will not block reply bar */
float: left; float: left;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 2.5em; width: 2.5em;
text-align: center; text-align: center;
color: white; color: white;
} }
.box-header-cw { .box-header-cw {
color: white; color: white;
background-color: #00c; background-color: #00c;
font-weight: bold; font-weight: bold;
border-radius: 3px; border-radius: 3px;
margin-right: .25em; margin-right: 0.25em;
padding: 0 .25em; padding: 0 0.25em;
} }
.box-header-cw-edit { .box-header-cw-edit {
@ -272,54 +271,59 @@
background-color: #00c; background-color: #00c;
border-radius: 5px; border-radius: 5px;
padding: 3px; padding: 3px;
margin:0 3px; margin: 0 3px;
} }
.box-header-cw-edit input { .box-header-cw-edit input {
font-size: .8em; font-size: 0.8em;
width: 8em; width: 8em;
padding: 0 3px; padding: 0 3px;
} }
.box-header-cw-edit button { .box-header-cw-edit button {
font-size: .8em; font-size: 0.8em;
margin: 0 3px; margin: 0 3px;
background: white; background: white;
} }
.box-header-name { .box-header-name {
color: white; color: white;
background-color: #3338; background-color: #3338;
font-weight: bold; font-weight: bold;
border-radius: 5px; border-radius: 5px;
margin-right: .5em; margin-right: 0.5em;
padding: .1em .5em; padding: 0.1em 0.5em;
} }
.box-header-name.author-title { .box-header-name.author-title {
color: white; color: white;
background-color: #333; background-color: #333;
} }
.root-dark-mode .box-header-cw { .root-dark-mode .box-header-cw {
background-color: #00a; background-color: #00a;
} }
.filter-name-bar { .filter-name-bar {
animation: slide-in-from-top .15s ease-out; animation: slide-in-from-top 0.15s ease-out;
position: sticky; position: sticky;
top: 1em; top: 1em;
} }
@keyframes slide-in-from-top { @keyframes slide-in-from-top {
0% {opacity: 0; transform: translateY(-50%);} 0% {
100% {opacity: 1;} opacity: 0;
transform: translateY(-50%);
}
100% {
opacity: 1;
}
} }
.reply-header-badge { .reply-header-badge {
float: right; float: right;
padding: 0 .5em; padding: 0 0.5em;
opacity: .4; opacity: 0.4;
} }
.export-textarea { .export-textarea {
@ -353,19 +357,20 @@
color: red; color: red;
} }
.box-poll li > div, .box-poll li > button { .box-poll li > div,
.box-poll li > button {
height: 36px !important; height: 36px !important;
} }
.box-poll li > div > div.styles_labels__2rz-F { .box-poll li > div > div.styles_labels__2rz-F {
top: 50% !important; top: 50% !important;
transform: translateY(-50%); transform: translateY(-50%);
} }
.box-poll span, .box-poll button { .box-poll span,
.box-poll button {
font-size: 13px !important; font-size: 13px !important;
white-space: pre-line !important; white-space: pre-line !important;
} }
.box-poll button { .box-poll button {

585
src/Flows.js

@ -23,7 +23,7 @@ import LazyLoad, { forceCheck } from './react-lazyload/src';
import { TokenCtx, ReplyForm } from './UserAction'; import { TokenCtx, ReplyForm } from './UserAction';
import { API, parse_replies } from './flows_api'; import { API, parse_replies } from './flows_api';
import { cache } from './cache'; import { cache } from './cache';
import { save_attentions } from './Attention' import { save_attentions } from './Attention';
import Poll from 'react-polls'; import Poll from 'react-polls';
/* /*
@ -97,8 +97,14 @@ class Reply extends PureComponent {
render() { render() {
const { const {
info, color_picker, show_pid, do_filter_name, do_delete, info,
do_report, do_block, search_param color_picker,
show_pid,
do_filter_name,
do_delete,
do_report,
do_block,
search_param,
} = this.props; } = this.props;
const author = info.name, const author = info.name,
replyText = info.text; replyText = info.text;
@ -126,9 +132,7 @@ class Reply extends PureComponent {
</span> </span>
)} )}
&nbsp; &nbsp;
{( {<span className="box-header-name">{info.name}</span>}
<span className="box-header-name">{info.name}</span>
)}
{info.author_title && ( {info.author_title && (
<span className="box-header-name author-title">{`"${info.author_title}"`}</span> <span className="box-header-name author-title">{`"${info.author_title}"`}</span>
)} )}
@ -138,21 +142,21 @@ class Reply extends PureComponent {
onClick={() => { onClick={() => {
do_delete('cid', info.cid); do_delete('cid', info.cid);
}} }}
> 🗑 </span> >
{' '}
🗑{' '}
</span>
)} )}
{!!do_block && ( {!!do_block && (
<span <span className="clickable" onClick={do_block}>
className="clickable" {' '}
onClick={do_block} 🚫{' '}
> 🚫 </span> </span>
)} )}
{!!do_report && ( {!!do_report && (
<> <>
&nbsp; &nbsp;
<span <span className="clickable" onClick={do_report}>
className="clickable"
onClick={do_report}
>
<span className="icon icon-flag" /> <span className="icon icon-flag" />
</span> </span>
&nbsp; &nbsp;
@ -219,9 +223,21 @@ class FlowItem extends PureComponent {
render() { render() {
const { const {
info, is_quote, cached, attention, can_del, do_filter_name, do_delete, info,
do_edit_cw, timestamp, img_clickable, color_picker, is_quote,
show_pid, do_vote, do_block, search_param cached,
attention,
can_del,
do_filter_name,
do_delete,
do_edit_cw,
timestamp,
img_clickable,
color_picker,
show_pid,
do_vote,
do_block,
search_param,
} = this.props; } = this.props;
const { cw } = this.state; const { cw } = this.state;
return ( return (
@ -241,9 +257,7 @@ class FlowItem extends PureComponent {
parseInt(info.pid, 10) > window.LATEST_POST_ID && ( parseInt(info.pid, 10) > window.LATEST_POST_ID && (
<div className="flow-item-dot" /> <div className="flow-item-dot" />
)} )}
{!!attention && !cached && ( {!!attention && !cached && <div className="flow-item-dot" />}
<div className="flow-item-dot" />
)}
<div className="box-header"> <div className="box-header">
{!!do_filter_name && ( {!!do_filter_name && (
<span <span
@ -259,9 +273,7 @@ class FlowItem extends PureComponent {
<span className="box-header-badge"> <span className="box-header-badge">
{info.likenum}&nbsp; {info.likenum}&nbsp;
<span <span
className={ className={'icon icon-' + (attention ? 'star-ok' : 'star')}
'icon icon-' + (attention ? 'star-ok' : 'star')
}
/> />
</span> </span>
)} )}
@ -272,10 +284,7 @@ class FlowItem extends PureComponent {
</span> </span>
)} )}
<code className="box-id"> <code className="box-id">
<a <a href={'##' + info.pid} onClick={this.copy_link.bind(this)}>
href={'##' + info.pid}
onClick={this.copy_link.bind(this)}
>
#{info.pid} #{info.pid}
</a> </a>
</code> </code>
@ -283,22 +292,23 @@ class FlowItem extends PureComponent {
{info.author_title && ( {info.author_title && (
<span className="box-header-name author-title">{`"${info.author_title}"`}</span> <span className="box-header-name author-title">{`"${info.author_title}"`}</span>
)} )}
{info.is_reported && ( {info.is_reported && <span className="danger-info"> R </span>}
<span className="danger-info"> R </span>
)}
{!!do_delete && !!info.can_del && ( {!!do_delete && !!info.can_del && (
<span <span
className="clickable" className="clickable"
onClick={() => { onClick={() => {
do_delete('pid', info.pid); do_delete('pid', info.pid);
}} }}
> 🗑 </span> >
{' '}
🗑{' '}
</span>
)} )}
{!!do_block && ( {!!do_block && (
<span <span className="clickable" onClick={do_block}>
className="clickable" {' '}
onClick={do_block} 🚫{' '}
> 🚫 </span> </span>
)} )}
{info.dangerous_user && ( {info.dangerous_user && (
<span className="danger-info"> {info.dangerous_user} </span> <span className="danger-info"> {info.dangerous_user} </span>
@ -306,30 +316,24 @@ class FlowItem extends PureComponent {
{info.blocked_count && ( {info.blocked_count && (
<span className="danger-info"> {info.blocked_count} </span> <span className="danger-info"> {info.blocked_count} </span>
)} )}
{info.cw !== null && {info.cw !== null && (!do_edit_cw || !info.can_del) && (
(!do_edit_cw || !info.can_del) && ( <span className="box-header-cw">{info.cw}</span>
<span className="box-header-cw">{info.cw}</span>
)} )}
{ {!!do_edit_cw && !!info.can_del && (
!!do_edit_cw && !!info.can_del && ( <div className="box-header-cw-edit clickable">
<div className="box-header-cw-edit clickable"> <input
<input type="text"
type="text" value={cw}
value={cw} maxLength="32"
maxLength="32" placeholder="编辑折叠警告"
placeholder="编辑折叠警告" onChange={this.on_cw_change.bind(this)}
onChange={this.on_cw_change.bind(this)} />
/> <button type="button" onClick={(e) => do_edit_cw(cw, info.pid)}>
<button type="button" 更新
onClick={(e)=>do_edit_cw(cw, info.pid)}> </button>
更新 </div>
</button> )}
</div> {info.allow_search && <span> 📢 </span>}
)
}
{
info.allow_search && <span> 📢 </span>
}
<Time stamp={info.timestamp} short={!img_clickable} /> <Time stamp={info.timestamp} short={!img_clickable} />
</div> </div>
{!!info.hot_score && ( {!!info.hot_score && (
@ -343,14 +347,14 @@ class FlowItem extends PureComponent {
search_param={search_param} search_param={search_param}
/> />
</div> </div>
{ info.poll && ( {info.poll && (
<div className={!do_vote ? "box-poll disabled" : "box-poll"}> <div className={!do_vote ? 'box-poll disabled' : 'box-poll'}>
<Poll <Poll
key={info.poll.vote || 'x'} key={info.poll.vote || 'x'}
question={""} question={''}
answers={info.poll.answers} answers={info.poll.answers}
onVote={do_vote || (() => {})} onVote={do_vote || (() => {})}
customStyles={{'theme': 'cyan'}} customStyles={{ theme: 'cyan' }}
noStorage={true} noStorage={true}
vote={localStorage['VOTE_RECORD:' + info.pid] || info.poll.vote} vote={localStorage['VOTE_RECORD:' + info.pid] || info.poll.vote}
/> />
@ -358,8 +362,7 @@ class FlowItem extends PureComponent {
)} )}
{!!(attention && info.variant.latest_reply) && ( {!!(attention && info.variant.latest_reply) && (
<p className="box-footer"> <p className="box-footer">
最新回复{' '} 最新回复 <Time stamp={info.variant.latest_reply} short={false} />
<Time stamp={info.variant.latest_reply} short={false} />
</p> </p>
)} )}
</div> </div>
@ -432,7 +435,7 @@ class FlowSidebar extends PureComponent {
info: update_count info: update_count
? Object.assign({}, prev.info, { ? Object.assign({}, prev.info, {
reply: '' + json.data.length, reply: '' + json.data.length,
likenum: ''+json.likenum, likenum: '' + json.likenum,
}) })
: prev.info, : prev.info,
attention: !!json.attention, attention: !!json.attention,
@ -473,16 +476,16 @@ class FlowSidebar extends PureComponent {
this.setState({ this.setState({
attention: json.attention, attention: json.attention,
info: Object.assign({}, prev_info, { info: Object.assign({}, prev_info, {
likenum: ''+json.likenum, likenum: '' + json.likenum,
}), }),
}); });
let saved_attentions = window.saved_attentions; let saved_attentions = window.saved_attentions;
if (json.attention && !saved_attentions.includes(pid)) { if (json.attention && !saved_attentions.includes(pid)) {
saved_attentions.unshift(pid) saved_attentions.unshift(pid);
} else if (!json.attention && saved_attentions.includes(pid)) { } else if (!json.attention && saved_attentions.includes(pid)) {
const idx = saved_attentions.indexOf(pid); const idx = saved_attentions.indexOf(pid);
saved_attentions.splice(idx, 1) saved_attentions.splice(idx, 1);
} }
window.saved_attentions = saved_attentions; window.saved_attentions = saved_attentions;
save_attentions(); save_attentions();
@ -490,8 +493,8 @@ class FlowSidebar extends PureComponent {
this.syncState({ this.syncState({
attention: json.attention, attention: json.attention,
info: Object.assign({}, prev_info, { info: Object.assign({}, prev_info, {
likenum: ''+json.likenum, likenum: '' + json.likenum,
}), }),
}); });
}) })
.catch((e) => { .catch((e) => {
@ -551,15 +554,15 @@ class FlowSidebar extends PureComponent {
block(name, type, id, on_complete) { block(name, type, id, on_complete) {
if (confirm(`确定拉黑${name}吗?后续将不会收到其发布的任何内容`)) { if (confirm(`确定拉黑${name}吗?后续将不会收到其发布的任何内容`)) {
API.block(type, id, this.props.token) API.block(type, id, this.props.token)
.then((json) => { .then((json) => {
let data = json.data; let data = json.data;
alert(`操作成功,其成为危险用户进度 ${data.curr}/${data.threshold}`) alert(`操作成功,其成为危险用户进度 ${data.curr}/${data.threshold}`);
!!on_complete && on_complete(); !!on_complete && on_complete();
}) })
.catch((e) => { .catch((e) => {
alert('拉黑失败\n' + e); alert('拉黑失败\n' + e);
console.error(e) console.error(e);
}); });
} }
} }
@ -590,7 +593,7 @@ class FlowSidebar extends PureComponent {
} }
} }
make_do_delete(token, on_complete=null) { make_do_delete(token, on_complete = null) {
const do_delete = (type, id) => { const do_delete = (type, id) => {
let note = prompt(`将删除${type}=${id}, 备注:`, '(无)'); let note = prompt(`将删除${type}=${id}, 备注:`, '(无)');
if (note !== null) { if (note !== null) {
@ -604,27 +607,29 @@ class FlowSidebar extends PureComponent {
console.error(e); console.error(e);
}); });
} }
} };
return do_delete; return do_delete;
} }
do_edit_cw(cw, id) { do_edit_cw(cw, id) {
API.update_cw(cw, id, this.props.token) API.update_cw(cw, id, this.props.token)
.then((json) => { .then((json) => {
this.setState({ this.setState(
info: Object.assign({}, this.state.info, { cw: cw }), {
info: Object.assign({}, this.state.info, { cw: cw }),
}, },
() => { () => {
this.syncState({ this.syncState({
info: this.state.info, info: this.state.info,
}); });
}); },
alert('已更新'); );
}) alert('已更新');
.catch((e) => { })
alert('更新失败\n' + e); .catch((e) => {
console.error(e); alert('更新失败\n' + e);
}); console.error(e);
});
} }
render() { render() {
@ -671,12 +676,16 @@ class FlowSidebar extends PureComponent {
do_filter_name={ do_filter_name={
replies_cnt[DZ_NAME] > 1 ? this.set_filter_name.bind(this) : null replies_cnt[DZ_NAME] > 1 ? this.set_filter_name.bind(this) : null
} }
do_delete={this.make_do_delete(this.props.token, ()=>{window.location.reload();})} do_delete={this.make_do_delete(this.props.token, () => {
window.location.reload();
})}
do_edit_cw={this.do_edit_cw.bind(this)} do_edit_cw={this.do_edit_cw.bind(this)}
do_vote={this.do_vote.bind(this)} do_vote={this.do_vote.bind(this)}
do_block={() => {this.block( do_block={() => {
'洞主', 'post', this.state.info.pid, () => {window.location.reload();} this.block('洞主', 'post', this.state.info.pid, () => {
)}} window.location.reload();
});
}}
/> />
</ClickHandler> </ClickHandler>
); );
@ -766,41 +775,57 @@ class FlowSidebar extends PureComponent {
条回复被删除 条回复被删除
</div> </div>
)} )}
{replies_to_show.map((reply, i) => !reply.blocked && ( {replies_to_show.map(
<LazyLoad (reply, i) =>
key={i} !reply.blocked && (
offset={1500} <LazyLoad
height="5em" key={i}
overflow={true} offset={1500}
once={true} height="5em"
> overflow={true}
<ClickHandler once={true}
callback={(e) => { >
this.show_reply_bar(reply.name, e); <ClickHandler
}} callback={(e) => {
> this.show_reply_bar(reply.name, e);
<Reply }}
info={reply} >
color_picker={this.color_picker} <Reply
show_pid={show_pid} info={reply}
search_param={this.props.search_param} color_picker={this.color_picker}
set_variant={(variant) => { show_pid={show_pid}
this.set_variant(reply.cid, variant); search_param={this.props.search_param}
}} set_variant={(variant) => {
do_filter_name={ this.set_variant(reply.cid, variant);
replies_cnt[reply.name] > 1 }}
? this.set_filter_name.bind(this) do_filter_name={
: null replies_cnt[reply.name] > 1
} ? this.set_filter_name.bind(this)
do_delete={this.make_do_delete(this.props.token, this.load_replies.bind(this))} : null
do_block={() => {this.block( }
reply.name, 'comment', reply.cid, this.load_replies.bind(this) do_delete={this.make_do_delete(
)}} this.props.token,
do_report={(e) => {this.report(e, `评论区${reply.name},评论id ${reply.cid}`)}} this.load_replies.bind(this),
/> )}
</ClickHandler> do_block={() => {
</LazyLoad> this.block(
))} reply.name,
'comment',
reply.cid,
this.load_replies.bind(this),
);
}}
do_report={(e) => {
this.report(
e,
`评论区${reply.name},评论id ${reply.cid}`,
);
}}
/>
</ClickHandler>
</LazyLoad>
),
)}
{this.state.rev && main_thread_elem} {this.state.rev && main_thread_elem}
{this.props.token ? ( {this.props.token ? (
<ReplyForm <ReplyForm
@ -820,19 +845,27 @@ class FlowSidebar extends PureComponent {
class FlowItemRow extends PureComponent { class FlowItemRow extends PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this.needFold = props.info.cw && this.needFold =
props.info.cw &&
!props.search_param && !props.search_param &&
(window.config.whitelist_cw.indexOf('*')==-1 && window.config.whitelist_cw.indexOf(props.info.cw)==-1) && window.config.whitelist_cw.indexOf('*') == -1 &&
props.mode !== 'attention' && props.mode !== 'attention_finished'; window.config.whitelist_cw.indexOf(props.info.cw) == -1 &&
props.mode !== 'attention' &&
props.mode !== 'attention_finished';
this.color_picker = new ColorPicker(); this.color_picker = new ColorPicker();
this.state = { this.state = {
replies: props.info.comments ? parse_replies(props.info.comments, this.color_picker) : [], replies: props.info.comments
? parse_replies(props.info.comments, this.color_picker)
: [],
reply_status: 'done', reply_status: 'done',
reply_error: null, reply_error: null,
info: Object.assign({}, props.info, { variant: {} }), info: Object.assign({}, props.info, { variant: {} }),
hidden: window.config.block_words_v2.some((word) => hidden:
(window.config.block_words_v2.some((word) =>
props.info.text.includes(word), props.info.text.includes(word),
) && !props.info.can_del || this.needFold, ) &&
!props.info.can_del) ||
this.needFold,
attention: props.info.attention, attention: props.info.attention,
cached: true, // default no display anything cached: true, // default no display anything
}; };
@ -841,7 +874,7 @@ class FlowItemRow extends PureComponent {
componentDidMount() { componentDidMount() {
// cache from getlist, so always to this to update attention // cache from getlist, so always to this to update attention
if (!this.props.info.comments) { if (!this.props.info.comments) {
//if (true || parseInt(this.state.info.reply, 10)) { //if (true || parseInt(this.state.info.reply, 10)) {
this.load_replies(null, /*update_count=*/ false); this.load_replies(null, /*update_count=*/ false);
} }
} }
@ -918,14 +951,10 @@ class FlowItemRow extends PureComponent {
} }
render() { render() {
const {show_sidebar, token, search_param, is_quote } = this.props; const { show_sidebar, token, search_param, is_quote } = this.props;
let show_pid = load_single_meta(show_sidebar, token, [ let show_pid = load_single_meta(show_sidebar, token, [this.state.info.pid]);
this.state.info.pid,
]);
let hl_rules = [ let hl_rules = [['pid', PID_RE]];
['pid', PID_RE],
];
let parts = split_text(this.state.info.text, hl_rules); let parts = split_text(this.state.info.text, hl_rules);
//console.log('hl:', parts,this.state.info.pid); //console.log('hl:', parts,this.state.info.pid);
@ -939,12 +968,11 @@ class FlowItemRow extends PureComponent {
QUOTE_BLACKLIST.indexOf(content) === -1 && QUOTE_BLACKLIST.indexOf(content) === -1 &&
parseInt(content) < parseInt(this.state.info.pid) parseInt(content) < parseInt(this.state.info.pid)
) { ) {
if (quote_id === null) if (quote_id === null) quote_id = parseInt(content);
quote_id = parseInt(content); else {
else { quote_id = null;
quote_id = null; break;
break; }
}
} }
} }
@ -988,15 +1016,20 @@ class FlowItemRow extends PureComponent {
<p>{this.state.reply_error}</p> <p>{this.state.reply_error}</p>
</div> </div>
)} )}
{this.state.replies.slice(0, PREVIEW_REPLY_COUNT).map((reply) => !reply.blocked && ( {this.state.replies
<Reply .slice(0, PREVIEW_REPLY_COUNT)
key={reply.cid} .map(
info={reply} (reply) =>
color_picker={this.color_picker} !reply.blocked && (
show_pid={show_pid} <Reply
search_param={search_param} key={reply.cid}
/> info={reply}
))} color_picker={this.color_picker}
show_pid={show_pid}
search_param={search_param}
/>
),
)}
{this.state.replies.length > PREVIEW_REPLY_COUNT && ( {this.state.replies.length > PREVIEW_REPLY_COUNT && (
<div className="box box-tip"> <div className="box box-tip">
还有 {this.state.replies.length - PREVIEW_REPLY_COUNT} 还有 {this.state.replies.length - PREVIEW_REPLY_COUNT}
@ -1007,58 +1040,60 @@ class FlowItemRow extends PureComponent {
); );
if (this.state.hidden) { if (this.state.hidden) {
return this.needFold && ( return (
<div this.needFold && (
className="flow-item-row flow-item-row-with-prompt"
onClick={(event) => {
if (!CLICKABLE_TAGS[event.target.tagName.toLowerCase()])
this.show_sidebar();
}}
>
<div <div
className={ className="flow-item-row flow-item-row-with-prompt"
'flow-item' + (this.props.is_quote ? ' flow-item-quote' : '') onClick={(event) => {
} if (!CLICKABLE_TAGS[event.target.tagName.toLowerCase()])
this.show_sidebar();
}}
> >
{!!this.props.is_quote && ( <div
<div className="quote-tip black-outline"> className={
<div> 'flow-item' + (this.props.is_quote ? ' flow-item-quote' : '')
<span className="icon icon-quote" /> }
>
{!!this.props.is_quote && (
<div className="quote-tip black-outline">
<div>
<span className="icon icon-quote" />
</div>
{/*<div>*/}
{/* <small>提到</small>*/}
{/*</div>*/}
</div> </div>
{/*<div>*/} )}
{/* <small>提到</small>*/} <div className="box">
{/*</div>*/} <div className="box-header">
</div> {!!this.props.do_filter_name && (
)} <span
<div className="box"> className="reply-header-badge clickable"
<div className="box-header"> onClick={() => {
{!!this.props.do_filter_name && ( this.props.do_filter_name(DZ_NAME);
<span }}
className="reply-header-badge clickable" >
onClick={() => { <span className="icon icon-locate" />
this.props.do_filter_name(DZ_NAME); </span>
}} )}
> <code className="box-id">#{this.props.info.pid}</code>
<span className="icon icon-locate" /> &nbsp;
{this.props.info.author_title && (
<span className="box-header-name author-title">{`"${this.props.info.author_title}"`}</span>
)}
{this.props.info.cw !== null && (
<span className="box-header-cw">{this.props.info.cw}</span>
)}
<Time stamp={this.props.info.timestamp} short={true} />
<span className="box-header-badge">
{this.needFold ? '已折叠' : '已屏蔽'}
</span> </span>
)} <div style={{ clear: 'both' }} />
<code className="box-id">#{this.props.info.pid}</code> </div>
&nbsp;
{this.props.info.author_title && (
<span className="box-header-name author-title">{`"${this.props.info.author_title}"`}</span>
)}
{this.props.info.cw !== null && (
<span className="box-header-cw">{this.props.info.cw}</span>
)}
<Time stamp={this.props.info.timestamp} short={true} />
<span className="box-header-badge">
{this.needFold ? '已折叠' : '已屏蔽'}
</span>
<div style={{ clear: 'both' }} />
</div> </div>
</div> </div>
</div> </div>
</div> )
); );
} }
@ -1163,37 +1198,40 @@ function FlowChunk(props) {
{({ value: token }) => ( {({ value: token }) => (
<div className="flow-chunk"> <div className="flow-chunk">
{!!props.title && <TitleLine text={props.title} />} {!!props.title && <TitleLine text={props.title} />}
{props.list.map((info, ind) => !info.blocked && ( {props.list.map(
<LazyLoad (info, ind) =>
key={info.key || info.pid} !info.blocked && (
offset={500} <LazyLoad
height="15em" key={info.key || info.pid}
hiddenIfInvisible={false} offset={500}
> height="15em"
<div> hiddenIfInvisible={false}
{!!( >
props.deletion_detect && <div>
props.mode === 'list' && {!!(
ind && props.deletion_detect &&
props.list[ind - 1].pid - info.pid > 1 props.mode === 'list' &&
) && ( ind &&
<div className="flow-item-row"> props.list[ind - 1].pid - info.pid > 1
<div className="box box-tip flow-item box-danger"> ) && (
{props.list[ind - 1].pid - info.pid - 1} 条被删除 <div className="flow-item-row">
</div> <div className="box box-tip flow-item box-danger">
{props.list[ind - 1].pid - info.pid - 1} 条被删除
</div>
</div>
)}
<FlowItemRow
info={info}
mode={props.mode}
show_sidebar={props.show_sidebar}
token={token}
deletion_detect={props.deletion_detect}
search_param={props.search_param}
/>
</div> </div>
)} </LazyLoad>
<FlowItemRow ),
info={info} )}
mode={props.mode}
show_sidebar={props.show_sidebar}
token={token}
deletion_detect={props.deletion_detect}
search_param={props.search_param}
/>
</div>
</LazyLoad>
))}
</div> </div>
)} )}
</TokenCtx.Consumer> </TokenCtx.Consumer>
@ -1205,23 +1243,23 @@ export class Flow extends PureComponent {
super(props); super(props);
let submode = window[props.mode.toUpperCase() + '_SUBMODE_BACKUP']; let submode = window[props.mode.toUpperCase() + '_SUBMODE_BACKUP'];
if (submode === undefined) { if (submode === undefined) {
submode = props.mode === 'list' ? (window.config.by_c ? 1 : 0) : 0; submode = props.mode === 'list' ? (window.config.by_c ? 1 : 0) : 0;
} }
this.state = { this.state = {
submode: submode, submode: submode,
} };
} }
get_submode_names(mode) { get_submode_names(mode) {
switch(mode) { switch (mode) {
case('list'): case 'list':
return ['最新', '最近回复', '近期热门', '随机']; return ['最新', '最近回复', '近期热门', '随机'];
case('attention'): case 'attention':
return ['线上关注', '本地收藏']; return ['线上关注', '本地收藏'];
case('search'): case 'search':
return ['Tag搜索', '全文搜索', '头衔'] return ['Tag搜索', '全文搜索', '头衔'];
} }
return [] return [];
} }
set_submode(submode) { set_submode(submode) {
@ -1233,7 +1271,7 @@ export class Flow extends PureComponent {
render() { render() {
const { submode } = this.state; const { submode } = this.state;
const submode_names = this.get_submode_names(this.props.mode) const submode_names = this.get_submode_names(this.props.mode);
return ( return (
<> <>
<div className="aux-margin flow-submode-choice"> <div className="aux-margin flow-submode-choice">
@ -1257,11 +1295,10 @@ export class Flow extends PureComponent {
token={this.props.token} token={this.props.token}
/> />
</> </>
) );
} }
} }
class SubFlow extends PureComponent { class SubFlow extends PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
@ -1406,11 +1443,13 @@ class SubFlow extends PureComponent {
? json.data ? json.data
: !use_regex : !use_regex
? json.data.filter((post) => { ? json.data.filter((post) => {
return this.state.search_param return this.state.search_param
.split(' ') .split(' ')
.every((keyword) => post.text.includes(keyword)); .every((keyword) => post.text.includes(keyword));
}) // Not using regex }) // Not using regex
: json.data.filter((post) => !!post.text.match(regex_search)), // Using regex : json.data.filter(
(post) => !!post.text.match(regex_search),
), // Using regex
}, },
mode: 'attention_finished', mode: 'attention_finished',
loading_status: 'done', loading_status: 'done',
@ -1419,18 +1458,18 @@ class SubFlow extends PureComponent {
window.saved_attentions = Array.from( window.saved_attentions = Array.from(
new Set([ new Set([
...window.saved_attentions, ...window.saved_attentions,
...json.data.map(post => post.pid) ...json.data.map((post) => post.pid),
]) ]),
).sort((a, b) => (b - a)); ).sort((a, b) => b - a);
save_attentions(); save_attentions();
} }
}) })
.catch(failed); .catch(failed);
} else if (this.props.submode === 1) { } else if (this.props.submode === 1) {
const PERPAGE = 50; const PERPAGE = 50;
let pids = window.saved_attentions.sort( let pids = window.saved_attentions
(a, b) => (b - a) .sort((a, b) => b - a)
).slice((page - 1) * PERPAGE, page * PERPAGE); .slice((page - 1) * PERPAGE, page * PERPAGE);
if (pids.length) { if (pids.length) {
API.get_multi(pids, this.props.token) API.get_multi(pids, this.props.token)
.then((json) => { .then((json) => {
@ -1458,7 +1497,7 @@ class SubFlow extends PureComponent {
console.log('local attention finished'); console.log('local attention finished');
this.setState({ this.setState({
loading_status: 'done', loading_status: 'done',
mode: 'attention_finished' mode: 'attention_finished',
}); });
return; return;
} }
@ -1496,18 +1535,23 @@ class SubFlow extends PureComponent {
} }
trunc_string(s, max_len) { trunc_string(s, max_len) {
return s.substr(0, max_len) + ( return s.substr(0, max_len) + (s.length > max_len ? '...' : '');
s.length > max_len ? '...' : ''
)
} }
gen_export() { gen_export() {
this.setState({ this.setState({
can_export: false, can_export: false,
export_text: "以下是你关注的洞及摘要,复制保存到本地吧。\n\n" + this.state.chunks.data.map( export_text:
p => `#${p.pid}: ${ '以下是你关注的洞及摘要,复制保存到本地吧。\n\n' +
this.trunc_string(p.text.replaceAll('\n', ' '), 50) this.state.chunks.data
}`).join('\n\n') .map(
(p) =>
`#${p.pid}: ${this.trunc_string(
p.text.replaceAll('\n', ' '),
50,
)}`,
)
.join('\n\n'),
}); });
} }
@ -1515,9 +1559,14 @@ class SubFlow extends PureComponent {
const should_deletion_detect = localStorage['DELETION_DETECT'] === 'on'; const should_deletion_detect = localStorage['DELETION_DETECT'] === 'on';
return ( return (
<div className="flow-container"> <div className="flow-container">
{this.state.mode === 'attention_finished' && this.props.submode == 0 && ( {this.state.mode === 'attention_finished' && this.props.submode == 0 && (
<button className="export-btn" type="button" onClick={this.gen_export.bind(this)}>导出</button> <button
className="export-btn"
type="button"
onClick={this.gen_export.bind(this)}
>
导出
</button>
)} )}
{this.state.export_text && ( {this.state.export_text && (

1
src/Message.css

@ -16,4 +16,3 @@
vertical-align: top; vertical-align: top;
padding: 3px; padding: 3px;
} }

42
src/Message.js

@ -11,7 +11,7 @@ export class MessageViewer extends PureComponent {
loading_status: 'idle', loading_status: 'idle',
msg: [], msg: [],
}; };
this.input_suf_ref=React.createRef(); this.input_suf_ref = React.createRef();
} }
componentDidMount() { componentDidMount() {
@ -25,12 +25,9 @@ export class MessageViewer extends PureComponent {
loading_status: 'loading', loading_status: 'loading',
}, },
() => { () => {
fetch( fetch(API_BASE + '/systemlog', {
API_BASE + '/systemlog', headers: { 'User-Token': this.props.token },
{ })
headers: {'User-Token': this.props.token},
}
)
.then(get_json) .then(get_json)
.then((json) => { .then((json) => {
this.setState({ this.setState({
@ -53,10 +50,9 @@ export class MessageViewer extends PureComponent {
} }
do_set_token() { do_set_token() {
if (this.state.loading_status==='loading') if (this.state.loading_status === 'loading') return;
return;
if (!this.input_suf_ref.current.value) { if (!this.input_suf_ref.current.value) {
alert("不建议后缀为空"); alert('不建议后缀为空');
return; return;
} }
let tt = this.state.tmp_token + '_' + this.input_suf_ref.current.value; let tt = this.state.tmp_token + '_' + this.input_suf_ref.current.value;
@ -84,20 +80,29 @@ export class MessageViewer extends PureComponent {
else if (this.state.loading_status === 'done') else if (this.state.loading_status === 'done')
return ( return (
<> <>
<br/> <br />
<p> <p>
最近一次重置 <Time stamp={this.state.start_time} short={false} /> 最近一次重置 <Time stamp={this.state.start_time} short={false} />
</p> </p>
<p> <p>
随机盐 <b>{this.state.salt}</b> 随机盐 <b>{this.state.salt}</b>
</p> </p>
<br/> <br />
<div> <div>
<p>15分钟临时token:</p> <p>15分钟临时token:</p>
<div className="input-prepend">{this.state.tmp_token}_ </div> <div className="input-prepend">{this.state.tmp_token}_ </div>
<input type="text" className="input-suf" ref={this.input_suf_ref} placeholder="自定义后缀" maxLength={10}/> <input
<button type="button" disabled={this.state.loading_status==='loading'} type="text"
onClick={(e)=>this.do_set_token()}> className="input-suf"
ref={this.input_suf_ref}
placeholder="自定义后缀"
maxLength={10}
/>
<button
type="button"
disabled={this.state.loading_status === 'loading'}
onClick={(e) => this.do_set_token()}
>
使用 使用
</button> </button>
</div> </div>
@ -105,8 +110,7 @@ export class MessageViewer extends PureComponent {
<div className="box" key={msg.type + msg.timestamp}> <div className="box" key={msg.type + msg.timestamp}>
<div className="box-header"> <div className="box-header">
<Time stamp={msg.timestamp} short={false} /> <Time stamp={msg.timestamp} short={false} />
&nbsp; &nbsp; &nbsp;
&nbsp;
<b>{msg.type}</b> <b>{msg.type}</b>
&nbsp; &nbsp;
<span className="box-header-name">{msg.user}</span> <span className="box-header-name">{msg.user}</span>
@ -115,9 +119,9 @@ export class MessageViewer extends PureComponent {
<pre>{msg.detail}</pre> <pre>{msg.detail}</pre>
</div> </div>
</div> </div>
))} ))}
</> </>
) );
else return null; else return null;
} }
} }

16
src/PressureHelper.css

@ -1,16 +1,16 @@
.pressure-box { .pressure-box {
border: 500px /* also change js! */ solid orange; border: 500px /* also change js! */ solid orange;
position: fixed; position: fixed;
margin: auto; margin: auto;
z-index: 100; z-index: 100;
pointer-events: none; pointer-events: none;
} }
.pressure-box-empty { .pressure-box-empty {
visibility: hidden; visibility: hidden;
} }
.pressure-box-fired { .pressure-box-fired {
border-color: orangered; border-color: orangered;
pointer-events: initial !important; pointer-events: initial !important;
} }

232
src/Sidebar.css

@ -1,183 +1,193 @@
.sidebar-shadow { .sidebar-shadow {
will-change: opacity; will-change: opacity;
opacity: 0; opacity: 0;
background-color: black; background-color: black;
pointer-events: none; pointer-events: none;
transition: opacity 150ms ease-out; transition: opacity 150ms ease-out;
position: fixed; position: fixed;
left: 0; left: 0;
top: 0; top: 0;
height: 100%; height: 100%;
width: 100%; width: 100%;
z-index: 20; z-index: 20;
} }
.sidebar-on .sidebar-shadow { .sidebar-on .sidebar-shadow {
opacity: .3; opacity: 0.3;
pointer-events: initial; pointer-events: initial;
} }
.sidebar-on .sidebar-shadow:active { .sidebar-on .sidebar-shadow:active {
opacity: .5; opacity: 0.5;
transition: unset; transition: unset;
} }
.root-dark-mode .sidebar-on .sidebar-shadow { .root-dark-mode .sidebar-on .sidebar-shadow {
opacity: .65; opacity: 0.65;
} }
.root-dark-mode .sidebar-on .sidebar-shadow:active { .root-dark-mode .sidebar-on .sidebar-shadow:active {
opacity: .8; opacity: 0.8;
} }
.sidebar { .sidebar {
user-select: text; user-select: text;
position: fixed; position: fixed;
top: 0; top: 0;
/* think twice before you use 100vh /* think twice before you use 100vh
https://dev.to/peiche/100vh-behavior-on-chrome-2hm8 https://dev.to/peiche/100vh-behavior-on-chrome-2hm8
*/ */
height: 100%; height: 100%;
background-color: rgba(255,255,255,.7); background-color: rgba(255, 255, 255, 0.7);
overflow-y: auto; overflow-y: auto;
padding-top: 3em; padding-top: 3em;
/* padding-bottom: 1em; */ /* move to sidebar-content */ /* padding-bottom: 1em; */ /* move to sidebar-content */
backdrop-filter: blur(5px); backdrop-filter: blur(5px);
} }
.sidebar-content { .sidebar-content {
backdrop-filter: blur(0px); /* fix scroll performance issues */ backdrop-filter: blur(0px); /* fix scroll performance issues */
} }
.root-dark-mode .sidebar { .root-dark-mode .sidebar {
background-color: hsla(0,0%,5%,.4); background-color: hsla(0, 0%, 5%, 0.4);
} }
.sidebar, .sidebar-title { .sidebar,
left: 700px; .sidebar-title {
will-change: opacity, transform; left: 700px;
z-index: 21; will-change: opacity, transform;
width: calc(100% - 700px); z-index: 21;
width: calc(100% - 700px);
} }
.sidebar-on .sidebar, .sidebar-on .sidebar-title { .sidebar-on .sidebar,
animation: sidebar-fadein .15s cubic-bezier(0.15, 0.4, 0.6, 1); .sidebar-on .sidebar-title {
animation: sidebar-fadein 0.15s cubic-bezier(0.15, 0.4, 0.6, 1);
} }
.sidebar-off .sidebar, .sidebar-off .sidebar-title { .sidebar-off .sidebar,
visibility: hidden; .sidebar-off .sidebar-title {
pointer-events: none; visibility: hidden;
backdrop-filter: none; pointer-events: none;
animation: sidebar-fadeout .2s cubic-bezier(0.15, 0.4, 0.6, 1); backdrop-filter: none;
animation: sidebar-fadeout 0.2s cubic-bezier(0.15, 0.4, 0.6, 1);
} }
.sidebar-container { .sidebar-container {
animation: sidebar-initial .25s linear; /* skip initial animation */ animation: sidebar-initial 0.25s linear; /* skip initial animation */
} }
@keyframes sidebar-fadeout { @keyframes sidebar-fadeout {
from { from {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
transform: none; transform: none;
backdrop-filter: none; backdrop-filter: none;
} }
to { to {
visibility: visible; visibility: visible;
opacity: 0; opacity: 0;
transform: translateX(40vw); transform: translateX(40vw);
backdrop-filter: none; backdrop-filter: none;
} }
} }
@keyframes sidebar-fadein { @keyframes sidebar-fadein {
from { from {
opacity: 0; opacity: 0;
transform: translateX(40vw); transform: translateX(40vw);
backdrop-filter: none; backdrop-filter: none;
} }
to { to {
opacity: 1; opacity: 1;
transform: none; transform: none;
backdrop-filter: none; backdrop-filter: none;
} }
} }
@keyframes sidebar-initial { @keyframes sidebar-initial {
from {opacity: 0;} from {
to {opacity: 0;} opacity: 0;
}
to {
opacity: 0;
}
} }
.sidebar-title { .sidebar-title {
text-shadow: 0 0 3px white; text-shadow: 0 0 3px white;
font-weight: bold; font-weight: bold;
position: fixed; position: fixed;
width: 100%; width: 100%;
top: 0; top: 0;
line-height: 3em; line-height: 3em;
padding-left: .5em; padding-left: 0.5em;
background-color: rgba(255,255,255,.6); background-color: rgba(255, 255, 255, 0.6);
pointer-events: none; pointer-events: none;
backdrop-filter: blur(5px); backdrop-filter: blur(5px);
box-shadow: 0 3px 5px rgba(0,0,0,.2); box-shadow: 0 3px 5px rgba(0, 0, 0, 0.2);
} }
.root-dark-mode .sidebar-title { .root-dark-mode .sidebar-title {
background-color: hsla(0,0%,18%,.6); background-color: hsla(0, 0%, 18%, 0.6);
color: var(--foreground-dark); color: var(--foreground-dark);
text-shadow: 0 0 3px black; text-shadow: 0 0 3px black;
} }
.sidebar-title a { .sidebar-title a {
pointer-events: initial; pointer-events: initial;
} }
/* move all padding to sidebar-content - the scrolling div (overflow-y: auto) */ /* move all padding to sidebar-content - the scrolling div (overflow-y: auto) */
/* .sidebar, */ /* .sidebar, */
.sidebar-content, .sidebar-content,
.sidebar-title { .sidebar-title {
padding-left: 1em; padding-left: 1em;
padding-right: 1em; padding-right: 1em;
} }
.sidebar-content { .sidebar-content {
padding-bottom: 1em; padding-bottom: 1em;
} }
@media screen and (max-width: 1300px) { @media screen and (max-width: 1300px) {
.sidebar, .sidebar-title { .sidebar,
left: calc(100% - 550px); .sidebar-title {
width: 550px;/* left: calc(100% - 550px);
width: 550px; /*
padding-left: .5em; padding-left: .5em;
padding-right: .5em; */ padding-right: .5em; */
} }
.sidebar-content, .sidebar-title { .sidebar-content,
padding-left: .5em; .sidebar-title {
padding-right: .5em; padding-left: 0.5em;
} padding-right: 0.5em;
}
} }
@media screen and (max-width: 580px) { @media screen and (max-width: 580px) {
.sidebar, .sidebar-title { .sidebar,
left: 27px; .sidebar-title {
width: calc(100% - 27px); left: 27px;
/* padding-left: .25em; width: calc(100% - 27px);
/* padding-left: .25em;
padding-right: .25em; */ padding-right: .25em; */
} }
.sidebar-content, .sidebar-title { .sidebar-content,
padding-left: .25em; .sidebar-title {
padding-right: .25em; padding-left: 0.25em;
} padding-right: 0.25em;
}
} }
.sidebar-flow-item { .sidebar-flow-item {
display: block; display: block;
} }
.sidebar-flow-item .box { .sidebar-flow-item .box {
width: 100%; width: 100%;
} }
.sidebar-content-show { .sidebar-content-show {
height: 100%; height: 100%;
overflow-y: auto; overflow-y: auto;
} }
.sidebar-content-hide{ .sidebar-content-hide {
/* will make lazyload working correctly */ /* will make lazyload working correctly */
height: 0; height: 0;
padding: 0; padding: 0;
overflow-y: scroll; overflow-y: scroll;
} }

98
src/Title.css

@ -1,88 +1,88 @@
.title-bar { .title-bar {
z-index: 10; z-index: 10;
position: sticky; position: sticky;
top: -4em; top: -4em;
left: 0; left: 0;
width: 100%; width: 100%;
height: 7em; height: 7em;
background-color: rgba(255,255,255,.8); background-color: rgba(255, 255, 255, 0.8);
box-shadow: 0 0 25px rgba(0,0,0,.4); box-shadow: 0 0 25px rgba(0, 0, 0, 0.4);
margin-bottom: 1em; margin-bottom: 1em;
backdrop-filter: blur(5px); backdrop-filter: blur(5px);
} }
.root-dark-mode .title-bar { .root-dark-mode .title-bar {
background-color: hsla(0,0%,12%,.8); background-color: hsla(0, 0%, 12%, 0.8);
box-shadow: 0 0 5px rgba(255,255,255,.1); box-shadow: 0 0 5px rgba(255, 255, 255, 0.1);
} }
.control-bar { .control-bar {
display: flex; display: flex;
margin-top: .5em; margin-top: 0.5em;
line-height: 2em; line-height: 2em;
} }
.control-btn { .control-btn {
flex: 0 0 4.5em; flex: 0 0 4.5em;
text-align: center; text-align: center;
color: black; color: black;
border-radius: 5px; border-radius: 5px;
} }
.control-btn:hover { .control-btn:hover {
background-color: #666666; background-color: #666666;
color: white; color: white;
} }
.control-btn-label { .control-btn-label {
margin-left: .25rem; margin-left: 0.25rem;
font-size: .9em; font-size: 0.9em;
vertical-align: .05em; vertical-align: 0.05em;
} }
@media screen and (max-width: 900px) { @media screen and (max-width: 900px) {
.control-btn { .control-btn {
flex: 0 0 2.5em; flex: 0 0 2.5em;
} }
.control-btn-label { .control-btn-label {
display: none; display: none;
} }
.control-search { .control-search {
padding: 0 .5em; padding: 0 0.5em;
} }
} }
.root-dark-mode .control-btn { .root-dark-mode .control-btn {
color: var(--foreground-dark); color: var(--foreground-dark);
opacity: .9; opacity: 0.9;
} }
.root-dark-mode .control-btn:hover { .root-dark-mode .control-btn:hover {
color: var(--foreground-dark); color: var(--foreground-dark);
opacity: 1; opacity: 1;
} }
.control-search { .control-search {
flex: auto; flex: auto;
color: black; color: black;
background-color: rgba(255,255,255,.3) !important; background-color: rgba(255, 255, 255, 0.3) !important;
margin: 0 .5em; margin: 0 0.5em;
min-width: 8em; min-width: 8em;
} }
.control-search:focus { .control-search:focus {
background-color: white !important; background-color: white !important;
} }
.root-dark-mode .control-search { .root-dark-mode .control-search {
background-color: hsla(0,0%,35%,.6) !important; background-color: hsla(0, 0%, 35%, 0.6) !important;
color: var(--foreground-dark); color: var(--foreground-dark);
} }
.root-dark-mode .control-search:focus { .root-dark-mode .control-search:focus {
background-color: hsl(0,0%,80%) !important; background-color: hsl(0, 0%, 80%) !important;
color: black !important; color: black !important;
} }
.list-menu { .list-menu {
text-align: center; text-align: center;
} }
.help-desc-box p { .help-desc-box p {
margin: .5em; margin: 0.5em;
} }

12
src/Title.js

@ -36,10 +36,11 @@ class ControlBar extends PureComponent {
); );
} }
window.addEventListener("hashchange", window.addEventListener(
'hashchange',
() => { () => {
let text = decodeURIComponent(window.location.hash).substr(1); let text = decodeURIComponent(window.location.hash).substr(1);
if(text && text[0]!='#') { if (text && text[0] != '#') {
console.log('search', text); console.log('search', text);
this.setState( this.setState(
{ {
@ -51,7 +52,7 @@ class ControlBar extends PureComponent {
); );
} }
}, },
false false,
); );
} }
@ -136,7 +137,10 @@ class ControlBar extends PureComponent {
className="control-search" className="control-search"
value={this.state.search_text} value={this.state.search_text}
placeholder={ placeholder={
this.props.mode === 'attention' ? '在关注列表中搜索' : '关键词 / tag / #树洞号'} this.props.mode === 'attention'
? '在关注列表中搜索'
: '关键词 / tag / #树洞号'
}
onChange={this.on_change_bound} onChange={this.on_change_bound}
onKeyPress={this.on_keypress_bound} onKeyPress={this.on_keypress_bound}
/> />

111
src/UserAction.css

@ -1,120 +1,119 @@
.login-form p { .login-form p {
margin: 1em 0; margin: 1em 0;
text-align: center; text-align: center;
} }
.login-form button { .login-form button {
min-width: 6rem; min-width: 6rem;
} }
.reply-form { .reply-form {
display: flex; display: flex;
} }
.reply-sticky { .reply-sticky {
position: sticky; position: sticky;
bottom: 0; bottom: 0;
} }
.reply-form textarea { .reply-form textarea {
resize: vertical; resize: vertical;
flex: 1; flex: 1;
min-height: 2em; min-height: 2em;
height: 4em; height: 4em;
} }
.reply-form button { .reply-form button {
flex: 0 0 3em; flex: 0 0 3em;
margin-right: 0; margin-right: 0;
} }
.reply-preview { .reply-preview {
flex: 1; flex: 1;
min-height: 2em; min-height: 2em;
} }
.post-form-bar { .post-form-bar {
line-height: 2em; line-height: 2em;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
margin-bottom: .5em; margin-bottom: 0.5em;
} }
.post-form-bar .checkbox-bar { .post-form-bar .checkbox-bar {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
} }
.post-form-bar .checkbox-bar label { .post-form-bar .checkbox-bar label {
flex: 0 0 auto; flex: 0 0 auto;
margin: 0 0.5rem; margin: 0 0.5rem;
} }
.post-form-bar input[type=file] { .post-form-bar input[type='file'] {
border: 0; border: 0;
padding: 0 0 0 .5em; padding: 0 0 0 0.5em;
} }
@media screen and (max-width: 580px) { @media screen and (max-width: 580px) {
.post-form-bar input[type=file] { .post-form-bar input[type='file'] {
width: 120px; width: 120px;
} }
} }
@media screen and (max-width: 320px) { @media screen and (max-width: 320px) {
.post-form-bar input[type=file] { .post-form-bar input[type='file'] {
width: 100px; width: 100px;
} }
} }
.post-form-bar button { .post-form-bar button {
flex: 0 0 6em; flex: 0 0 6em;
margin-right: 0; margin-right: 0;
} }
@media screen and (max-width: 580px) { @media screen and (max-width: 580px) {
.post-form-bar button { .post-form-bar button {
flex: 0 0 4.5em; flex: 0 0 4.5em;
margin-right: 0; margin-right: 0;
} }
} }
.post-form-img-tip { .post-form-img-tip {
font-size: small; font-size: small;
margin-top: -.5em; margin-top: -0.5em;
margin-bottom: .5em; margin-bottom: 0.5em;
} }
.post-form textarea { .post-form textarea {
resize: vertical; resize: vertical;
width: 100%; width: 100%;
min-height: 5em; min-height: 5em;
height: 20em; height: 20em;
} }
.post-preview { .post-preview {
width: 100%; width: 100%;
min-height: 5em; min-height: 5em;
} }
.life-info-table { .life-info-table {
width: 100%; width: 100%;
margin: auto; margin: auto;
} }
@media screen and (min-width: 375px) { @media screen and (min-width: 375px) {
.life-info-table { .life-info-table {
width: 315px; width: 315px;
} }
} }
.life-info-table td { .life-info-table td {
padding: .25em; padding: 0.25em;
} }
.life-info-table td:nth-child(1) { .life-info-table td:nth-child(1) {
font-weight: bold; font-weight: bold;
text-align: right; text-align: right;
} }
.life-info-error a { .life-info-error a {
--var-link-color: hsl(25,100%,45%); --var-link-color: hsl(25, 100%, 45%);
} }
.spoiler-input { .spoiler-input {

115
src/UserAction.js

@ -12,11 +12,8 @@ import { ConfigUI } from './Config';
import fixOrientation from 'fix-orientation'; import fixOrientation from 'fix-orientation';
import copy from 'copy-to-clipboard'; import copy from 'copy-to-clipboard';
import { cache } from './cache'; import { cache } from './cache';
import { import { API, get_json } from './flows_api';
API, import { save_attentions } from './Attention';
get_json,
} from './flows_api';
import { save_attentions } from './Attention'
import './UserAction.css'; import './UserAction.css';
@ -57,7 +54,9 @@ export function InfoSidebar(props) {
<span className="icon icon-textfile" /> <span className="icon icon-textfile" />
<label>树洞规范试行</label> <label>树洞规范试行</label>
</a> </a>
<p><em>强烈建议开始使用前先看一遍所有设置选项</em></p> <p>
<em>强烈建议开始使用前先看一遍所有设置选项</em>
</p>
</div> </div>
<div className="box help-desc-box"> <div className="box help-desc-box">
<p> <p>
@ -87,15 +86,18 @@ export function InfoSidebar(props) {
</div> </div>
<div className="box help-desc-box"> <div className="box help-desc-box">
<p>意见反馈请加tag #意见反馈 或到github后端的issue区</p> <p>意见反馈请加tag #意见反馈 或到github后端的issue区</p>
<p>新T树洞强烈期待有其他更多树洞的出现一起分布式互联构建清华树洞族详情见 关于 中的描述</p> <p>
<p>联系我们<a href={"mailto:"+EMAIL}>{EMAIL}</a> </p> 新T树洞强烈期待有其他更多树洞的出现一起分布式互联构建清华树洞族详情见
关于 中的描述
</p>
<p>
联系我们<a href={'mailto:' + EMAIL}>{EMAIL}</a>
</p>
</div> </div>
<div className="box help-desc-box"> <div className="box help-desc-box">
<p> <p>
新T树洞 网页版 by @hole_thu基于 新T树洞 网页版 by @hole_thu基于
<a href="https://www.gnu.org/licenses/agpl-3.0.html" <a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank">
target="_blank"
>
AGPLv3 AGPLv3
</a> </a>
协议在{' '} 协议在{' '}
@ -121,7 +123,8 @@ export function InfoSidebar(props) {
> >
T大树洞网页版 by @thuhole T大树洞网页版 by @thuhole
</a> </a>
<a href="https://reactjs.org/" target="_blank" rel="noopener"> {' '}
<a href="https://reactjs.org/" target="_blank" rel="noopener">
React React
</a> </a>
@ -139,8 +142,8 @@ export class LoginForm extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
'custom_title': window.TITLE || '' custom_title: window.TITLE || '',
} };
} }
update_title(title, token) { update_title(title, token) {
@ -151,10 +154,11 @@ export class LoginForm extends Component {
API.set_title(title, token) API.set_title(title, token)
.then((json) => { .then((json) => {
if (json.code === 0) { if (json.code === 0) {
window.TITLE = title window.TITLE = title;
alert('专属头衔设置成功'); alert('专属头衔设置成功');
} }
}).catch(err => alert("设置头衔出错了:\n"+ err)); })
.catch((err) => alert('设置头衔出错了:\n' + err));
} }
copy_token(token) { copy_token(token) {
@ -203,22 +207,27 @@ export class LoginForm extends Component {
复制 User Token 复制 User Token
</a> </a>
<br /> <br />
User Token仅用于开发bot切勿告知他人若怀疑被盗号请刷新Token(刷新功能即将上线) User
Token仅用于开发bot切勿告知他人若怀疑被盗号请刷新Token(刷新功能即将上线)
</p> </p>
<p> <p>
专属头衔 专属头衔
<input <input
value={this.state.custom_title} value={this.state.custom_title}
onChange={(e) => { onChange={(e) => {
this.setState({ custom_title: e.target.value}) this.setState({ custom_title: e.target.value });
}} }}
maxLength={10} maxLength={10}
/> />
<button <button
className="update-title-btn" className="update-title-btn"
type="button" type="button"
onClick={(e) => {this.update_title(this.state.custom_title, token.value)}} onClick={(e) => {
>提交</button> this.update_title(this.state.custom_title, token.value);
}}
>
提交
</button>
<br /> <br />
设置专属头衔后可在发言时选择使用重置后需重新设置临时用户如需保持头衔请使用相同后缀 设置专属头衔后可在发言时选择使用重置后需重新设置临时用户如需保持头衔请使用相同后缀
</p> </p>
@ -319,17 +328,14 @@ export class ReplyForm extends Component {
text: text, text: text,
use_title: use_title ? '1' : '', use_title: use_title ? '1' : '',
}); });
fetch( fetch(API_BASE + '/docomment', {
API_BASE + '/docomment', method: 'POST',
{ headers: {
method: 'POST', 'Content-Type': 'application/x-www-form-urlencoded',
headers: { 'User-Token': this.props.token,
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': this.props.token,
},
body: data,
}, },
) body: data,
})
.then(get_json) .then(get_json)
.then((json) => { .then((json) => {
if (json.code !== 0) { if (json.code !== 0) {
@ -338,7 +344,7 @@ export class ReplyForm extends Component {
let saved_attentions = window.saved_attentions; let saved_attentions = window.saved_attentions;
if (!saved_attentions.includes(pid)) { if (!saved_attentions.includes(pid)) {
saved_attentions.unshift(pid) saved_attentions.unshift(pid);
window.saved_attentions = saved_attentions; window.saved_attentions = saved_attentions;
save_attentions(); save_attentions();
} }
@ -393,7 +399,7 @@ export class ReplyForm extends Component {
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
this.toggle_preview(); this.toggle_preview();
}} }}
> >
{this.state.preview ? ( {this.state.preview ? (
@ -418,8 +424,8 @@ export class ReplyForm extends Component {
type="checkbox" type="checkbox"
onChange={this.on_use_title_change_bound} onChange={this.on_use_title_change_bound}
checked={this.state.use_title} checked={this.state.use_title}
/> />{' '}
{' '}使用头衔 使用头衔
</label> </label>
)} )}
</div> </div>
@ -490,18 +496,24 @@ export class PostForm extends Component {
} }
do_post() { do_post() {
const { cw, text, allow_search, use_title, has_poll, poll_options } = this.state; const {
cw,
text,
allow_search,
use_title,
has_poll,
poll_options,
} = this.state;
let data = new URLSearchParams({ let data = new URLSearchParams({
cw: cw, cw: cw,
text: text, text: text,
allow_search: allow_search ? '1' : '', allow_search: allow_search ? '1' : '',
use_title: use_title ? '1' : '', use_title: use_title ? '1' : '',
type: 'text' type: 'text',
}); });
if (has_poll) { if (has_poll) {
poll_options.forEach((opt) => { poll_options.forEach((opt) => {
if (opt) if (opt) data.append('poll_options', opt);
data.append('poll_options', opt);
}); });
} }
@ -692,15 +704,14 @@ export class PostForm extends Component {
let text = event.target.value; let text = event.target.value;
poll_options[idx] = text; poll_options[idx] = text;
if (!text && poll_options.length > 1) { if (!text && poll_options.length > 1) {
poll_options.splice(idx, 1) poll_options.splice(idx, 1);
} }
if (poll_options[poll_options.length - 1] && poll_options.length < 8) { if (poll_options[poll_options.length - 1] && poll_options.length < 8) {
poll_options.push('') poll_options.push('');
} }
this.setState({ poll_options: poll_options }); this.setState({ poll_options: poll_options });
} }
render() { render() {
const { has_poll, poll_options, preview, loading_status } = this.state; const { has_poll, poll_options, preview, loading_status } = this.state;
return ( return (
@ -748,8 +759,8 @@ export class PostForm extends Component {
type="checkbox" type="checkbox"
onChange={this.on_allow_search_change_bound} onChange={this.on_allow_search_change_bound}
checked={this.state.allow_search} checked={this.state.allow_search}
/> />{' '}
{' '}允许搜索 允许搜索
</label> </label>
{window.TITLE && ( {window.TITLE && (
<label> <label>
@ -757,8 +768,8 @@ export class PostForm extends Component {
type="checkbox" type="checkbox"
onChange={this.on_use_title_change_bound} onChange={this.on_use_title_change_bound}
checked={this.state.use_title} checked={this.state.use_title}
/> />{' '}
{' '}使用头衔 使用头衔
</label> </label>
)} )}
</div> </div>
@ -815,7 +826,7 @@ export class PostForm extends Component {
{has_poll && ( {has_poll && (
<div className="post-form-poll-options"> <div className="post-form-poll-options">
<h6>投票选项</h6> <h6>投票选项</h6>
{poll_options.map( (option, idx) => ( {poll_options.map((option, idx) => (
<input <input
key={idx} key={idx}
type="text" type="text"
@ -827,7 +838,9 @@ export class PostForm extends Component {
))} ))}
</div> </div>
)} )}
<br /><br /><br /> <br />
<br />
<br />
<p> <p>
<small> <small>
请遵守 请遵守
@ -839,7 +852,8 @@ export class PostForm extends Component {
</p> </p>
<p> <p>
<small> <small>
插入图片请使用图片外链Markdown格式 ![](图片链接) 支持动图支持多图推荐的图床 插入图片请使用图片外链Markdown格式 ![](图片链接)
支持动图支持多图推荐的图床
<a href="https://imgchr.com/" target="_blank"> <a href="https://imgchr.com/" target="_blank">
路过图床 路过图床
</a> </a>
@ -848,7 +862,10 @@ export class PostForm extends Component {
sm.ms sm.ms
</a> </a>
<a href="https://bbs.pku.edu.cn/v2/post-read.php?bid=154&threadid=3743" target="_blank"> <a
href="https://bbs.pku.edu.cn/v2/post-read.php?bid=154&threadid=3743"
target="_blank"
>
未名BBS 未名BBS
</a> </a>

173
src/flows_api.js

@ -1,4 +1,4 @@
import { get_json, gen_name} from './infrastructure/functions'; import { get_json, gen_name } from './infrastructure/functions';
import { API_BASE } from './Common'; import { API_BASE } from './Common';
import { cache } from './cache'; import { cache } from './cache';
@ -35,14 +35,11 @@ export const parse_replies = (replies, color_picker) =>
export const API = { 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 + '/getcomment?pid=' + pid, {
API_BASE + '/getcomment?pid=' + pid , headers: {
{ 'User-Token': token,
headers: { },
'User-Token': token, });
}
}
);
let json = await handle_response(response); let json = await handle_response(response);
// Why delete then put ?? // Why delete then put ??
//console.log('Put cache', json, pid, cache_version); //console.log('Put cache', json, pid, cache_version);
@ -70,17 +67,14 @@ export const API = {
let data = new URLSearchParams(); let data = new URLSearchParams();
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 + '/attention', {
API_BASE + '/attention', method: 'POST',
{ headers: {
method: 'POST', 'Content-Type': 'application/x-www-form-urlencoded',
headers: { 'User-Token': token,
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': token,
},
body: data,
}, },
); body: data,
});
// Delete cache to update `attention` on next reload // Delete cache to update `attention` on next reload
cache().delete(pid); cache().delete(pid);
return handle_response(response, false); return handle_response(response, false);
@ -90,55 +84,46 @@ export const API = {
let data = new URLSearchParams(); let data = new URLSearchParams();
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 + '/report', {
API_BASE + '/report', method: 'POST',
{ headers: {
method: 'POST', 'Content-Type': 'application/x-www-form-urlencoded',
headers: { 'User-Token': token,
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': token,
},
body: data,
}, },
); body: data,
});
return handle_response(response, false); return handle_response(response, false);
}, },
block: async (type, id, token) => { block: async (type, id, token) => {
let data = new URLSearchParams([ let data = new URLSearchParams([
['type', type], ['id', id] ['type', type],
['id', id],
]); ]);
let response = await fetch( let response = await fetch(API_BASE + '/block', {
API_BASE + '/block', method: 'POST',
{ headers: {
method: 'POST', 'Content-Type': 'application/x-www-form-urlencoded',
headers: { 'User-Token': token,
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': token,
},
body: data,
}, },
); body: data,
});
return handle_response(response, false); return handle_response(response, false);
}, },
del: async (type, id, note, token) => { del: async (type, id, note, token) => {
let data = new URLSearchParams(); let data = new URLSearchParams();
data.append('type', type); data.append('type', type);
data.append('id', id); data.append('id', id);
data.append('note', note); data.append('note', note);
let response = await fetch( let response = await fetch(API_BASE + '/delete', {
API_BASE + '/delete', method: 'POST',
{ headers: {
method: 'POST', 'Content-Type': 'application/x-www-form-urlencoded',
headers: { 'User-Token': token,
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': token,
},
body: data,
}, },
); body: data,
});
return handle_response(response, false); return handle_response(response, false);
}, },
@ -146,17 +131,14 @@ export const API = {
let data = new URLSearchParams(); let data = new URLSearchParams();
data.append('cw', cw); data.append('cw', cw);
data.append('pid', id); data.append('pid', id);
let response = await fetch( let response = await fetch(API_BASE + '/editcw', {
API_BASE + '/editcw', method: 'POST',
{ headers: {
method: 'POST', 'Content-Type': 'application/x-www-form-urlencoded',
headers: { 'User-Token': token,
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': token,
},
body: data,
}, },
); body: data,
});
return handle_response(response, false); return handle_response(response, false);
}, },
@ -166,7 +148,7 @@ export const API = {
window.config.no_c_post ? '&no_cw' : '' window.config.no_c_post ? '&no_cw' : ''
}&order_mode=${submode}`, }&order_mode=${submode}`,
{ {
headers: {'User-Token': token}, headers: { 'User-Token': token },
}, },
); );
return handle_response(response); return handle_response(response);
@ -174,58 +156,49 @@ export const API = {
get_search: async (page, keyword, token, submode) => { get_search: async (page, keyword, token, submode) => {
let response = await fetch( let response = await fetch(
`${API_BASE}/search?search_mode=${submode}&page=${page}&keywords=${ `${API_BASE}/search?search_mode=${submode}&page=${page}&keywords=${encodeURIComponent(
encodeURIComponent(keyword) keyword,
}&pagesize=${SEARCH_PAGESIZE}`, )}&pagesize=${SEARCH_PAGESIZE}`,
{ {
headers: {'User-Token': token}, headers: { 'User-Token': 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 + '/getone?pid=' + pid, {
API_BASE + '/getone?pid=' + pid, headers: { 'User-Token': token },
{ });
headers: {'User-Token': token},
}
);
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 + '/getattention', {
API_BASE + '/getattention', headers: { 'User-Token': token },
{ });
headers: {'User-Token': token},
}
);
return handle_response(response); return handle_response(response);
}, },
add_vote: async (vote, pid, token) => { add_vote: async (vote, pid, token) => {
let data = new URLSearchParams([ let data = new URLSearchParams([
['vote', vote], ['vote', vote],
['pid', pid] ['pid', pid],
]); ]);
let response = await fetch( let response = await fetch(API_BASE + '/vote', {
API_BASE + '/vote', method: 'POST',
{ headers: {
method: 'POST', 'Content-Type': 'application/x-www-form-urlencoded',
headers: { 'User-Token': token,
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': token,
},
body: data,
}, },
); body: data,
});
return handle_response(response, true); return handle_response(response, true);
}, },
get_multi: async (pids, token) => { get_multi: async (pids, token) => {
let response = await fetch( let response = await fetch(
API_BASE + '/getmulti?' + pids.map(pid => `pids=${pid}`).join('&'), API_BASE + '/getmulti?' + pids.map((pid) => `pids=${pid}`).join('&'),
{ {
headers: { headers: {
'User-Token': token, 'User-Token': token,
@ -238,18 +211,14 @@ export const API = {
set_title: async (title, token) => { set_title: async (title, token) => {
console.log('title: ', title); console.log('title: ', title);
let data = new URLSearchParams([['title', title]]); let data = new URLSearchParams([['title', title]]);
let response = await fetch( let response = await fetch(API_BASE + '/title', {
API_BASE + '/title', method: 'POST',
{ headers: {
method: 'POST', 'Content-Type': 'application/x-www-form-urlencoded',
headers: { 'User-Token': token,
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': token,
},
body: data,
}, },
); body: data,
});
return handle_response(response, true); return handle_response(response, true);
}, },
}; };

59
src/fonts_7/icomoon.css

@ -1,7 +1,6 @@
@font-face { @font-face {
font-family: 'icomoon'; font-family: 'icomoon';
src: src: url('icomoon.ttf?8qh3rt') format('truetype'),
url('icomoon.ttf?8qh3rt') format('truetype'),
url('icomoon.woff?8qh3rt') format('woff'), url('icomoon.woff?8qh3rt') format('woff'),
url('icomoon.svg?8qh3rt#icomoon') format('svg'); url('icomoon.svg?8qh3rt#icomoon') format('svg');
font-weight: normal; font-weight: normal;
@ -19,7 +18,7 @@
font-variant: normal; font-variant: normal;
text-transform: none; text-transform: none;
line-height: 1; line-height: 1;
vertical-align: -.0625em; vertical-align: -0.0625em;
/* Better Font Rendering =========== */ /* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
@ -27,84 +26,84 @@
} }
.icon-send:before { .icon-send:before {
content: "\e900"; content: '\e900';
} }
.icon-textfile:before { .icon-textfile:before {
content: "\e926"; content: '\e926';
} }
.icon-history:before { .icon-history:before {
content: "\e94d"; content: '\e94d';
} }
.icon-reply:before { .icon-reply:before {
content: "\e96b"; content: '\e96b';
} }
.icon-quote:before { .icon-quote:before {
content: "\e977"; content: '\e977';
} }
.icon-loading:before { .icon-loading:before {
content: "\e979"; content: '\e979';
} }
.icon-login:before { .icon-login:before {
content: "\e98d"; content: '\e98d';
} }
.icon-settings:before { .icon-settings:before {
content: "\e994"; content: '\e994';
} }
.icon-stats:before { .icon-stats:before {
content: "\e99b"; content: '\e99b';
} }
.icon-locate:before { .icon-locate:before {
content: "\e9b3"; content: '\e9b3';
} }
.icon-upload:before { .icon-upload:before {
content: "\e9c3"; content: '\e9c3';
} }
.icon-flag:before { .icon-flag:before {
content: "\e9cc"; content: '\e9cc';
} }
.icon-attention:before { .icon-attention:before {
content: "\e9d3"; content: '\e9d3';
} }
.icon-star:before { .icon-star:before {
content: "\e9d7"; content: '\e9d7';
} }
.icon-star-ok:before { .icon-star-ok:before {
content: "\e9d9"; content: '\e9d9';
} }
.icon-plus:before { .icon-plus:before {
content: "\ea0a"; content: '\ea0a';
} }
.icon-about:before { .icon-about:before {
content: "\ea0c"; content: '\ea0c';
} }
.icon-close:before { .icon-close:before {
content: "\ea0d"; content: '\ea0d';
} }
.icon-logout:before { .icon-logout:before {
content: "\ea14"; content: '\ea14';
} }
.icon-refresh:before { .icon-refresh:before {
content: "\ea2e"; content: '\ea2e';
} }
.icon-forward:before { .icon-forward:before {
content: "\ea42"; content: '\ea42';
} }
.icon-back:before { .icon-back:before {
content: "\ea44"; content: '\ea44';
} }
.icon-order-rev:before { .icon-order-rev:before {
content: "\ea46"; content: '\ea46';
font-size: 1.2em; font-size: 1.2em;
} }
.icon-github:before { .icon-github:before {
content: "\eab0"; content: '\eab0';
} }
.icon-new-tab:before { .icon-new-tab:before {
content: "\ea7e"; content: '\ea7e';
} }
.icon-eye:before { .icon-eye:before {
content: "\e9ce"; content: '\e9ce';
} }
.icon-eye-blocked:before { .icon-eye-blocked:before {
content: "\e9d1"; content: '\e9d1';
} }

103
src/index.css

@ -1,87 +1,98 @@
body { body {
background-size: cover; background-size: cover;
user-select: none; user-select: none;
background-color: #333; background-color: #333;
} }
body.root-dark-mode { body.root-dark-mode {
background-color: black; background-color: black;
} }
html::-webkit-scrollbar { html::-webkit-scrollbar {
display: none; display: none;
} }
html { html {
scrollbar-width: none; scrollbar-width: none;
-ms-overflow-style: none; -ms-overflow-style: none;
} }
:root { :root {
--var-link-color: #00c; --var-link-color: #00c;
} }
.root-dark-mode .left-container, .root-dark-mode .sidebar, .root-dark-mode .sidebar-title, .root-dark-mode .balance-popover { .root-dark-mode .left-container,
--var-link-color: #9bf; .root-dark-mode .sidebar,
.root-dark-mode .sidebar-title,
.root-dark-mode .balance-popover {
--var-link-color: #9bf;
} }
a { a {
color: var(--var-link-color); color: var(--var-link-color);
} }
a:not(.no-underline):hover { a:not(.no-underline):hover {
border-bottom: 1px solid var(--var-link-color); border-bottom: 1px solid var(--var-link-color);
margin-bottom: -1px; margin-bottom: -1px;
} }
input, textarea { input,
border-radius: 5px; textarea {
border: 1px solid black; border-radius: 5px;
outline: none; border: 1px solid black;
margin: 0; outline: none;
margin: 0;
} }
input { input {
padding: 0 1em; padding: 0 1em;
line-height: 2em; line-height: 2em;
} }
audio { audio {
vertical-align: middle; vertical-align: middle;
} }
button, .button { button,
color: black; .button {
background-color: rgba(235,235,235,.5); color: black;
border-radius: 5px; background-color: rgba(235, 235, 235, 0.5);
text-align: center; border-radius: 5px;
border: 1px solid black; text-align: center;
line-height: 2em; border: 1px solid black;
margin: 0 .5rem; line-height: 2em;
margin: 0 0.5rem;
} }
.root-dark-mode button, .root-dark-mode .button { .root-dark-mode button,
background-color: hsl(0,0%,30%); .root-dark-mode .button {
color: var(--foreground-dark); background-color: hsl(0, 0%, 30%);
color: var(--foreground-dark);
} }
button:hover, .button:hover { button:hover,
background-color: rgba(255,255,255,.7); .button:hover {
background-color: rgba(255, 255, 255, 0.7);
} }
.root-dark-mode button:hover, .root-dark-mode .button:hover { .root-dark-mode button:hover,
background-color: hsl(0,0%,40%); .root-dark-mode .button:hover {
background-color: hsl(0, 0%, 40%);
} }
button:disabled, .button:disabled { button:disabled,
background-color: rgba(128,128,128,.5); .button:disabled {
background-color: rgba(128, 128, 128, 0.5);
} }
.root-dark-mode button:disabled, .root-dark-mode .button:disabled { .root-dark-mode button:disabled,
background-color: hsl(0,0%,20%); .root-dark-mode .button:disabled {
color: hsl(0,0%,60%); background-color: hsl(0, 0%, 20%);
color: hsl(0, 0%, 60%);
} }
.root-dark-mode input:not([type=file]), .root-dark-mode textarea { .root-dark-mode input:not([type='file']),
background-color: hsl(0,0%,30%); .root-dark-mode textarea {
color: var(--foreground-dark); background-color: hsl(0, 0%, 30%);
color: var(--foreground-dark);
} }
.root-dark-mode input:not([type=file])::placeholder { .root-dark-mode input:not([type='file'])::placeholder {
color: var(--foreground-dark); color: var(--foreground-dark);
} }

2
src/index.js

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import './index.css'; import './index.css';
import './fonts_7/icomoon.css' import './fonts_7/icomoon.css';
import App from './App'; import App from './App';
//import {elevate} from './infrastructure/elevator'; //import {elevate} from './infrastructure/elevator';
import registerServiceWorker from './registerServiceWorker'; import registerServiceWorker from './registerServiceWorker';

120
src/infrastructure/functions.js

@ -1,75 +1,72 @@
export function get_json(res) { export function get_json(res) {
if(!res.ok) { if (!res.ok) {
return ( return res.text().then((t) => {
res.text().then((t) => { console.log('error:', res);
console.log('error:', res); t = t.length < 100 ? t : '';
t = t.length < 100 ? t : ''; throw Error(`${res.status} ${res.statusText} ${t}`);
throw Error(`${res.status} ${res.statusText} ${t}`); });
}) }
); return res.text().then((t) => {
try {
return JSON.parse(t);
} catch (e) {
console.error('json parse error');
console.trace(e);
console.log(t);
throw new SyntaxError('JSON Parse Error ' + t.substr(0, 50));
} }
return ( });
res
.text()
.then((t)=>{
try {
return JSON.parse(t);
} catch(e) {
console.error('json parse error');
console.trace(e);
console.log(t);
throw new SyntaxError('JSON Parse Error '+t.substr(0,50));
}
})
);
} }
export function listen_darkmode(override) { // override: true/false/undefined export function listen_darkmode(override) {
function update_color_scheme() { // override: true/false/undefined
if(override===undefined ? window.matchMedia('(prefers-color-scheme: dark)').matches : override) function update_color_scheme() {
document.body.classList.add('root-dark-mode'); if (
else override === undefined
document.body.classList.remove('root-dark-mode'); ? window.matchMedia('(prefers-color-scheme: dark)').matches
} : override
)
document.body.classList.add('root-dark-mode');
else document.body.classList.remove('root-dark-mode');
}
update_color_scheme();
window.matchMedia('(prefers-color-scheme: dark)').addListener(() => {
update_color_scheme(); update_color_scheme();
window.matchMedia('(prefers-color-scheme: dark)').addListener(()=>{ });
update_color_scheme();
});
} }
const NAMES = [ const NAMES = [
'Alice', 'Alice',
'Bob', 'Bob',
'Carol', 'Carol',
'Dave', 'Dave',
'Eve', 'Eve',
'Francis', 'Francis',
'Grace', 'Grace',
'Hans', 'Hans',
'Isabella', 'Isabella',
'Jason', 'Jason',
'Kate', 'Kate',
'Louis', 'Louis',
'Margaret', 'Margaret',
'Nathan', 'Nathan',
'Olivia', 'Olivia',
'Paul', 'Paul',
'Queen', 'Queen',
'Richard', 'Richard',
'Susan', 'Susan',
'Thomas', 'Thomas',
'Uma', 'Uma',
'Vivian', 'Vivian',
'Winnie', 'Winnie',
'Xander', 'Xander',
'Yasmine', 'Yasmine',
'Zach' 'Zach',
] ];
export function gen_name(name_id) { export function gen_name(name_id) {
if (name_id == 0) if (name_id == 0) return '洞主';
return '洞主';
let r = name_id; let r = name_id;
let name = ''; let name = '';
@ -81,4 +78,3 @@ export function gen_name(name_id) {
return name.substr(1); return name.substr(1);
} }

35
src/infrastructure/global.css

@ -1,37 +1,40 @@
:root { :root {
--foreground-dark: hsl(0,0%,93%); --foreground-dark: hsl(0, 0%, 93%);
} }
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
overflow-x: hidden; overflow-x: hidden;
text-size-adjust: 100%; text-size-adjust: 100%;
} }
body, textarea, pre { body,
font-family: 'Segoe UI', '微软雅黑', 'Microsoft YaHei', sans-serif; textarea,
pre {
font-family: 'Segoe UI', '微软雅黑', 'Microsoft YaHei', sans-serif;
} }
* { * {
box-sizing: border-box; box-sizing: border-box;
word-wrap: break-word; word-wrap: break-word;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
} }
p, pre { p,
margin: 0; pre {
margin: 0;
} }
a { a {
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
} }
pre { pre {
white-space: pre-line; white-space: pre-line;
} }
code { code {
font-family: Consolas, Courier, monospace; font-family: Consolas, Courier, monospace;
} }

366
src/infrastructure/widgets.css

@ -1,312 +1,322 @@
.centered-line { .centered-line {
overflow: hidden; overflow: hidden;
text-align: center; text-align: center;
} }
.centered-line::before, .centered-line::before,
.centered-line::after { .centered-line::after {
background-color: #000; background-color: #000;
content: ""; content: '';
display: inline-block; display: inline-block;
height: 1px; height: 1px;
position: relative; position: relative;
vertical-align: middle; vertical-align: middle;
width: 50%; width: 50%;
} }
.root-dark-mode .centered-line { .root-dark-mode .centered-line {
color: var(--foreground-dark); color: var(--foreground-dark);
} }
.root-dark-mode .centered-line::before, .root-dark-mode .centered-line::after { .root-dark-mode .centered-line::before,
background-color: var(--foreground-dark); .root-dark-mode .centered-line::after {
background-color: var(--foreground-dark);
} }
.centered-line::before { .centered-line::before {
right: 1em; right: 1em;
margin-left: -50%; margin-left: -50%;
} }
.centered-line::after { .centered-line::after {
left: 1em; left: 1em;
margin-right: -50%; margin-right: -50%;
} }
.title-line { .title-line {
color: #fff; color: #fff;
margin-top: 1em; margin-top: 1em;
} }
.title-line::before, .title-line::before,
.title-line::after { .title-line::after {
background-color: #fff; background-color: #fff;
box-shadow: 0 1px 1px #000; box-shadow: 0 1px 1px #000;
} }
.root-dark-mode .title-line { .root-dark-mode .title-line {
color: var(--foreground-dark); color: var(--foreground-dark);
} }
.root-dark-mode .title-line::before, .root-dark-mode .title-line::after { .root-dark-mode .title-line::before,
background-color: var(--foreground-dark); .root-dark-mode .title-line::after {
background-color: var(--foreground-dark);
} }
.app-switcher { .app-switcher {
display: flex; display: flex;
height: 2em; height: 2em;
text-align: center; text-align: center;
margin: 0 .1em; margin: 0 0.1em;
user-select: none; user-select: none;
} }
.app-switcher-desc { .app-switcher-desc {
margin: 0 .5em; margin: 0 0.5em;
flex: 1 1 0; flex: 1 1 0;
opacity: .5; opacity: 0.5;
height: 2em; height: 2em;
line-height: 2rem; line-height: 2rem;
font-size: .8em; font-size: 0.8em;
} }
.root-dark-mode .app-switcher-desc { .root-dark-mode .app-switcher-desc {
color: var(--foreground-dark); color: var(--foreground-dark);
} }
@media screen and (max-width: 570px) { @media screen and (max-width: 570px) {
.app-switcher-desc { .app-switcher-desc {
flex: 1 1 0; flex: 1 1 0;
display: none; display: none;
} }
.app-switcher-item { .app-switcher-item {
flex: 1 1 0 !important; flex: 1 1 0 !important;
padding: 0 !important; padding: 0 !important;
} }
.app-switcher-dropdown-title { .app-switcher-dropdown-title {
padding-left: 0 !important; padding-left: 0 !important;
padding-right: 0 !important; padding-right: 0 !important;
text-align: center !important; text-align: center !important;
} }
.app-switcher-dropdown-item { .app-switcher-dropdown-item {
margin-left: -2em !important; margin-left: -2em !important;
margin-right: 0 !important; margin-right: 0 !important;
} }
} }
.app-switcher a:hover { /* reset underline from /hole style */ .app-switcher a:hover {
border-bottom: unset; /* reset underline from /hole style */
margin-bottom: unset; border-bottom: unset;
margin-bottom: unset;
} }
.app-switcher-desc a { .app-switcher-desc a {
color: unset; color: unset;
} }
.app-switcher-left { .app-switcher-left {
text-align: right; text-align: right;
} }
.app-switcher-right { .app-switcher-right {
text-align: left; text-align: left;
} }
.app-switcher-item { .app-switcher-item {
flex: 0 0 auto; flex: 0 0 auto;
border-radius: 3px; border-radius: 3px;
height: 1.6em; height: 1.6em;
line-height: 1.6em; line-height: 1.6em;
margin: .2em .1em; margin: 0.2em 0.1em;
padding: 0 .45em; padding: 0 0.45em;
} }
a.app-switcher-item, .app-switcher-item a { a.app-switcher-item,
transition: unset; /* override ant design */ .app-switcher-item a {
color: black; transition: unset; /* override ant design */
color: black;
} }
.app-switcher-item img { .app-switcher-item img {
width: 1.2rem; width: 1.2rem;
height: 1.2rem; height: 1.2rem;
position: relative; position: relative;
top: .2rem; top: 0.2rem;
vertical-align: unset; /* override ant design */ vertical-align: unset; /* override ant design */
} }
.app-switcher-item span:not(:empty) { .app-switcher-item span:not(:empty) {
margin-left: .2rem; margin-left: 0.2rem;
} }
.app-switcher-logo-hover { .app-switcher-logo-hover {
margin-left: -1.2rem; margin-left: -1.2rem;
} }
.app-switcher-item:hover { .app-switcher-item:hover {
background-color: black; background-color: black;
color: white !important; color: white !important;
} }
.app-switcher-item:hover a { .app-switcher-item:hover a {
color: white !important; color: white !important;
} }
.app-switcher-item-current { .app-switcher-item-current {
background-color: rgba(0,0,0,.4); background-color: rgba(0, 0, 0, 0.4);
text-shadow: 0 0 5px rgba(0,0,0,.5); text-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
color: white !important; color: white !important;
} }
.app-switcher-item-current a { .app-switcher-item-current a {
color: white !important; color: white !important;
} }
.root-dark-mode .app-switcher-item, .root-dark-mode .app-switcher-dropdown-title a { .root-dark-mode .app-switcher-item,
color: var(--foreground-dark); .root-dark-mode .app-switcher-dropdown-title a {
color: var(--foreground-dark);
} }
.root-dark-mode .app-switcher-item:hover, .root-dark-mode .app-switcher-item-current, .root-dark-mode .app-switcher-dropdown-title:hover a { .root-dark-mode .app-switcher-item:hover,
background-color: #555; .root-dark-mode .app-switcher-item-current,
color: var(--foreground-dark); .root-dark-mode .app-switcher-dropdown-title:hover a {
background-color: #555;
color: var(--foreground-dark);
} }
.app-switcher-item:hover .app-switcher-logo-normal, .app-switcher-item-current .app-switcher-logo-normal { .app-switcher-item:hover .app-switcher-logo-normal,
opacity: 0; .app-switcher-item-current .app-switcher-logo-normal {
opacity: 0;
} }
.app-switcher-item:not(.app-switcher-item-current):not(:hover) .app-switcher-logo-hover { .app-switcher-item:not(.app-switcher-item-current):not(:hover)
opacity: 0; .app-switcher-logo-hover {
opacity: 0;
} }
.root-dark-mode .app-switcher-logo-normal { .root-dark-mode .app-switcher-logo-normal {
opacity: 0 !important; opacity: 0 !important;
} }
.root-dark-mode .app-switcher-logo-hover { .root-dark-mode .app-switcher-logo-hover {
opacity: 1 !important; opacity: 1 !important;
} }
.app-switcher-dropdown { .app-switcher-dropdown {
padding: 0; padding: 0;
text-align: left; text-align: left;
} }
.app-switcher-dropdown:not(:hover) { .app-switcher-dropdown:not(:hover) {
max-height: 1.6rem; max-height: 1.6rem;
overflow: hidden; overflow: hidden;
} }
.app-switcher-dropdown-item { .app-switcher-dropdown-item {
background-color: hsla(0,0%,35%,.9); background-color: hsla(0, 0%, 35%, 0.9);
padding: .125em .25em; padding: 0.125em 0.25em;
margin-left: -.75em; margin-left: -0.75em;
margin-right: -.75em; margin-right: -0.75em;
position: relative; position: relative;
z-index: 10; z-index: 10;
cursor: pointer; cursor: pointer;
} }
.app-switcher-dropdown-item:hover { .app-switcher-dropdown-item:hover {
background-color: rgba(0,0,0,.9); background-color: rgba(0, 0, 0, 0.9);
} }
.app-switcher-dropdown-item:nth-child(2) { .app-switcher-dropdown-item:nth-child(2) {
border-top-left-radius: 3px; border-top-left-radius: 3px;
border-top-right-radius: 3px; border-top-right-radius: 3px;
} }
.app-switcher-dropdown-item:last-child { .app-switcher-dropdown-item:last-child {
border-bottom-left-radius: 3px; border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px; border-bottom-right-radius: 3px;
} }
.app-switcher-dropdown-title { .app-switcher-dropdown-title {
padding-bottom: .2em; padding-bottom: 0.2em;
padding-left: .5em; padding-left: 0.5em;
padding-right: .25em; padding-right: 0.25em;
} }
.app-switcher-dropdown-title a { .app-switcher-dropdown-title a {
cursor: unset; cursor: unset;
} }
.thuhole-login-popup { .thuhole-login-popup {
font-size: 1rem; font-size: 1rem;
background-color: #f7f7f7; background-color: #f7f7f7;
color: black; color: black;
position: fixed; position: fixed;
left: 50%; left: 50%;
top: 50%; top: 50%;
width: 320px; width: 320px;
z-index: 114515; z-index: 114515;
transform: translateX(-50%) translateY(-50%); transform: translateX(-50%) translateY(-50%);
border-radius: 5px; border-radius: 5px;
} }
.thuhole-login-popup a { .thuhole-login-popup a {
color: #00c; color: #00c;
} }
.thuhole-login-popup p { .thuhole-login-popup p {
margin: 1.25em 0; margin: 1.25em 0;
text-align: center; text-align: center;
} }
.thuhole-login-popup-info p { .thuhole-login-popup-info p {
margin: .25em 1em; margin: 0.25em 1em;
text-align: left; text-align: left;
} }
.thuhole-login-popup-info ul { .thuhole-login-popup-info ul {
margin: .75em 1em; margin: 0.75em 1em;
text-align: left; text-align: left;
font-size: 75%; font-size: 75%;
} }
/* override ant design */ /* override ant design */
.thuhole-login-popup input, .thuhole-login-popup button { .thuhole-login-popup input,
font-size: .85em; .thuhole-login-popup button {
vertical-align: middle; font-size: 0.85em;
} vertical-align: middle;
.thuhole-login-popup input:not([type="checkbox"]) { }
width: 8rem; .thuhole-login-popup input:not([type='checkbox']) {
border-radius: 5px; width: 8rem;
border: 1px solid black; border-radius: 5px;
outline: none; border: 1px solid black;
margin: 0; outline: none;
padding: 0 .5em; margin: 0;
line-height: 2em; padding: 0 0.5em;
line-height: 2em;
} }
.thuhole-login-popup button { .thuhole-login-popup button {
min-width: 6rem; min-width: 6rem;
color: black; color: black;
background-color: rgba(235,235,235,.5); background-color: rgba(235, 235, 235, 0.5);
border-radius: 5px; border-radius: 5px;
text-align: center; text-align: center;
border: 1px solid black; border: 1px solid black;
line-height: 2em; line-height: 2em;
margin: 0 .5rem; margin: 0 0.5rem;
} }
.thuhole-login-popup button:hover { .thuhole-login-popup button:hover {
background-color: rgba(255,255,255,.7); background-color: rgba(255, 255, 255, 0.7);
} }
.thuhole-login-popup button:disabled { .thuhole-login-popup button:disabled {
background-color: rgba(128,128,128,.5); background-color: rgba(128, 128, 128, 0.5);
} }
.thuhole-login-type { .thuhole-login-type {
display: inline-block; display: inline-block;
width: 6rem; width: 6rem;
margin: 0 .5rem; margin: 0 0.5rem;
} }
.thuhole-login-popup-shadow { .thuhole-login-popup-shadow {
opacity: .5; opacity: 0.5;
background-color: black; background-color: black;
position: fixed; position: fixed;
left: 0; left: 0;
top: 0; top: 0;
height: 100%; height: 100%;
width: 100%; width: 100%;
z-index: 114514; z-index: 114514;
} }
.thuhole-login-popup label.perm-item { .thuhole-login-popup label.perm-item {
font-size: .8em; font-size: 0.8em;
vertical-align: .1rem; vertical-align: 0.1rem;
margin-left: .5rem; margin-left: 0.5rem;
} }
.aux-margin { .aux-margin {
width: calc(100% - 2 * 50px); width: calc(100% - 2 * 50px);
margin: 0 50px; margin: 0 50px;
} }
@media screen and (max-width: 1300px) { @media screen and (max-width: 1300px) {
.aux-margin { .aux-margin {
width: calc(100% - 2 * 10px); width: calc(100% - 2 * 10px);
margin: 0 10px; margin: 0 10px;
} }
} }
.title { .title {
font-size: 1.5em; font-size: 1.5em;
height: 4rem; height: 4rem;
padding-top: 1rem; padding-top: 1rem;
text-align: center; text-align: center;
} }
.time-str { .time-str {
color: #999999; color: #999999;
} }
a.button { a.button {

274
src/infrastructure/widgets.js

@ -1,4 +1,4 @@
import React, {Component, PureComponent} from 'react'; import React, { Component, PureComponent } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import TimeAgo from 'react-timeago'; import TimeAgo from 'react-timeago';
@ -8,56 +8,62 @@ import buildFormatter from 'react-timeago/lib/formatters/buildFormatter';
import './global.css'; import './global.css';
import './widgets.css'; import './widgets.css';
import {get_json, API_VERSION_PARAM} from './functions'; import { get_json, API_VERSION_PARAM } from './functions';
function pad2(x) { function pad2(x) {
return x<10 ? '0'+x : ''+x; return x < 10 ? '0' + x : '' + x;
} }
export function format_time(time) { export function format_time(time) {
return `${time.getMonth()+1}-${pad2(time.getDate())} ${time.getHours()}:${pad2(time.getMinutes())}:${pad2(time.getSeconds())}`; return `${time.getMonth() + 1}-${pad2(
time.getDate(),
)} ${time.getHours()}:${pad2(time.getMinutes())}:${pad2(time.getSeconds())}`;
} }
const chinese_format=buildFormatter(chineseStrings); const chinese_format = buildFormatter(chineseStrings);
export function Time(props) { export function Time(props) {
const time=new Date(props.stamp*1000); const time = new Date(props.stamp * 1000);
return ( return (
<span className={"time-str"}> <span className={'time-str'}>
<TimeAgo date={time} formatter={chinese_format} title={time.toLocaleString('zh-CN', { <TimeAgo
timeZone: 'Asia/Shanghai', date={time}
hour12: false, formatter={chinese_format}
})} /> title={time.toLocaleString('zh-CN', {
&nbsp; timeZone: 'Asia/Shanghai',
{!props.short ? format_time(time) : null} hour12: false,
</span> })}
); />
&nbsp;
{!props.short ? format_time(time) : null}
</span>
);
} }
export function TitleLine(props) { export function TitleLine(props) {
return ( return (
<p className="centered-line title-line aux-margin"> <p className="centered-line title-line aux-margin">
<span className="black-outline">{props.text}</span> <span className="black-outline">{props.text}</span>
</p> </p>
) );
} }
export function GlobalTitle(props) { export function GlobalTitle(props) {
return ( return (
<div className="aux-margin"> <div className="aux-margin">
<div className="title"> <div className="title">
<p className="centered-line">{props.text}</p> <p className="centered-line">{props.text}</p>
</div> </div>
</div> </div>
); );
} }
class LoginPopupSelf extends Component { class LoginPopupSelf extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state={ this.state = {
loading_status: 'idle', loading_status: 'idle',
} };
this.input_token_ref=React.createRef(); this.input_token_ref = React.createRef();
}; }
setThuhole(e, tar, ref) { setThuhole(e, tar, ref) {
console.log(tar); console.log(tar);
@ -67,105 +73,111 @@ class LoginPopupSelf extends Component {
alert('T大树洞已经没有啦😭'); alert('T大树洞已经没有啦😭');
} }
render() { render() {
return (
return ( <div>
<div> <div className="thuhole-login-popup-shadow" />
<div className="thuhole-login-popup-shadow" /> <div className="thuhole-login-popup">
<div className="thuhole-login-popup"> <p>
<p> <b>通过第三方验证登陆新T树洞</b>
<b>通过第三方验证登陆新T树洞</b> </p>
</p> <p>
<p> <a href="/_login?p=cs" target="_blank">
<a href="/_login?p=cs" target="_blank"> <span className="icon icon-login" />
<span className="icon icon-login" /> &nbsp;闭社
&nbsp;闭社 </a>
</a> </p>
</p> <p>
<p> <input ref={this.input_token_ref} placeholder="T大树洞Token" />
<input ref={this.input_token_ref} placeholder="T大树洞Token" /> <br />
<br/> <a
<a href="/_login?p=thuhole" target="_blank" href="/_login?p=thuhole"
onClick={(e) =>{this.setThuhole(e, e.target, this.input_token_ref)}} target="_blank"
> onClick={(e) => {
<span className="icon icon-login" /> this.setThuhole(e, e.target, this.input_token_ref);
&nbsp;T大树洞 }}
</a> >
</p> <span className="icon icon-login" />
<p> &nbsp;T大树洞
<small>前往Telegram群查询15分钟临时token</small> </a>
<br/> </p>
<a href="//t.me/THUChatBot" target="_blank" <p>
> <small>前往Telegram群查询15分钟临时token</small>
<span className="icon icon-login" /> <br />
&nbsp;清华大水群 <a href="//t.me/THUChatBot" target="_blank">
</a> <span className="icon icon-login" />
</p> &nbsp;清华大水群
<p> </a>
<button type="button" disabled </p>
> <p>
<span className="icon icon-login" /> <button type="button" disabled>
&nbsp;未名bbs <span className="icon icon-login" />
</button> &nbsp;未名bbs
</p> </button>
<p> </p>
<button type="button" disabled <p>
> <button type="button" disabled>
<span className="icon icon-login" /> <span className="icon icon-login" />
&nbsp;清华统一身份认证 &nbsp;清华统一身份认证
</button> </button>
</p> </p>
<hr /> <hr />
<p> <p>
<button onClick={this.props.on_close}> <button onClick={this.props.on_close}>取消</button>
取消 </p>
</button> <hr />
</p> <div className="thuhole-login-popup-info">
<hr/ > <p>提醒:</p>
<div className="thuhole-login-popup-info"> <ul>
<p>提醒: <li>
</p> {' '}
<ul> 无论采用哪种方式注册你后台记录的用户名都是本质实名的除临时token因为闭社/T大树洞的管理员可以根据你的闭社id/树洞评论区代号查到邮箱但是这不影响新T树洞的安全性新T树洞的匿名性来自隔离用户名与发布的内容而非试图隔离用户名与真实身份
<li> 无论采用哪种方式注册你后台记录的用户名都是本质实名的除临时token因为闭社/T大树洞的管理员可以根据你的闭社id/树洞评论区代号查到邮箱但是这不影响新T树洞的安全性新T树洞的匿名性来自隔离用户名与发布的内容而非试图隔离用户名与真实身份</li> </li>
<li> 由于T大树洞仍未提供授权接口使用T大树洞方式登陆需要用你的token在特定洞发布一段随机内容以确定身份这是否违反用户条例由T大树洞管理员决定需自行承担相关风险完成登陆后建议立即重置T大树洞token </li> <li>
<li> 目前一个人可能有两个帐号</li> {' '}
</ul> 由于T大树洞仍未提供授权接口使用T大树洞方式登陆需要用你的token在特定洞发布一段随机内容以确定身份这是否违反用户条例由T大树洞管理员决定需自行承担相关风险完成登陆后建议立即重置T大树洞token{' '}
</div> </li>
</div> <li> 目前一个人可能有两个帐号</li>
</div> </ul>
); </div>
} </div>
</div>
);
}
} }
export class LoginPopup extends Component { export class LoginPopup extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state={ this.state = {
popup_show: false, popup_show: false,
}; };
this.on_popup_bound=this.on_popup.bind(this); this.on_popup_bound = this.on_popup.bind(this);
this.on_close_bound=this.on_close.bind(this); this.on_close_bound = this.on_close.bind(this);
} }
on_popup() { on_popup() {
this.setState({ this.setState({
popup_show: true, popup_show: true,
}); });
} }
on_close() { on_close() {
this.setState({ this.setState({
popup_show: false, popup_show: false,
}); });
} }
render() { render() {
return ( return (
<> <>
{this.props.children(this.on_popup_bound)} {this.props.children(this.on_popup_bound)}
{this.state.popup_show && {this.state.popup_show && (
<LoginPopupSelf token_callback={this.props.token_callback} on_close={this.on_close_bound} /> <LoginPopupSelf
} token_callback={this.props.token_callback}
</> on_close={this.on_close_bound}
); />
} )}
</>
);
}
} }

Loading…
Cancel
Save