formatting
This commit is contained in:
14
src/App.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}`;
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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')}
|
||||
|
||||
351
src/Flows.css
351
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;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.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
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>
|
||||
)}
|
||||
|
||||
{(
|
||||
<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 && (
|
||||
<>
|
||||
|
||||
<span
|
||||
className="clickable"
|
||||
onClick={do_report}
|
||||
>
|
||||
<span className="clickable" onClick={do_report}>
|
||||
<span className="icon icon-flag" />
|
||||
</span>
|
||||
|
||||
@@ -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}
|
||||
<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>
|
||||
|
||||
{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>
|
||||
|
||||
{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 && (
|
||||
|
||||
@@ -16,4 +16,3 @@
|
||||
vertical-align: top;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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/>
|
||||
<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} />
|
||||
|
||||
|
||||
|
||||
<b>{msg.type}</b>
|
||||
|
||||
<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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
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;
|
||||
}
|
||||
|
||||
@@ -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
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}
|
||||
/>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}
|
||||
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
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);
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
103
src/index.css
103
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);
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
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;
|
||||
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,
|
||||
.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 .5em;
|
||||
line-height: 2em;
|
||||
.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 {
|
||||
|
||||
@@ -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,56 +8,62 @@ 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,
|
||||
})} />
|
||||
|
||||
{!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,
|
||||
})}
|
||||
/>
|
||||
|
||||
{!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);
|
||||
@@ -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" />
|
||||
闭社
|
||||
</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" />
|
||||
T大树洞
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<small>前往Telegram群查询15分钟临时token</small>
|
||||
<br/>
|
||||
<a href="//t.me/THUChatBot" target="_blank"
|
||||
>
|
||||
<span className="icon icon-login" />
|
||||
清华大水群
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<button type="button" disabled
|
||||
>
|
||||
<span className="icon icon-login" />
|
||||
未名bbs
|
||||
</button>
|
||||
</p>
|
||||
<p>
|
||||
<button type="button" disabled
|
||||
>
|
||||
<span className="icon icon-login" />
|
||||
清华统一身份认证
|
||||
</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" />
|
||||
闭社
|
||||
</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" />
|
||||
T大树洞
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<small>前往Telegram群查询15分钟临时token</small>
|
||||
<br />
|
||||
<a href="//t.me/THUChatBot" target="_blank">
|
||||
<span className="icon icon-login" />
|
||||
清华大水群
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<button type="button" disabled>
|
||||
<span className="icon icon-login" />
|
||||
未名bbs
|
||||
</button>
|
||||
</p>
|
||||
<p>
|
||||
<button type="button" disabled>
|
||||
<span className="icon icon-login" />
|
||||
清华统一身份认证
|
||||
</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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user