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. 2
      src/Markdown.css
  10. 1
      src/Message.css
  11. 48
      src/Message.js
  12. 18
      src/PressureHelper.css
  13. 232
      src/Sidebar.css
  14. 100
      src/Title.css
  15. 12
      src/Title.js
  16. 111
      src/UserAction.css
  17. 119
      src/UserAction.js
  18. 173
      src/flows_api.js
  19. 59
      src/fonts_7/icomoon.css
  20. 105
      src/index.css
  21. 2
      src/index.js
  22. 120
      src/infrastructure/functions.js
  23. 37
      src/infrastructure/global.css
  24. 366
      src/infrastructure/widgets.css
  25. 276
      src/infrastructure/widgets.js

14
src/App.js

@ -100,9 +100,12 @@ class App extends Component {
}
componentDidMount() {
if (window.location.protocol === 'http:' &&
window.location.hostname !== '127.0.0.1' && !window.location.hostname.endsWith('localhost')) {
window.location.protocol = 'https:'; // 因为CDN的原因先在前端做下https跳转
if (
window.location.protocol === 'http:' &&
window.location.hostname !== '127.0.0.1' &&
!window.location.hostname.endsWith('localhost')
) {
window.location.protocol = 'https:'; // 因为CDN的原因先在前端做下https跳转
return;
}
let arg = window.location.search;
@ -110,7 +113,10 @@ class App extends Component {
if (arg.startsWith('?token=')) {
let token = arg.substr(7);
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;
token = `${token.split('_')[0]}_${tmp_token_suf}`;
}

4
src/Attention.js

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

63
src/Common.css

@ -1,65 +1,64 @@
.clickable {
cursor: pointer;
cursor: pointer;
}
.bg-img {
position: fixed;
z-index: -1;
top: 0;
left: 0;
width: 100%;
height: 100%;
position: fixed;
z-index: -1;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.root-dark-mode .bg-img {
opacity: .65;
opacity: 0.65;
}
.black-outline {
text-shadow: /* also change .flow-item-row-with-prompt:hover::before */
-1px -1px 0 rgba(0,0,0,.6),
0 -1px 0 rgba(0,0,0,.6),
1px -1px 0 rgba(0,0,0,.6),
-1px 1px 0 rgba(0,0,0,.6),
0 1px 0 rgba(0,0,0,.6),
1px 1px 0 rgba(0,0,0,.6);
text-shadow: /* also change .flow-item-row-with-prompt:hover::before */ -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, 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, 0.6);
}
.search-query-highlight {
border-bottom: 1px solid black;
font-weight: bold;
border-bottom: 1px solid black;
font-weight: bold;
}
.root-dark-mode .search-query-highlight {
border-bottom: 1px solid white;
border-bottom: 1px solid white;
}
.url-pid-link {
opacity: .6;
opacity: 0.6;
}
:root {
--coloredspan-bgcolor-light: white;
--coloredspan-bgcolor-dark: black;
--coloredspan-bgcolor-light: white;
--coloredspan-bgcolor-dark: black;
}
.colored-span {
background-color: var(--coloredspan-bgcolor-light);
background-color: var(--coloredspan-bgcolor-light);
}
.root-dark-mode .colored-span {
background-color: var(--coloredspan-bgcolor-dark);
background-color: var(--coloredspan-bgcolor-dark);
}
.icon+label {
font-size: .9em;
vertical-align: .05em;
cursor: inherit;
padding: 0 .1rem;
margin-left: .15rem;
.icon + label {
font-size: 0.9em;
vertical-align: 0.05em;
cursor: inherit;
padding: 0 0.1rem;
margin-left: 0.15rem;
}
.ext-img, .ext-video {
.ext-img,
.ext-video {
max-width: 100%;
max-height: 2000px;
display: block;
@ -71,7 +70,7 @@
}
blockquote {
margin-left: 8px;
margin-left: 8px;
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],
['pid', PID_RE],
['nickname', NICKNAME_RE],
['tag', TAG_RE]
['tag', TAG_RE],
];
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([
'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);
@ -170,7 +175,7 @@ export class HighlightedMarkdown extends Component {
<span className="icon icon-new-tab" />
</a>
{is_video(p) && (
<video className="ext-video" src={p} controls loop/>
<video className="ext-video" src={p} controls loop />
)}
</>
) : rule === 'pid' ? (
@ -190,11 +195,7 @@ export class HighlightedMarkdown extends Component {
) : rule === 'search' ? (
<span className="search-query-highlight">{p}</span>
) : rule === 'tag' ? (
<a
href={p}
>
{p}
</a>
<a href={p}>{p}</a>
) : (
p
)}

34
src/Config.css

@ -1,31 +1,31 @@
.config-ui-header {
text-align: center;
top: 1em;
position: sticky;
text-align: center;
top: 1em;
position: sticky;
}
.config-description {
font-size: 0.75em;
font-size: 0.75em;
}
.config-select {
height: 2em;
height: 2em;
}
.config-textarea {
margin-top: 0.5em;
width: 100%;
max-width: 100%;
min-width: 100%;
height: 7em;
min-height: 2em;
margin-top: 0.5em;
width: 100%;
max-width: 100%;
min-width: 100%;
height: 7em;
min-height: 2em;
}
.bg-preview {
height: 18em;
width: 32em;
max-height: 60vh;
max-width: 100%;
margin: .5em auto 1em;
box-shadow: 0 1px 5px rgba(0,0,0,.4);
height: 18em;
width: 32em;
max-height: 60vh;
max-width: 100%;
margin: 0.5em auto 1em;
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 = {
'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/eriri.jpg':
@ -25,7 +24,7 @@ const BUILTIN_IMGS = {
const DEFAULT_CONFIG = {
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',
pressure: false,
easter_egg: true,
@ -33,7 +32,7 @@ const DEFAULT_CONFIG = {
no_c_post: false,
by_c: false,
block_words_v2: ['#天火', '#桃花石'],
whitelist_cw: []
whitelist_cw: [],
};
export function load_config() {
@ -53,7 +52,9 @@ export function load_config() {
});
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);
@ -377,7 +378,9 @@ export class ConfigUI extends PureComponent {
id="whitelist_cw"
callback={this.save_changes_bound}
name="展开指定的折叠警告"
description={'完全匹配的树洞不会被折叠,每行一个豁免词,也可使用一个星号("*")表示豁免所有'}
description={
'完全匹配的树洞不会被折叠,每行一个豁免词,也可使用一个星号("*")表示豁免所有'
}
display={(array) => array.join('\n')}
sift={(array) => array.filter((v) => v)}
parse={(string) => string.split('\n')}

353
src/Flows.css

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

585
src/Flows.js

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

2
src/Markdown.css

@ -1,3 +1,3 @@
.hljs {
white-space: pre-wrap;
}
}

1
src/Message.css

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

48
src/Message.js

@ -11,7 +11,7 @@ export class MessageViewer extends PureComponent {
loading_status: 'idle',
msg: [],
};
this.input_suf_ref=React.createRef();
this.input_suf_ref = React.createRef();
}
componentDidMount() {
@ -25,12 +25,9 @@ export class MessageViewer extends PureComponent {
loading_status: 'loading',
},
() => {
fetch(
API_BASE + '/systemlog',
{
headers: {'User-Token': this.props.token},
}
)
fetch(API_BASE + '/systemlog', {
headers: { 'User-Token': this.props.token },
})
.then(get_json)
.then((json) => {
this.setState({
@ -53,10 +50,9 @@ export class MessageViewer extends PureComponent {
}
do_set_token() {
if (this.state.loading_status==='loading')
return;
if (this.state.loading_status === 'loading') return;
if (!this.input_suf_ref.current.value) {
alert("不建议后缀为空");
alert('不建议后缀为空');
return;
}
let tt = this.state.tmp_token + '_' + this.input_suf_ref.current.value;
@ -65,7 +61,7 @@ export class MessageViewer extends PureComponent {
alert('已登录为临时用户,过期后需注销重新登陆');
window.location.reload();
}
render() {
if (this.state.loading_status === 'loading')
return <p className="box box-tip">加载中</p>;
@ -84,20 +80,29 @@ export class MessageViewer extends PureComponent {
else if (this.state.loading_status === 'done')
return (
<>
<br/>
<br />
<p>
最近一次重置 <Time stamp={this.state.start_time} short={false} />
</p>
<p>
随机盐 <b>{this.state.salt}</b>
</p>
<br/>
<div>
<p>15分钟临时token:</p>
<br />
<div>
<p>15分钟临时token:</p>
<div className="input-prepend">{this.state.tmp_token}_ </div>
<input type="text" 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()}>
<input
type="text"
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>
</div>
@ -105,8 +110,7 @@ export class MessageViewer extends PureComponent {
<div className="box" key={msg.type + msg.timestamp}>
<div className="box-header">
<Time stamp={msg.timestamp} short={false} />
&nbsp;
&nbsp;
&nbsp; &nbsp;
<b>{msg.type}</b>
&nbsp;
<span className="box-header-name">{msg.user}</span>
@ -115,9 +119,9 @@ export class MessageViewer extends PureComponent {
<pre>{msg.detail}</pre>
</div>
</div>
))}
))}
</>
)
);
else return null;
}
}

18
src/PressureHelper.css

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

232
src/Sidebar.css

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

100
src/Title.css

@ -1,88 +1,88 @@
.title-bar {
z-index: 10;
position: sticky;
top: -4em;
left: 0;
width: 100%;
height: 7em;
background-color: rgba(255,255,255,.8);
box-shadow: 0 0 25px rgba(0,0,0,.4);
margin-bottom: 1em;
backdrop-filter: blur(5px);
z-index: 10;
position: sticky;
top: -4em;
left: 0;
width: 100%;
height: 7em;
background-color: rgba(255, 255, 255, 0.8);
box-shadow: 0 0 25px rgba(0, 0, 0, 0.4);
margin-bottom: 1em;
backdrop-filter: blur(5px);
}
.root-dark-mode .title-bar {
background-color: hsla(0,0%,12%,.8);
box-shadow: 0 0 5px rgba(255,255,255,.1);
background-color: hsla(0, 0%, 12%, 0.8);
box-shadow: 0 0 5px rgba(255, 255, 255, 0.1);
}
.control-bar {
display: flex;
margin-top: .5em;
line-height: 2em;
display: flex;
margin-top: 0.5em;
line-height: 2em;
}
.control-btn {
flex: 0 0 4.5em;
text-align: center;
color: black;
border-radius: 5px;
flex: 0 0 4.5em;
text-align: center;
color: black;
border-radius: 5px;
}
.control-btn:hover {
background-color: #666666;
color: white;
background-color: #666666;
color: white;
}
.control-btn-label {
margin-left: .25rem;
font-size: .9em;
vertical-align: .05em;
margin-left: 0.25rem;
font-size: 0.9em;
vertical-align: 0.05em;
}
@media screen and (max-width: 900px) {
.control-btn {
flex: 0 0 2.5em;
}
.control-btn-label {
display: none;
}
.control-search {
padding: 0 .5em;
}
.control-btn {
flex: 0 0 2.5em;
}
.control-btn-label {
display: none;
}
.control-search {
padding: 0 0.5em;
}
}
.root-dark-mode .control-btn {
color: var(--foreground-dark);
opacity: .9;
color: var(--foreground-dark);
opacity: 0.9;
}
.root-dark-mode .control-btn:hover {
color: var(--foreground-dark);
opacity: 1;
color: var(--foreground-dark);
opacity: 1;
}
.control-search {
flex: auto;
color: black;
background-color: rgba(255,255,255,.3) !important;
margin: 0 .5em;
min-width: 8em;
flex: auto;
color: black;
background-color: rgba(255, 255, 255, 0.3) !important;
margin: 0 0.5em;
min-width: 8em;
}
.control-search:focus {
background-color: white !important;
background-color: white !important;
}
.root-dark-mode .control-search {
background-color: hsla(0,0%,35%,.6) !important;
color: var(--foreground-dark);
background-color: hsla(0, 0%, 35%, 0.6) !important;
color: var(--foreground-dark);
}
.root-dark-mode .control-search:focus {
background-color: hsl(0,0%,80%) !important;
color: black !important;
background-color: hsl(0, 0%, 80%) !important;
color: black !important;
}
.list-menu {
text-align: center;
text-align: center;
}
.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);
if(text && text[0]!='#') {
if (text && text[0] != '#') {
console.log('search', text);
this.setState(
{
@ -51,7 +52,7 @@ class ControlBar extends PureComponent {
);
}
},
false
false,
);
}
@ -136,7 +137,10 @@ class ControlBar extends PureComponent {
className="control-search"
value={this.state.search_text}
placeholder={
this.props.mode === 'attention' ? '在关注列表中搜索' : '关键词 / tag / #树洞号'}
this.props.mode === 'attention'
? '在关注列表中搜索'
: '关键词 / tag / #树洞号'
}
onChange={this.on_change_bound}
onKeyPress={this.on_keypress_bound}
/>

111
src/UserAction.css

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

119
src/UserAction.js

@ -12,11 +12,8 @@ import { ConfigUI } from './Config';
import fixOrientation from 'fix-orientation';
import copy from 'copy-to-clipboard';
import { cache } from './cache';
import {
API,
get_json,
} from './flows_api';
import { save_attentions } from './Attention'
import { API, get_json } from './flows_api';
import { save_attentions } from './Attention';
import './UserAction.css';
@ -57,7 +54,9 @@ export function InfoSidebar(props) {
<span className="icon icon-textfile" />
<label>树洞规范试行</label>
</a>
<p><em>强烈建议开始使用前先看一遍所有设置选项</em></p>
<p>
<em>强烈建议开始使用前先看一遍所有设置选项</em>
</p>
</div>
<div className="box help-desc-box">
<p>
@ -87,15 +86,18 @@ export function InfoSidebar(props) {
</div>
<div className="box help-desc-box">
<p>意见反馈请加tag #意见反馈 或到github后端的issue区</p>
<p>新T树洞强烈期待有其他更多树洞的出现一起分布式互联构建清华树洞族详情见 关于 中的描述</p>
<p>联系我们<a href={"mailto:"+EMAIL}>{EMAIL}</a> </p>
<p>
新T树洞强烈期待有其他更多树洞的出现一起分布式互联构建清华树洞族详情见
关于 中的描述
</p>
<p>
联系我们<a href={'mailto:' + EMAIL}>{EMAIL}</a>
</p>
</div>
<div className="box help-desc-box">
<p>
新T树洞 网页版 by @hole_thu基于
<a href="https://www.gnu.org/licenses/agpl-3.0.html"
target="_blank"
>
<a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank">
AGPLv3
</a>
协议在{' '}
@ -121,7 +123,8 @@ export function InfoSidebar(props) {
>
T大树洞网页版 by @thuhole
</a>
<a href="https://reactjs.org/" target="_blank" rel="noopener">
{' '}
<a href="https://reactjs.org/" target="_blank" rel="noopener">
React
</a>
@ -139,8 +142,8 @@ export class LoginForm extends Component {
constructor(props) {
super(props);
this.state = {
'custom_title': window.TITLE || ''
}
custom_title: window.TITLE || '',
};
}
update_title(title, token) {
@ -151,10 +154,11 @@ export class LoginForm extends Component {
API.set_title(title, token)
.then((json) => {
if (json.code === 0) {
window.TITLE = title
window.TITLE = title;
alert('专属头衔设置成功');
}
}).catch(err => alert("设置头衔出错了:\n"+ err));
})
.catch((err) => alert('设置头衔出错了:\n' + err));
}
copy_token(token) {
@ -203,22 +207,27 @@ export class LoginForm extends Component {
复制 User Token
</a>
<br />
User Token仅用于开发bot切勿告知他人若怀疑被盗号请刷新Token(刷新功能即将上线)
User
Token仅用于开发bot切勿告知他人若怀疑被盗号请刷新Token(刷新功能即将上线)
</p>
<p>
专属头衔
专属头衔
<input
value={this.state.custom_title}
value={this.state.custom_title}
onChange={(e) => {
this.setState({ custom_title: e.target.value})
this.setState({ custom_title: e.target.value });
}}
maxLength={10}
/>
<button
className="update-title-btn"
type="button"
onClick={(e) => {this.update_title(this.state.custom_title, token.value)}}
>提交</button>
onClick={(e) => {
this.update_title(this.state.custom_title, token.value);
}}
>
提交
</button>
<br />
设置专属头衔后可在发言时选择使用重置后需重新设置临时用户如需保持头衔请使用相同后缀
</p>
@ -319,17 +328,14 @@ export class ReplyForm extends Component {
text: text,
use_title: use_title ? '1' : '',
});
fetch(
API_BASE + '/docomment',
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': this.props.token,
},
body: data,
fetch(API_BASE + '/docomment', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': this.props.token,
},
)
body: data,
})
.then(get_json)
.then((json) => {
if (json.code !== 0) {
@ -338,7 +344,7 @@ export class ReplyForm extends Component {
let saved_attentions = window.saved_attentions;
if (!saved_attentions.includes(pid)) {
saved_attentions.unshift(pid)
saved_attentions.unshift(pid);
window.saved_attentions = saved_attentions;
save_attentions();
}
@ -393,7 +399,7 @@ export class ReplyForm extends Component {
<button
type="button"
onClick={() => {
this.toggle_preview();
this.toggle_preview();
}}
>
{this.state.preview ? (
@ -418,8 +424,8 @@ export class ReplyForm extends Component {
type="checkbox"
onChange={this.on_use_title_change_bound}
checked={this.state.use_title}
/>
{' '}使用头衔
/>{' '}
使用头衔
</label>
)}
</div>
@ -490,18 +496,24 @@ export class PostForm extends Component {
}
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({
cw: cw,
text: text,
allow_search: allow_search ? '1' : '',
use_title: use_title ? '1' : '',
type: 'text'
type: 'text',
});
if (has_poll) {
poll_options.forEach((opt) => {
if (opt)
data.append('poll_options', opt);
if (opt) data.append('poll_options', opt);
});
}
@ -692,15 +704,14 @@ export class PostForm extends Component {
let text = event.target.value;
poll_options[idx] = text;
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) {
poll_options.push('')
poll_options.push('');
}
this.setState({ poll_options: poll_options });
}
render() {
const { has_poll, poll_options, preview, loading_status } = this.state;
return (
@ -748,8 +759,8 @@ export class PostForm extends Component {
type="checkbox"
onChange={this.on_allow_search_change_bound}
checked={this.state.allow_search}
/>
{' '}允许搜索
/>{' '}
允许搜索
</label>
{window.TITLE && (
<label>
@ -757,8 +768,8 @@ export class PostForm extends Component {
type="checkbox"
onChange={this.on_use_title_change_bound}
checked={this.state.use_title}
/>
{' '}使用头衔
/>{' '}
使用头衔
</label>
)}
</div>
@ -815,7 +826,7 @@ export class PostForm extends Component {
{has_poll && (
<div className="post-form-poll-options">
<h6>投票选项</h6>
{poll_options.map( (option, idx) => (
{poll_options.map((option, idx) => (
<input
key={idx}
type="text"
@ -827,7 +838,9 @@ export class PostForm extends Component {
))}
</div>
)}
<br /><br /><br />
<br />
<br />
<br />
<p>
<small>
请遵守
@ -839,7 +852,8 @@ export class PostForm extends Component {
</p>
<p>
<small>
插入图片请使用图片外链Markdown格式 ![](图片链接) 支持动图支持多图推荐的图床
插入图片请使用图片外链Markdown格式 ![](图片链接)
支持动图支持多图推荐的图床
<a href="https://imgchr.com/" target="_blank">
路过图床
</a>
@ -848,7 +862,10 @@ export class PostForm extends Component {
sm.ms
</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
</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 { cache } from './cache';
@ -35,14 +35,11 @@ export const parse_replies = (replies, color_picker) =>
export const API = {
load_replies: async (pid, token, color_picker, cache_version) => {
pid = parseInt(pid);
let response = await fetch(
API_BASE + '/getcomment?pid=' + pid ,
{
headers: {
'User-Token': token,
}
}
);
let response = await fetch(API_BASE + '/getcomment?pid=' + pid, {
headers: {
'User-Token': token,
},
});
let json = await handle_response(response);
// Why delete then put ??
//console.log('Put cache', json, pid, cache_version);
@ -70,17 +67,14 @@ export const API = {
let data = new URLSearchParams();
data.append('pid', pid);
data.append('switch', attention ? '1' : '0');
let response = await fetch(
API_BASE + '/attention',
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': token,
},
body: data,
let response = await fetch(API_BASE + '/attention', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': token,
},
);
body: data,
});
// Delete cache to update `attention` on next reload
cache().delete(pid);
return handle_response(response, false);
@ -90,55 +84,46 @@ export const API = {
let data = new URLSearchParams();
data.append('pid', pid);
data.append('reason', reason);
let response = await fetch(
API_BASE + '/report',
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': token,
},
body: data,
let response = await fetch(API_BASE + '/report', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': token,
},
);
body: data,
});
return handle_response(response, false);
},
block: async (type, id, token) => {
let data = new URLSearchParams([
['type', type], ['id', id]
['type', type],
['id', id],
]);
let response = await fetch(
API_BASE + '/block',
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': token,
},
body: data,
let response = await fetch(API_BASE + '/block', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': token,
},
);
body: data,
});
return handle_response(response, false);
},
del: async (type, id, note, token) => {
let data = new URLSearchParams();
data.append('type', type);
data.append('id', id);
data.append('note', note);
let response = await fetch(
API_BASE + '/delete',
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': token,
},
body: data,
let response = await fetch(API_BASE + '/delete', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': token,
},
);
body: data,
});
return handle_response(response, false);
},
@ -146,17 +131,14 @@ export const API = {
let data = new URLSearchParams();
data.append('cw', cw);
data.append('pid', id);
let response = await fetch(
API_BASE + '/editcw',
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': token,
},
body: data,
let response = await fetch(API_BASE + '/editcw', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': token,
},
);
body: data,
});
return handle_response(response, false);
},
@ -166,7 +148,7 @@ export const API = {
window.config.no_c_post ? '&no_cw' : ''
}&order_mode=${submode}`,
{
headers: {'User-Token': token},
headers: { 'User-Token': token },
},
);
return handle_response(response);
@ -174,58 +156,49 @@ export const API = {
get_search: async (page, keyword, token, submode) => {
let response = await fetch(
`${API_BASE}/search?search_mode=${submode}&page=${page}&keywords=${
encodeURIComponent(keyword)
}&pagesize=${SEARCH_PAGESIZE}`,
`${API_BASE}/search?search_mode=${submode}&page=${page}&keywords=${encodeURIComponent(
keyword,
)}&pagesize=${SEARCH_PAGESIZE}`,
{
headers: {'User-Token': token},
}
headers: { 'User-Token': token },
},
);
return handle_response(response);
},
get_single: async (pid, token) => {
let response = await fetch(
API_BASE + '/getone?pid=' + pid,
{
headers: {'User-Token': token},
}
);
let response = await fetch(API_BASE + '/getone?pid=' + pid, {
headers: { 'User-Token': token },
});
return handle_response(response);
},
get_attention: async (token) => {
let response = await fetch(
API_BASE + '/getattention',
{
headers: {'User-Token': token},
}
);
let response = await fetch(API_BASE + '/getattention', {
headers: { 'User-Token': token },
});
return handle_response(response);
},
add_vote: async (vote, pid, token) => {
let data = new URLSearchParams([
['vote', vote],
['pid', pid]
['pid', pid],
]);
let response = await fetch(
API_BASE + '/vote',
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': token,
},
body: data,
let response = await fetch(API_BASE + '/vote', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': token,
},
);
body: data,
});
return handle_response(response, true);
},
get_multi: async (pids, token) => {
let response = await fetch(
API_BASE + '/getmulti?' + pids.map(pid => `pids=${pid}`).join('&'),
API_BASE + '/getmulti?' + pids.map((pid) => `pids=${pid}`).join('&'),
{
headers: {
'User-Token': token,
@ -238,18 +211,14 @@ export const API = {
set_title: async (title, token) => {
console.log('title: ', title);
let data = new URLSearchParams([['title', title]]);
let response = await fetch(
API_BASE + '/title',
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': token,
},
body: data,
let response = await fetch(API_BASE + '/title', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Token': token,
},
);
body: data,
});
return handle_response(response, true);
},
};

59
src/fonts_7/icomoon.css

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

105
src/index.css

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

2
src/index.js

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

120
src/infrastructure/functions.js

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

37
src/infrastructure/global.css

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

366
src/infrastructure/widgets.css

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

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

Loading…
Cancel
Save