many new
This commit is contained in:
Executable
+16
@@ -0,0 +1,16 @@
|
||||
.BBCodes
|
||||
font-size: 1.3rem
|
||||
margin-bottom: .2rem
|
||||
|
||||
svg
|
||||
cursor: pointer
|
||||
padding: 0
|
||||
|
||||
svg:first-child
|
||||
padding-right: .5rem
|
||||
|
||||
svg:last-child
|
||||
padding-left: .5rem
|
||||
|
||||
svg:not(:first-child):not(:last-child)
|
||||
padding: 0 .5rem
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
import React, {useState} from "react";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faBold,
|
||||
faExclamationTriangle,
|
||||
faItalic,
|
||||
faStrikethrough,
|
||||
faTint,
|
||||
faUnderline
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import './BBEditor.sass';
|
||||
import TextField from "../TextField/TextField";
|
||||
|
||||
interface Props extends React.DetailedHTMLProps<React.TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement> {
|
||||
inputRef: React.RefObject<HTMLTextAreaElement>
|
||||
}
|
||||
|
||||
const BBEditor = ({ inputRef, ref, ...props }: Props) => {
|
||||
const [text, setText] = useState('');
|
||||
|
||||
const addTag = (event: React.MouseEvent, tag: string, defaultValue?: string) => {
|
||||
event.preventDefault();
|
||||
const [start, end] = [inputRef.current!!.selectionStart, inputRef.current!!.selectionEnd];
|
||||
|
||||
const openTag = `${tag}${defaultValue ? `=${defaultValue}` : ''}`;
|
||||
setText(`${text.slice(0, start)}[${openTag}]${text.slice(start, end)}[/${tag}]${text.slice(end, text.length)}`)
|
||||
inputRef.current!!.focus();
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ width: '100%' }}>
|
||||
<div className="BBCodes">
|
||||
<FontAwesomeIcon onClick={event => addTag(event, 'b')} icon={faBold}/>
|
||||
<FontAwesomeIcon onClick={event => addTag(event, 'i')} icon={faItalic}/>
|
||||
<FontAwesomeIcon onClick={event => addTag(event, 'u')} icon={faUnderline}/>
|
||||
<FontAwesomeIcon onClick={event => addTag(event, 's')} icon={faStrikethrough}/>
|
||||
<FontAwesomeIcon onClick={event => addTag(event, 'spoiler')} icon={faExclamationTriangle}/>
|
||||
<FontAwesomeIcon onClick={event => addTag(event, 'color', 'white')} icon={faTint}/>
|
||||
</div>
|
||||
<TextField
|
||||
ref={inputRef}
|
||||
style={{ width: '100%' }}
|
||||
value={text}
|
||||
onChange={event => setText(event.target.value)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default BBEditor;
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
@import "../../Constants"
|
||||
|
||||
.Button
|
||||
cursor: pointer
|
||||
padding: .2rem
|
||||
border-radius: $borderRadius
|
||||
background-color: var(--block-color)
|
||||
border: 1px solid var(--main-color)
|
||||
color: var(--main-color)
|
||||
|
||||
&:disabled
|
||||
color: var(--sec-color)
|
||||
|
||||
&:hover
|
||||
background-color: var(--block-sec-color)
|
||||
color: var(--link-color)
|
||||
border: 1px solid var(--link-color)
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
import React, {ButtonHTMLAttributes} from "react";
|
||||
import './Button.sass';
|
||||
import classNames from "../../classNames";
|
||||
|
||||
const Button = (restProps: ButtonHTMLAttributes<HTMLElement>) => {
|
||||
const { className, children, ...props } = restProps;
|
||||
return (
|
||||
<button className={classNames('Button', className)} {...props}>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default Button;
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
@import "../../Constants"
|
||||
|
||||
.Comment
|
||||
display: flex
|
||||
background-color: var(--block-color)
|
||||
border-radius: $borderRadius
|
||||
padding: $blockPadding * 2
|
||||
|
||||
&:not(:last-child)
|
||||
margin-bottom: $blockPadding
|
||||
|
||||
.Comment__User_Avatar
|
||||
border-radius: 50%
|
||||
width: 48px
|
||||
height: 48px
|
||||
margin-right: .5rem
|
||||
|
||||
&__Content
|
||||
.Comment__User_Username
|
||||
font-size: 1.1rem
|
||||
font-weight: 600
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
import './Comment.sass'
|
||||
import {Link} from "react-router-dom";
|
||||
// @ts-ignore
|
||||
import parser from 'bbcode-to-react';
|
||||
import Loading from "../Loading";
|
||||
|
||||
type Props = {
|
||||
user: UserType | undefined
|
||||
text: string
|
||||
}
|
||||
|
||||
class Comment extends React.Component<Props> {
|
||||
render() {
|
||||
const { user, text } = this.props;
|
||||
if(!user) return <Loading/>;
|
||||
|
||||
return (user &&
|
||||
<div className={'Comment'}>
|
||||
<img className={'Comment__User_Avatar'} src={user.avatar} alt={user.username}/>
|
||||
<div className="Comment__Content">
|
||||
<Link to={`/user/${user.id}`} className="Comment__User_Username">{user.username}</Link>
|
||||
<pre className={'Comment__Text'}>{parser.toReact(text)}</pre>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Comment;
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
.Buttons
|
||||
display: flex
|
||||
justify-content: flex-end
|
||||
|
||||
.Button
|
||||
margin: 1rem .5rem 0
|
||||
font-size: 1.3rem
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
import React from "react";
|
||||
import Button from "../Button/Button";
|
||||
import './ConfirmPopout.sass'
|
||||
import '../Modal/Modal.sass';
|
||||
import {connect} from "react-redux";
|
||||
import {hideModal} from "../../store/actions";
|
||||
|
||||
type Props = {
|
||||
hideModal: () => void
|
||||
onConfirm?: () => void
|
||||
onDismiss?: () => void
|
||||
title?: string
|
||||
children: any
|
||||
}
|
||||
|
||||
class ConfirmPopout extends React.Component<Props> {
|
||||
onConfirm = () => {
|
||||
const { hideModal, onConfirm } = this.props;
|
||||
if(onConfirm) onConfirm();
|
||||
hideModal();
|
||||
}
|
||||
|
||||
onDismiss = () => {
|
||||
const { hideModal, onDismiss } = this.props;
|
||||
if(onDismiss) onDismiss();
|
||||
hideModal();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children, title } = this.props;
|
||||
|
||||
return (
|
||||
<div className="Modal__Overlay">
|
||||
<div className={"Modal"}>
|
||||
{title && <div className="Modal__Header">
|
||||
{title}
|
||||
</div>}
|
||||
{children}
|
||||
<div className={'Buttons'}>
|
||||
<Button onClick={this.onConfirm}>Да</Button>
|
||||
<Button onClick={this.onDismiss}>Нет</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(null,
|
||||
dispatch => ({
|
||||
hideModal: () => dispatch(hideModal())
|
||||
})
|
||||
)(ConfirmPopout);
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
@import "../../Constants"
|
||||
|
||||
.Group
|
||||
background-color: var(--sec-bg-color)
|
||||
border-radius: $borderRadius
|
||||
padding: .5rem
|
||||
|
||||
&__Title
|
||||
font-size: 1.2rem
|
||||
font-weight: 600
|
||||
margin-bottom: .5rem
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
import React, {HTMLAttributes} from "react";
|
||||
import classNames from "../../classNames";
|
||||
import './Group.sass'
|
||||
|
||||
interface Props extends HTMLAttributes<HTMLElement> {
|
||||
title: string
|
||||
direction?: 'column' | 'row'
|
||||
}
|
||||
|
||||
const Group = (restProps: Props) => {
|
||||
const { title, direction, className, children, ...props } = restProps;
|
||||
let style: React.CSSProperties = {};
|
||||
if(direction === 'column') {
|
||||
style = {
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className={classNames('Group', className)} {...props}>
|
||||
<div className="Group__Title">{title}</div>
|
||||
<div style={style}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Group;
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
.Input
|
||||
background-color: var(--sec-block-color) !important
|
||||
border: none
|
||||
border-bottom: 1px solid var(--main-color) !important
|
||||
color: var(--main-color)
|
||||
|
||||
&:disabled
|
||||
color: var(--sec-color)
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
import React, {InputHTMLAttributes} from "react";
|
||||
import classNames from "../../classNames";
|
||||
import './Input.sass';
|
||||
|
||||
const Input = (restProps: InputHTMLAttributes<HTMLElement>) => {
|
||||
const { className, ...props } = restProps;
|
||||
return (
|
||||
<input className={classNames('Input', className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
export default Input;
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
// @ts-ignore
|
||||
import Loader from 'react-loader-spinner'
|
||||
|
||||
const Loading: React.FC = () => (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Loader color='#aaa' type='Rings' style={{ height: '100%' }} />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Loading;
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
.Modal__Overlay
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
position: fixed
|
||||
top: 0
|
||||
right: 0
|
||||
left: 0
|
||||
bottom: 0
|
||||
background-color: #0f0f0fe9
|
||||
z-index: 9999
|
||||
opacity: 1
|
||||
overflow: hidden
|
||||
|
||||
.Modal
|
||||
background-color: var(--main-bg-color)
|
||||
padding: .5rem
|
||||
|
||||
.Modal__Header
|
||||
display: flex
|
||||
justify-content: space-between
|
||||
align-items: center
|
||||
font-size: 1.4rem
|
||||
font-weight: 700
|
||||
margin-bottom: .5rem
|
||||
|
||||
.Modal__CloseButton
|
||||
background: none
|
||||
font-size: 2rem
|
||||
padding: 0
|
||||
height: 2.5rem
|
||||
width: 2.5rem
|
||||
border: none
|
||||
color: var(--main-color)
|
||||
cursor: pointer
|
||||
|
||||
&:hover
|
||||
color: var(--link-color)
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
import React from "react";
|
||||
import './Modal.sass'
|
||||
import {connect} from "react-redux";
|
||||
import {setModal} from "../../store/actions";
|
||||
|
||||
type Props = {
|
||||
setModal: (modal: React.ReactNode | null) => void
|
||||
children: any
|
||||
className?: string
|
||||
title?: string
|
||||
}
|
||||
|
||||
class Modal extends React.Component<Props> {
|
||||
private modal: HTMLDivElement | null | undefined;
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('keyup', this.handleKeyUp, false);
|
||||
document.addEventListener('mousedown', this.handleOutsideClick, false);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('keyup', this.handleKeyUp, false);
|
||||
document.removeEventListener('mousedown', this.handleOutsideClick, false);
|
||||
}
|
||||
|
||||
handleKeyUp = (e: KeyboardEvent) => {
|
||||
if(e.code === 'Escape') {
|
||||
const { setModal } = this.props;
|
||||
e.preventDefault();
|
||||
setModal(null);
|
||||
}
|
||||
};
|
||||
|
||||
handleOutsideClick = (e: any) => {
|
||||
if(this.modal && !this.modal.contains(e.target)) {
|
||||
const { setModal } = this.props;
|
||||
setModal(null);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { setModal, children, title } = this.props;
|
||||
|
||||
return (
|
||||
<div className="Modal__Overlay">
|
||||
<div className={"Modal"} ref={node => (this.modal = node)}>
|
||||
<div className={this.props.className}>
|
||||
<div className="Modal__Header">
|
||||
{title && title}
|
||||
<button className={"Modal__CloseButton"} onClick={() => setModal(null)}>X</button>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(null,
|
||||
dispatch => ({
|
||||
setModal: (modal: React.ReactNode | null) => {
|
||||
dispatch(setModal(modal))
|
||||
}
|
||||
})
|
||||
)(Modal);
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
.Post
|
||||
background-color: var(--block-color)
|
||||
padding: .5rem
|
||||
|
||||
&__Title
|
||||
font-size: 1.2rem
|
||||
|
||||
&__Content
|
||||
border-top: var(--link-color) 1px solid
|
||||
border-bottom: var(--link-color) 1px solid
|
||||
|
||||
&__Footer
|
||||
display: flex
|
||||
justify-content: space-between
|
||||
|
||||
&:not(:last-child)
|
||||
margin-bottom: 1rem
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
// @ts-ignore
|
||||
import parser from 'bbcode-to-react';
|
||||
import classNames from "../../classNames";
|
||||
import './NewsPost.sass';
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
short_post: string
|
||||
author_id: number
|
||||
author: string
|
||||
date: Date
|
||||
className?: string
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
const NewsPost = (restProps: Props) => {
|
||||
const { title, short_post, author, author_id, className, onClick, ...props } = restProps;
|
||||
const date = new Date(restProps.date);
|
||||
|
||||
return (
|
||||
<div className={classNames('Post', className)} {...props} onClick={onClick}>
|
||||
<div className="Post__Title">{title}</div>
|
||||
<pre className='Post__Content'>{parser.toReact(short_post)}</pre>
|
||||
<div className="Post__Footer">
|
||||
<div>
|
||||
Автор: {author}
|
||||
</div>
|
||||
<div>Дата: {date.toLocaleDateString()}, {date.toLocaleTimeString()}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default NewsPost;
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
@import "../../Constants"
|
||||
|
||||
.Animate-Notification-exit
|
||||
opacity: 0
|
||||
|
||||
.Notification
|
||||
background-color: var(--block-color)
|
||||
border: 1px solid var(--link-color)
|
||||
padding: .5rem
|
||||
box-sizing: border-box
|
||||
max-width: 500px
|
||||
width: 500px
|
||||
cursor: pointer
|
||||
margin-top: 1rem
|
||||
|
||||
&--Error
|
||||
background: var(--error-bg-color)
|
||||
|
||||
&--Success
|
||||
background: var(--success-bg-color)
|
||||
|
||||
&:not(:last-child)
|
||||
margin-bottom: .5rem
|
||||
|
||||
.Progress
|
||||
height: 5px
|
||||
background: white
|
||||
Executable
+73
@@ -0,0 +1,73 @@
|
||||
import React from "react";
|
||||
import './NotificationMessage.sass'
|
||||
import {capitalize} from "../../utils";
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode
|
||||
level?: 'info' | 'success' | 'error'
|
||||
}
|
||||
|
||||
type State = {
|
||||
time: number
|
||||
paused: boolean
|
||||
};
|
||||
|
||||
class NotificationMessage extends React.Component<Props, State> {
|
||||
timer: NodeJS.Timeout | undefined;
|
||||
|
||||
state = {
|
||||
time: 5000, paused: false
|
||||
};
|
||||
|
||||
onMouseOver = () => {
|
||||
this.setState({ paused: true });
|
||||
};
|
||||
|
||||
onMouseOut = () => {
|
||||
this.setState({ paused: false });
|
||||
};
|
||||
|
||||
onClick = () => {
|
||||
this.timer = setInterval(() => this.interval(80), 1);
|
||||
this.setState({ paused: false });
|
||||
};
|
||||
|
||||
interval = (step=50) => {
|
||||
const { time, paused } = this.state;
|
||||
if(time <= 0 && this.timer) clearInterval(this.timer);
|
||||
if(!paused) this.setState({ time: time-step });
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
this.timer = setInterval(() => this.interval(10), 1)
|
||||
};
|
||||
|
||||
componentWillUnmount(): void {
|
||||
if(this.timer) clearTimeout(this.timer);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { time } = this.state;
|
||||
const { children, level } = this.props;
|
||||
if(time <= 0)
|
||||
return null;
|
||||
|
||||
const styleClass = level ? `Notification--${capitalize(level)}` : '';
|
||||
const width = window.screen.width >= 500 ? 500 : window.screen.width;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`Notification ${styleClass}`}
|
||||
onMouseOver={this.onMouseOver}
|
||||
onMouseOut={this.onMouseOut}
|
||||
onClick={this.onClick}
|
||||
style={{ width }}
|
||||
>
|
||||
{children}
|
||||
<div className="Progress" style={{ width: `${time / 5000 * 100}%` }}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default NotificationMessage;
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
@import '../../Constants'
|
||||
|
||||
.NovelItem
|
||||
background-color: var(--block-color)
|
||||
padding-bottom: $blockPadding
|
||||
border-radius: $borderRadius
|
||||
margin: .5rem
|
||||
|
||||
@media (min-width: 601px)
|
||||
width: $novelBlockSize
|
||||
@media (max-width: 600px)
|
||||
width: 100%
|
||||
|
||||
display: flex
|
||||
|
||||
&.Grid
|
||||
flex-direction: column
|
||||
align-items: center
|
||||
|
||||
&.Table
|
||||
width: 100%
|
||||
padding: .2rem
|
||||
box-sizing: border-box
|
||||
flex-direction: row
|
||||
justify-content: space-between
|
||||
align-items: center
|
||||
|
||||
&:not(:last-child)
|
||||
margin: 0 0 .5rem
|
||||
|
||||
&:last-child
|
||||
margin: 0
|
||||
|
||||
.NovelItem__Status
|
||||
white-space: nowrap
|
||||
|
||||
|
||||
&__Image
|
||||
border-radius: $borderRadius $borderRadius 0 0
|
||||
background-size: cover
|
||||
background-position: center
|
||||
width: 100%
|
||||
height: 130px
|
||||
|
||||
&__Title
|
||||
width: 100%
|
||||
text-align: center
|
||||
display: inline-block
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
padding: .2rem
|
||||
box-sizing: border-box
|
||||
font-weight: 400
|
||||
|
||||
&__Status
|
||||
font-weight: 300
|
||||
display: flex
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import { TranslateStatus } from '../../api/api';
|
||||
import { Link } from 'react-router-dom';
|
||||
import './NovelItem.sass';
|
||||
import {capitalize, generateClassName} from "../../utils";
|
||||
|
||||
type Props = {
|
||||
id: number
|
||||
original_name: string
|
||||
viewType: string
|
||||
novelId?: number
|
||||
localized_name?: string
|
||||
image?: string
|
||||
status?: string
|
||||
mark?: number
|
||||
}
|
||||
|
||||
const NovelItem = ({ id, original_name, localized_name, image, status, mark, viewType, novelId }: Props) => (
|
||||
<Link to={`/novels/${id}`} className={generateClassName("NovelItem", capitalize(viewType) || 'Grid')}>
|
||||
{viewType === 'grid' && (<>
|
||||
<div className='NovelItem__Image' style={{ backgroundImage: `url(${image || '/static/img/no-image.png'})` }}/>
|
||||
<div className="NovelItem__Title">
|
||||
{localized_name || original_name}
|
||||
</div>
|
||||
{status && <div className="NovelItem__Status">
|
||||
{TranslateStatus[status]} {mark !== undefined && mark > 0 && mark}
|
||||
</div>}
|
||||
</>)}
|
||||
{viewType === 'table' && (<>
|
||||
<div className="NovelItem__Component">{novelId || ''}</div>
|
||||
<div className="NovelItem__Title">
|
||||
{localized_name || original_name}
|
||||
</div>
|
||||
{status && <div className="NovelItem__Status">
|
||||
{TranslateStatus[status]} {mark !== undefined && mark > 0 && mark}
|
||||
</div>}
|
||||
</>)}
|
||||
</Link>
|
||||
);
|
||||
|
||||
export default NovelItem;
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
import React from "react";
|
||||
|
||||
type Props = {
|
||||
sitekey: string
|
||||
theme?: 'dark' | 'light'
|
||||
onVerify?: (res: string) => void
|
||||
onError?: (error: string) => void
|
||||
onExpired?: () => void
|
||||
}
|
||||
|
||||
type State = {
|
||||
isReady: boolean
|
||||
}
|
||||
|
||||
class Recaptcha extends React.Component<Props, State> {
|
||||
private readonly interval: NodeJS.Timeout;
|
||||
state: State = {
|
||||
isReady: false
|
||||
}
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.interval = setInterval(() => {
|
||||
// @ts-ignore
|
||||
if(typeof window?.grecaptcha?.render === 'function') {
|
||||
clearInterval(this.interval);
|
||||
this.setState({ isReady: true })
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) {
|
||||
const { isReady: isReadyPrev } = prevState;
|
||||
const { isReady } = this.state;
|
||||
|
||||
if(!isReadyPrev && isReady) {
|
||||
const { sitekey, theme, onExpired, onVerify } = this.props;
|
||||
// @ts-ignore
|
||||
window.grecaptcha.render(document.getElementById('g-recaptcha'), {
|
||||
sitekey, theme,
|
||||
callback: onVerify || undefined,
|
||||
'expired-callback': onExpired || undefined
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
reset = () => {
|
||||
// @ts-ignore
|
||||
if(typeof window?.grecaptcha?.reset === "function") window.grecaptcha.reset(document.getElementById('g-recaptcha'));
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
id="g-recaptcha"
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Recaptcha;
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
@import "../../Constants"
|
||||
|
||||
.Spoiler
|
||||
cursor: pointer
|
||||
|
||||
&:hover
|
||||
color: var(--link-color)
|
||||
text-decoration: underline
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
import React from "react";
|
||||
import './Spoiler.sass'
|
||||
|
||||
type Props = {
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
type State = {
|
||||
expand: boolean
|
||||
}
|
||||
|
||||
class Spoiler extends React.Component<Props, State> {
|
||||
state = {
|
||||
expand: false
|
||||
};
|
||||
|
||||
render() {
|
||||
const { expand } = this.state;
|
||||
const { children } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="Spoiler"
|
||||
onClick={() => this.setState({ expand: !expand })}
|
||||
style={{ fontStyle: expand ? 'normal' : 'italic' }}
|
||||
>
|
||||
{expand ? children : '[Спойлер]'}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Spoiler;
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
// @ts-ignore
|
||||
import { Tag } from 'bbcode-to-react';
|
||||
import Spoiler from "./Spoiler";
|
||||
import React from 'react';
|
||||
|
||||
class SpoilerTag extends Tag {
|
||||
toReact() {
|
||||
// @ts-ignore
|
||||
return (<Spoiler>{this.getContent(true)}</Spoiler>)
|
||||
}
|
||||
}
|
||||
|
||||
export default SpoilerTag;
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
// @ts-ignore
|
||||
import { Tag } from 'bbcode-to-react';
|
||||
import React from 'react';
|
||||
|
||||
class ColorTag extends Tag {
|
||||
toReact() {
|
||||
// @ts-ignore
|
||||
const text = this.getContent(true);
|
||||
// @ts-ignore
|
||||
const color = this.params.color;
|
||||
// @ts-ignore
|
||||
return <span style={{ color }}>{text}</span>;
|
||||
}
|
||||
}
|
||||
|
||||
export default ColorTag;
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
.TextField
|
||||
color: var(--main-color)
|
||||
background-color: var(--block-color)
|
||||
font-size: 1rem
|
||||
padding: .25rem
|
||||
border: none
|
||||
border-radius: .2rem
|
||||
box-sizing: border-box
|
||||
|
||||
&:disabled
|
||||
color: var(--sec-color)
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
import React, {TextareaHTMLAttributes} from "react";
|
||||
import classNames from "../../classNames";
|
||||
import './TextField.sass';
|
||||
|
||||
const TextField = React.forwardRef(
|
||||
(restProps: React.DetailedHTMLProps<React.TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>, ref: React.Ref<HTMLTextAreaElement>) => {
|
||||
const { className, ...props } = restProps;
|
||||
return (
|
||||
<textarea className={classNames('TextField', className)} ref={ref} {...props} />
|
||||
)
|
||||
})
|
||||
|
||||
export default TextField;
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
.Title
|
||||
font-size: 1.2rem
|
||||
font-weight: 600
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
import './Title.sass';
|
||||
|
||||
const Title = ({ children }: any) => (
|
||||
<div className={'Title'}>{children}</div>
|
||||
)
|
||||
|
||||
export default Title;
|
||||
Reference in New Issue
Block a user