forked from newthuhole/hole_thu_frontend
update
add search and refresh add lazyload fix ui
This commit is contained in:
5
package-lock.json
generated
5
package-lock.json
generated
@@ -8783,6 +8783,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-4.0.0.tgz",
|
||||||
"integrity": "sha512-FlsPxavEyMuR6TjVbSSywovXSEyOg6ZDj5+Z8nbsRl9EkOzAhEIcS+GLoQDC5fz/t9suhUXWmUrOBrgeUvrMxw=="
|
"integrity": "sha512-FlsPxavEyMuR6TjVbSSywovXSEyOg6ZDj5+Z8nbsRl9EkOzAhEIcS+GLoQDC5fz/t9suhUXWmUrOBrgeUvrMxw=="
|
||||||
},
|
},
|
||||||
|
"react-lazyload": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-lazyload/-/react-lazyload-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-0z3qmL+qtSERdfKFpn0yKXm+1Gg1ZLZBXnCzHhSGiu1L8iDARuCkbOypxEx9+ETxZvMnXj98xvWCs5jyXTuM2w=="
|
||||||
|
},
|
||||||
"react-scripts": {
|
"react-scripts": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-1.1.4.tgz",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^16.4.2",
|
"react": "^16.4.2",
|
||||||
"react-dom": "^16.4.2",
|
"react-dom": "^16.4.2",
|
||||||
|
"react-lazyload": "^2.3.0",
|
||||||
"react-scripts": "1.1.4",
|
"react-scripts": "1.1.4",
|
||||||
"react-timeago": "^4.1.9"
|
"react-timeago": "^4.1.9"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<html lang="zh">
|
<html lang="zh">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, shrink-to-fit=yes">
|
||||||
<title>P大树洞(非官方)</title>
|
<title>P大树洞(非官方)</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -20,10 +20,6 @@
|
|||||||
To begin the development, run `npm start` or `yarn start`.
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
-->
|
-->
|
||||||
<script>
|
|
||||||
if(/Android|iPhone|iPad|iPod/.test(navigator.userAgent))
|
|
||||||
alert('你为什么不用 PKU Helper 客户端呢?');
|
|
||||||
</script>
|
|
||||||
<script>
|
<script>
|
||||||
var cnzz_s_tag = document.createElement('script');
|
var cnzz_s_tag = document.createElement('script');
|
||||||
cnzz_s_tag.type = 'text/javascript';
|
cnzz_s_tag.type = 'text/javascript';
|
||||||
|
|||||||
16
src/App.js
16
src/App.js
@@ -2,6 +2,7 @@ import React, {Component} from 'react';
|
|||||||
import {Flow} from './Flows';
|
import {Flow} from './Flows';
|
||||||
import {Title} from './Title';
|
import {Title} from './Title';
|
||||||
import {Sidebar} from './Sidebar';
|
import {Sidebar} from './Sidebar';
|
||||||
|
import {ControlBar} from './ControlBar';
|
||||||
|
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -9,6 +10,8 @@ class App extends Component {
|
|||||||
this.state={
|
this.state={
|
||||||
sidebar_title: null,
|
sidebar_title: null,
|
||||||
sidebar_content: null,
|
sidebar_content: null,
|
||||||
|
search_text: null,
|
||||||
|
flow_render_key: +new Date(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,12 +22,23 @@ class App extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set_search_text(text) {
|
||||||
|
this.setState({
|
||||||
|
search_text: text,
|
||||||
|
flow_render_key: +new Date(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<div className="bg-img" />
|
||||||
<Title callback={this.show_sidebar.bind(this)} />
|
<Title callback={this.show_sidebar.bind(this)} />
|
||||||
<div className="left-container">
|
<div className="left-container">
|
||||||
<Flow callback={this.show_sidebar.bind(this)} mode="list" />
|
<ControlBar set_search_text={this.set_search_text.bind(this)} />
|
||||||
|
<Flow key={this.state.flow_render_key}
|
||||||
|
callback={this.show_sidebar.bind(this)} search_text={this.state.search_text}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Sidebar do_close={()=>{
|
<Sidebar do_close={()=>{
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.centered-line::before,
|
.centered-line::before,
|
||||||
@@ -24,3 +25,14 @@
|
|||||||
left: 0.5em;
|
left: 0.5em;
|
||||||
margin-right: -50%;
|
margin-right: -50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-img {
|
||||||
|
position: fixed;
|
||||||
|
z-index: -1;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: midnightblue url(/eriri_bg.jpg) fixed center center;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ export function Time(props) {
|
|||||||
|
|
||||||
export function CenteredLine(props) {
|
export function CenteredLine(props) {
|
||||||
return (
|
return (
|
||||||
<p className="centered-line">
|
<p className="centered-line aux-margin">
|
||||||
<span>{props.text}</span>
|
<span>{props.text}</span>
|
||||||
</p>
|
</p>
|
||||||
)
|
)
|
||||||
|
|||||||
17
src/ControlBar.css
Normal file
17
src/ControlBar.css
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
.control-bar {
|
||||||
|
display: flex;
|
||||||
|
line-height: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-bar .refresh-btn {
|
||||||
|
flex: 0 0 100px;
|
||||||
|
color: black;
|
||||||
|
background-color: rgba(255,255,255,.9);
|
||||||
|
border-radius: 5px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.control-bar input {
|
||||||
|
flex: auto;
|
||||||
|
color: black;
|
||||||
|
background-color: rgba(255,255,255,.9);
|
||||||
|
}
|
||||||
42
src/ControlBar.js
Normal file
42
src/ControlBar.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import React, {Component} from 'react';
|
||||||
|
import './ControlBar.css';
|
||||||
|
|
||||||
|
export class ControlBar extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state={
|
||||||
|
search_text: '',
|
||||||
|
};
|
||||||
|
this.set_search_text=props.set_search_text;
|
||||||
|
}
|
||||||
|
|
||||||
|
on_change(event) {
|
||||||
|
this.setState({
|
||||||
|
search_text: event.target.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
on_keypress(event) {
|
||||||
|
if(event.key==='Enter')
|
||||||
|
this.set_search_text(this.state.search_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
do_refresh() {
|
||||||
|
this.setState({
|
||||||
|
search_text: '',
|
||||||
|
});
|
||||||
|
this.set_search_text(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="control-bar aux-margin">
|
||||||
|
<a className="refresh-btn" onClick={this.do_refresh.bind(this)}>最新树洞</a>
|
||||||
|
|
||||||
|
<input value={this.state.search_text} placeholder="搜索"
|
||||||
|
onChange={this.on_change.bind(this)} onKeyPress={this.on_keypress.bind(this)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,9 +6,6 @@
|
|||||||
box-shadow: 0 5px 20px #999;
|
box-shadow: 0 5px 20px #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-container .centered-line {
|
|
||||||
width: calc(100% - 2 * 50px);
|
|
||||||
}
|
|
||||||
.flow-item {
|
.flow-item {
|
||||||
flex: 0 0 600px;
|
flex: 0 0 600px;
|
||||||
}
|
}
|
||||||
@@ -19,20 +16,33 @@
|
|||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-container .centered-line,
|
.left-container .aux-margin,
|
||||||
.left-container .flow-item {
|
.left-container .flow-item {
|
||||||
margin-left: 50px;
|
margin-left: 50px;
|
||||||
}
|
}
|
||||||
|
.left-container .aux-margin {
|
||||||
|
width: calc(100% - 2 * 50px);
|
||||||
|
}
|
||||||
|
|
||||||
:not(.sidebar-flow-item).flow-item-row {
|
@media screen and (max-width: 1200px) {
|
||||||
|
.left-container .aux-margin,
|
||||||
|
.left-container .flow-item {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
.left-container .aux-margin {
|
||||||
|
width: calc(100% - 2 * 10px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-container .flow-item-row {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: margin-left 200ms ease-out;
|
transition: margin-left 200ms ease-out;
|
||||||
}
|
}
|
||||||
:not(.sidebar-flow-item).flow-item-row:hover {
|
.left-container .flow-item-row:hover {
|
||||||
margin-left: -10px;
|
margin-left: -10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:not(.sidebar-flow-item).flow-item-row {
|
.left-container .flow-item-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|||||||
88
src/Flows.js
88
src/Flows.js
@@ -1,9 +1,11 @@
|
|||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import {Time, CenteredLine} from './Common.js';
|
import {Time, CenteredLine} from './Common.js';
|
||||||
import './Flows.css';
|
import './Flows.css';
|
||||||
|
import LazyLoad from 'react-lazyload';
|
||||||
|
|
||||||
const IMAGE_BASE='http://www.pkuhelper.com/services/pkuhole/images/';
|
const IMAGE_BASE='http://www.pkuhelper.com/services/pkuhole/images/';
|
||||||
const AUDIO_BASE='http://www.pkuhelper.com/services/pkuhole/audios/';
|
const AUDIO_BASE='http://www.pkuhelper.com/services/pkuhole/audios/';
|
||||||
|
const SEARCH_PAGESIZE=50;
|
||||||
|
|
||||||
function Reply(props) {
|
function Reply(props) {
|
||||||
return (
|
return (
|
||||||
@@ -20,7 +22,7 @@ function Reply(props) {
|
|||||||
function ReplyPlaceholder(props) {
|
function ReplyPlaceholder(props) {
|
||||||
return (
|
return (
|
||||||
<div className="box">
|
<div className="box">
|
||||||
正在加载 {props.count} 条回复
|
加载中
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -29,8 +31,8 @@ function FlowItem(props) {
|
|||||||
return (
|
return (
|
||||||
<div className="flow-item box">
|
<div className="flow-item box">
|
||||||
<div className="box-header">
|
<div className="box-header">
|
||||||
{parseInt(props.info.likenum, 10) && <span className="box-header-badge">{props.info.likenum}★</span>}
|
{!!parseInt(props.info.likenum, 10) && <span className="box-header-badge">{props.info.likenum}★</span>}
|
||||||
{parseInt(props.info.reply, 10) && <span className="box-header-badge">{props.info.reply} 回复</span>}
|
{!!parseInt(props.info.reply, 10) && <span className="box-header-badge">{props.info.reply} 回复</span>}
|
||||||
<span className="box-id">#{props.info.pid}</span>
|
<span className="box-id">#{props.info.pid}</span>
|
||||||
<Time stamp={props.info.timestamp} />
|
<Time stamp={props.info.timestamp} />
|
||||||
</div>
|
</div>
|
||||||
@@ -49,15 +51,20 @@ class FlowItemRow extends Component {
|
|||||||
reply_loading: false,
|
reply_loading: false,
|
||||||
};
|
};
|
||||||
this.info=props.info;
|
this.info=props.info;
|
||||||
if(parseInt(props.info.reply,10)) {
|
}
|
||||||
this.state.reply_loading=true;
|
|
||||||
|
componentDidMount() {
|
||||||
|
if(parseInt(this.info.reply,10)) {
|
||||||
|
this.setState({
|
||||||
|
reply_loading: true,
|
||||||
|
});
|
||||||
this.load_replies();
|
this.load_replies();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
load_replies() {
|
load_replies() {
|
||||||
console.log('fetching reply',this.info.pid);
|
console.log('fetching reply',this.info.pid);
|
||||||
fetch('http://www.pkuhelper.com:10301/pkuhelper/../services/pkuhole/api.php?action=getcomment&pid='+this.info.pid)
|
fetch('http://www.pkuhelper.com:10301/services/pkuhole/api.php?action=getcomment&pid='+this.info.pid)
|
||||||
.then((res)=>res.json())
|
.then((res)=>res.json())
|
||||||
.then((json)=>{
|
.then((json)=>{
|
||||||
if(json.code!==0)
|
if(json.code!==0)
|
||||||
@@ -80,7 +87,7 @@ class FlowItemRow extends Component {
|
|||||||
</div>
|
</div>
|
||||||
)}}>
|
)}}>
|
||||||
<FlowItem info={this.info} />
|
<FlowItem info={this.info} />
|
||||||
{this.state.reply_loading && <ReplyPlaceholder count={this.info.reply} />}
|
{!!this.state.reply_loading && <ReplyPlaceholder count={this.info.reply} />}
|
||||||
{this.state.replies.map((reply)=><Reply info={reply} key={reply.cid} />)}
|
{this.state.replies.map((reply)=><Reply info={reply} key={reply.cid} />)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -91,7 +98,11 @@ function FlowChunk(props) {
|
|||||||
return (
|
return (
|
||||||
<div className="flow-chunk">
|
<div className="flow-chunk">
|
||||||
<CenteredLine text={props.title} />
|
<CenteredLine text={props.title} />
|
||||||
{props.list.map((info)=><FlowItemRow key={info.pid} info={info} callback={props.callback} />)}
|
{props.list.map((info)=>(
|
||||||
|
<LazyLoad key={info.pid} offset={500} height="15em">
|
||||||
|
<FlowItemRow info={info} callback={props.callback} />
|
||||||
|
</LazyLoad>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -100,7 +111,8 @@ export class Flow extends Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state={
|
this.state={
|
||||||
mode: props.mode,
|
mode: props.search_text===null ? 'list' : 'search',
|
||||||
|
search_param: props.search_text,
|
||||||
loaded_pages: 0,
|
loaded_pages: 0,
|
||||||
chunks: [],
|
chunks: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -113,27 +125,49 @@ export class Flow extends Component {
|
|||||||
throw new Error('bad page');
|
throw new Error('bad page');
|
||||||
if(page===this.state.loaded_pages+1) {
|
if(page===this.state.loaded_pages+1) {
|
||||||
console.log('fetching page',page);
|
console.log('fetching page',page);
|
||||||
|
if(this.state.mode==='list') {
|
||||||
|
fetch('http://www.pkuhelper.com:10301/services/pkuhole/api.php?action=getlist&p='+page)
|
||||||
|
.then((res)=>res.json())
|
||||||
|
.then((json)=>{
|
||||||
|
if(json.code!==0)
|
||||||
|
throw new Error(json.code);
|
||||||
|
this.setState((prev,props)=>({
|
||||||
|
chunks: prev.chunks.concat([{
|
||||||
|
title: 'Page '+page,
|
||||||
|
data: json.data,
|
||||||
|
}]),
|
||||||
|
loading: false,
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
.catch((err)=>{
|
||||||
|
console.trace(err);
|
||||||
|
alert('load failed');
|
||||||
|
});
|
||||||
|
} else if(this.state.mode==='search') {
|
||||||
|
fetch(
|
||||||
|
'http://www.pkuhelper.com:10301/services/pkuhole/api.php?action=search'+
|
||||||
|
'&pagesize='+SEARCH_PAGESIZE*page+
|
||||||
|
'&keywords='+encodeURIComponent(this.state.search_param)
|
||||||
|
)
|
||||||
|
.then((res)=>res.json())
|
||||||
|
.then((json)=>{
|
||||||
|
if(json.code!==0)
|
||||||
|
throw new Error(json.code);
|
||||||
|
const finished=json.data.length<SEARCH_PAGESIZE;
|
||||||
|
this.setState((prev,props)=>({
|
||||||
|
chunks: [{
|
||||||
|
title: 'Result for "'+this.state.search_param+'"',
|
||||||
|
data: json.data,
|
||||||
|
mode: finished ? 'search_finished' : 'search',
|
||||||
|
}],
|
||||||
|
loading: false,
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
}
|
||||||
this.setState((prev,props)=>({
|
this.setState((prev,props)=>({
|
||||||
loaded_pages: prev.loaded_pages+1,
|
loaded_pages: prev.loaded_pages+1,
|
||||||
loading: true,
|
loading: true,
|
||||||
}));
|
}));
|
||||||
fetch('http://www.pkuhelper.com:10301/pkuhelper/../services/pkuhole/api.php?action=getlist&p='+page)
|
|
||||||
.then((res)=>res.json())
|
|
||||||
.then((json)=>{
|
|
||||||
if(json.code!==0)
|
|
||||||
throw new Error(json.code);
|
|
||||||
this.setState((prev,props)=>({
|
|
||||||
chunks: prev.chunks.concat([{
|
|
||||||
title: 'Page '+page,
|
|
||||||
data: json.data,
|
|
||||||
}]),
|
|
||||||
loading: false,
|
|
||||||
}));
|
|
||||||
})
|
|
||||||
.catch((err)=>{
|
|
||||||
console.trace(err);
|
|
||||||
alert('load failed');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +195,7 @@ export class Flow extends Component {
|
|||||||
{this.state.chunks.map((chunk)=>(
|
{this.state.chunks.map((chunk)=>(
|
||||||
<FlowChunk title={chunk.title} list={chunk.data} key={chunk.title} callback={this.props.callback} />
|
<FlowChunk title={chunk.title} list={chunk.data} key={chunk.title} callback={this.props.callback} />
|
||||||
))}
|
))}
|
||||||
<CenteredLine text={this.state.loading ? 'Loading More...' : '© xmcp'} />
|
<CenteredLine text={this.state.loading ? 'Loading...' : '© xmcp'} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,10 +33,10 @@
|
|||||||
|
|
||||||
@media screen and (max-width: 1200px) {
|
@media screen and (max-width: 1200px) {
|
||||||
.sidebar {
|
.sidebar {
|
||||||
width: 600px;
|
width: 550px;
|
||||||
}
|
}
|
||||||
.sidebar-on .sidebar {
|
.sidebar-on .sidebar {
|
||||||
left: calc(100% - 600px);
|
left: calc(100% - 550px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
line-height: 2em;
|
line-height: 2em;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
background-color: rgba(255,255,255,.8);
|
background-color: rgba(255,255,255,.8);
|
||||||
padding: 0 .5em;
|
padding: 0 50px;
|
||||||
box-shadow: 0 0 25px #999;
|
box-shadow: 0 0 25px #999;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
body {
|
body {
|
||||||
min-width: 700px;
|
min-width: 620px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-family: '微软雅黑', 'Microsoft YaHei', sans-serif;
|
font-family: '微软雅黑', 'Microsoft YaHei', sans-serif;
|
||||||
background-color: black;
|
|
||||||
background-image: url(/eriri_bg.jpg);
|
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-attachment: fixed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body::-webkit-scrollbar {
|
body::-webkit-scrollbar {
|
||||||
@@ -25,5 +22,12 @@ a {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #00c;
|
color: #00c;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin: 0 .5em;
|
padding: 0 .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
padding: 0 1em;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid black;
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user