Merge remote-tracking branch 'origin/dev' into prepare-inbox
This commit is contained in:
commit
3af9f5d7e4
|
@ -124,7 +124,6 @@
|
||||||
.buttonSubscribe {
|
.buttonSubscribe {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
aspect-ratio: 1/1;
|
aspect-ratio: 1/1;
|
||||||
background: #f6f6f6;
|
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
float: right;
|
float: right;
|
||||||
|
@ -139,7 +138,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttonWrite {
|
.buttonWrite {
|
||||||
background: #f7f7f7;
|
|
||||||
color: #000;
|
color: #000;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
transition: background-color 0.3s, color 0.3s;
|
transition: background-color 0.3s, color 0.3s;
|
||||||
|
@ -193,7 +191,8 @@
|
||||||
.buttonSubscribe {
|
.buttonSubscribe {
|
||||||
aspect-ratio: auto;
|
aspect-ratio: auto;
|
||||||
background-color: #000;
|
background-color: #000;
|
||||||
border-radius: 2px;
|
border-color: #000;
|
||||||
|
border-radius: 0.8rem;
|
||||||
float: none;
|
float: none;
|
||||||
padding-bottom: 0.6rem;
|
padding-bottom: 0.6rem;
|
||||||
padding-top: 0.6rem;
|
padding-top: 0.6rem;
|
||||||
|
|
|
@ -79,7 +79,7 @@ export const AuthorCard = (props: AuthorCardProps) => {
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={!props.compact}>
|
<Show when={!props.compact}>
|
||||||
<button class={clsx(style.buttonWrite, style.button, 'button')}>
|
<button class={clsx(style.buttonWrite, style.button, 'button button--subscribe-topic')}>
|
||||||
<Icon name="edit" class={style.icon} />
|
<Icon name="edit" class={style.icon} />
|
||||||
{t('Write')}
|
{t('Write')}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -6,7 +6,7 @@ export const AuthorFull = (props: { author: Author }) => {
|
||||||
return (
|
return (
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="user-details">
|
<div class="col-md-8 offset-md-2 user-details">
|
||||||
<AuthorCard author={props.author} compact={false} isAuthorPage={true} />
|
<AuthorCard author={props.author} compact={false} isAuthorPage={true} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -54,6 +54,14 @@
|
||||||
@include font-size(1.5rem);
|
@include font-size(1.5rem);
|
||||||
|
|
||||||
padding-top: 1.6rem;
|
padding-top: 1.6rem;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.footerCopyrightSocial {
|
.footerCopyrightSocial {
|
||||||
|
|
|
@ -26,8 +26,8 @@ export const Footer = () => {
|
||||||
slug: '/about/dogma'
|
slug: '/about/dogma'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Terms of use',
|
title: 'Principles',
|
||||||
slug: '/about/terms-of-use'
|
slug: '/about/principles'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'How to write an article',
|
title: 'How to write an article',
|
||||||
|
|
|
@ -14,6 +14,9 @@
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
@include font-size(2rem);
|
@include font-size(2rem);
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
padding: 0.2em 0.5em 0.3em 0;
|
padding: 0.2em 0.5em 0.3em 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
@ -33,8 +36,8 @@
|
||||||
padding: 0 0.5em;
|
padding: 0 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
.button {
|
||||||
margin-top: 0;
|
border-radius: 0;
|
||||||
padding-bottom: 0.8rem;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { createSignal, onMount } from 'solid-js'
|
import { createSignal, onMount } from 'solid-js'
|
||||||
import styles from './Subscribe.module.scss'
|
import styles from './Subscribe.module.scss'
|
||||||
import { t } from '../../utils/intl'
|
import { t } from '../../utils/intl'
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
let emailElement: HTMLInputElement | undefined
|
let emailElement: HTMLInputElement | undefined
|
||||||
|
@ -14,7 +15,10 @@ export default () => {
|
||||||
return (
|
return (
|
||||||
<div class={styles.subscribeForm}>
|
<div class={styles.subscribeForm}>
|
||||||
<input type="email" name="email" ref={emailElement} placeholder={t('Fill email')} value={title()} />
|
<input type="email" name="email" ref={emailElement} placeholder={t('Fill email')} value={title()} />
|
||||||
<button class="button--light" onClick={() => emailElement?.value && subscribe()}>
|
<button
|
||||||
|
class={clsx(styles.button, 'button--light')}
|
||||||
|
onClick={() => emailElement?.value && subscribe()}
|
||||||
|
>
|
||||||
{t('Subscribe')}
|
{t('Subscribe')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,3 +1,61 @@
|
||||||
|
.editor {
|
||||||
|
flex: 1;
|
||||||
|
padding-top: 1em;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: rgb(0 100 200);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited {
|
||||||
|
color: rgb(0 80 160);
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
-webkit-padding: 0.4em 0;
|
||||||
|
padding: 0.4em;
|
||||||
|
margin: 0 0 0.5em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:disabled {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
color: #333;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:not(:disabled):active {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus {
|
||||||
|
border-color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -5,3 +63,11 @@
|
||||||
.markdown {
|
.markdown {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 4px 10px rgb(0 0 0 / 25%);
|
||||||
|
color: #000;
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import type { EditorView } from 'prosemirror-view'
|
||||||
import type { EditorState } from 'prosemirror-state'
|
import type { EditorState } from 'prosemirror-state'
|
||||||
import { useState } from '../store/context'
|
import { useState } from '../store/context'
|
||||||
import { ProseMirror } from './ProseMirror'
|
import { ProseMirror } from './ProseMirror'
|
||||||
import '../styles/Editor.scss'
|
|
||||||
import styles from './Editor.module.scss'
|
import styles from './Editor.module.scss'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
|
|
||||||
|
@ -14,7 +13,7 @@ export const Editor = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProseMirror
|
<ProseMirror
|
||||||
cssClass={clsx('editor', 'col-md-6', 'shift-content', {
|
cssClass={clsx(styles.editor, 'col-md-6', 'shift-content', {
|
||||||
[styles.error]: store.error,
|
[styles.error]: store.error,
|
||||||
[styles.markdown]: store.markdown
|
[styles.markdown]: store.markdown
|
||||||
})}
|
})}
|
||||||
|
|
52
src/components/Editor/components/Error.module.scss
Normal file
52
src/components/Editor/components/Error.module.scss
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
.error {
|
||||||
|
width: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 50px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
height: 50px;
|
||||||
|
padding: 0 20px;
|
||||||
|
font-size: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
outline: none;
|
||||||
|
text-decoration: none;
|
||||||
|
background: none;
|
||||||
|
font-family: inherit;
|
||||||
|
color: var(--foreground);
|
||||||
|
border: 1px solid var(--foreground);
|
||||||
|
|
||||||
|
&.primary {
|
||||||
|
color: var(--primary-foreground);
|
||||||
|
border: 0;
|
||||||
|
background: var(--primary-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
width: 100%;
|
||||||
|
height: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background: var(--foreground);
|
||||||
|
border: 1px solid var(--foreground);
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { Switch, Match } from 'solid-js'
|
import { Switch, Match } from 'solid-js'
|
||||||
import { useState } from '../store/context'
|
import { useState } from '../store/context'
|
||||||
import '../styles/Button.scss'
|
import styles from './Error.module.scss'
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const [store] = useState()
|
const [store] = useState()
|
||||||
|
@ -24,8 +24,8 @@ const InvalidState = (props: { title: string }) => {
|
||||||
const onClick = () => ctrl.clean()
|
const onClick = () => ctrl.clean()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="error">
|
<div class={styles.error}>
|
||||||
<div class="container">
|
<div class={styles.container}>
|
||||||
<h1>{props.title}</h1>
|
<h1>{props.title}</h1>
|
||||||
<p>
|
<p>
|
||||||
There is an error with the editor state. This is probably due to an old version in which the data
|
There is an error with the editor state. This is probably due to an old version in which the data
|
||||||
|
@ -35,7 +35,7 @@ const InvalidState = (props: { title: string }) => {
|
||||||
<pre>
|
<pre>
|
||||||
<code>{JSON.stringify(store.error.props)}</code>
|
<code>{JSON.stringify(store.error.props)}</code>
|
||||||
</pre>
|
</pre>
|
||||||
<button class="primary" onClick={onClick}>
|
<button class={styles.primary} onClick={onClick}>
|
||||||
Clean
|
Clean
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,13 +53,13 @@ const Other = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="error">
|
<div class={styles.error}>
|
||||||
<div class="container">
|
<div class={styles.container}>
|
||||||
<h1>An error occurred.</h1>
|
<h1>An error occurred.</h1>
|
||||||
<pre>
|
<pre>
|
||||||
<code>{getMessage()}</code>
|
<code>{getMessage()}</code>
|
||||||
</pre>
|
</pre>
|
||||||
<button class="primary" onClick={onClick}>
|
<button class={styles.primary} onClick={onClick}>
|
||||||
Close
|
Close
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
border-color: var(--background);
|
border-color: var(--background);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
|
||||||
&.dark {
|
.dark & {
|
||||||
background: var(--foreground);
|
background: var(--foreground);
|
||||||
color: var(--background);
|
color: var(--background);
|
||||||
border-color: var(--foreground);
|
border-color: var(--foreground);
|
|
@ -1,6 +1,7 @@
|
||||||
import type { JSX } from 'solid-js/jsx-runtime'
|
import type { JSX } from 'solid-js/jsx-runtime'
|
||||||
import type { Config } from '../store/context'
|
import type { Config } from '../store/context'
|
||||||
import '../styles/Layout.scss'
|
import { clsx } from 'clsx'
|
||||||
|
import styles from './Layout.module.scss'
|
||||||
|
|
||||||
export type Styled = {
|
export type Styled = {
|
||||||
children: JSX.Element
|
children: JSX.Element
|
||||||
|
@ -12,7 +13,11 @@ export type Styled = {
|
||||||
|
|
||||||
export const Layout = (props: Styled) => {
|
export const Layout = (props: Styled) => {
|
||||||
return (
|
return (
|
||||||
<div onMouseEnter={props.onMouseEnter} class="layout container" data-testid={props['data-testid']}>
|
<div
|
||||||
|
onMouseEnter={props.onMouseEnter}
|
||||||
|
class={clsx(styles.layout, 'container')}
|
||||||
|
data-testid={props['data-testid']}
|
||||||
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { EditorState, EditorStateConfig, Transaction } from 'prosemirror-state'
|
||||||
import { EditorView } from 'prosemirror-view'
|
import { EditorView } from 'prosemirror-view'
|
||||||
import { Schema } from 'prosemirror-model'
|
import { Schema } from 'prosemirror-model'
|
||||||
import type { NodeViewFn, ProseMirrorExtension, ProseMirrorState } from '../prosemirror/helpers'
|
import type { NodeViewFn, ProseMirrorExtension, ProseMirrorState } from '../prosemirror/helpers'
|
||||||
|
import '../styles/ProseMirror.scss'
|
||||||
|
|
||||||
interface ProseMirrorProps {
|
interface ProseMirrorProps {
|
||||||
cssClass?: string
|
cssClass?: string
|
||||||
|
|
|
@ -1,3 +1,221 @@
|
||||||
.withMargin {
|
.sidebarContainer {
|
||||||
margin-bottom: 10px;
|
color: rgb(255 255 255 / 50%);
|
||||||
|
@include font-size(1.6rem);
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: var(--foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
@include font-size(120%);
|
||||||
|
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
height: auto;
|
||||||
|
min-height: 50px;
|
||||||
|
padding: 0 1rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarOff {
|
||||||
|
background: #1f1f1f;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 40px 20px 20px;
|
||||||
|
top: 0;
|
||||||
|
transform: translateX(0);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: none;
|
||||||
|
width: 350px;
|
||||||
|
|
||||||
|
.sidebarContainerHidden & {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarOpener {
|
||||||
|
color: #000;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 1;
|
||||||
|
position: absolute;
|
||||||
|
top: 1em;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cmask id='mask0_1090_23825' style='mask-type:alpha' maskUnits='userSpaceOnUse' x='0' y='14' width='4' height='4'%3E%3Crect y='14.8237' width='3.17647' height='3.17647' fill='%23fff'/%3E%3C/mask%3E%3Cg mask='url(%23mask0_1090_23825)'%3E%3Cpath d='M16.0941 1.05908H0.847027C0.379194 1.05908 0 1.43828 0 1.90611V18.0003L3.38824 14.612H16.0942C16.562 14.612 16.9412 14.2328 16.9412 13.765V1.90614C16.9412 1.43831 16.562 1.05912 16.0942 1.05912L16.0941 1.05908ZM15.2471 12.9179H1.69412V2.7532H15.2471V12.9179Z' fill='black'/%3E%3C/g%3E%3Crect x='1' y='1' width='16' height='12.8235' stroke='black' stroke-width='2'/%3E%3Crect x='4.23535' y='3.17627' width='9.52941' height='2.11765' fill='black'/%3E%3Crect x='4.23535' y='9.5293' width='7.41176' height='2.11765' fill='black'/%3E%3Crect x='4.23535' y='6.35303' width='5.29412' height='2.11765' fill='black'/%3E%3C/svg%3E");
|
||||||
|
content: '';
|
||||||
|
height: 18px;
|
||||||
|
left: 100%;
|
||||||
|
margin-left: 0.3em;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarCloser {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M13.1517 0.423857L0.42375 13.1518L2.84812 15.5761L15.576 2.84822L13.1517 0.423857Z M15.576 13.1518L2.84812 0.423855L0.423751 2.84822L13.1517 15.5761L15.576 13.1518Z' fill='white'/%3E%3C/svg%3E%0A");
|
||||||
|
cursor: pointer;
|
||||||
|
height: 16px;
|
||||||
|
opacity: 1;
|
||||||
|
position: absolute;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
top: 20px;
|
||||||
|
width: 16px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarLabel {
|
||||||
|
color: var(--foreground);
|
||||||
|
|
||||||
|
> i {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarContainer button,
|
||||||
|
.sidebarContainer a,
|
||||||
|
.sidebarItem {
|
||||||
|
margin: 0;
|
||||||
|
outline: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarContainer a,
|
||||||
|
.sidebarItem {
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 2px 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarLink {
|
||||||
|
background: none;
|
||||||
|
border: 0;
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: inherit;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
> span i {
|
||||||
|
position: relative;
|
||||||
|
box-shadow: none;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
color: var(--foreground);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.draft {
|
||||||
|
color: rgb(255 255 255 / 50%);
|
||||||
|
line-height: 1.4;
|
||||||
|
margin: 0 0 1em 1.5em;
|
||||||
|
width: calc(100% - 2rem);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> span {
|
||||||
|
justify-self: flex-end;
|
||||||
|
margin-left: auto;
|
||||||
|
|
||||||
|
> i {
|
||||||
|
border: 1px solid;
|
||||||
|
border-bottom-width: 2px;
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
display: inline-block;
|
||||||
|
color: inherit;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin: 0 0.5em 0 0;
|
||||||
|
padding: 1px 4px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.themeSwitcher {
|
||||||
|
border-bottom: 1px solid rgb(255 255 255 / 30%);
|
||||||
|
border-top: 1px solid rgb(255 255 255 / 30%);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 1rem;
|
||||||
|
padding: 1em 0;
|
||||||
|
|
||||||
|
input[type='checkbox'] {
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
+ label {
|
||||||
|
background: url("data:image/svg+xml,%3Csvg width='10' height='10' viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.20869 7.73227C5.22953 7.36499 4.38795 6.70402 3.79906 5.83976C3.2103 4.97565 2.90318 3.95064 2.91979 2.90512C2.93639 1.8597 3.27597 0.844915 3.8919 0C2.82862 0.254038 1.87585 0.844877 1.17594 1.68438C0.475894 2.52388 0.0660276 3.5671 0.00731938 4.6585C-0.0513888 5.74989 0.244296 6.83095 0.850296 7.74073C1.45631 8.65037 2.34006 9.33992 3.36994 9.70637C4.39987 10.073 5.52063 10.0969 6.56523 9.77466C7.60985 9.45247 8.52223 8.80134 9.16667 7.91837C8.1842 8.15404 7.15363 8.08912 6.20869 7.73205V7.73227Z' fill='white'/%3E%3C/svg%3E%0A")
|
||||||
|
no-repeat 30px 9px,
|
||||||
|
url("data:image/svg+xml,%3Csvg width='12' height='12' viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.41196 0H5.58811V2.43024H6.41196V0ZM5.99988 8.96576C4.36601 8.96576 3.03419 7.63397 3.03419 6.00007C3.04792 4.3662 4.36598 3.04818 5.99988 3.03439C7.63375 3.03439 8.96557 4.3662 8.96557 6.00007C8.96557 7.63395 7.63375 8.96576 5.99988 8.96576ZM5.58811 9.56977H6.41196V12H5.58811V9.56977ZM12.0002 5.58811H9.56996V6.41196H12.0002V5.58811ZM0 5.58811H2.43024V6.41196H0V5.58811ZM8.81339 3.76727L10.5318 2.04891L9.94925 1.46641L8.23089 3.18477L8.81339 3.76727ZM3.7745 8.8129L2.05614 10.5313L1.47364 9.94877L3.192 8.2304L3.7745 8.8129ZM9.95043 10.5269L10.5329 9.94437L8.81456 8.22601L8.23207 8.80851L9.95043 10.5269ZM3.76864 3.18731L3.18614 3.76981L1.46778 2.05145L2.05028 1.46895L3.76864 3.18731Z' fill='%231F1F1F'/%3E%3C/svg%3E%0A")
|
||||||
|
#000 no-repeat 8px 8px;
|
||||||
|
border-radius: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
height: 28px;
|
||||||
|
line-height: 10em;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
width: 46px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 100%;
|
||||||
|
content: '';
|
||||||
|
height: 16px;
|
||||||
|
left: 6px;
|
||||||
|
position: absolute;
|
||||||
|
top: 6px;
|
||||||
|
transition: left 0.3s, color 0.3s;
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked + label {
|
||||||
|
background-color: #fff;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: #1f1f1f;
|
||||||
|
left: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,25 @@
|
||||||
import { For, Show, createEffect, createSignal, onCleanup } from 'solid-js'
|
import { For, Show, createEffect, createSignal, onCleanup, onMount } from 'solid-js'
|
||||||
import { unwrap } from 'solid-js/store'
|
import { unwrap } from 'solid-js/store'
|
||||||
import { undo, redo } from 'prosemirror-history'
|
import { undo, redo } from 'prosemirror-history'
|
||||||
import { Draft, useState } from '../store/context'
|
import { Draft, useState } from '../store/context'
|
||||||
import { mod } from '../env'
|
|
||||||
import * as remote from '../remote'
|
import * as remote from '../remote'
|
||||||
import { isEmpty } from '../prosemirror/helpers'
|
import { isEmpty } from '../prosemirror/helpers'
|
||||||
import type { Styled } from './Layout'
|
import type { Styled } from './Layout'
|
||||||
import '../styles/Sidebar.scss'
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import styles from './Sidebar.module.scss'
|
import styles from './Sidebar.module.scss'
|
||||||
|
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
|
||||||
|
import { useEscKeyDownHandler } from '../../../utils/useEscKeyDownHandler'
|
||||||
|
import { hideModal } from '../../../stores/ui'
|
||||||
|
|
||||||
const Off = (props) => <div class="sidebar-off">{props.children}</div>
|
const Off = (props) => <div class={styles.sidebarOff}>{props.children}</div>
|
||||||
|
|
||||||
const Label = (props: Styled) => <h3 class="sidebar-label">{props.children}</h3>
|
const Label = (props: Styled) => <h3 class={styles.sidebarLabel}>{props.children}</h3>
|
||||||
|
|
||||||
const Link = (
|
const Link = (
|
||||||
props: Styled & { withMargin?: boolean; disabled?: boolean; title?: string; className?: string }
|
props: Styled & { withMargin?: boolean; disabled?: boolean; title?: string; className?: string }
|
||||||
) => (
|
) => (
|
||||||
<button
|
<button
|
||||||
class={clsx('sidebar-link', props.className, {
|
class={clsx(styles.sidebarLink, props.className, {
|
||||||
[styles.withMargin]: props.withMargin
|
[styles.withMargin]: props.withMargin
|
||||||
})}
|
})}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
|
@ -33,10 +34,12 @@ const Link = (
|
||||||
export const Sidebar = () => {
|
export const Sidebar = () => {
|
||||||
const [store, ctrl] = useState()
|
const [store, ctrl] = useState()
|
||||||
const [lastAction, setLastAction] = createSignal<string | undefined>()
|
const [lastAction, setLastAction] = createSignal<string | undefined>()
|
||||||
|
|
||||||
const toggleTheme = () => {
|
const toggleTheme = () => {
|
||||||
document.body.classList.toggle('dark')
|
document.body.classList.toggle('dark')
|
||||||
ctrl.updateConfig({ theme: document.body.className })
|
ctrl.updateConfig({ theme: document.body.className })
|
||||||
}
|
}
|
||||||
|
|
||||||
const collabText = () => {
|
const collabText = () => {
|
||||||
if (store.collab?.started) {
|
if (store.collab?.started) {
|
||||||
return 'Stop'
|
return 'Stop'
|
||||||
|
@ -70,14 +73,12 @@ export const Sidebar = () => {
|
||||||
const onCopyAllAsMd = () =>
|
const onCopyAllAsMd = () =>
|
||||||
remote.copyAllAsMarkdown(editorView().state).then(() => setLastAction('copy-md'))
|
remote.copyAllAsMarkdown(editorView().state).then(() => setLastAction('copy-md'))
|
||||||
const onDiscard = () => ctrl.discard()
|
const onDiscard = () => ctrl.discard()
|
||||||
const [isHidden, setIsHidden] = createSignal<boolean | false>()
|
const [isHidden, setIsHidden] = createSignal(true)
|
||||||
|
|
||||||
const toggleSidebar = () => {
|
const toggleSidebar = () => {
|
||||||
setIsHidden(!isHidden())
|
setIsHidden((oldIsHidden) => !oldIsHidden)
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleSidebar()
|
|
||||||
|
|
||||||
const onCollab = () => {
|
const onCollab = () => {
|
||||||
const state = unwrap(store)
|
const state = unwrap(store)
|
||||||
store.collab?.started ? ctrl.stopCollab(state) : ctrl.startCollab(state)
|
store.collab?.started ? ctrl.stopCollab(state) : ctrl.startCollab(state)
|
||||||
|
@ -117,7 +118,7 @@ export const Sidebar = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// eslint-disable-next-line solid/no-react-specific-props
|
// eslint-disable-next-line solid/no-react-specific-props
|
||||||
<Link className="draft" onClick={() => onOpenDraft(p.draft)} data-testid="open">
|
<Link className={styles.draft} onClick={() => onOpenDraft(p.draft)} data-testid="open">
|
||||||
{text()} {p.draft.path && '📎'}
|
{text()} {p.draft.path && '📎'}
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
|
@ -131,7 +132,7 @@ export const Sidebar = () => {
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
setLastAction()
|
setLastAction()
|
||||||
}, store.lastModified)
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!lastAction()) return
|
if (!lastAction()) return
|
||||||
|
@ -141,63 +142,84 @@ export const Sidebar = () => {
|
||||||
onCleanup(() => clearTimeout(id))
|
onCleanup(() => clearTimeout(id))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const [mod, setMod] = createSignal<'Ctrl' | 'Cmd'>('Ctrl')
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
setMod(navigator.platform.includes('Mac') ? 'Cmd' : 'Ctrl')
|
||||||
|
})
|
||||||
|
|
||||||
|
const containerRef: { current: HTMLElement } = {
|
||||||
|
current: null
|
||||||
|
}
|
||||||
|
|
||||||
|
useEscKeyDownHandler(() => setIsHidden(true))
|
||||||
|
useOutsideClickHandler({
|
||||||
|
containerRef,
|
||||||
|
predicate: () => !isHidden(),
|
||||||
|
handler: () => setIsHidden(true)
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={'sidebar-container' + (isHidden() ? ' sidebar-container--hidden' : '')}>
|
<div
|
||||||
<span class="sidebar-opener" onClick={toggleSidebar}>
|
class={clsx(styles.sidebarContainer, {
|
||||||
|
[styles.sidebarContainerHidden]: isHidden()
|
||||||
|
})}
|
||||||
|
ref={(el) => (containerRef.current = el)}
|
||||||
|
>
|
||||||
|
<span class={styles.sidebarOpener} onClick={toggleSidebar}>
|
||||||
Советы и предложения
|
Советы и предложения
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<Off onClick={() => editorView().focus()}>
|
<Off onClick={() => editorView().focus()}>
|
||||||
<div class="sidebar-closer" onClick={toggleSidebar} />
|
<div class={styles.sidebarCloser} onClick={toggleSidebar} />
|
||||||
<Show when={true}>
|
|
||||||
<div>
|
|
||||||
{store.path && (
|
|
||||||
<Label>
|
|
||||||
<i>({store.path.slice(Math.max(0, store.path.length - 24))})</i>
|
|
||||||
</Label>
|
|
||||||
)}
|
|
||||||
<Link>Пригласить соавторов</Link>
|
|
||||||
<Link>Настройки публикации</Link>
|
|
||||||
<Link>История правок</Link>
|
|
||||||
|
|
||||||
<div class="theme-switcher">
|
<div>
|
||||||
Ночная тема
|
{store.path && (
|
||||||
<input type="checkbox" name="theme" id="theme" onClick={toggleTheme} />
|
<Label>
|
||||||
<label for="theme">Ночная тема</label>
|
<i>({store.path.slice(Math.max(0, store.path.length - 24))})</i>
|
||||||
</div>
|
</Label>
|
||||||
<Link
|
)}
|
||||||
onClick={onDiscard}
|
<Link>Пригласить соавторов</Link>
|
||||||
disabled={!store.path && store.drafts.length === 0 && isEmpty(store.text)}
|
<Link>Настройки публикации</Link>
|
||||||
data-testid="discard"
|
<Link>История правок</Link>
|
||||||
>
|
|
||||||
{discardText()} <Keys keys={[mod, 'w']} />
|
<div class={styles.themeSwitcher}>
|
||||||
</Link>
|
Ночная тема
|
||||||
<Link onClick={onUndo}>
|
<input type="checkbox" name="theme" id="theme" onClick={toggleTheme} />
|
||||||
Undo <Keys keys={[mod, 'z']} />
|
<label for="theme">Ночная тема</label>
|
||||||
</Link>
|
|
||||||
<Link onClick={onRedo}>
|
|
||||||
Redo <Keys keys={[mod, 'Shift', 'z']} />
|
|
||||||
</Link>
|
|
||||||
<Link onClick={onToggleMarkdown} data-testid="markdown">
|
|
||||||
Markdown mode {store.markdown && '✅'} <Keys keys={[mod, 'm']} />
|
|
||||||
</Link>
|
|
||||||
<Link onClick={onCopyAllAsMd}>Copy all as MD {lastAction() === 'copy-md' && '📋'}</Link>
|
|
||||||
<Show when={store.drafts.length > 0}>
|
|
||||||
<h4>Drafts:</h4>
|
|
||||||
<p>
|
|
||||||
<For each={store.drafts}>{(draft) => <DraftLink draft={draft} />}</For>
|
|
||||||
</p>
|
|
||||||
</Show>
|
|
||||||
<Link onClick={onCollab} title={store.collab?.error ? 'Connection error' : ''}>
|
|
||||||
Collab {collabText()}
|
|
||||||
</Link>
|
|
||||||
<Show when={collabUsers() > 0}>
|
|
||||||
<span>
|
|
||||||
{collabUsers()} {collabUsers() === 1 ? 'user' : 'users'} connected
|
|
||||||
</span>
|
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
<Link
|
||||||
|
onClick={onDiscard}
|
||||||
|
disabled={!store.path && store.drafts.length === 0 && isEmpty(store.text)}
|
||||||
|
data-testid="discard"
|
||||||
|
>
|
||||||
|
{discardText()} <Keys keys={[mod(), 'w']} />
|
||||||
|
</Link>
|
||||||
|
<Link onClick={onUndo}>
|
||||||
|
Undo <Keys keys={[mod(), 'z']} />
|
||||||
|
</Link>
|
||||||
|
<Link onClick={onRedo}>
|
||||||
|
Redo <Keys keys={[mod(), 'Shift', 'z']} />
|
||||||
|
</Link>
|
||||||
|
<Link onClick={onToggleMarkdown} data-testid="markdown">
|
||||||
|
Markdown mode {store.markdown && '✅'} <Keys keys={[mod(), 'm']} />
|
||||||
|
</Link>
|
||||||
|
<Link onClick={onCopyAllAsMd}>Copy all as MD {lastAction() === 'copy-md' && '📋'}</Link>
|
||||||
|
<Show when={store.drafts.length > 0}>
|
||||||
|
<h4>Drafts:</h4>
|
||||||
|
<p>
|
||||||
|
<For each={store.drafts}>{(draft) => <DraftLink draft={draft} />}</For>
|
||||||
|
</p>
|
||||||
|
</Show>
|
||||||
|
<Link onClick={onCollab} title={store.collab?.error ? 'Connection error' : ''}>
|
||||||
|
Collab {collabText()}
|
||||||
|
</Link>
|
||||||
|
<Show when={collabUsers() > 0}>
|
||||||
|
<span>
|
||||||
|
{collabUsers()} {collabUsers() === 1 ? 'user' : 'users'} connected
|
||||||
|
</span>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
</Off>
|
</Off>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const dbPromise = async () => {
|
import { openDB } from 'idb'
|
||||||
const { openDB } = await import('idb')
|
|
||||||
|
const dbPromise = () => {
|
||||||
return openDB('discours.io', 2, {
|
return openDB('discours.io', 2, {
|
||||||
upgrade(db) {
|
upgrade(db) {
|
||||||
db.createObjectStore('keyval')
|
db.createObjectStore('keyval')
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
export const isDark = () => (window as any).matchMedia('(prefers-color-scheme: dark)').matches
|
export const isDark = () =>
|
||||||
export const mod = 'Ctrl'
|
typeof window !== undefined && window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
export const alt = 'Alt'
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ import { keymap } from 'prosemirror-keymap'
|
||||||
import type { ProseMirrorExtension } from '../helpers'
|
import type { ProseMirrorExtension } from '../helpers'
|
||||||
import type OrderedMap from 'orderedmap'
|
import type OrderedMap from 'orderedmap'
|
||||||
|
|
||||||
|
import layoutStyles from '../../components/Layout.module.scss'
|
||||||
|
|
||||||
const plainSchema = new Schema({
|
const plainSchema = new Schema({
|
||||||
nodes: {
|
nodes: {
|
||||||
doc: {
|
doc: {
|
||||||
|
@ -53,6 +55,6 @@ export default (plain = false): ProseMirrorExtension => ({
|
||||||
keymap(buildKeymap(schema)),
|
keymap(buildKeymap(schema)),
|
||||||
keymap(baseKeymap),
|
keymap(baseKeymap),
|
||||||
history(),
|
history(),
|
||||||
dropCursor({ class: 'drop-cursor' })
|
dropCursor({ class: layoutStyles.dropCursor })
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,13 +2,14 @@ import { renderGrouped } from 'prosemirror-menu'
|
||||||
import { Plugin } from 'prosemirror-state'
|
import { Plugin } from 'prosemirror-state'
|
||||||
import type { ProseMirrorExtension } from '../helpers'
|
import type { ProseMirrorExtension } from '../helpers'
|
||||||
import { buildMenuItems } from './menu'
|
import { buildMenuItems } from './menu'
|
||||||
|
import editorStyles from '../../components/Editor.module.scss'
|
||||||
|
|
||||||
export class SelectionTooltip {
|
export class SelectionTooltip {
|
||||||
tooltip: any
|
tooltip: any
|
||||||
|
|
||||||
constructor(view: any, schema: any) {
|
constructor(view: any, schema: any) {
|
||||||
this.tooltip = document.createElement('div')
|
this.tooltip = document.createElement('div')
|
||||||
this.tooltip.className = 'tooltip'
|
this.tooltip.className = editorStyles.tooltip
|
||||||
view.dom.parentNode.appendChild(this.tooltip)
|
view.dom.parentNode.appendChild(this.tooltip)
|
||||||
const { dom } = renderGrouped(view, buildMenuItems(schema).fullMenu as any)
|
const { dom } = renderGrouped(view, buildMenuItems(schema).fullMenu as any)
|
||||||
this.tooltip.appendChild(dom)
|
this.tooltip.appendChild(dom)
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { undo as yUndo, redo as yRedo } from 'y-prosemirror'
|
||||||
import debounce from 'lodash/debounce'
|
import debounce from 'lodash/debounce'
|
||||||
import { createSchema, createExtensions, createEmptyText } from '../prosemirror/setup'
|
import { createSchema, createExtensions, createEmptyText } from '../prosemirror/setup'
|
||||||
import { State, Draft, Config, ServiceError, newState, ExtensionsProps, EditorActions } from './context'
|
import { State, Draft, Config, ServiceError, newState, ExtensionsProps, EditorActions } from './context'
|
||||||
import { mod } from '../env'
|
|
||||||
import { serialize, createMarkdownParser } from '../markdown'
|
import { serialize, createMarkdownParser } from '../markdown'
|
||||||
import db from '../db'
|
import db from '../db'
|
||||||
import { isEmpty, isInitialized } from '../prosemirror/helpers'
|
import { isEmpty, isInitialized } from '../prosemirror/helpers'
|
||||||
|
@ -102,13 +101,13 @@ export const createCtrl = (initial: State): [Store<State>, EditorActions] => {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const keymap = {
|
const keymap: ExtensionsProps['keymap'] = {
|
||||||
[`${mod}-w`]: discard,
|
[`Mod-w`]: discard,
|
||||||
[`${mod}-z`]: onUndo,
|
[`Mod-z`]: onUndo,
|
||||||
[`Shift-${mod}-z`]: onRedo,
|
[`Shift-Mod-z`]: onRedo,
|
||||||
[`${mod}-y`]: onRedo,
|
[`Mod-y`]: onRedo,
|
||||||
[`${mod}-m`]: toggleMarkdown
|
[`Mod-m`]: toggleMarkdown
|
||||||
} as ExtensionsProps['keymap']
|
}
|
||||||
|
|
||||||
const createTextFromDraft = async (draft: Draft) => {
|
const createTextFromDraft = async (draft: Draft) => {
|
||||||
const state = unwrap(store)
|
const state = unwrap(store)
|
||||||
|
|
|
@ -63,7 +63,6 @@ export interface Collab {
|
||||||
export type LoadingType = 'loading' | 'initialized'
|
export type LoadingType = 'loading' | 'initialized'
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
isMac?: boolean
|
|
||||||
text?: ProseMirrorState
|
text?: ProseMirrorState
|
||||||
editorView?: EditorView
|
editorView?: EditorView
|
||||||
extensions?: ProseMirrorExtension[]
|
extensions?: ProseMirrorExtension[]
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
button {
|
|
||||||
height: 50px;
|
|
||||||
padding: 0 20px;
|
|
||||||
font-size: 18px;
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
outline: none;
|
|
||||||
text-decoration: none;
|
|
||||||
background: none;
|
|
||||||
font-family: inherit;
|
|
||||||
color: var(--foreground);
|
|
||||||
border: 1px solid var(--foreground);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
button.primary {
|
|
||||||
color: var(--primary-foreground);
|
|
||||||
border: 0;
|
|
||||||
background: var(--primary-background);
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
.error {
|
|
||||||
width: 100%;
|
|
||||||
overflow: y-auto;
|
|
||||||
padding: 50px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.error .container {
|
|
||||||
max-width: 800px;
|
|
||||||
width: 100%;
|
|
||||||
height: fit-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error pre {
|
|
||||||
background: var(--foreground);
|
|
||||||
border: 1px solid var(--foreground);
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
border-radius: 2px;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
.index {
|
|
||||||
width: 350px;
|
|
||||||
}
|
|
|
@ -1,61 +1,3 @@
|
||||||
.editor {
|
|
||||||
flex: 1;
|
|
||||||
padding-top: 1em;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: rgb(0 100 200);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:visited {
|
|
||||||
color: rgb(0 80 160);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
input,
|
|
||||||
button,
|
|
||||||
select,
|
|
||||||
textarea {
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
-webkit-padding: 0.4em 0;
|
|
||||||
padding: 0.4em;
|
|
||||||
margin: 0 0 0.5em;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:disabled {
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
color: #333;
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:disabled {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:not(:disabled):active {
|
|
||||||
background-color: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:focus {
|
|
||||||
border-color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ProseMirror {
|
.ProseMirror {
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
|
@ -385,11 +327,3 @@ li.ProseMirror-selectednode::after {
|
||||||
background: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M13.1512 0.423856L0.423263 13.1518L2.84763 15.5761L15.5756 2.84822L13.1512 0.423856Z M15.5755 13.1518L2.84763 0.423855L0.423263 2.84822L13.1512 15.5761L15.5755 13.1518Z' fill='%23393840'/%3E%3C/svg%3E%0A")
|
background: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M13.1512 0.423856L0.423263 13.1518L2.84763 15.5761L15.5756 2.84822L13.1512 0.423856Z M15.5755 13.1518L2.84763 0.423855L0.423263 2.84822L13.1512 15.5761L15.5755 13.1518Z' fill='%23393840'/%3E%3C/svg%3E%0A")
|
||||||
center no-repeat;
|
center no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip {
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: 0 4px 10px rgb(0 0 0 / 25%);
|
|
||||||
color: #000;
|
|
||||||
display: flex;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
|
@ -1,226 +0,0 @@
|
||||||
.sidebar-container {
|
|
||||||
color: rgb(255 255 255 / 50%);
|
|
||||||
@include font-size(1.6rem);
|
|
||||||
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
top: 0;
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: var(--foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
@include font-size(120%);
|
|
||||||
|
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
height: auto;
|
|
||||||
min-height: 50px;
|
|
||||||
padding: 0 1rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-off {
|
|
||||||
background: #1f1f1f;
|
|
||||||
height: 100%;
|
|
||||||
min-height: 100vh;
|
|
||||||
padding: 40px 20px 20px;
|
|
||||||
top: 0;
|
|
||||||
transform: translateX(0);
|
|
||||||
transition: transform 0.3s;
|
|
||||||
overflow-y: auto;
|
|
||||||
scrollbar-width: none;
|
|
||||||
width: 350px;
|
|
||||||
|
|
||||||
.sidebar-container--hidden & {
|
|
||||||
transform: translateX(100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-opener {
|
|
||||||
color: #000;
|
|
||||||
cursor: pointer;
|
|
||||||
opacity: 1;
|
|
||||||
position: absolute;
|
|
||||||
top: 1em;
|
|
||||||
transition: opacity 0.3s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cmask id='mask0_1090_23825' style='mask-type:alpha' maskUnits='userSpaceOnUse' x='0' y='14' width='4' height='4'%3E%3Crect y='14.8237' width='3.17647' height='3.17647' fill='%23fff'/%3E%3C/mask%3E%3Cg mask='url(%23mask0_1090_23825)'%3E%3Cpath d='M16.0941 1.05908H0.847027C0.379194 1.05908 0 1.43828 0 1.90611V18.0003L3.38824 14.612H16.0942C16.562 14.612 16.9412 14.2328 16.9412 13.765V1.90614C16.9412 1.43831 16.562 1.05912 16.0942 1.05912L16.0941 1.05908ZM15.2471 12.9179H1.69412V2.7532H15.2471V12.9179Z' fill='black'/%3E%3C/g%3E%3Crect x='1' y='1' width='16' height='12.8235' stroke='black' stroke-width='2'/%3E%3Crect x='4.23535' y='3.17627' width='9.52941' height='2.11765' fill='black'/%3E%3Crect x='4.23535' y='9.5293' width='7.41176' height='2.11765' fill='black'/%3E%3Crect x='4.23535' y='6.35303' width='5.29412' height='2.11765' fill='black'/%3E%3C/svg%3E");
|
|
||||||
content: '';
|
|
||||||
height: 18px;
|
|
||||||
left: 100%;
|
|
||||||
margin-left: 0.3em;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
width: 18px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-closer {
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M13.1517 0.423857L0.42375 13.1518L2.84812 15.5761L15.576 2.84822L13.1517 0.423857Z M15.576 13.1518L2.84812 0.423855L0.423751 2.84822L13.1517 15.5761L15.576 13.1518Z' fill='white'/%3E%3C/svg%3E%0A");
|
|
||||||
cursor: pointer;
|
|
||||||
height: 16px;
|
|
||||||
opacity: 1;
|
|
||||||
position: absolute;
|
|
||||||
transition: opacity 0.3s;
|
|
||||||
top: 20px;
|
|
||||||
width: 16px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-label {
|
|
||||||
color: var(--foreground);
|
|
||||||
|
|
||||||
> i {
|
|
||||||
text-transform: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-sub {
|
|
||||||
margin: 10px 0;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-container button,
|
|
||||||
.sidebar-container a,
|
|
||||||
.sidebar-item {
|
|
||||||
margin: 0;
|
|
||||||
outline: none;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
line-height: 24px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-container a,
|
|
||||||
.sidebar-item {
|
|
||||||
font-size: 18px;
|
|
||||||
padding: 2px 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-link {
|
|
||||||
background: none;
|
|
||||||
border: 0;
|
|
||||||
color: inherit;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: inherit;
|
|
||||||
justify-content: flex-start;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: #fff !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
> span i {
|
|
||||||
position: relative;
|
|
||||||
box-shadow: none;
|
|
||||||
top: 1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[disabled] {
|
|
||||||
color: var(--foreground);
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.draft {
|
|
||||||
color: rgb(255 255 255 / 50%);
|
|
||||||
line-height: 1.4;
|
|
||||||
margin: 0 0 1em 1.5em;
|
|
||||||
width: calc(100% - 2rem);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> span {
|
|
||||||
justify-self: flex-end;
|
|
||||||
margin-left: auto;
|
|
||||||
|
|
||||||
> i {
|
|
||||||
border: 1px solid;
|
|
||||||
border-bottom-width: 2px;
|
|
||||||
border-radius: 0.2rem;
|
|
||||||
display: inline-block;
|
|
||||||
color: inherit;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.4;
|
|
||||||
margin: 0 0.5em 0 0;
|
|
||||||
padding: 1px 4px;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-switcher {
|
|
||||||
border-bottom: 1px solid rgb(255 255 255 / 30%);
|
|
||||||
border-top: 1px solid rgb(255 255 255 / 30%);
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin: 1rem;
|
|
||||||
padding: 1em 0;
|
|
||||||
|
|
||||||
input[type='checkbox'] {
|
|
||||||
opacity: 0;
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
+ label {
|
|
||||||
background: url("data:image/svg+xml,%3Csvg width='10' height='10' viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.20869 7.73227C5.22953 7.36499 4.38795 6.70402 3.79906 5.83976C3.2103 4.97565 2.90318 3.95064 2.91979 2.90512C2.93639 1.8597 3.27597 0.844915 3.8919 0C2.82862 0.254038 1.87585 0.844877 1.17594 1.68438C0.475894 2.52388 0.0660276 3.5671 0.00731938 4.6585C-0.0513888 5.74989 0.244296 6.83095 0.850296 7.74073C1.45631 8.65037 2.34006 9.33992 3.36994 9.70637C4.39987 10.073 5.52063 10.0969 6.56523 9.77466C7.60985 9.45247 8.52223 8.80134 9.16667 7.91837C8.1842 8.15404 7.15363 8.08912 6.20869 7.73205V7.73227Z' fill='white'/%3E%3C/svg%3E%0A")
|
|
||||||
no-repeat 30px 9px,
|
|
||||||
url("data:image/svg+xml,%3Csvg width='12' height='12' viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.41196 0H5.58811V2.43024H6.41196V0ZM5.99988 8.96576C4.36601 8.96576 3.03419 7.63397 3.03419 6.00007C3.04792 4.3662 4.36598 3.04818 5.99988 3.03439C7.63375 3.03439 8.96557 4.3662 8.96557 6.00007C8.96557 7.63395 7.63375 8.96576 5.99988 8.96576ZM5.58811 9.56977H6.41196V12H5.58811V9.56977ZM12.0002 5.58811H9.56996V6.41196H12.0002V5.58811ZM0 5.58811H2.43024V6.41196H0V5.58811ZM8.81339 3.76727L10.5318 2.04891L9.94925 1.46641L8.23089 3.18477L8.81339 3.76727ZM3.7745 8.8129L2.05614 10.5313L1.47364 9.94877L3.192 8.2304L3.7745 8.8129ZM9.95043 10.5269L10.5329 9.94437L8.81456 8.22601L8.23207 8.80851L9.95043 10.5269ZM3.76864 3.18731L3.18614 3.76981L1.46778 2.05145L2.05028 1.46895L3.76864 3.18731Z' fill='%231F1F1F'/%3E%3C/svg%3E%0A")
|
|
||||||
#000 no-repeat 8px 8px;
|
|
||||||
border-radius: 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
display: block;
|
|
||||||
height: 28px;
|
|
||||||
line-height: 10em;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
transition: background-color 0.3s;
|
|
||||||
width: 46px;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 100%;
|
|
||||||
content: '';
|
|
||||||
height: 16px;
|
|
||||||
left: 6px;
|
|
||||||
position: absolute;
|
|
||||||
top: 6px;
|
|
||||||
transition: left 0.3s, color 0.3s;
|
|
||||||
width: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:checked + label {
|
|
||||||
background-color: #fff;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
background-color: #1f1f1f;
|
|
||||||
left: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -134,7 +134,7 @@ export const Header = (props: Props) => {
|
||||||
<div class={styles.usernav}>
|
<div class={styles.usernav}>
|
||||||
<div class={clsx(styles.userControl, styles.userControl, 'col')}>
|
<div class={clsx(styles.userControl, styles.userControl, 'col')}>
|
||||||
<div class={clsx(styles.userControlItem, styles.userControlItemWritePost)}>
|
<div class={clsx(styles.userControlItem, styles.userControlItemWritePost)}>
|
||||||
<a href="/create">
|
<a href="/create" onClick={handleClientRouteLinkClick}>
|
||||||
<span class={styles.textLabel}>{t('Create post')}</span>
|
<span class={styles.textLabel}>{t('Create post')}</span>
|
||||||
<Icon name="pencil" class={styles.icon} />
|
<Icon name="pencil" class={styles.icon} />
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -3,6 +3,7 @@ import type { JSX } from 'solid-js'
|
||||||
import { getLogger } from '../../utils/logger'
|
import { getLogger } from '../../utils/logger'
|
||||||
import './Modal.scss'
|
import './Modal.scss'
|
||||||
import { hideModal, useModalStore } from '../../stores/ui'
|
import { hideModal, useModalStore } from '../../stores/ui'
|
||||||
|
import { useEscKeyDownHandler } from '../../utils/useEscKeyDownHandler'
|
||||||
|
|
||||||
const log = getLogger('modal')
|
const log = getLogger('modal')
|
||||||
|
|
||||||
|
@ -11,10 +12,6 @@ interface ModalProps {
|
||||||
children: JSX.Element
|
children: JSX.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
const keydownHandler = (e: KeyboardEvent) => {
|
|
||||||
if (e.key === 'Escape') hideModal()
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Modal = (props: ModalProps) => {
|
export const Modal = (props: ModalProps) => {
|
||||||
const { modal } = useModalStore()
|
const { modal } = useModalStore()
|
||||||
|
|
||||||
|
@ -22,13 +19,7 @@ export const Modal = (props: ModalProps) => {
|
||||||
if (event.target.classList.contains('modalwrap')) hideModal()
|
if (event.target.classList.contains('modalwrap')) hideModal()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
useEscKeyDownHandler(() => hideModal())
|
||||||
window.addEventListener('keydown', keydownHandler)
|
|
||||||
|
|
||||||
onCleanup(() => {
|
|
||||||
window.removeEventListener('keydown', keydownHandler)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const [visible, setVisible] = createSignal(false)
|
const [visible, setVisible] = createSignal(false)
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.topBorderItem {
|
||||||
|
border-top: 2px solid;
|
||||||
|
padding-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
border: none;
|
border: none;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { createEffect, createSignal, JSX, onCleanup, onMount, Show } from 'solid-js'
|
import { createEffect, createSignal, JSX, onCleanup, onMount, Show } from 'solid-js'
|
||||||
import styles from './Popup.module.scss'
|
import styles from './Popup.module.scss'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
|
import { useOutsideClickHandler } from '../../utils/useOutsideClickHandler'
|
||||||
|
|
||||||
type HorizontalAnchor = 'center' | 'right'
|
type HorizontalAnchor = 'center' | 'right'
|
||||||
|
|
||||||
|
@ -22,29 +23,18 @@ export const Popup = (props: PopupProps) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let container: HTMLDivElement | undefined
|
const containerRef: { current: HTMLElement } = { current: null }
|
||||||
|
|
||||||
const handleClickOutside = (event: MouseEvent & { target: Element }) => {
|
useOutsideClickHandler({
|
||||||
if (!isVisible()) {
|
containerRef,
|
||||||
return
|
predicate: () => isVisible(),
|
||||||
}
|
handler: () => setIsVisible(false)
|
||||||
|
|
||||||
if (event.target === container || container?.contains(event.target)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsVisible(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
document.addEventListener('click', handleClickOutside, { capture: true })
|
|
||||||
onCleanup(() => document.removeEventListener('click', handleClickOutside, { capture: true }))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const toggle = () => setIsVisible((oldVisible) => !oldVisible)
|
const toggle = () => setIsVisible((oldVisible) => !oldVisible)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span class={clsx(styles.container, props.containerCssClass)} ref={container}>
|
<span class={clsx(styles.container, props.containerCssClass)} ref={(el) => (containerRef.current = el)}>
|
||||||
<span onClick={toggle}>{props.trigger}</span>
|
<span onClick={toggle}>{props.trigger}</span>
|
||||||
<Show when={isVisible()}>
|
<Show when={isVisible()}>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Popup, PopupProps } from './Popup'
|
import { Popup, PopupProps } from './Popup'
|
||||||
import { signOut, useAuthStore } from '../../stores/auth'
|
import { signOut, useAuthStore } from '../../stores/auth'
|
||||||
|
import styles from './Popup.module.scss'
|
||||||
|
|
||||||
type ProfilePopupProps = Omit<PopupProps, 'children'>
|
type ProfilePopupProps = Omit<PopupProps, 'children'>
|
||||||
|
|
||||||
|
@ -27,7 +28,7 @@ export const ProfilePopup = (props: ProfilePopupProps) => {
|
||||||
<li>
|
<li>
|
||||||
<a href="#">Настройки</a>
|
<a href="#">Настройки</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li class={styles.topBorderItem}>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
|
|
45
src/components/Pages/ConnectPage.tsx
Normal file
45
src/components/Pages/ConnectPage.tsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { MainLayout } from '../Layouts/MainLayout'
|
||||||
|
|
||||||
|
export const ConnectPage = () => {
|
||||||
|
return (
|
||||||
|
<MainLayout>
|
||||||
|
<article class="container container--static-page">
|
||||||
|
<div class="row">
|
||||||
|
<h1 class="col-md-8 offset-md-2">
|
||||||
|
<span class="wrapped">Предложить идею</span>
|
||||||
|
</h1>
|
||||||
|
<div class="col-md-8 col-lg-6 offset-md-3">
|
||||||
|
<p>
|
||||||
|
Хотите что-то предложить, обсудить или посоветовать? Поделиться темой или идеей? Напишите нам
|
||||||
|
скорее! Если укажете свою почту, мы обязательно ответим.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form action=".">
|
||||||
|
<div class="pretty-form__item">
|
||||||
|
<select id="subject">
|
||||||
|
<option value="">Сотрудничество</option>
|
||||||
|
<option value="">Посоветовать тему</option>
|
||||||
|
<option value="">Сообщить об ошибке</option>
|
||||||
|
<option value="">Предложить проект</option>
|
||||||
|
<option value="">Волонтерство</option>
|
||||||
|
<option value="">Другое</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="pretty-form__item">
|
||||||
|
<input type="text" id="contact-email" placeholder="Email для обратной связи" />
|
||||||
|
<label for="contact-email">Email для обратной связи</label>
|
||||||
|
</div>
|
||||||
|
<div class="pretty-form__item">
|
||||||
|
<textarea id="message" placeholder="Текст сообщения" />
|
||||||
|
<label for="message">Текст сообщения</label>
|
||||||
|
</div>
|
||||||
|
<button class="button">Отправить письмо</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</MainLayout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ConnectPage
|
|
@ -1,11 +1,15 @@
|
||||||
import { newState } from '../Editor/store/context'
|
import { lazy, Suspense } from 'solid-js'
|
||||||
import { MainLayout } from '../Layouts/MainLayout'
|
import { MainLayout } from '../Layouts/MainLayout'
|
||||||
import { CreateView } from '../Views/Create'
|
import { Loading } from '../Loading'
|
||||||
|
|
||||||
|
const CreateView = lazy(() => import('../Views/Create'))
|
||||||
|
|
||||||
export const CreatePage = () => {
|
export const CreatePage = () => {
|
||||||
return (
|
return (
|
||||||
<MainLayout>
|
<MainLayout>
|
||||||
<CreateView state={newState()} />
|
<Suspense fallback={<Loading />}>
|
||||||
|
<CreateView />
|
||||||
|
</Suspense>
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
124
src/components/Pages/about/DiscussionRulesPage.tsx
Normal file
124
src/components/Pages/about/DiscussionRulesPage.tsx
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
import { MainLayout } from '../../Layouts/MainLayout'
|
||||||
|
import { t } from '../../../utils/intl'
|
||||||
|
|
||||||
|
export const DiscussionRulesPage = () => {
|
||||||
|
const title = t('Discussion rules')
|
||||||
|
return (
|
||||||
|
<MainLayout>
|
||||||
|
<article class="container container--static-page">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8 offset-md-2">
|
||||||
|
<h1>
|
||||||
|
<span class="wrapped" innerHTML={title} />
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div class="col-lg-10 offset-md-1">
|
||||||
|
<p>
|
||||||
|
Открытая редакция существует благодаря дружному сообществу авторов
|
||||||
|
и читателей — вдумчивых и сознательных людей, приверженных ценностям
|
||||||
|
гуманизма, демократии и прав человека. Мы очень ценим атмосферу осмысленного
|
||||||
|
общения, которая здесь сложилась. Чтобы сохранить ее такой же уютной
|
||||||
|
и творческой, мы составили правила общения в сообществе, руководствуясь
|
||||||
|
которыми каждый мог бы соучаствовать в плодотворных дискуссиях, не задевая
|
||||||
|
других. Ключевой принцип этих правил предельно прост — уважайте ближних,
|
||||||
|
постарайтесь не нарушать законы Российской Федерации без крайней
|
||||||
|
на то необходимости и помните, что в дискуссиях чутких
|
||||||
|
и здравомыслящих людей рождается истина.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>За что можно получить дырку в карме и выиграть бан в сообществе</h3>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Оскорбления, личные нападки, травля и угрозы. В любом виде. Конкретного
|
||||||
|
человека или социальной группы — не суть. Агрессия, переход
|
||||||
|
на личности и токсичность едва ли способствуют плодотворному общению.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Шовинизм, расизм, сексизм, гомофобия, пропаганда ненависти, педофилии, суицида,
|
||||||
|
распространение детской порнографии и другого человеконенавистнического контента.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Спам, реклама, фейкньюз, ссылки на пропагандистские СМИ, вбросы дезинформации,
|
||||||
|
специально уводящий от темы флуд, провокации, разжигание конфликтов, намеренный
|
||||||
|
срыв дискуссий.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Неаргументированная критика и комментарии вроде «отстой», «зачем
|
||||||
|
я это увидел/а», «не читал, но осуждаю»,
|
||||||
|
«либераху порвало», «лол», «скатились»,
|
||||||
|
«первый нах» и тому подобные. Односложные реплики не подразумевают
|
||||||
|
возможность обогащающего диалога, не продуктивны и никак не помогают
|
||||||
|
авторам делать материалы лучше, а читателям — разобраться.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>За что можно получить лучи добра и благодарности в сообществе</h3>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Вежливость и конструктивность.</strong> Мы выступаем
|
||||||
|
за конструктивный диалог, аргументированные комментарии и доброжелательное
|
||||||
|
отношение друг к другу. Задавайте содержательные вопросы, пишите развернутые
|
||||||
|
комментарии, подкрепляйте их аргументами, чтобы диалог был полезен всем участникам,
|
||||||
|
помогая глубже понять тему и разобраться в вопросе. И, пожалуйста, уважайте
|
||||||
|
собеседника, даже если он вам лично не импонирует: только так получаются
|
||||||
|
продуктивные дискуссии.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Обмен знаниями и историями.</strong> Осмысленные высказывания по теме
|
||||||
|
поста, оригинальные рассуждения, рассказы о личном опыте и проектах, обмен
|
||||||
|
профессиональной экспертизой, наблюдения и реальные истории
|
||||||
|
из жизни — чем больше мы делимся друг с другом знаниями, тем
|
||||||
|
интереснее и плодотворнее становится наше общение. Помните, что каждый вдумчивый
|
||||||
|
ответ повышает качество дискуссий в сообществе и делает чтение самиздата ещё
|
||||||
|
интереснее.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Чувство юмора и добродушие.</strong> Остроумие и дружелюбие
|
||||||
|
не только направляют дискуссии в продуктивное русло, но и улучшают
|
||||||
|
настроение. Не вредите негативом, которого в интернете и без нас хватает,
|
||||||
|
и не травите на корню классные инициативы — всё великое
|
||||||
|
начинается с малого. Мы за поддерживающую и вдохновляющую атмосферу
|
||||||
|
в сообществе. Надеемся, вы тоже.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Благодарность и поддержка.</strong> Если публикация вам зашла,
|
||||||
|
не стесняйтесь ставить лайки, делиться понравившимися материалами, благодарить
|
||||||
|
авторов, читателей, художников и редакторов в комментариях. Цените
|
||||||
|
и поддерживайте классные проекты, сильные тексты, новое искусство, осмысленные
|
||||||
|
комментарии и вклад других в самиздат — сотрудничество делает нас
|
||||||
|
сильнее и усиливает звучание идей и смыслов, которые помогают лучше понимать
|
||||||
|
мир.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</MainLayout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// for lazy loading
|
||||||
|
export default DiscussionRulesPage
|
|
@ -5,7 +5,7 @@ import { MainLayout } from '../../Layouts/MainLayout'
|
||||||
export const DogmaPage = () => {
|
export const DogmaPage = () => {
|
||||||
return (
|
return (
|
||||||
<MainLayout>
|
<MainLayout>
|
||||||
<article class="container">
|
<article class="container container--static-page">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h4 class="col-md-8 offset-md-2">Редакционные принципы</h4>
|
<h4 class="col-md-8 offset-md-2">Редакционные принципы</h4>
|
||||||
<div class="col-md-8 col-lg-6 offset-md-3">
|
<div class="col-md-8 col-lg-6 offset-md-3">
|
||||||
|
|
|
@ -14,7 +14,7 @@ export const GuidePage = () => {
|
||||||
{/*<Meta property="og:image:width" content="1200" />*/}
|
{/*<Meta property="og:image:width" content="1200" />*/}
|
||||||
{/*<Meta property="og:image:height" content="630" />*/}
|
{/*<Meta property="og:image:height" content="630" />*/}
|
||||||
|
|
||||||
<article class="container">
|
<article class="container container--static-page">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<nav class="content-index">
|
<nav class="content-index">
|
||||||
|
|
|
@ -11,7 +11,7 @@ export const HelpPage = () => {
|
||||||
|
|
||||||
{/*<Modal name="thank">Благодарим!</Modal>*/}
|
{/*<Modal name="thank">Благодарим!</Modal>*/}
|
||||||
|
|
||||||
<article class="container discours-help">
|
<article class="container container--static-page discours-help">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<nav class="content-index">
|
<nav class="content-index">
|
||||||
|
|
|
@ -15,7 +15,7 @@ export const ManifestPage = () => {
|
||||||
<Modal name="subscribe">
|
<Modal name="subscribe">
|
||||||
<Subscribe />
|
<Subscribe />
|
||||||
</Modal>
|
</Modal>
|
||||||
<article class="container">
|
<article class="container container--static-page">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<nav class="content-index">
|
<nav class="content-index">
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { t } from '../../../utils/intl'
|
||||||
export const PartnersPage = () => {
|
export const PartnersPage = () => {
|
||||||
return (
|
return (
|
||||||
<MainLayout>
|
<MainLayout>
|
||||||
<article class="container">
|
<article class="container container--static-page">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-8 offset-md-2">{t('Partners')}</div>
|
<div class="col-md-8 offset-md-2">{t('Partners')}</div>
|
||||||
<div class="col-md-8 col-lg-6 offset-md-3" />
|
<div class="col-md-8 col-lg-6 offset-md-3" />
|
||||||
|
|
185
src/components/Pages/about/PrinciplesPage.tsx
Normal file
185
src/components/Pages/about/PrinciplesPage.tsx
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
import { MainLayout } from '../../Layouts/MainLayout'
|
||||||
|
import { t } from '../../../utils/intl'
|
||||||
|
|
||||||
|
export const PrinciplesPage = () => {
|
||||||
|
const title = t('Principles')
|
||||||
|
return (
|
||||||
|
<MainLayout>
|
||||||
|
<article class="container container--static-page">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8 offset-md-2">
|
||||||
|
<h1>
|
||||||
|
<span class="wrapped">{title}</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div class="col-lg-10 offset-md-1">
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Горизонтальность</strong>. Мы все разные, и это классно. Вертикалей
|
||||||
|
в мире достаточно, мы — горизонтальное сообщество и ценим наши
|
||||||
|
различия, потому что знаем — в них наша сила. Благодаря разнообразию
|
||||||
|
сотен голосов, усиливающих друг друга, в сообществе складывается неповторимая
|
||||||
|
синергия, которая помогает вместе достигать большего.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Многоголосие</strong>. Мы ценим свободу слова и аргументированные
|
||||||
|
мнения. Предоставляя трибуну каждому, кому есть что сказать, самиздат отражает полифонию
|
||||||
|
позиций, знаний и опыта, которые открывают более полную картину реальности.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Взаимопомощь</strong>. Мы помогаем друг другу, потому что хотим, чтобы
|
||||||
|
в мире было еще больше хорошего. Обсуждая что-то, мы всегда интересуемся, чем
|
||||||
|
можем помочь. В самиздате можно найти специалистов практически в любых сферах
|
||||||
|
и получить поддержку от сотен людей. Благодаря коллективной экспертизе
|
||||||
|
глобального сообщества в самиздате выходят крутейшие публикации, которыми можно
|
||||||
|
вечно гордиться.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Взаимоуважение</strong>. Мы ценим, искренне уважаем друг друга
|
||||||
|
и вместо борщевиков враждебности культивируем цветы добра, мира, знания
|
||||||
|
и юмора. Нам некогда доказывать друг другу, кто круче. Гораздо приятнее
|
||||||
|
сотрудничать, помогать и создавать что-то важное, интересное и полезное.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Созидание</strong>. Мы создаем, потому что любим создавать. Мы открыто
|
||||||
|
делимся опытом, дарим идеи, обмениваемся мнениями и благодарим за критику,
|
||||||
|
используя ее для совершенствования мастерства и саморазвития. Мы знаем,
|
||||||
|
что мир не идеальное место, и делаем всё возможное, чтобы он стал лучше.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="h2" id="participation">
|
||||||
|
<span class="wrapped">Как участвовать в самиздате</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="col-lg-10 offset-md-1">
|
||||||
|
<p>
|
||||||
|
Открытая редакция объединяет сотни потрясающих людей со всего мира, которые делают
|
||||||
|
крутейшие вещи. Это пространство, где доверяют, вдохновляют, исследуют и создают новое
|
||||||
|
вместе. Поскольку все в сообществе очень разные, как-то мы собрались и решили
|
||||||
|
зафиксировать базовые ценности открытой редакции, а заодно придумали универсальные
|
||||||
|
правила взаимодействия, чтобы общение было не только плодотворным,
|
||||||
|
но и приятным для всех участников сообщества.
|
||||||
|
</p>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Действуем, помогаем и делимся</strong>. В редакции мы создаем
|
||||||
|
свои проекты и помогаем другим создавать свои — советами, делом,
|
||||||
|
участием, вовлеченностью. Мы открыто делимся опытом, мнениями и идеями, потому
|
||||||
|
что ценим силу сотрудничества и знаем, что идеи реализуются скорее, лучше
|
||||||
|
и веселее, если над ними трудиться сообща.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Общаемся дружелюбно</strong>. Помните, по ту сторону монитора
|
||||||
|
находятся реальные люди. Неуважение ранит других так же, как ранило бы вас
|
||||||
|
самих. Поэтому не стоит кричать (даже капслоком), заполнять эфир желчью
|
||||||
|
и бросаться грубостями — так вы рискуете не только растерять
|
||||||
|
доверие окружающих, но и остаться непонятым.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Критикуем и реагируем конструктивно</strong>. Самиздат про то, чтобы
|
||||||
|
разбираться в сложных вещах всем сообществом, поэтому мы тактично и без
|
||||||
|
агрессии делимся мнениями, стараясь убедительно аргументировать позиции.
|
||||||
|
И с благодарностью принимаем критику, используя ее для улучшения наших
|
||||||
|
проектов. Мы верим, что каждый участник сообщества имеет добрые намерения,
|
||||||
|
и придерживаемся принципов доброжелательной критики, стараемся делиться
|
||||||
|
советами — лучшим средством для самосовершенствования. Обоснованная критика
|
||||||
|
помогает и адресату, и всем участникам сообщества досконально изучить тему
|
||||||
|
и глубже разобраться в проблеме.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Решаем трудности не агрессией, а диалогом</strong>. Обесценивать
|
||||||
|
мнения и оскорблять других людей только потому, что вы с ними
|
||||||
|
не согласны, — не лучший способ донести свою точку зрения. Конечно,
|
||||||
|
важно высказаться, если вас что-то не устраивает и откровенно бесит.
|
||||||
|
Но прежде чем сжигать оппонента гневом, попробуйте понять, почему этот
|
||||||
|
«нехороший человек» так поступает. Возможно, аргументы собеседника окажутся
|
||||||
|
убедительными или вам удастся изменить его мнение. В любом случае конфликты
|
||||||
|
решаются в диалогах и проходят, а налаженное взаимопонимание останется
|
||||||
|
надолго.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Не переходим на личности — это признак токсичности</strong>
|
||||||
|
. Всегда мудрее обсуждать точку зрения человека, а не его самого, даже если
|
||||||
|
он вам не импонирует. Предвзятое отношение ограничивает кругозор, добавляет
|
||||||
|
преждевременные морщины и не помогает окружающим стать лучше. Вежливость
|
||||||
|
и взаимоуважение — краеугольная основа вдумчивых и осмысленных
|
||||||
|
дискуссий.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Благодарим за помощь</strong>. Благодарите коллег даже за самые,
|
||||||
|
казалось бы, простые вещи. «Спасибо» не зря называют волшебным
|
||||||
|
словом — на искренней благодарности держится любое подлинное
|
||||||
|
сотрудничество. Поддержка воодушевляет на новые подвиги и напоминает, что мир
|
||||||
|
делают прекрасным не машины, а живые люди.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Даем еще один шанс</strong>. Все совершают ошибки, и за один проступок
|
||||||
|
не стоит вычеркивать людей из жизни. Ошибки нужны, чтобы на них учиться
|
||||||
|
и делать выводы. Однако если многократно и систематически нарушать правила
|
||||||
|
сообщества, наверняка можно заслужить минусы в карму от других участников
|
||||||
|
и потерять доступ к сообществу.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Вместе создаем идеальную среду общения</strong>. Открытая редакция —
|
||||||
|
это утопическое пространство обогащающей и осмысленной коммуникации. Атмосфера
|
||||||
|
горизонтального сообщества складывается из действий каждого, поэтому
|
||||||
|
мы действуем так, чтобы способствовать сотворчеству, коллективному познанию
|
||||||
|
и развитию самиздата и нашей альтернативной интеллектуальной медиасреды.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Помним, что всё в сообществе зависит от нас</strong>. Если нам чего-то
|
||||||
|
не хватает, мы начинаем действовать — рассказываем об идее,
|
||||||
|
находим единомышленников, готовим и запускаем проект. Так в сообществе
|
||||||
|
становится на одну крутую активность больше. Так появилось наше сообщество. Так
|
||||||
|
появился самиздат и все проекты открытой редакции. Чтобы в сообществе
|
||||||
|
случилось что-то прекрасное, достаточно просто положить этому начало.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</MainLayout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// for lazy loading
|
||||||
|
export default PrinciplesPage
|
|
@ -6,7 +6,7 @@ import { t } from '../../../utils/intl'
|
||||||
export const ProjectsPage = () => {
|
export const ProjectsPage = () => {
|
||||||
return (
|
return (
|
||||||
<MainLayout>
|
<MainLayout>
|
||||||
<article class="container">
|
<article class="container container--static-page">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-8 offset-md-2">{t('Projects')}</div>
|
<div class="col-md-8 offset-md-2">{t('Projects')}</div>
|
||||||
<div class="col-md-8 col-lg-6 offset-md-3" />
|
<div class="col-md-8 col-lg-6 offset-md-3" />
|
||||||
|
|
|
@ -9,7 +9,7 @@ export const TermsOfUsePage = () => {
|
||||||
{/*<Meta name="keywords" content={`Discours.io, ${t('Terms of use')}, ${t('Terms of use', 'en')}`} />*/}
|
{/*<Meta name="keywords" content={`Discours.io, ${t('Terms of use')}, ${t('Terms of use', 'en')}`} />*/}
|
||||||
{/*<Meta property="og:title" content={title} />*/}
|
{/*<Meta property="og:title" content={title} />*/}
|
||||||
{/*<Meta property="og:description" content={title} />*/}
|
{/*<Meta property="og:description" content={title} />*/}
|
||||||
<article class="container">
|
<article class="container container--static-page">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<nav class="content-index">
|
<nav class="content-index">
|
||||||
|
|
|
@ -10,16 +10,15 @@ export const ThanksPage = () => {
|
||||||
{/*<Meta property="og:title" content={title} />*/}
|
{/*<Meta property="og:title" content={title} />*/}
|
||||||
{/*<Meta property="og:description" content={title} />*/}
|
{/*<Meta property="og:description" content={title} />*/}
|
||||||
|
|
||||||
<article class="container">
|
<article class="container container--static-page">
|
||||||
<div class="container open-post margin-top-20px ng-scope">
|
<div class="row">
|
||||||
<div class="row">
|
<div class="col-md-8 offset-md-2">
|
||||||
<div class="col-md-8 offset-md-2">
|
<h1>
|
||||||
<h1>
|
<span class="wrapped">{title}</span>
|
||||||
<span class="wrapped">{title}</span>
|
</h1>
|
||||||
</h1>
|
</div>
|
||||||
</div>
|
<div class="col-md-8 col-lg-6 offset-md-3">
|
||||||
<div class="col-md-8 col-lg-6 offset-md-3">
|
{/*
|
||||||
{/*
|
|
||||||
<h3><b>Команда</b></h3>
|
<h3><b>Команда</b></h3>
|
||||||
<p>
|
<p>
|
||||||
Константин Ворович — исполнительный директор,
|
Константин Ворович — исполнительный директор,
|
||||||
|
@ -44,48 +43,47 @@ export const ThanksPage = () => {
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
*/}
|
*/}
|
||||||
<h3>Неоценимый вклад в Дискурс внесли и вносят</h3>
|
<h3>Неоценимый вклад в Дискурс внесли и вносят</h3>
|
||||||
<p>
|
<p>
|
||||||
Мария Бессмертная, Дамир Бикчурин, Константин Ворович, Ян Выговский, Эльдар Гариффулин,
|
Мария Бессмертная, Дамир Бикчурин, Константин Ворович, Ян Выговский, Эльдар Гариффулин,
|
||||||
Павел Гафаров, Виктория Гендлина, Александр Гусев, Данила Давыдов, Константин Дубовик,
|
Павел Гафаров, Виктория Гендлина, Александр Гусев, Данила Давыдов, Константин Дубовик,
|
||||||
Вячеслав Еременко, Кристина Ибрагим, Екатерина Ильина, Анна Капаева, Яна Климова, Александр
|
Вячеслав Еременко, Кристина Ибрагим, Екатерина Ильина, Анна Капаева, Яна Климова, Александр
|
||||||
Коренков, Ирэна Лесневская, Игорь Лобанов, Анастасия Лозовая, Григорий Ломизе, Евгений
|
Коренков, Ирэна Лесневская, Игорь Лобанов, Анастасия Лозовая, Григорий Ломизе, Евгений
|
||||||
Медведев, Павел Никулин, Николай Носачевский, Андрей Орловский, Михаил Панин, Антон Панов,
|
Медведев, Павел Никулин, Николай Носачевский, Андрей Орловский, Михаил Панин, Антон Панов,
|
||||||
Павел Пепперштейн, Любовь Покровская, Илья Розовский, Денис Светличный, Павел Соколов,
|
Павел Пепперштейн, Любовь Покровская, Илья Розовский, Денис Светличный, Павел Соколов, Сергей
|
||||||
Сергей Стрельников, Глеб Струнников, Николай Тарковский, Кирилл Филимонов, Алексей Хапов,
|
Стрельников, Глеб Струнников, Николай Тарковский, Кирилл Филимонов, Алексей Хапов, Екатерина
|
||||||
Екатерина Харитонова
|
Харитонова
|
||||||
</p>
|
</p>
|
||||||
<h3>Авторы</h3>
|
<h3>Авторы</h3>
|
||||||
<p>
|
<p>
|
||||||
Мы безмерно благодарны{' '}
|
Мы безмерно благодарны{' '}
|
||||||
<a href="/authors" target="_blank" rel="noopener noreferrer">
|
<a href="/authors" target="_blank" rel="noopener noreferrer">
|
||||||
каждому автору
|
каждому автору
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
за участие и поддержку проекта. Сегодня, когда для большинства деньги стали целью
|
за участие и поддержку проекта. Сегодня, когда для большинства деньги стали целью
|
||||||
и основным источником мотивации, бескорыстная помощь и основанный
|
и основным источником мотивации, бескорыстная помощь и основанный на энтузиазме
|
||||||
на энтузиазме труд бесценны. Именно вы своим трудом каждый день делаете Дискурс
|
труд бесценны. Именно вы своим трудом каждый день делаете Дискурс таким, какой
|
||||||
таким, какой он есть.
|
он есть.
|
||||||
</p>
|
</p>
|
||||||
<h3>Иллюстраторы</h3>
|
<h3>Иллюстраторы</h3>
|
||||||
<p>
|
<p>
|
||||||
Ольга Аверинова, Регина Акчурина, Айгуль Берхеева, Екатерина Вакуленко, Анастасия Викулова,
|
Ольга Аверинова, Регина Акчурина, Айгуль Берхеева, Екатерина Вакуленко, Анастасия Викулова,
|
||||||
Мария Власенко, Ванесса Гаврилова, Ольга Горше, Ксения Горшкова, Ангелина Гребенюкова, Илья
|
Мария Власенко, Ванесса Гаврилова, Ольга Горше, Ксения Горшкова, Ангелина Гребенюкова, Илья
|
||||||
Diliago, Антон Жаголкин, Саша Керова, Ольга Машинец, Злата Мечетина, Тала Никитина, Никита
|
Diliago, Антон Жаголкин, Саша Керова, Ольга Машинец, Злата Мечетина, Тала Никитина, Никита
|
||||||
Поздняков, Матвей Сапегин, Татьяна Сафонова, Виктория Шибаева
|
Поздняков, Матвей Сапегин, Татьяна Сафонова, Виктория Шибаева
|
||||||
</p>
|
</p>
|
||||||
<h3>Меценаты</h3>
|
<h3>Меценаты</h3>
|
||||||
<p>
|
<p>
|
||||||
Дискурс существует исключительно на пожертвования читателей. Мы бесконечно
|
Дискурс существует исключительно на пожертвования читателей. Мы бесконечно
|
||||||
признательны всем, кто нас поддерживает. Ваши пожертвования — финансовый
|
признательны всем, кто нас поддерживает. Ваши пожертвования — финансовый фундамент
|
||||||
фундамент журнала. Благодаря вам мы развиваем платформу качественной журналистики,
|
журнала. Благодаря вам мы развиваем платформу качественной журналистики, которая помогает
|
||||||
которая помогает самым разным авторам быть услышанными. Стать нашим меценатом
|
самым разным авторам быть услышанными. Стать нашим меценатом и подписаться
|
||||||
и подписаться на ежемесячную поддержку проекта можно{' '}
|
на ежемесячную поддержку проекта можно{' '}
|
||||||
<a href="/about/help" target="_self">
|
<a href="/about/help" target="_self">
|
||||||
здесь
|
здесь
|
||||||
</a>
|
</a>
|
||||||
.
|
.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
|
@ -18,15 +18,18 @@ import { FeedPage } from './Pages/FeedPage'
|
||||||
import { ArticlePage } from './Pages/ArticlePage'
|
import { ArticlePage } from './Pages/ArticlePage'
|
||||||
import { SearchPage } from './Pages/SearchPage'
|
import { SearchPage } from './Pages/SearchPage'
|
||||||
import { FourOuFourPage } from './Pages/FourOuFourPage'
|
import { FourOuFourPage } from './Pages/FourOuFourPage'
|
||||||
|
import { DiscussionRulesPage } from './Pages/about/DiscussionRulesPage'
|
||||||
import { DogmaPage } from './Pages/about/DogmaPage'
|
import { DogmaPage } from './Pages/about/DogmaPage'
|
||||||
import { GuidePage } from './Pages/about/GuidePage'
|
import { GuidePage } from './Pages/about/GuidePage'
|
||||||
import { HelpPage } from './Pages/about/HelpPage'
|
import { HelpPage } from './Pages/about/HelpPage'
|
||||||
import { ManifestPage } from './Pages/about/ManifestPage'
|
import { ManifestPage } from './Pages/about/ManifestPage'
|
||||||
import { PartnersPage } from './Pages/about/PartnersPage'
|
import { PartnersPage } from './Pages/about/PartnersPage'
|
||||||
|
import { PrinciplesPage } from './Pages/about/PrinciplesPage'
|
||||||
import { ProjectsPage } from './Pages/about/ProjectsPage'
|
import { ProjectsPage } from './Pages/about/ProjectsPage'
|
||||||
import { TermsOfUsePage } from './Pages/about/TermsOfUsePage'
|
import { TermsOfUsePage } from './Pages/about/TermsOfUsePage'
|
||||||
import { ThanksPage } from './Pages/about/ThanksPage'
|
import { ThanksPage } from './Pages/about/ThanksPage'
|
||||||
import { CreatePage } from './Pages/CreatePage'
|
import { CreatePage } from './Pages/CreatePage'
|
||||||
|
import { ConnectPage } from './Pages/ConnectPage'
|
||||||
import { renewSession } from '../stores/auth'
|
import { renewSession } from '../stores/auth'
|
||||||
|
|
||||||
// TODO: lazy load
|
// TODO: lazy load
|
||||||
|
@ -57,6 +60,7 @@ type RootSearchParams = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const pagesMap: Record<keyof Routes, Component<PageProps>> = {
|
const pagesMap: Record<keyof Routes, Component<PageProps>> = {
|
||||||
|
connect: ConnectPage,
|
||||||
create: CreatePage,
|
create: CreatePage,
|
||||||
home: HomePage,
|
home: HomePage,
|
||||||
topics: AllTopicsPage,
|
topics: AllTopicsPage,
|
||||||
|
@ -66,12 +70,14 @@ const pagesMap: Record<keyof Routes, Component<PageProps>> = {
|
||||||
feed: FeedPage,
|
feed: FeedPage,
|
||||||
article: ArticlePage,
|
article: ArticlePage,
|
||||||
search: SearchPage,
|
search: SearchPage,
|
||||||
|
discussionRules: DiscussionRulesPage,
|
||||||
dogma: DogmaPage,
|
dogma: DogmaPage,
|
||||||
guide: GuidePage,
|
guide: GuidePage,
|
||||||
help: HelpPage,
|
help: HelpPage,
|
||||||
manifest: ManifestPage,
|
manifest: ManifestPage,
|
||||||
projects: ProjectsPage,
|
projects: ProjectsPage,
|
||||||
partners: PartnersPage,
|
partners: PartnersPage,
|
||||||
|
principles: PrinciplesPage,
|
||||||
termsOfUse: TermsOfUsePage,
|
termsOfUse: TermsOfUsePage,
|
||||||
thanks: ThanksPage
|
thanks: ThanksPage
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { capitalize, plural } from '../../utils'
|
import { capitalize, plural } from '../../utils'
|
||||||
import style from './Card.module.scss'
|
import styles from './Card.module.scss'
|
||||||
import { createMemo, Show } from 'solid-js'
|
import { createMemo, Show } from 'solid-js'
|
||||||
import type { Topic } from '../../graphql/types.gen'
|
import type { Topic } from '../../graphql/types.gen'
|
||||||
import { FollowingEntity } from '../../graphql/types.gen'
|
import { FollowingEntity } from '../../graphql/types.gen'
|
||||||
|
@ -43,20 +43,20 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={style.topic}
|
class={styles.topic}
|
||||||
classList={{
|
classList={{
|
||||||
row: !props.compact && !props.subscribeButtonBottom,
|
row: !props.compact && !props.subscribeButtonBottom,
|
||||||
[style.topicInRow]: props.isTopicInRow
|
[styles.topicInRow]: props.isTopicInRow
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div classList={{ 'col-md-7': !props.compact && !props.subscribeButtonBottom }}>
|
<div classList={{ 'col-md-7': !props.compact && !props.subscribeButtonBottom }}>
|
||||||
<Show when={props.topic.title}>
|
<Show when={props.topic.title}>
|
||||||
<div class={style.topicTitle}>
|
<div class={styles.topicTitle}>
|
||||||
<a href={`/topic/${props.topic.slug}`}>{capitalize(props.topic.title || '')}</a>
|
<a href={`/topic/${props.topic.slug}`}>{capitalize(props.topic.title || '')}</a>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={props.topic.pic}>
|
<Show when={props.topic.pic}>
|
||||||
<div class={style.topicAvatar}>
|
<div class={styles.topicAvatar}>
|
||||||
<a href={props.topic.slug}>
|
<a href={props.topic.slug}>
|
||||||
<img src={props.topic.pic} alt={props.topic.title} />
|
<img src={props.topic.pic} alt={props.topic.title} />
|
||||||
</a>
|
</a>
|
||||||
|
@ -65,7 +65,7 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
|
|
||||||
<Show when={!props.compact && props.topic?.body}>
|
<Show when={!props.compact && props.topic?.body}>
|
||||||
<div
|
<div
|
||||||
class={style.topicDescription}
|
class={styles.topicDescription}
|
||||||
classList={{ 'topic-description--short': props.shortDescription }}
|
classList={{ 'topic-description--short': props.shortDescription }}
|
||||||
>
|
>
|
||||||
{props.topic.body}
|
{props.topic.body}
|
||||||
|
@ -73,9 +73,9 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={props.topic?.stat}>
|
<Show when={props.topic?.stat}>
|
||||||
<div class={style.topicDetails}>
|
<div class={styles.topicDetails}>
|
||||||
<Show when={!props.compact}>
|
<Show when={!props.compact}>
|
||||||
<span class={style.topicDetailsTtem} classList={{ compact: props.compact }}>
|
<span class={styles.topicDetailsTtem} classList={{ compact: props.compact }}>
|
||||||
{props.topic.stat?.shouts +
|
{props.topic.stat?.shouts +
|
||||||
' ' +
|
' ' +
|
||||||
t('post') +
|
t('post') +
|
||||||
|
@ -84,7 +84,7 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
locale() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's']
|
locale() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's']
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span class={style.topicDetailsTtem} classList={{ compact: props.compact }}>
|
<span class={styles.topicDetailsTtem} classList={{ compact: props.compact }}>
|
||||||
{props.topic.stat?.authors +
|
{props.topic.stat?.authors +
|
||||||
' ' +
|
' ' +
|
||||||
t('author') +
|
t('author') +
|
||||||
|
@ -93,7 +93,7 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
locale() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's']
|
locale() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's']
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span class={style.topicDetailsItem} classList={{ compact: props.compact }}>
|
<span class={styles.topicDetailsItem} classList={{ compact: props.compact }}>
|
||||||
{props.topic.stat?.followers +
|
{props.topic.stat?.followers +
|
||||||
' ' +
|
' ' +
|
||||||
t('follower') +
|
t('follower') +
|
||||||
|
@ -131,15 +131,15 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
<Show
|
<Show
|
||||||
when={subscribed()}
|
when={subscribed()}
|
||||||
fallback={
|
fallback={
|
||||||
<button onClick={() => subscribe(true)} class="button--light">
|
<button onClick={() => subscribe(true)} class="button--light button--subscribe-topic">
|
||||||
<Show when={props.iconButton}>{/*<Icon name={}/>*/}</Show>
|
<Show when={props.iconButton}>+</Show>
|
||||||
|
|
||||||
<Show when={!props.iconButton}>+ {t('Follow')}</Show>
|
<Show when={!props.iconButton}>+ {t('Follow')}</Show>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<button onClick={() => subscribe(false)} class="button--light">
|
<button onClick={() => subscribe(false)} class="button--light button--subscribe-topic">
|
||||||
<Show when={props.iconButton}>{/*<Icon name={}/>*/}</Show>
|
<Show when={props.iconButton}>-</Show>
|
||||||
|
|
||||||
<Show when={!props.iconButton}>- {t('Unfollow')}</Show>
|
<Show when={!props.iconButton}>- {t('Unfollow')}</Show>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
export const ConnectView = () => (
|
|
||||||
<>
|
|
||||||
<article class="container">
|
|
||||||
<div class="row">
|
|
||||||
<h1 class="col-md-8 offset-md-2">
|
|
||||||
<span class="wrapped">Предложить идею</span>
|
|
||||||
</h1>
|
|
||||||
<div class="col-md-8 col-lg-6 offset-md-3">
|
|
||||||
<p>
|
|
||||||
Хотите что-то предложить, обсудить или посоветовать? Поделиться темой или идеей? Напишите нам
|
|
||||||
скорее! Если укажете свою почту, мы обязательно ответим.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<form action=".">
|
|
||||||
<div class="pretty-form__item">
|
|
||||||
<select id="subject">
|
|
||||||
<option value="">Сотрудничество</option>
|
|
||||||
<option value="">Посоветовать тему</option>
|
|
||||||
<option value="">Сообщить об ошибке</option>
|
|
||||||
<option value="">Предложить проект</option>
|
|
||||||
<option value="">Волонтерство</option>
|
|
||||||
<option value="">Другое</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="pretty-form__item">
|
|
||||||
<input type="text" id="contact-email" placeholder="Email для обратной связи" />
|
|
||||||
<label for="contact-email">Email для обратной связи</label>
|
|
||||||
</div>
|
|
||||||
<div class="pretty-form__item">
|
|
||||||
<textarea id="message" placeholder="Текст сообщения" />
|
|
||||||
<label for="message">Текст сообщения</label>
|
|
||||||
</div>
|
|
||||||
<button class="button">Отправить письмо</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</>
|
|
||||||
)
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Show, onCleanup, createEffect, onError, onMount, untrack } from 'solid-js'
|
import { Show, onCleanup, createEffect, onError, onMount, untrack, createSignal } from 'solid-js'
|
||||||
import { createMutable, unwrap } from 'solid-js/store'
|
import { createMutable, unwrap } from 'solid-js/store'
|
||||||
import { State, StateContext } from '../Editor/store/context'
|
import { State, StateContext, newState } from '../Editor/store/context'
|
||||||
import { createCtrl } from '../Editor/store/actions'
|
import { createCtrl } from '../Editor/store/actions'
|
||||||
import { Layout } from '../Editor/components/Layout'
|
import { Layout } from '../Editor/components/Layout'
|
||||||
import { Editor } from '../Editor/components/Editor'
|
import { Editor } from '../Editor/components/Editor'
|
||||||
|
@ -9,16 +9,18 @@ import ErrorView from '../Editor/components/Error'
|
||||||
|
|
||||||
const matchDark = () => window.matchMedia('(prefers-color-scheme: dark)')
|
const matchDark = () => window.matchMedia('(prefers-color-scheme: dark)')
|
||||||
|
|
||||||
export const CreateView = (props: { state: State }) => {
|
export const CreateView = () => {
|
||||||
let isMac = false
|
const [isMounted, setIsMounted] = createSignal(false)
|
||||||
|
|
||||||
|
onMount(() => setIsMounted(true))
|
||||||
|
|
||||||
const onChangeTheme = () => ctrl.updateTheme()
|
const onChangeTheme = () => ctrl.updateTheme()
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
isMac = window?.navigator.platform.includes('Mac')
|
|
||||||
matchDark().addEventListener('change', onChangeTheme)
|
matchDark().addEventListener('change', onChangeTheme)
|
||||||
onCleanup(() => matchDark().removeEventListener('change', onChangeTheme))
|
onCleanup(() => matchDark().removeEventListener('change', onChangeTheme))
|
||||||
})
|
})
|
||||||
|
|
||||||
const [store, ctrl] = createCtrl({ ...props.state, isMac })
|
const [store, ctrl] = createCtrl(newState())
|
||||||
const mouseEnterCoords = createMutable({ x: 0, y: 0 })
|
const mouseEnterCoords = createMutable({ x: 0, y: 0 })
|
||||||
|
|
||||||
const onMouseEnter = (e: MouseEvent) => {
|
const onMouseEnter = (e: MouseEvent) => {
|
||||||
|
@ -52,17 +54,21 @@ export const CreateView = (props: { state: State }) => {
|
||||||
}, store.loading)
|
}, store.loading)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StateContext.Provider value={[store, ctrl]}>
|
<Show when={isMounted()}>
|
||||||
<Layout
|
<StateContext.Provider value={[store, ctrl]}>
|
||||||
config={store.config}
|
<Layout
|
||||||
data-testid={store.error ? 'error' : store.loading}
|
config={store.config}
|
||||||
onMouseEnter={onMouseEnter}
|
data-testid={store.error ? 'error' : store.loading}
|
||||||
>
|
onMouseEnter={onMouseEnter}
|
||||||
<Show when={!store.error} fallback={<ErrorView />}>
|
>
|
||||||
<Editor />
|
<Show when={!store.error} fallback={<ErrorView />}>
|
||||||
<Sidebar />
|
<Editor />
|
||||||
</Show>
|
<Sidebar />
|
||||||
</Layout>
|
</Show>
|
||||||
</StateContext.Provider>
|
</Layout>
|
||||||
|
</StateContext.Provider>
|
||||||
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default CreateView
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
"Discours is an intellectual environment, a web space and tools that allows authors to collaborate with readers and come together to co-create publications and media projects": "Дискурс — это интеллектуальная среда, веб-пространство и инструменты, которые позволяют авторам сотрудничать с читателями и объединяться для совместного создания публикаций и медиапроектов",
|
"Discours is an intellectual environment, a web space and tools that allows authors to collaborate with readers and come together to co-create publications and media projects": "Дискурс — это интеллектуальная среда, веб-пространство и инструменты, которые позволяют авторам сотрудничать с читателями и объединяться для совместного создания публикаций и медиапроектов",
|
||||||
"Discours is created with our common effort": "Дискурс существует благодаря нашему общему вкладу",
|
"Discours is created with our common effort": "Дискурс существует благодаря нашему общему вкладу",
|
||||||
"Discussing": "Обсуждаемое",
|
"Discussing": "Обсуждаемое",
|
||||||
|
"Discussion rules": "Правила сообществ самиздата в соцсетях",
|
||||||
"Dogma": "Догма",
|
"Dogma": "Догма",
|
||||||
"Edit": "Редактировать",
|
"Edit": "Редактировать",
|
||||||
"Email": "Почта",
|
"Email": "Почта",
|
||||||
|
@ -76,6 +77,7 @@
|
||||||
"Please confirm your email to finish": "Подтвердите почту и действие совершится",
|
"Please confirm your email to finish": "Подтвердите почту и действие совершится",
|
||||||
"Popular": "Популярное",
|
"Popular": "Популярное",
|
||||||
"Popular authors": "Популярные авторы",
|
"Popular authors": "Популярные авторы",
|
||||||
|
"Principles": "Принципы сообщества",
|
||||||
"Publications": "Публикации",
|
"Publications": "Публикации",
|
||||||
"Quit": "Выйти",
|
"Quit": "Выйти",
|
||||||
"Reason uknown": "Причина неизвестна",
|
"Reason uknown": "Причина неизвестна",
|
||||||
|
@ -97,7 +99,8 @@
|
||||||
"Successfully authorized": "Авторизация успешна",
|
"Successfully authorized": "Авторизация успешна",
|
||||||
"Suggest an idea": "Предложить идею",
|
"Suggest an idea": "Предложить идею",
|
||||||
"Support us": "Помочь журналу",
|
"Support us": "Помочь журналу",
|
||||||
"Terms of use": "Условия использования",
|
"Terms of use": "Правила сайта",
|
||||||
|
"Thank you": "Благодарности",
|
||||||
"To leave a comment please": "Чтобы оставить комментарий, необходимо",
|
"To leave a comment please": "Чтобы оставить комментарий, необходимо",
|
||||||
"Top authors": "Рейтинг авторов",
|
"Top authors": "Рейтинг авторов",
|
||||||
"Top commented": "Самое комментируемое",
|
"Top commented": "Самое комментируемое",
|
||||||
|
|
14
src/pages/about/discussion-rules.astro
Normal file
14
src/pages/about/discussion-rules.astro
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
import Zine from '../../layouts/zine.astro'
|
||||||
|
import { Root } from '../../components/Root'
|
||||||
|
import { initRouter } from '../../stores/router'
|
||||||
|
|
||||||
|
const { pathname, search } = Astro.url
|
||||||
|
initRouter(pathname, search)
|
||||||
|
|
||||||
|
Astro.response.headers.set('Cache-Control', 's-maxage=1, stale-while-revalidate')
|
||||||
|
---
|
||||||
|
|
||||||
|
<Zine>
|
||||||
|
<Root client:load />
|
||||||
|
</Zine>
|
14
src/pages/about/principles.astro
Normal file
14
src/pages/about/principles.astro
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
import Zine from '../../layouts/zine.astro'
|
||||||
|
import { Root } from '../../components/Root'
|
||||||
|
import { initRouter } from '../../stores/router'
|
||||||
|
|
||||||
|
const { pathname, search } = Astro.url
|
||||||
|
initRouter(pathname, search)
|
||||||
|
|
||||||
|
Astro.response.headers.set('Cache-Control', 's-maxage=1, stale-while-revalidate')
|
||||||
|
---
|
||||||
|
|
||||||
|
<Zine>
|
||||||
|
<Root client:load />
|
||||||
|
</Zine>
|
14
src/pages/connect.astro
Normal file
14
src/pages/connect.astro
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
import Zine from '../layouts/zine.astro'
|
||||||
|
import { Root } from '../components/Root'
|
||||||
|
import { initRouter } from '../stores/router'
|
||||||
|
|
||||||
|
const { pathname, search } = Astro.url
|
||||||
|
initRouter(pathname, search)
|
||||||
|
|
||||||
|
Astro.response.headers.set('Cache-Control', 's-maxage=1, stale-while-revalidate')
|
||||||
|
---
|
||||||
|
|
||||||
|
<Zine>
|
||||||
|
<Root client:load />
|
||||||
|
</Zine>
|
|
@ -6,6 +6,7 @@ import { useStore } from '@nanostores/solid'
|
||||||
// TODO: more
|
// TODO: more
|
||||||
export interface Routes {
|
export interface Routes {
|
||||||
home: void
|
home: void
|
||||||
|
connect: void
|
||||||
create: void
|
create: void
|
||||||
topics: void
|
topics: void
|
||||||
topic: 'slug'
|
topic: 'slug'
|
||||||
|
@ -15,10 +16,12 @@ export interface Routes {
|
||||||
article: 'slug'
|
article: 'slug'
|
||||||
search: 'q'
|
search: 'q'
|
||||||
dogma: void
|
dogma: void
|
||||||
|
discussionRules: void
|
||||||
guide: void
|
guide: void
|
||||||
help: void
|
help: void
|
||||||
manifest: void
|
manifest: void
|
||||||
partners: void
|
partners: void
|
||||||
|
principles: void
|
||||||
projects: void
|
projects: void
|
||||||
termsOfUse: void
|
termsOfUse: void
|
||||||
thanks: void
|
thanks: void
|
||||||
|
@ -28,6 +31,7 @@ const searchParamsStore = createSearchParams()
|
||||||
const routerStore = createRouter<Routes>(
|
const routerStore = createRouter<Routes>(
|
||||||
{
|
{
|
||||||
home: '/',
|
home: '/',
|
||||||
|
connect: '/connect',
|
||||||
create: '/create',
|
create: '/create',
|
||||||
topics: '/topics',
|
topics: '/topics',
|
||||||
topic: '/topic/:slug',
|
topic: '/topic/:slug',
|
||||||
|
@ -37,10 +41,12 @@ const routerStore = createRouter<Routes>(
|
||||||
search: '/search/:q?',
|
search: '/search/:q?',
|
||||||
article: '/:slug',
|
article: '/:slug',
|
||||||
dogma: '/about/dogma',
|
dogma: '/about/dogma',
|
||||||
|
discussionRules: '/about/discussion-rules',
|
||||||
guide: '/about/guide',
|
guide: '/about/guide',
|
||||||
help: '/about/help',
|
help: '/about/help',
|
||||||
manifest: '/about/manifest',
|
manifest: '/about/manifest',
|
||||||
partners: '/about/partners',
|
partners: '/about/partners',
|
||||||
|
principles: '/about/principles',
|
||||||
projects: '/about/projects',
|
projects: '/about/projects',
|
||||||
termsOfUse: '/about/terms-of-use',
|
termsOfUse: '/about/terms-of-use',
|
||||||
thanks: '/about/thanks'
|
thanks: '/about/thanks'
|
||||||
|
|
|
@ -26,6 +26,7 @@ html {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
overscroll-behavior-y: none;
|
overscroll-behavior-y: none;
|
||||||
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
@ -69,10 +70,14 @@ section {
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
h2 {
|
h2 {
|
||||||
|
line-height: 1.3;
|
||||||
|
|
||||||
.wrapped {
|
.wrapped {
|
||||||
background: #000;
|
background: #000;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 0 0.15em;
|
padding: 0 0.15em;
|
||||||
|
box-decoration-break: clone;
|
||||||
|
-webkit-box-decoration-break: clone;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,6 +176,7 @@ button {
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
background: #000;
|
background: #000;
|
||||||
|
box-sizing: border-box;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
@ -179,6 +185,7 @@ button {
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
|
@ -187,8 +194,10 @@ button {
|
||||||
}
|
}
|
||||||
|
|
||||||
.button--subscribe {
|
.button--subscribe {
|
||||||
padding: 0.6rem 1.2rem;
|
background: #fff;
|
||||||
|
border: 2px solid #f6f6f6;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
|
padding: 0.6rem 1.2rem;
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
@ -210,10 +219,23 @@ button {
|
||||||
@include font-size(1.5rem);
|
@include font-size(1.5rem);
|
||||||
|
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
height: auto;
|
||||||
margin-top: 0.6rem;
|
margin-top: 0.6rem;
|
||||||
padding: 0.6rem 1.2rem 0.6rem 1rem;
|
padding: 0.6rem 1.2rem 0.6rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button--subscribe-topic {
|
||||||
|
background: #fff;
|
||||||
|
border: 2px solid #000;
|
||||||
|
border-radius: 0.8rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #000;
|
||||||
|
color: #fff;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
form {
|
form {
|
||||||
.pretty-form__item {
|
.pretty-form__item {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -562,6 +584,10 @@ astro-island {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.container--static-page {
|
||||||
|
padding-top: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.shift-content {
|
.shift-content {
|
||||||
@include media-breakpoint-up(md) {
|
@include media-breakpoint-up(md) {
|
||||||
margin-left: 127px;
|
margin-left: 127px;
|
||||||
|
|
15
src/utils/useEscKeyDownHandler.ts
Normal file
15
src/utils/useEscKeyDownHandler.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { onCleanup, onMount } from 'solid-js'
|
||||||
|
|
||||||
|
export const useEscKeyDownHandler = (onEscKeyDown: () => void) => {
|
||||||
|
const keydownHandler = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') onEscKeyDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
window.addEventListener('keydown', keydownHandler)
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
window.removeEventListener('keydown', keydownHandler)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
27
src/utils/useOutsideClickHandler.ts
Normal file
27
src/utils/useOutsideClickHandler.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { onCleanup, onMount } from 'solid-js'
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
predicate?: () => boolean
|
||||||
|
containerRef: { current: HTMLElement }
|
||||||
|
handler: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useOutsideClickHandler = (options: Options) => {
|
||||||
|
const { predicate, containerRef, handler } = options
|
||||||
|
const handleClickOutside = (event: MouseEvent & { target: Element }) => {
|
||||||
|
if (predicate && !predicate()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.target === containerRef.current || containerRef.current?.contains(event.target)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
options.handler()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
document.addEventListener('click', handleClickOutside, { capture: true })
|
||||||
|
onCleanup(() => document.removeEventListener('click', handleClickOutside, { capture: true }))
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user