prettier
This commit is contained in:
parent
4d69749f55
commit
190a3226d5
|
@ -1,7 +1,7 @@
|
|||
import styles from './Banner.module.scss'
|
||||
import { t } from '../../utils/intl'
|
||||
import { showModal } from '../../stores/ui'
|
||||
import {clsx} from "clsx";
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Icon } from '../Nav/Icon'
|
|||
import Subscribe from './Subscribe'
|
||||
import { t } from '../../utils/intl'
|
||||
import { locale } from '../../stores/ui'
|
||||
import {clsx} from "clsx";
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
export const Footer = () => {
|
||||
const locale_title = createMemo(() => (locale() === 'ru' ? 'English' : 'Русский'))
|
||||
|
|
|
@ -10,10 +10,10 @@ export const Editor = () => {
|
|||
const onReconfigure = (text: EditorState) => ctrl.setState({ text })
|
||||
const onChange = (text: EditorState) => ctrl.setState({ text, lastModified: new Date() })
|
||||
// const editorCss = (config) => css``
|
||||
const style = () => (store.error ? `display: none;` : (store.markdown ? `white-space: pre-wrap;` : ''))
|
||||
const style = () => (store.error ? `display: none;` : store.markdown ? `white-space: pre-wrap;` : '')
|
||||
return (
|
||||
<ProseMirror
|
||||
className='editor col-md-6 shift-content'
|
||||
className="editor col-md-6 shift-content"
|
||||
style={style()}
|
||||
editorView={store.editorView}
|
||||
text={store.text}
|
||||
|
|
|
@ -7,13 +7,13 @@ export default () => {
|
|||
return (
|
||||
<Switch fallback={<Other />}>
|
||||
<Match when={store.error.id === 'invalid_state'}>
|
||||
<InvalidState title='Invalid State' />
|
||||
<InvalidState title="Invalid State" />
|
||||
</Match>
|
||||
<Match when={store.error.id === 'invalid_config'}>
|
||||
<InvalidState title='Invalid Config' />
|
||||
<InvalidState title="Invalid Config" />
|
||||
</Match>
|
||||
<Match when={store.error.id === 'invalid_draft'}>
|
||||
<InvalidState title='Invalid Draft' />
|
||||
<InvalidState title="Invalid Draft" />
|
||||
</Match>
|
||||
</Switch>
|
||||
)
|
||||
|
@ -24,8 +24,8 @@ const InvalidState = (props: { title: string }) => {
|
|||
const onClick = () => ctrl.clean()
|
||||
|
||||
return (
|
||||
<div class='error'>
|
||||
<div class='container'>
|
||||
<div class="error">
|
||||
<div class="container">
|
||||
<h1>{props.title}</h1>
|
||||
<p>
|
||||
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>
|
||||
<code>{JSON.stringify(store.error.props)}</code>
|
||||
</pre>
|
||||
<button class='primary' onClick={onClick}>
|
||||
<button class="primary" onClick={onClick}>
|
||||
Clean
|
||||
</button>
|
||||
</div>
|
||||
|
@ -53,13 +53,13 @@ const Other = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div class='error'>
|
||||
<div class='container'>
|
||||
<div class="error">
|
||||
<div class="container">
|
||||
<h1>An error occurred.</h1>
|
||||
<pre>
|
||||
<code>{getMessage()}</code>
|
||||
</pre>
|
||||
<button class='primary' onClick={onClick}>
|
||||
<button class="primary" onClick={onClick}>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
import type { JSX } from 'solid-js/jsx-runtime';
|
||||
import type { JSX } from 'solid-js/jsx-runtime'
|
||||
import type { Config } from '../store/context'
|
||||
import '../styles/Layout.scss'
|
||||
|
||||
export type Styled = {
|
||||
children: JSX.Element;
|
||||
config?: Config;
|
||||
'data-testid'?: string;
|
||||
onClick?: () => void;
|
||||
onMouseEnter?: (e: MouseEvent) => void;
|
||||
children: JSX.Element
|
||||
config?: Config
|
||||
'data-testid'?: string
|
||||
onClick?: () => void
|
||||
onMouseEnter?: (e: MouseEvent) => void
|
||||
}
|
||||
|
||||
export const Layout = (props: Styled) => {
|
||||
return (<div onMouseEnter={props.onMouseEnter} class='layout container' data-testid={props['data-testid']}>
|
||||
{props.children}
|
||||
</div>)
|
||||
return (
|
||||
<div onMouseEnter={props.onMouseEnter} class="layout container" data-testid={props['data-testid']}>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,14 +6,14 @@ import { Schema } from 'prosemirror-model'
|
|||
import type { NodeViewFn, ProseMirrorExtension, ProseMirrorState } from '../prosemirror/helpers'
|
||||
|
||||
interface ProseMirrorProps {
|
||||
style?: string;
|
||||
className?: string;
|
||||
text?: Store<ProseMirrorState>;
|
||||
editorView?: Store<EditorView>;
|
||||
extensions?: Store<ProseMirrorExtension[]>;
|
||||
onInit: (s: EditorState, v: EditorView) => void;
|
||||
onReconfigure: (s: EditorState) => void;
|
||||
onChange: (s: EditorState) => void;
|
||||
style?: string
|
||||
className?: string
|
||||
text?: Store<ProseMirrorState>
|
||||
editorView?: Store<EditorView>
|
||||
extensions?: Store<ProseMirrorExtension[]>
|
||||
onInit: (s: EditorState, v: EditorView) => void
|
||||
onReconfigure: (s: EditorState) => void
|
||||
onChange: (s: EditorState) => void
|
||||
}
|
||||
|
||||
export const ProseMirror = (props: ProseMirrorProps) => {
|
||||
|
@ -28,45 +28,39 @@ export const ProseMirror = (props: ProseMirrorProps) => {
|
|||
props.onChange(newState)
|
||||
}
|
||||
|
||||
createEffect((payload: [EditorState, ProseMirrorExtension[]]) => {
|
||||
const [prevText, prevExtensions] = payload
|
||||
const text = unwrap(props.text)
|
||||
const extensions: ProseMirrorExtension[] = unwrap(props.extensions)
|
||||
if (!text || !extensions?.length) {
|
||||
createEffect(
|
||||
(payload: [EditorState, ProseMirrorExtension[]]) => {
|
||||
const [prevText, prevExtensions] = payload
|
||||
const text = unwrap(props.text)
|
||||
const extensions: ProseMirrorExtension[] = unwrap(props.extensions)
|
||||
if (!text || !extensions?.length) {
|
||||
return [text, extensions]
|
||||
}
|
||||
|
||||
if (!props.editorView) {
|
||||
const { editorState, nodeViews } = createEditorState(text, extensions)
|
||||
const view = new EditorView(editorRef, { state: editorState, nodeViews, dispatchTransaction })
|
||||
view.focus()
|
||||
props.onInit(editorState, view)
|
||||
return [editorState, extensions]
|
||||
}
|
||||
|
||||
if (extensions !== prevExtensions || (!(text instanceof EditorState) && text !== prevText)) {
|
||||
const { editorState, nodeViews } = createEditorState(text, extensions, prevText)
|
||||
if (!editorState) return
|
||||
editorView().updateState(editorState)
|
||||
editorView().setProps({ nodeViews, dispatchTransaction })
|
||||
props.onReconfigure(editorState)
|
||||
editorView().focus()
|
||||
return [editorState, extensions]
|
||||
}
|
||||
|
||||
return [text, extensions]
|
||||
}
|
||||
|
||||
if (!props.editorView) {
|
||||
const { editorState, nodeViews } = createEditorState(text, extensions)
|
||||
const view = new EditorView(editorRef, { state: editorState, nodeViews, dispatchTransaction })
|
||||
view.focus()
|
||||
props.onInit(editorState, view)
|
||||
return [editorState, extensions]
|
||||
}
|
||||
|
||||
if (extensions !== prevExtensions || (!(text instanceof EditorState) && text !== prevText)) {
|
||||
const { editorState, nodeViews } = createEditorState(text, extensions, prevText)
|
||||
if (!editorState) return
|
||||
editorView().updateState(editorState)
|
||||
editorView().setProps({ nodeViews, dispatchTransaction })
|
||||
props.onReconfigure(editorState)
|
||||
editorView().focus()
|
||||
return [editorState, extensions]
|
||||
}
|
||||
|
||||
return [text, extensions]
|
||||
},
|
||||
[props.text, props.extensions]
|
||||
},
|
||||
[props.text, props.extensions]
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
style={props.style}
|
||||
ref={editorRef}
|
||||
class={props.className}
|
||||
spell-check={false}
|
||||
/>
|
||||
)
|
||||
return <div style={props.style} ref={editorRef} class={props.className} spell-check={false} />
|
||||
}
|
||||
|
||||
const createEditorState = (
|
||||
|
@ -74,8 +68,8 @@ const createEditorState = (
|
|||
extensions: ProseMirrorExtension[],
|
||||
prevText?: EditorState
|
||||
): {
|
||||
editorState: EditorState;
|
||||
nodeViews: { [key: string]: NodeViewFn };
|
||||
editorState: EditorState
|
||||
nodeViews: { [key: string]: NodeViewFn }
|
||||
} => {
|
||||
const reconfigure = text instanceof EditorState && prevText?.schema
|
||||
let schemaSpec = { nodes: {} }
|
||||
|
@ -104,7 +98,7 @@ const createEditorState = (
|
|||
editorState = text.reconfigure({ schema, plugins } as EditorStateConfig)
|
||||
} else if (text instanceof EditorState) {
|
||||
editorState = EditorState.fromJSON({ schema, plugins }, text.toJSON())
|
||||
} else if (text){
|
||||
} else if (text) {
|
||||
console.debug(text)
|
||||
editorState = EditorState.fromJSON({ schema, plugins }, text)
|
||||
}
|
||||
|
|
|
@ -8,16 +8,16 @@ import { isEmpty } from '../prosemirror/helpers'
|
|||
import type { Styled } from './Layout'
|
||||
import '../styles/Sidebar.scss'
|
||||
|
||||
const Off = (props) => <div class='sidebar-off'>{props.children}</div>
|
||||
const Off = (props) => <div class="sidebar-off">{props.children}</div>
|
||||
|
||||
const Label = (props: Styled) => <h3 class='sidebar-label'>{props.children}</h3>
|
||||
const Label = (props: Styled) => <h3 class="sidebar-label">{props.children}</h3>
|
||||
|
||||
const Link = (
|
||||
props: Styled & { withMargin?: boolean; disabled?: boolean; title?: string; className?: string }
|
||||
) => (
|
||||
<button
|
||||
class={`sidebar-link${props.className ? ' ' + props.className : ''}`}
|
||||
style={{ "margin-bottom": props.withMargin ? '10px' : '' }}
|
||||
style={{ 'margin-bottom': props.withMargin ? '10px' : '' }}
|
||||
onClick={props.onClick}
|
||||
disabled={props.disabled}
|
||||
title={props.title}
|
||||
|
@ -34,22 +34,23 @@ export const Sidebar = () => {
|
|||
document.body.classList.toggle('dark')
|
||||
ctrl.updateConfig({ theme: document.body.className })
|
||||
}
|
||||
const collabText = () => (store.collab?.started ? 'Stop' : (store.collab?.error ? 'Restart 🚨' : 'Start'))
|
||||
const collabText = () => (store.collab?.started ? 'Stop' : store.collab?.error ? 'Restart 🚨' : 'Start')
|
||||
const editorView = () => unwrap(store.editorView)
|
||||
const onToggleMarkdown = () => ctrl.toggleMarkdown()
|
||||
const onOpenDraft = (draft: Draft) => ctrl.openDraft(unwrap(draft))
|
||||
const collabUsers = () => store.collab?.y?.provider.awareness.meta.size ?? 0
|
||||
const onUndo = () => undo(editorView().state, editorView().dispatch)
|
||||
const onRedo = () => redo(editorView().state, editorView().dispatch)
|
||||
const onCopyAllAsMd = () => remote.copyAllAsMarkdown(editorView().state).then(() => setLastAction('copy-md'))
|
||||
const onCopyAllAsMd = () =>
|
||||
remote.copyAllAsMarkdown(editorView().state).then(() => setLastAction('copy-md'))
|
||||
const onDiscard = () => ctrl.discard()
|
||||
const [isHidden, setIsHidden] = createSignal<boolean | false>()
|
||||
|
||||
const toggleSidebar = () => {
|
||||
setIsHidden(!isHidden());
|
||||
setIsHidden(!isHidden())
|
||||
}
|
||||
|
||||
toggleSidebar();
|
||||
toggleSidebar()
|
||||
|
||||
const onCollab = () => {
|
||||
const state = unwrap(store)
|
||||
|
@ -84,11 +85,13 @@ export const Sidebar = () => {
|
|||
}
|
||||
|
||||
const text = () =>
|
||||
p.draft.path ? p.draft.path.slice(Math.max(0, p.draft.path.length - length)) : getContent(p.draft.text?.doc)
|
||||
p.draft.path
|
||||
? p.draft.path.slice(Math.max(0, p.draft.path.length - length))
|
||||
: getContent(p.draft.text?.doc)
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line solid/no-react-specific-props
|
||||
<Link className='draft' onClick={() => onOpenDraft(p.draft)} data-testid='open'>
|
||||
<Link className="draft" onClick={() => onOpenDraft(p.draft)} data-testid="open">
|
||||
{text()} {p.draft.path && '📎'}
|
||||
</Link>
|
||||
)
|
||||
|
@ -96,9 +99,7 @@ export const Sidebar = () => {
|
|||
|
||||
const Keys = (props) => (
|
||||
<span>
|
||||
<For each={props.keys}>{(k: Element) => (
|
||||
<i>{k}</i>
|
||||
)}</For>
|
||||
<For each={props.keys}>{(k: Element) => <i>{k}</i>}</For>
|
||||
</span>
|
||||
)
|
||||
|
||||
|
@ -116,10 +117,12 @@ export const Sidebar = () => {
|
|||
|
||||
return (
|
||||
<div class={'sidebar-container' + (isHidden() ? ' sidebar-container--hidden' : '')}>
|
||||
<span class='sidebar-opener' onClick={toggleSidebar}>Советы и предложения</span>
|
||||
<span class="sidebar-opener" onClick={toggleSidebar}>
|
||||
Советы и предложения
|
||||
</span>
|
||||
|
||||
<Off onClick={() => editorView().focus()}>
|
||||
<div class='sidebar-closer' onClick={toggleSidebar}/>
|
||||
<div class="sidebar-closer" onClick={toggleSidebar} />
|
||||
<Show when={true}>
|
||||
<div>
|
||||
{store.path && (
|
||||
|
@ -127,27 +130,25 @@ export const Sidebar = () => {
|
|||
<i>({store.path.slice(Math.max(0, store.path.length - 24))})</i>
|
||||
</Label>
|
||||
)}
|
||||
<Link>
|
||||
Пригласить соавторов
|
||||
</Link>
|
||||
<Link>
|
||||
Настройки публикации
|
||||
</Link>
|
||||
<Link>
|
||||
История правок
|
||||
</Link>
|
||||
<Link>Пригласить соавторов</Link>
|
||||
<Link>Настройки публикации</Link>
|
||||
<Link>История правок</Link>
|
||||
|
||||
<div class='theme-switcher'>
|
||||
<div class="theme-switcher">
|
||||
Ночная тема
|
||||
<input type='checkbox' name='theme' id='theme' onClick={toggleTheme} />
|
||||
<label for='theme'>Ночная тема</label>
|
||||
<input type="checkbox" name="theme" id="theme" onClick={toggleTheme} />
|
||||
<label for="theme">Ночная тема</label>
|
||||
</div>
|
||||
<Link
|
||||
onClick={onDiscard}
|
||||
disabled={!store.path && store.drafts.length === 0 && isEmpty(store.text)}
|
||||
data-testid='discard'
|
||||
data-testid="discard"
|
||||
>
|
||||
{store.path ? 'Close' : (store.drafts.length > 0 && isEmpty(store.text) ? 'Delete ⚠️' : 'Clear')}{' '}
|
||||
{store.path
|
||||
? 'Close'
|
||||
: store.drafts.length > 0 && isEmpty(store.text)
|
||||
? 'Delete ⚠️'
|
||||
: 'Clear'}{' '}
|
||||
<Keys keys={[mod, 'w']} />
|
||||
</Link>
|
||||
<Link onClick={onUndo}>
|
||||
|
@ -156,7 +157,7 @@ export const Sidebar = () => {
|
|||
<Link onClick={onRedo}>
|
||||
Redo <Keys keys={[mod, 'Shift', 'z']} />
|
||||
</Link>
|
||||
<Link onClick={onToggleMarkdown} data-testid='markdown'>
|
||||
<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>
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import markdownit from 'markdown-it'
|
||||
import { MarkdownSerializer, MarkdownParser, defaultMarkdownSerializer, MarkdownSerializerState } from 'prosemirror-markdown'
|
||||
import {
|
||||
MarkdownSerializer,
|
||||
MarkdownParser,
|
||||
defaultMarkdownSerializer,
|
||||
MarkdownSerializerState
|
||||
} from 'prosemirror-markdown'
|
||||
import type { Node, Schema } from 'prosemirror-model'
|
||||
import type { EditorState } from 'prosemirror-state'
|
||||
|
||||
|
@ -12,7 +17,6 @@ export const serialize = (state: EditorState) => {
|
|||
return text
|
||||
}
|
||||
|
||||
|
||||
function findAlignment(cell: Node): string | null {
|
||||
const alignment = cell.attrs.style as string
|
||||
if (!alignment) {
|
||||
|
|
|
@ -36,13 +36,13 @@ export default (plain = false): ProseMirrorExtension => ({
|
|||
schema: () =>
|
||||
plain
|
||||
? {
|
||||
nodes: plainSchema.spec.nodes,
|
||||
marks: plainSchema.spec.marks
|
||||
}
|
||||
nodes: plainSchema.spec.nodes,
|
||||
marks: plainSchema.spec.marks
|
||||
}
|
||||
: {
|
||||
nodes: (markdownSchema.spec.nodes as OrderedMap<NodeSpec>).update('blockquote', blockquoteSchema),
|
||||
marks: markdownSchema.spec.marks
|
||||
},
|
||||
nodes: (markdownSchema.spec.nodes as OrderedMap<NodeSpec>).update('blockquote', blockquoteSchema),
|
||||
marks: markdownSchema.spec.marks
|
||||
},
|
||||
plugins: (prev, schema) => [
|
||||
...prev,
|
||||
keymap({
|
||||
|
|
|
@ -10,26 +10,26 @@ const blank = '\u00A0'
|
|||
|
||||
const onArrow =
|
||||
(dir: 'left' | 'right') =>
|
||||
(state: EditorState, dispatch: (tr: Transaction) => void, editorView: EditorView) => {
|
||||
if (!state.selection.empty) return false
|
||||
const $pos = state.selection.$head
|
||||
const isCode = $pos.marks().find((m: Mark) => m.type.name === 'code')
|
||||
const tr = state.tr
|
||||
(state: EditorState, dispatch: (tr: Transaction) => void, editorView: EditorView) => {
|
||||
if (!state.selection.empty) return false
|
||||
const $pos = state.selection.$head
|
||||
const isCode = $pos.marks().find((m: Mark) => m.type.name === 'code')
|
||||
const tr = state.tr
|
||||
|
||||
if (dir === 'left') {
|
||||
const up = editorView.endOfTextblock('up')
|
||||
if (!$pos.nodeBefore && up && isCode) {
|
||||
tr.insertText(blank, $pos.pos - 1, $pos.pos)
|
||||
dispatch(tr)
|
||||
}
|
||||
} else {
|
||||
const down = editorView.endOfTextblock('down')
|
||||
if (!$pos.nodeAfter && down && isCode) {
|
||||
tr.insertText(blank, $pos.pos, $pos.pos + 1)
|
||||
dispatch(tr)
|
||||
}
|
||||
if (dir === 'left') {
|
||||
const up = editorView.endOfTextblock('up')
|
||||
if (!$pos.nodeBefore && up && isCode) {
|
||||
tr.insertText(blank, $pos.pos - 1, $pos.pos)
|
||||
dispatch(tr)
|
||||
}
|
||||
} else {
|
||||
const down = editorView.endOfTextblock('down')
|
||||
if (!$pos.nodeAfter && down && isCode) {
|
||||
tr.insertText(blank, $pos.pos, $pos.pos + 1)
|
||||
dispatch(tr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const codeKeymap = {
|
||||
ArrowLeft: onArrow('left'),
|
||||
|
|
|
@ -2,7 +2,11 @@ import { ySyncPlugin, yCursorPlugin, yUndoPlugin } from 'y-prosemirror'
|
|||
import type { ProseMirrorExtension } from '../helpers'
|
||||
import type { YOptions } from '../../store/context'
|
||||
|
||||
interface YUser { background: string, foreground: string, name: string }
|
||||
interface YUser {
|
||||
background: string
|
||||
foreground: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export const cursorBuilder = (user: YUser): HTMLElement => {
|
||||
const cursor = document.createElement('span')
|
||||
|
@ -19,10 +23,10 @@ export default (y: YOptions): ProseMirrorExtension => ({
|
|||
plugins: (prev) =>
|
||||
y
|
||||
? [
|
||||
...prev,
|
||||
ySyncPlugin(y.type),
|
||||
yCursorPlugin(y.provider.awareness, { cursorBuilder }),
|
||||
yUndoPlugin()
|
||||
]
|
||||
...prev,
|
||||
ySyncPlugin(y.type),
|
||||
yCursorPlugin(y.provider.awareness, { cursorBuilder }),
|
||||
yUndoPlugin()
|
||||
]
|
||||
: prev
|
||||
})
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
} from 'prosemirror-menu'
|
||||
|
||||
import { wrapInList } from 'prosemirror-schema-list'
|
||||
import type{ NodeSelection } from 'prosemirror-state'
|
||||
import type { NodeSelection } from 'prosemirror-state'
|
||||
|
||||
import { TextField, openPrompt } from './prompt'
|
||||
import type { ProseMirrorExtension } from '../helpers'
|
||||
|
@ -22,7 +22,6 @@ import type { Schema } from 'prosemirror-model'
|
|||
|
||||
// Helpers to create specific types of items
|
||||
|
||||
|
||||
const cut = (something) => something.filter(Boolean)
|
||||
|
||||
function canInsert(state, nodeType) {
|
||||
|
@ -45,7 +44,11 @@ function insertImageItem(nodeType) {
|
|||
return canInsert(state, nodeType)
|
||||
},
|
||||
run(state, _, view) {
|
||||
const { from, to, node: { attrs } } = state.selection as NodeSelection
|
||||
const {
|
||||
from,
|
||||
to,
|
||||
node: { attrs }
|
||||
} = state.selection as NodeSelection
|
||||
|
||||
openPrompt({
|
||||
title: 'Insert image',
|
||||
|
@ -78,7 +81,9 @@ function cmdItem(cmd, options) {
|
|||
|
||||
for (const prop in options) passedOptions[prop] = options[prop]
|
||||
|
||||
if ((!options.enable || options.enable === true) && !options.select) { passedOptions[options.enable ? 'enable' : 'select'] = (state) => cmd(state) }
|
||||
if ((!options.enable || options.enable === true) && !options.select) {
|
||||
passedOptions[options.enable ? 'enable' : 'select'] = (state) => cmd(state)
|
||||
}
|
||||
|
||||
return new MenuItem(passedOptions)
|
||||
}
|
||||
|
@ -130,7 +135,7 @@ function linkItem(markType) {
|
|||
href: new TextField({
|
||||
label: 'Link target',
|
||||
required: true
|
||||
}),
|
||||
})
|
||||
},
|
||||
callback(attrs) {
|
||||
toggleMark(markType, attrs)(view.state, view.dispatch)
|
||||
|
@ -214,7 +219,7 @@ export function buildMenuItems(schema: Schema<any, any>) {
|
|||
icon: {
|
||||
width: 13,
|
||||
height: 16,
|
||||
path: "M9.82857 7.76C10.9371 6.99429 11.7143 5.73714 11.7143 4.57143C11.7143 1.98857 9.71428 0 7.14286 0H0V16H8.04571C10.4343 16 12.2857 14.0571 12.2857 11.6686C12.2857 9.93143 11.3029 8.44571 9.82857 7.76ZM3.42799 2.85708H6.85656C7.80513 2.85708 8.57085 3.6228 8.57085 4.57137C8.57085 5.51994 7.80513 6.28565 6.85656 6.28565H3.42799V2.85708ZM3.42799 13.1429H7.42799C8.37656 13.1429 9.14228 12.3772 9.14228 11.4286C9.14228 10.4801 8.37656 9.71434 7.42799 9.71434H3.42799V13.1429Z"
|
||||
path: 'M9.82857 7.76C10.9371 6.99429 11.7143 5.73714 11.7143 4.57143C11.7143 1.98857 9.71428 0 7.14286 0H0V16H8.04571C10.4343 16 12.2857 14.0571 12.2857 11.6686C12.2857 9.93143 11.3029 8.44571 9.82857 7.76ZM3.42799 2.85708H6.85656C7.80513 2.85708 8.57085 3.6228 8.57085 4.57137C8.57085 5.51994 7.80513 6.28565 6.85656 6.28565H3.42799V2.85708ZM3.42799 13.1429H7.42799C8.37656 13.1429 9.14228 12.3772 9.14228 11.4286C9.14228 10.4801 8.37656 9.71434 7.42799 9.71434H3.42799V13.1429Z'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -225,7 +230,7 @@ export function buildMenuItems(schema: Schema<any, any>) {
|
|||
icon: {
|
||||
width: 14,
|
||||
height: 16,
|
||||
path: "M4.39216 0V3.42857H6.81882L3.06353 12.5714H0V16H8.78431V12.5714H6.35765L10.1129 3.42857H13.1765V0H4.39216Z"
|
||||
path: 'M4.39216 0V3.42857H6.81882L3.06353 12.5714H0V16H8.78431V12.5714H6.35765L10.1129 3.42857H13.1765V0H4.39216Z'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -320,17 +325,16 @@ export function buildMenuItems(schema: Schema<any, any>) {
|
|||
})
|
||||
}
|
||||
|
||||
r.typeMenu = new Dropdown(
|
||||
cut([r.makeHead1, r.makeHead2, r.makeHead3, r.typeMenu, r.wrapBlockQuote]),
|
||||
{ label: 'Тт',
|
||||
class: 'editor-dropdown' // TODO: use this class
|
||||
// FIXME: icon svg code shouldn't be here
|
||||
// icon: {
|
||||
// width: 12,
|
||||
// height: 12,
|
||||
// path: "M6.39999 3.19998V0H20.2666V3.19998H14.9333V15.9999H11.7333V3.19998H6.39999ZM3.19998 8.5334H0V5.33342H9.59994V8.5334H6.39996V16H3.19998V8.5334Z"
|
||||
// }
|
||||
}) as MenuItem
|
||||
r.typeMenu = new Dropdown(cut([r.makeHead1, r.makeHead2, r.makeHead3, r.typeMenu, r.wrapBlockQuote]), {
|
||||
label: 'Тт',
|
||||
class: 'editor-dropdown' // TODO: use this class
|
||||
// FIXME: icon svg code shouldn't be here
|
||||
// icon: {
|
||||
// width: 12,
|
||||
// height: 12,
|
||||
// path: "M6.39999 3.19998V0H20.2666V3.19998H14.9333V15.9999H11.7333V3.19998H6.39999ZM3.19998 8.5334H0V5.33342H9.59994V8.5334H6.39996V16H3.19998V8.5334Z"
|
||||
// }
|
||||
}) as MenuItem
|
||||
r.blockMenu = []
|
||||
r.listMenu = [cut([r.wrapBulletList, r.wrapOrderedList])]
|
||||
r.inlineMenu = [cut([r.toggleStrong, r.toggleEm, r.toggleMark])]
|
||||
|
|
|
@ -1,60 +1,54 @@
|
|||
import { renderGrouped } from "prosemirror-menu";
|
||||
import { Plugin } from "prosemirror-state";
|
||||
import type { ProseMirrorExtension } from "../helpers";
|
||||
import { buildMenuItems } from "./menu";
|
||||
import { renderGrouped } from 'prosemirror-menu'
|
||||
import { Plugin } from 'prosemirror-state'
|
||||
import type { ProseMirrorExtension } from '../helpers'
|
||||
import { buildMenuItems } from './menu'
|
||||
|
||||
export class SelectionTooltip {
|
||||
tooltip: any;
|
||||
tooltip: any
|
||||
|
||||
constructor(view: any, schema: any) {
|
||||
this.tooltip = document.createElement("div");
|
||||
this.tooltip.className = "tooltip";
|
||||
view.dom.parentNode.appendChild(this.tooltip);
|
||||
const { dom } = renderGrouped(view, buildMenuItems(schema).fullMenu as any);
|
||||
this.tooltip.appendChild(dom);
|
||||
this.update(view, null);
|
||||
this.tooltip = document.createElement('div')
|
||||
this.tooltip.className = 'tooltip'
|
||||
view.dom.parentNode.appendChild(this.tooltip)
|
||||
const { dom } = renderGrouped(view, buildMenuItems(schema).fullMenu as any)
|
||||
this.tooltip.appendChild(dom)
|
||||
this.update(view, null)
|
||||
}
|
||||
|
||||
update(view: any, lastState: any) {
|
||||
const state = view.state;
|
||||
if (
|
||||
lastState &&
|
||||
lastState.doc.eq(state.doc) &&
|
||||
lastState.selection.eq(state.selection)
|
||||
)
|
||||
{return;}
|
||||
|
||||
if (state.selection.empty) {
|
||||
this.tooltip.style.display = "none";
|
||||
return;
|
||||
const state = view.state
|
||||
if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.tooltip.style.display = "";
|
||||
const { from, to } = state.selection;
|
||||
if (state.selection.empty) {
|
||||
this.tooltip.style.display = 'none'
|
||||
return
|
||||
}
|
||||
|
||||
this.tooltip.style.display = ''
|
||||
const { from, to } = state.selection
|
||||
const start = view.coordsAtPos(from),
|
||||
end = view.coordsAtPos(to);
|
||||
const box = this.tooltip.offsetParent.getBoundingClientRect();
|
||||
const left = Math.max((start.left + end.left) / 2, start.left + 3);
|
||||
this.tooltip.style.left = left - box.left + "px";
|
||||
this.tooltip.style.bottom = box.bottom - (start.top + 15) + "px";
|
||||
end = view.coordsAtPos(to)
|
||||
const box = this.tooltip.offsetParent.getBoundingClientRect()
|
||||
const left = Math.max((start.left + end.left) / 2, start.left + 3)
|
||||
this.tooltip.style.left = left - box.left + 'px'
|
||||
this.tooltip.style.bottom = box.bottom - (start.top + 15) + 'px'
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.tooltip.remove();
|
||||
this.tooltip.remove()
|
||||
}
|
||||
}
|
||||
|
||||
export function toolTip(schema: any) {
|
||||
return new Plugin({
|
||||
view(editorView: any) {
|
||||
return new SelectionTooltip(editorView, schema);
|
||||
},
|
||||
});
|
||||
return new SelectionTooltip(editorView, schema)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default (): ProseMirrorExtension => ({
|
||||
plugins: (prev, schema) => [
|
||||
...prev,
|
||||
toolTip(schema)
|
||||
]
|
||||
plugins: (prev, schema) => [...prev, toolTip(schema)]
|
||||
})
|
||||
|
|
|
@ -10,7 +10,7 @@ export const tableInputRule = (schema: Schema) =>
|
|||
new RegExp('^\\|{2,}\\s$'),
|
||||
(state: EditorState, match: string[], start: number, end: number) => {
|
||||
const tr = state.tr
|
||||
const columns = Array.from({length: match[0].trim().length - 1})
|
||||
const columns = Array.from({ length: match[0].trim().length - 1 })
|
||||
const headers = columns.map(() => schema.node(schema.nodes.table_header, {}))
|
||||
const cells = columns.map(() => schema.node(schema.nodes.table_cell, {}))
|
||||
const table = schema.node(schema.nodes.table, {}, [
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
import { DOMOutputSpec, DOMSerializer, Node as ProsemirrorNode, NodeSpec, NodeType, Schema } from 'prosemirror-model'
|
||||
import {
|
||||
DOMOutputSpec,
|
||||
DOMSerializer,
|
||||
Node as ProsemirrorNode,
|
||||
NodeSpec,
|
||||
NodeType,
|
||||
Schema
|
||||
} from 'prosemirror-model'
|
||||
import type { EditorView } from 'prosemirror-view'
|
||||
import { wrappingInputRule , inputRules } from 'prosemirror-inputrules'
|
||||
import { wrappingInputRule, inputRules } from 'prosemirror-inputrules'
|
||||
import { splitListItem } from 'prosemirror-schema-list'
|
||||
import { keymap } from 'prosemirror-keymap'
|
||||
import type { NodeViewFn, ProseMirrorExtension } from '../helpers'
|
||||
|
@ -59,8 +66,8 @@ class TodoItemView {
|
|||
this.dom = res.dom
|
||||
this.contentDOM = res.contentDOM
|
||||
this.view = view
|
||||
this.getPos = getPos;
|
||||
(this.dom as HTMLElement).querySelector('input').addEventListener('click', this.handleClick.bind(this))
|
||||
this.getPos = getPos
|
||||
;(this.dom as HTMLElement).querySelector('input').addEventListener('click', this.handleClick.bind(this))
|
||||
}
|
||||
|
||||
handleClick(e: MouseEvent) {
|
||||
|
@ -90,5 +97,5 @@ export default (): ProseMirrorExtension => ({
|
|||
todo_item: (node: ProsemirrorNode, view: EditorView, getPos: () => number) => {
|
||||
return new TodoItemView(node, view, getPos)
|
||||
}
|
||||
} as unknown as { [key:string]: NodeViewFn }
|
||||
} as unknown as { [key: string]: NodeViewFn }
|
||||
})
|
||||
|
|
|
@ -3,9 +3,9 @@ import type { Node, Schema, SchemaSpec } from 'prosemirror-model'
|
|||
import type { Decoration, EditorView, NodeView } from 'prosemirror-view'
|
||||
|
||||
export interface ProseMirrorExtension {
|
||||
schema?: (prev: SchemaSpec) => SchemaSpec;
|
||||
plugins?: (prev: Plugin[], schema: Schema) => Plugin[];
|
||||
nodeViews?: { [key: string]: NodeViewFn };
|
||||
schema?: (prev: SchemaSpec) => SchemaSpec
|
||||
plugins?: (prev: Plugin[], schema: Schema) => Plugin[]
|
||||
nodeViews?: { [key: string]: NodeViewFn }
|
||||
}
|
||||
|
||||
export type ProseMirrorState = EditorState | unknown
|
||||
|
|
|
@ -5,7 +5,11 @@ import { Doc, XmlFragment } from 'yjs'
|
|||
// import type { Reaction } from '../../../graphql/types.gen'
|
||||
// import { setReactions } from '../../../stores/editor'
|
||||
|
||||
export const roomConnect = (room: string, username = '', keyname = 'collab'): [XmlFragment, WebrtcProvider] => {
|
||||
export const roomConnect = (
|
||||
room: string,
|
||||
username = '',
|
||||
keyname = 'collab'
|
||||
): [XmlFragment, WebrtcProvider] => {
|
||||
const ydoc = new Doc()
|
||||
// const yarr = ydoc.getArray(keyname + '-reactions')
|
||||
// TODO: use reactions
|
||||
|
|
|
@ -16,7 +16,6 @@ const isText = (x) => x && x.doc && x.selection
|
|||
const isState = (x) => typeof x.lastModified !== 'string' && Array.isArray(x.drafts || [])
|
||||
const isDraft = (x): boolean => x && (x.text || x.path)
|
||||
|
||||
|
||||
export const createCtrl = (initial: State): [Store<State>, EditorActions] => {
|
||||
const [store, setState] = createStore(initial)
|
||||
|
||||
|
@ -54,8 +53,6 @@ export const createCtrl = (initial: State): [Store<State>, EditorActions] => {
|
|||
return true
|
||||
}
|
||||
|
||||
|
||||
|
||||
const toggleMarkdown = () => {
|
||||
const state = unwrap(store)
|
||||
const editorState = store.text as EditorState
|
||||
|
@ -65,7 +62,9 @@ export const createCtrl = (initial: State): [Store<State>, EditorActions] => {
|
|||
|
||||
if (markdown) {
|
||||
const lines = serialize(editorState).split('\n')
|
||||
const nodes = lines.map((text) => text ? { type: 'paragraph', content: [{ type: 'text', text }] } : { type: 'paragraph' })
|
||||
const nodes = lines.map((text) =>
|
||||
text ? { type: 'paragraph', content: [{ type: 'text', text }] } : { type: 'paragraph' }
|
||||
)
|
||||
|
||||
doc = { type: 'doc', content: nodes }
|
||||
} else {
|
||||
|
@ -275,26 +274,27 @@ export const createCtrl = (initial: State): [Store<State>, EditorActions] => {
|
|||
})
|
||||
}
|
||||
|
||||
const saveState = () => debounce(async (state: State) => {
|
||||
const data: State = {
|
||||
lastModified: state.lastModified,
|
||||
drafts: state.drafts,
|
||||
config: state.config,
|
||||
path: state.path,
|
||||
markdown: state.markdown,
|
||||
collab: {
|
||||
room: state.collab?.room
|
||||
const saveState = () =>
|
||||
debounce(async (state: State) => {
|
||||
const data: State = {
|
||||
lastModified: state.lastModified,
|
||||
drafts: state.drafts,
|
||||
config: state.config,
|
||||
path: state.path,
|
||||
markdown: state.markdown,
|
||||
collab: {
|
||||
room: state.collab?.room
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isInitialized(state.text)) {
|
||||
data.text = store.editorView.state.toJSON()
|
||||
} else if (state.text) {
|
||||
data.text = state.text
|
||||
}
|
||||
if (isInitialized(state.text)) {
|
||||
data.text = store.editorView.state.toJSON()
|
||||
} else if (state.text) {
|
||||
data.text = state.text
|
||||
}
|
||||
|
||||
db.set('state', JSON.stringify(data))
|
||||
}, 200)
|
||||
db.set('state', JSON.stringify(data))
|
||||
}, 200)
|
||||
|
||||
const setFullscreen = (fullscreen: boolean) => {
|
||||
setState({ fullscreen })
|
||||
|
@ -380,7 +380,7 @@ export const createCtrl = (initial: State): [Store<State>, EditorActions] => {
|
|||
}
|
||||
|
||||
const updateTheme = () => {
|
||||
const { theme } = getTheme(unwrap(store))
|
||||
const { theme } = getTheme(unwrap(store))
|
||||
setState('config', { theme })
|
||||
}
|
||||
|
||||
|
|
|
@ -19,65 +19,65 @@ export interface ExtensionsProps {
|
|||
typewriterMode?: boolean
|
||||
}
|
||||
export interface Args {
|
||||
cwd?: string;
|
||||
draft?: string;
|
||||
room?: string;
|
||||
text?: any;
|
||||
cwd?: string
|
||||
draft?: string
|
||||
room?: string
|
||||
text?: any
|
||||
}
|
||||
|
||||
export interface PrettierConfig {
|
||||
printWidth: number;
|
||||
tabWidth: number;
|
||||
useTabs: boolean;
|
||||
semi: boolean;
|
||||
singleQuote: boolean;
|
||||
printWidth: number
|
||||
tabWidth: number
|
||||
useTabs: boolean
|
||||
semi: boolean
|
||||
singleQuote: boolean
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
theme: string;
|
||||
theme: string
|
||||
// codeTheme: string;
|
||||
font: string;
|
||||
fontSize: number;
|
||||
contentWidth: number;
|
||||
typewriterMode: boolean;
|
||||
prettier: PrettierConfig;
|
||||
font: string
|
||||
fontSize: number
|
||||
contentWidth: number
|
||||
typewriterMode: boolean
|
||||
prettier: PrettierConfig
|
||||
}
|
||||
|
||||
export interface ErrorObject {
|
||||
id: string;
|
||||
props?: unknown;
|
||||
id: string
|
||||
props?: unknown
|
||||
}
|
||||
|
||||
export interface YOptions {
|
||||
type: XmlFragment;
|
||||
provider: WebrtcProvider;
|
||||
type: XmlFragment
|
||||
provider: WebrtcProvider
|
||||
}
|
||||
|
||||
export interface Collab {
|
||||
started?: boolean;
|
||||
error?: boolean;
|
||||
room?: string;
|
||||
y?: YOptions;
|
||||
started?: boolean
|
||||
error?: boolean
|
||||
room?: string
|
||||
y?: YOptions
|
||||
}
|
||||
|
||||
export type LoadingType = 'loading' | 'initialized'
|
||||
|
||||
export interface State {
|
||||
isMac?: boolean
|
||||
text?: ProseMirrorState;
|
||||
editorView?: EditorView;
|
||||
extensions?: ProseMirrorExtension[];
|
||||
markdown?: boolean;
|
||||
lastModified?: Date;
|
||||
drafts: Draft[];
|
||||
config: Config;
|
||||
error?: ErrorObject;
|
||||
loading?: LoadingType;
|
||||
fullscreen?: boolean;
|
||||
collab?: Collab;
|
||||
path?: string;
|
||||
args?: Args;
|
||||
keymap?: { [key: string]: Command; }
|
||||
text?: ProseMirrorState
|
||||
editorView?: EditorView
|
||||
extensions?: ProseMirrorExtension[]
|
||||
markdown?: boolean
|
||||
lastModified?: Date
|
||||
drafts: Draft[]
|
||||
config: Config
|
||||
error?: ErrorObject
|
||||
loading?: LoadingType
|
||||
fullscreen?: boolean
|
||||
collab?: Collab
|
||||
path?: string
|
||||
args?: Args
|
||||
keymap?: { [key: string]: Command }
|
||||
}
|
||||
|
||||
export interface Draft {
|
||||
|
@ -91,7 +91,7 @@ export interface Draft {
|
|||
|
||||
export interface EditorActions {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[key:string]: any
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export class ServiceError extends Error {
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
color: var(--background);
|
||||
border-color: var(--foreground);
|
||||
}
|
||||
|
||||
|
||||
.drop-cursor {
|
||||
height: 2px !important;
|
||||
opacity: 0.5;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.sidebar-container {
|
||||
color: rgba(255,255,255,0.5);
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-family: 'Muller';
|
||||
@include font-size(1.6rem);
|
||||
overflow: hidden;
|
||||
|
@ -143,7 +143,7 @@
|
|||
}
|
||||
|
||||
&.draft {
|
||||
color: rgba(255,255,255,0.5);
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
line-height: 1.4;
|
||||
margin: 0 0 1em 1.5em;
|
||||
width: calc(100% - 2rem);
|
||||
|
@ -176,20 +176,22 @@
|
|||
}
|
||||
|
||||
.theme-switcher {
|
||||
border-bottom: 1px solid rgba(255,255,255,0.3);
|
||||
border-top: 1px solid rgba(255,255,255,0.3);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.3);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 1rem;
|
||||
padding: 1em 0;
|
||||
|
||||
input[type=checkbox] {
|
||||
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;
|
||||
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;
|
||||
|
|
|
@ -7,7 +7,7 @@ export default (props: { article: Shout }) => (
|
|||
<div class="floor floor--one-article">
|
||||
<div class="wide-container row">
|
||||
<div class="col-12">
|
||||
<ArticleCard article={props.article} settings={{isSingle: true}} />
|
||||
<ArticleCard article={props.article} settings={{ isSingle: true }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -148,11 +148,11 @@
|
|||
margin-bottom: 8px;
|
||||
|
||||
/* Red/500 */
|
||||
color: #D00820;
|
||||
color: #d00820;
|
||||
|
||||
a {
|
||||
color: #D00820;
|
||||
border-color: #D00820;
|
||||
color: #d00820;
|
||||
border-color: #d00820;
|
||||
|
||||
&:hover {
|
||||
color: white;
|
||||
|
|
|
@ -44,10 +44,10 @@ export const Header = (props: Props) => {
|
|||
const toggleFixed = () => setFixed(!fixed())
|
||||
// effects
|
||||
createEffect(() => {
|
||||
const isFixed = fixed() || (modal() && modal() !== 'share');
|
||||
const isFixed = fixed() || (modal() && modal() !== 'share')
|
||||
|
||||
document.body.classList.toggle('fixed', isFixed);
|
||||
document.body.classList.toggle(styles.fixed, isFixed && !modal());
|
||||
document.body.classList.toggle('fixed', isFixed)
|
||||
document.body.classList.toggle(styles.fixed, isFixed && !modal())
|
||||
})
|
||||
|
||||
// derived
|
||||
|
|
|
@ -54,28 +54,29 @@ export const ManifestPage = () => {
|
|||
|
||||
<div class="col-lg-10 offset-md-1">
|
||||
<p>
|
||||
Дискурс — независимый художественно-аналитический журнал с горизонтальной редакцией,
|
||||
основанный на принципах свободы слова, прямой демократии и совместного редактирования.
|
||||
Дискурс создаётся открытым медиасообществом ученых, журналистов, музыкантов, писателей,
|
||||
предпринимателей, философов, инженеров, художников и специалистов со всего мира,
|
||||
объединившихся, чтобы вместе делать общий журнал и объяснять с разных точек
|
||||
зрения мозаичную картину современности.
|
||||
Дискурс — независимый художественно-аналитический журнал с горизонтальной
|
||||
редакцией, основанный на принципах свободы слова, прямой демократии и совместного
|
||||
редактирования. Дискурс создаётся открытым медиасообществом ученых, журналистов, музыкантов,
|
||||
писателей, предпринимателей, философов, инженеров, художников и специалистов
|
||||
со всего мира, объединившихся, чтобы вместе делать общий журнал и объяснять
|
||||
с разных точек зрения мозаичную картину современности.
|
||||
</p>
|
||||
<p>
|
||||
Мы пишем о культуре, науке и обществе, рассказываем о новых идеях и современном искусстве,
|
||||
публикуем статьи, исследования, репортажи, интервью людей, чью прямую речь стоит услышать,
|
||||
и работы художников из разных стран — от фильмов и музыки
|
||||
до живописи и фотографии. Помогая друг другу делать публикации качественнее
|
||||
и общим голосованием выбирая лучшие материалы для журнала, мы создаём новую
|
||||
горизонтальную журналистику, чтобы честно рассказывать о важном и интересном.
|
||||
Мы пишем о культуре, науке и обществе, рассказываем о новых идеях
|
||||
и современном искусстве, публикуем статьи, исследования, репортажи, интервью людей, чью
|
||||
прямую речь стоит услышать, и работы художников из разных стран —
|
||||
от фильмов и музыки до живописи и фотографии. Помогая друг другу делать
|
||||
публикации качественнее и общим голосованием выбирая лучшие материалы для журнала,
|
||||
мы создаём новую горизонтальную журналистику, чтобы честно рассказывать о важном
|
||||
и интересном.
|
||||
</p>
|
||||
<p>
|
||||
Редакция Дискурса открыта для всех: у нас нет цензуры, запретных тем и идеологических рамок.
|
||||
Каждый может <a href="/create">прислать материал</a> в журнал
|
||||
и <a href="/about/guide">присоединиться к редакции</a>. Предоставляя трибуну
|
||||
для независимой журналистики и художественных проектов, мы помогаем людям
|
||||
рассказывать свои истории так, чтобы они были услышаны. Мы убеждены: чем больше
|
||||
голосов будет звучать на Дискурсе, тем громче в полифонии мнений будет слышна истина.
|
||||
Редакция Дискурса открыта для всех: у нас нет цензуры, запретных тем
|
||||
и идеологических рамок. Каждый может <a href="/create">прислать материал</a>{' '}
|
||||
в журнал и <a href="/about/guide">присоединиться к редакции</a>. Предоставляя
|
||||
трибуну для независимой журналистики и художественных проектов, мы помогаем людям
|
||||
рассказывать свои истории так, чтобы они были услышаны. Мы убеждены: чем больше голосов
|
||||
будет звучать на Дискурсе, тем громче в полифонии мнений будет слышна истина.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -91,23 +92,26 @@ export const ManifestPage = () => {
|
|||
</p>
|
||||
<h3 id="contribute">Предлагать материалы</h3>
|
||||
<p>
|
||||
<a href="/create">Создавайте</a> свои статьи и художественные работы — лучшие из них будут
|
||||
опубликованы в журнале. Дискурс — некоммерческое издание, авторы публикуются
|
||||
в журнале на общественных началах, получая при этом <a href="/create?collab=true">поддержку</a> редакции,
|
||||
право голоса, множество других возможностей и читателей по всему миру.
|
||||
<a href="/create">Создавайте</a> свои статьи и художественные работы —
|
||||
лучшие из них будут опубликованы в журнале. Дискурс — некоммерческое
|
||||
издание, авторы публикуются в журнале на общественных началах, получая при этом{' '}
|
||||
<a href="/create?collab=true">поддержку</a> редакции, право голоса, множество других
|
||||
возможностей и читателей по всему миру.
|
||||
</p>
|
||||
<h3 id="donate">Поддерживать проект</h3>
|
||||
<p>Дискурс существует на пожертвования читателей. Если вам нравится журнал, пожалуйста,</p>
|
||||
<p>
|
||||
<a href="/about/help">поддержите</a> нашу работу. Ваши пожертвования пойдут на выпуск новых
|
||||
материалов, оплату серверов, труда программистов, дизайнеров и редакторов.
|
||||
Дискурс существует на пожертвования читателей. Если вам нравится журнал, пожалуйста,
|
||||
</p>
|
||||
<p>
|
||||
<a href="/about/help">поддержите</a> нашу работу. Ваши пожертвования пойдут на выпуск
|
||||
новых материалов, оплату серверов, труда программистов, дизайнеров и редакторов.
|
||||
</p>
|
||||
<h3 id="cooperation">Сотрудничать с журналом</h3>
|
||||
<p>
|
||||
Мы всегда открыты для сотрудничества и рады единомышленникам. Если вы хотите помогать
|
||||
журналу с редактурой, корректурой, иллюстрациями, переводами, версткой, подкастами,
|
||||
мероприятиями, фандрайзингом или как-то ещё — скорее пишите нам
|
||||
на <a href="mailto:welcome@discours.io">welcome@discours.io</a>.
|
||||
мероприятиями, фандрайзингом или как-то ещё — скорее пишите нам на
|
||||
<a href="mailto:welcome@discours.io">welcome@discours.io</a>.
|
||||
</p>
|
||||
<p>
|
||||
Если вы представляете некоммерческую организацию и хотите сделать с нами
|
||||
|
@ -116,25 +120,26 @@ export const ManifestPage = () => {
|
|||
</p>
|
||||
<p>
|
||||
Если вы разработчик и хотите помогать с развитием сайта Дискурса,{' '}
|
||||
<a href="mailto:services@discours.io">присоединяйтесь к IT-команде самиздата</a>. Открытый
|
||||
код платформы для независимой журналистики, а также всех наших спецпроектов
|
||||
и медиаинструментов находится <a href="https://github.com/Discours">в свободном доступе на GitHub</a>.
|
||||
<a href="mailto:services@discours.io">присоединяйтесь к IT-команде самиздата</a>.
|
||||
Открытый код платформы для независимой журналистики, а также всех наших спецпроектов
|
||||
и медиаинструментов находится{' '}
|
||||
<a href="https://github.com/Discours">в свободном доступе на GitHub</a>.
|
||||
</p>
|
||||
<h3 id="follow">Как еще можно помочь</h3>
|
||||
<p>
|
||||
Советуйте Дискурс друзьям и знакомым. Обсуждайте и распространяйте наши
|
||||
публикации — все материалы открытой редакции можно читать и перепечатывать
|
||||
бесплатно. Подпишитесь на самиздат{' '}
|
||||
<a href="https://vk.com/discoursio">ВКонтакте</a>,
|
||||
бесплатно. Подпишитесь на самиздат <a href="https://vk.com/discoursio">ВКонтакте</a>,
|
||||
в <a href="https://facebook.com/discoursio">Фейсбуке</a>
|
||||
и в <a href="https://t.me/discoursio">Телеграме</a>, а также
|
||||
на <Opener name="subscribe">рассылку лучших материалов</Opener>,
|
||||
чтобы не пропустить ничего интересного.
|
||||
и в <a href="https://t.me/discoursio">Телеграме</a>, а также на
|
||||
<Opener name="subscribe">рассылку лучших материалов</Opener>, чтобы не пропустить
|
||||
ничего интересного.
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://forms.gle/9UnHBAz9Q3tjH5dAA">Рассказывайте о впечатлениях</a>
|
||||
от материалов открытой редакции, <Opener name="feedback">делитесь идеями</Opener>,
|
||||
интересными темами, о которых хотели бы узнать больше, и историями, которые нужно рассказать.
|
||||
интересными темами, о которых хотели бы узнать больше, и историями, которые нужно
|
||||
рассказать.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -145,9 +150,9 @@ export const ManifestPage = () => {
|
|||
<div class="col-lg-10 offset-md-1">
|
||||
Если вы хотите предложить материал, сотрудничать, рассказать о проблеме, которую нужно
|
||||
осветить, сообщить об ошибке или баге, что-то обсудить, уточнить или посоветовать,
|
||||
пожалуйста, <Opener name="feedback">напишите нам здесь</Opener> или
|
||||
на почту <a href="mailto:welcome@discours.io">welcome@discours.io</a>. Мы обязательно
|
||||
ответим и постараемся реализовать все хорошие задумки.
|
||||
пожалуйста, <Opener name="feedback">напишите нам здесь</Opener> или на почту{' '}
|
||||
<a href="mailto:welcome@discours.io">welcome@discours.io</a>. Мы обязательно ответим
|
||||
и постараемся реализовать все хорошие задумки.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -47,7 +47,6 @@ import { CreatePage } from './Pages/CreatePage'
|
|||
// const ThanksPage = lazy(() => import('./Pages/about/ThanksPage'))
|
||||
// const CreatePage = lazy(() => import('./Pages/about/CreatePage'))
|
||||
|
||||
|
||||
type RootSearchParams = {
|
||||
modal: string
|
||||
lang: string
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { gql } from '@urql/core'
|
||||
|
||||
|
||||
export default gql`
|
||||
mutation CreateShoutMutations($shout: ShoutInput!) {
|
||||
createShout(input: $shout) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { gql } from '@urql/core'
|
||||
|
||||
|
||||
export default gql`
|
||||
mutation DeleteShoutMutation($shout: String!) {
|
||||
deleteShout(slug: $shout) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { gql } from '@urql/core'
|
||||
|
||||
|
||||
export default gql`
|
||||
mutation DeleteReactionMutation($id: Int!) {
|
||||
deleteReaction(id: $id) {
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
--danger-color: #fc6847;
|
||||
--lightgray-color: rgb(84 16 17 / 6%);
|
||||
--font: -apple-system, blinkmacsystemfont, 'Segoe UI', roboto, oxygen, ubuntu, cantarell, 'Open Sans',
|
||||
'Helvetica Neue', sans-serif;
|
||||
'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
|
|
Loading…
Reference in New Issue
Block a user