From adc0fe639342cd8d70803bcf9dd0a562ca97dcce Mon Sep 17 00:00:00 2001 From: tonyrewin Date: Fri, 7 Oct 2022 22:35:53 +0300 Subject: [PATCH 01/56] refactor and fixes --- astro.config.ts | 10 +- package.json | 25 +- src/components/Article/FullArticle.tsx | 2 +- src/components/Article/Tooltip.tsx | 9 +- src/components/Discours/Donate.tsx | 25 +- src/components/Editor/Error.tsx | 2 +- src/components/Editor/Layout.tsx | 2 +- src/components/Editor/Sidebar.tsx | 2 +- src/components/Editor/index.tsx | 2 +- .../index.ts => prosemirror/context.ts} | 18 +- .../Editor/{store => prosemirror}/ctrl.ts | 279 +++-------- .../Editor/prosemirror/extension/collab.ts | 20 +- src/components/Editor/prosemirror/index.tsx | 4 +- .../Editor/prosemirror}/p2p.ts | 21 +- src/components/Editor/prosemirror/setup.ts | 12 +- src/components/Feed/Beside.tsx | 4 +- src/components/Feed/Group.tsx | 3 +- src/components/Feed/Row3.tsx | 3 +- src/components/Nav/AuthModal.tsx | 18 +- src/components/Nav/Modal.tsx | 4 +- src/components/Nav/Opener.tsx | 3 +- src/components/Nav/Private.tsx | 2 +- src/components/Views/AllAuthors.tsx | 3 +- src/components/Views/Create.tsx | 4 +- src/components/Views/Home.tsx | 2 +- src/graphql/query/author-followers.ts | 2 +- src/graphql/query/author-following.ts | 2 +- src/graphql/query/authors-all.ts | 2 +- src/graphql/query/authors-by-slugs.ts | 2 +- src/stores/{create.ts => editor.ts} | 13 +- src/stores/zine/authors.ts | 11 +- src/stores/zine/topics.ts | 15 +- src/utils/groupby.ts | 6 +- src/utils/index.ts | 3 +- src/utils/sortby.ts | 9 +- yarn.lock | 467 ++++-------------- 36 files changed, 314 insertions(+), 697 deletions(-) rename src/components/Editor/{store/index.ts => prosemirror/context.ts} (87%) rename src/components/Editor/{store => prosemirror}/ctrl.ts (63%) rename src/{utils => components/Editor/prosemirror}/p2p.ts (61%) rename src/stores/{create.ts => editor.ts} (64%) diff --git a/astro.config.ts b/astro.config.ts index 472928ed..5733ee29 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -43,16 +43,13 @@ const astroConfig: AstroUserConfig = { // eslint-disable-next-line sonarjs/cognitive-complexity manualChunks(id) { if (id.includes('node_modules')) { - let chunkid = 'vendor' + let chunkid if (id.includes('solid')) { chunkid = 'solid' } if (id.includes('acorn')) { chunkid = 'acorn' } - if (id.includes('simple-peer')) { - chunkid = 'simple-peer' - } if (id.includes('prosemirror')) { chunkid = 'prosemirror' } @@ -66,9 +63,10 @@ const astroConfig: AstroUserConfig = { id.includes('yjs') || id.includes('y-prosemirror') || id.includes('y-protocols') || - id.includes('y-webrtc') + id.includes('y-webrtc') || + id.includes('simple-peer') ) { - chunkid = 'yjs' + chunkid = 'p2p' } return chunkid } diff --git a/package.json b/package.json index 07e2d198..9077d4bf 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "vercel-build": "astro build" }, "dependencies": { - "@aws-sdk/client-s3": "^3.178.0", + "@aws-sdk/client-s3": "^3.186.0", "@nanostores/persistent": "^0.7.0", "@nanostores/router": "^0.7.0", "@nanostores/solid": "^0.3.0", @@ -46,6 +46,7 @@ }, "devDependencies": { "@astrojs/language-server": "^0.27.0", + "@astrojs/markdown-remark": "^1.1.3", "@astrojs/solid-js": "^1.1.0", "@astrojs/vercel": "^2.1.0", "@babel/core": "^7.18.13", @@ -57,8 +58,8 @@ "@graphql-tools/url-loader": "^7.16.4", "@graphql-typed-document-node/core": "^3.1.1", "@popperjs/core": "^2.11.6", - "@solid-devtools/debugger": "^0.9.1", - "@solid-devtools/logger": "^0.4.7", + "@solid-devtools/debugger": "^0.11.1", + "@solid-devtools/logger": "^0.4.9", "@types/express": "^4.17.14", "@types/node": "^18.7.19", "@types/uuid": "^8.3.4", @@ -75,18 +76,16 @@ "clsx": "^1.2.1", "cookie": "^0.5.0", "cookie-signature": "^1.2.0", - "eslint": "8.22.0", - "eslint-config-stylelint": "^16.0.0", + "eslint": "^8.22.0", + "eslint-config-stylelint": "^17.0.0", "eslint-import-resolver-typescript": "^3.5.0", - "eslint-mdx": "^2.0.2", - "eslint-plugin-astro": "^0.19.0", + "eslint-plugin-astro": "^0.20.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsx-a11y": "^6.6.1", - "eslint-plugin-mdx": "^2.0.2", "eslint-plugin-promise": "^6.0.1", - "eslint-plugin-solid": "^0.7.1", + "eslint-plugin-solid": "^0.7.3", "eslint-plugin-sonarjs": "^0.15.0", - "eslint-plugin-unicorn": "^43.0.2", + "eslint-plugin-unicorn": "^44.0.2", "graphql": "^16.6.0", "graphql-tag": "^2.12.6", "graphql-ws": "^5.11.2", @@ -119,7 +118,7 @@ "rollup": "~2.79.1", "rollup-plugin-visualizer": "^5.8.2", "sass": "^1.55.0", - "solid-devtools": "^0.16.2", + "solid-devtools": "^0.18.2", "solid-js": "^1.5.6", "solid-js-form": "^0.1.5", "solid-jsx": "^0.9.1", @@ -133,14 +132,14 @@ "stylelint-order": "^5.0.0", "stylelint-scss": "^4.3.0", "swiper": "^8.4.2", - "ts-debounce": "^4.0.0", "ts-node": "^10.9.1", "typescript": "^4.8.3", "undici": "^5.10.0", "unique-names-generator": "^4.7.1", "uuid": "^9.0.0", "vite": "^3.1.3", - "y-prosemirror": "^1.1.3", + "ws": "^8.9.0", + "y-prosemirror": "^1.2.0", "y-protocols": "^1.0.5", "y-webrtc": "^10.2.3", "yjs": "^13.5.41" diff --git a/src/components/Article/FullArticle.tsx b/src/components/Article/FullArticle.tsx index a499adc4..7d8311ec 100644 --- a/src/components/Article/FullArticle.tsx +++ b/src/components/Article/FullArticle.tsx @@ -37,7 +37,7 @@ const formatDate = (date: Date) => { } export const FullArticle = (props: ArticleProps) => { - const body = createMemo(() => props.article.body.toString().trim()) + const body = createMemo(() => props.article?.body?.toString().trim()) const { session } = useAuthStore() onMount(() => { diff --git a/src/components/Article/Tooltip.tsx b/src/components/Article/Tooltip.tsx index dfd623d4..37e14dd6 100644 --- a/src/components/Article/Tooltip.tsx +++ b/src/components/Article/Tooltip.tsx @@ -1,7 +1,12 @@ import './Tooltip.scss' -import { createSignal } from 'solid-js' +import { createSignal, JSX } from 'solid-js' -export const Tooltip: (p: any) => any = (props: any) => { +interface TooltipProps { + children?: JSX.Element + link?: string +} + +export const Tooltip = (props: TooltipProps) => { const [isShown, setShowed] = createSignal(false) const show = () => setShowed(true) return ( diff --git a/src/components/Discours/Donate.tsx b/src/components/Discours/Donate.tsx index 4ed54633..480beeba 100644 --- a/src/components/Discours/Donate.tsx +++ b/src/components/Discours/Donate.tsx @@ -1,29 +1,32 @@ +import '../../styles/help.scss' import { createSignal, onMount } from 'solid-js' import { showModal, warn } from '../../stores/ui' -import '../../styles/help.scss' +import { t } from '../../utils/intl' export const Donate = () => { const once = '' const monthly = 'Monthly' const cpOptions = { publicId: 'pk_0a37bab30ffc6b77b2f93d65f2aed', - description: 'Поддержка журнала и развитие Дискурса', + description: t('Help discours to grow'), currency: 'RUB' } let amountSwitchElement: HTMLDivElement | undefined let customAmountElement: HTMLInputElement | undefined - let CustomerReciept: any - let widget: any - + const [widget, setWidget] = createSignal() + const [customerReciept, setCustomerReciept] = createSignal({}) const [showingPayment, setShowingPayment] = createSignal() const [period, setPeriod] = createSignal(monthly) const [amount, setAmount] = createSignal(0) onMount(() => { - widget = new (window as any).cp.CloudPayments() // Checkout(cpOptions) + const { + cp: { CloudPayments } + } = window as any // Checkout(cpOptions) + setWidget(new CloudPayments()) console.log('[donate] payments initiated') - CustomerReciept = { + setCustomerReciept({ Items: [ //товарные позиции { @@ -46,7 +49,7 @@ export const Donate = () => { credit: 0, // Сумма постоплатой(в кредит) (2 знака после запятой) provision: 0 // Сумма оплаты встречным предоставлением (сертификаты, др. мат.ценности) (2 знака после запятой) } - } + }) }) const show = () => { @@ -57,7 +60,7 @@ export const Donate = () => { amountSwitchElement?.querySelector('input[type=radio]:checked') setAmount(Number.parseInt(customAmountElement?.value || choice?.value || '0')) console.log('[donate] input amount ' + amount) - widget.charge( + ;(widget() as any).charge( { // options ...cpOptions, @@ -69,11 +72,11 @@ export const Donate = () => { // accountId: 'user@example.com', //идентификатор плательщика (обязательно для создания подписки) data: { CloudPayments: { - CustomerReciept, + CustomerReciept: customerReciept(), recurrent: { interval: period(), // local solid's signal period: 1, // internal widget's - CustomerReciept // чек для регулярных платежей + CustomerReciept: customerReciept() // чек для регулярных платежей } } } diff --git a/src/components/Editor/Error.tsx b/src/components/Editor/Error.tsx index c6d002d1..20a0b43f 100644 --- a/src/components/Editor/Error.tsx +++ b/src/components/Editor/Error.tsx @@ -1,5 +1,5 @@ import { Switch, Match, createMemo } from 'solid-js' -import { ErrorObject, useState } from './store' +import { ErrorObject, useState } from './prosemirror/context' const InvalidState = (props: { title: string }) => { const [store, ctrl] = useState() diff --git a/src/components/Editor/Layout.tsx b/src/components/Editor/Layout.tsx index 5f0dd1e4..e0e0e3d1 100644 --- a/src/components/Editor/Layout.tsx +++ b/src/components/Editor/Layout.tsx @@ -1,4 +1,4 @@ -import type { Config } from './store' +import type { Config } from './prosemirror/context' import './Layout.scss' export type Styled = { diff --git a/src/components/Editor/Sidebar.tsx b/src/components/Editor/Sidebar.tsx index eec66b64..08a8dac0 100644 --- a/src/components/Editor/Sidebar.tsx +++ b/src/components/Editor/Sidebar.tsx @@ -1,7 +1,7 @@ import { For, Show, createEffect, createSignal, onCleanup } from 'solid-js' import { unwrap } from 'solid-js/store' // import { undo, redo } from 'prosemirror-history' -import { File, useState /*, Config, PrettierConfig */ } from './store' +import { File, useState /*, Config, PrettierConfig */ } from './prosemirror/context' import { clsx } from 'clsx' import type { Styled } from './Layout' // import type { EditorState } from 'prosemirror-state' diff --git a/src/components/Editor/index.tsx b/src/components/Editor/index.tsx index 49841c3a..ea3614bb 100644 --- a/src/components/Editor/index.tsx +++ b/src/components/Editor/index.tsx @@ -1,7 +1,7 @@ import './Editor.scss' import type { EditorView } from 'prosemirror-view' import type { EditorState } from 'prosemirror-state' -import { useState } from './store' +import { useState } from './prosemirror/context' import { ProseMirror } from './prosemirror' export default () => { diff --git a/src/components/Editor/store/index.ts b/src/components/Editor/prosemirror/context.ts similarity index 87% rename from src/components/Editor/store/index.ts rename to src/components/Editor/prosemirror/context.ts index e6df1cea..4e4cc8ef 100644 --- a/src/components/Editor/store/index.ts +++ b/src/components/Editor/prosemirror/context.ts @@ -1,13 +1,11 @@ import { createContext, useContext } from 'solid-js' import type { Store } from 'solid-js/store' -import type { XmlFragment } from 'yjs' import type { WebrtcProvider } from 'y-webrtc' -import type { ProseMirrorExtension, ProseMirrorState } from '../prosemirror/state' -import type { Reaction } from '../../../graphql/types.gen' +import type { ProseMirrorExtension, ProseMirrorState } from './state' import type { EditorView } from 'prosemirror-view' +import type { YXmlFragment } from 'yjs/dist/src/internals' -export const isMac = true - +export const isMac = true // FIXME export const mod = isMac ? 'Cmd' : 'Ctrl' export const alt = isMac ? 'Cmd' : 'Alt' @@ -15,7 +13,7 @@ export interface Args { cwd?: string file?: string room?: string - text?: any + text?: string } export interface PrettierConfig { @@ -43,8 +41,8 @@ export interface ErrorObject { props: unknown } -export interface YOptions { - type: XmlFragment +export interface PeerData { + payload: YXmlFragment provider: WebrtcProvider } @@ -52,13 +50,13 @@ export interface Collab { started?: boolean error?: boolean room?: string - y?: YOptions + y?: PeerData } export type LoadingType = 'loading' | 'initialized' export interface File { - text?: { [key: string]: any } + text?: { [key: string]: string } lastModified?: string path?: string markdown?: boolean diff --git a/src/components/Editor/store/ctrl.ts b/src/components/Editor/prosemirror/ctrl.ts similarity index 63% rename from src/components/Editor/store/ctrl.ts rename to src/components/Editor/prosemirror/ctrl.ts index dfc387d8..5134602c 100644 --- a/src/components/Editor/store/ctrl.ts +++ b/src/components/Editor/prosemirror/ctrl.ts @@ -4,23 +4,20 @@ import type { EditorState } from 'prosemirror-state' import { undo, redo } from 'prosemirror-history' import { selectAll, deleteSelection } from 'prosemirror-commands' import { undo as yUndo, redo as yRedo } from 'y-prosemirror' -import { debounce } from 'ts-debounce' -// import * as remote from '../prosemirror/remote' -import { createSchema, createExtensions, createEmptyText } from '../prosemirror/setup' -import { State, File, Config, ServiceError, newState } from '.' -// import { isTauri, mod } from '../env' +import { debounce } from 'lodash' +import { createSchema, createExtensions, createEmptyText, InitOpts } from '../prosemirror/setup' +import { State, File, Config, ServiceError, newState, PeerData } from '../prosemirror/context' import { serialize, createMarkdownParser } from '../prosemirror/markdown' -import { isEmpty, isInitialized } from '../prosemirror/state' +import { isEmpty, isInitialized, ProseMirrorExtension } from '../prosemirror/state' import { isServer } from 'solid-js/web' -import { roomConnect } from '../../../utils/p2p' +import { roomConnect } from '../prosemirror/p2p' const mod = 'Ctrl' -const isTauri = false -const isText = (x: any) => x && x.doc && x.selection -const isState = (x: any) => typeof x.lastModified !== 'string' && Array.isArray(x.files) -const isFile = (x: any): boolean => x && (x.text || x.path) +const isText = (x): boolean => x && x.doc && x.selection +const isState = (x): boolean => typeof x.lastModified !== 'string' && Array.isArray(x.files) +const isFile = (x): boolean => x && (x.text || x.path) -export const createCtrl = (initial: State): [Store, any] => { +export const createCtrl = (initial: State): [Store, { [key: string]: any }] => { const [store, setState] = createStore(initial) const discardText = async () => { @@ -35,7 +32,7 @@ export const createCtrl = (initial: State): [Store, any] => { } else { const extensions = createExtensions({ config: state.config ?? store.config, - markdown: (state.markdown && store.markdown) as any, + markdown: state.markdown && store.markdown, keymap }) @@ -72,35 +69,6 @@ export const createCtrl = (initial: State): [Store, any] => { ] } - const newFile = () => { - if (isEmpty(store.text) && !store.path) { - return - } - - const state: State = unwrap(store) - let files = state.files - - if (!state.error) { - files = addToFiles(files, state) - } - - const extensions: any[] = createExtensions({ - config: state.config ?? store.config, - markdown: state.markdown, - keymap - }) - - setState({ - text: createEmptyText(), - extensions, - files, - lastModified: undefined, - path: undefined, - error: undefined, - collab: undefined - }) - } - const discard = async () => { if (store.path) { await discardText() @@ -112,36 +80,12 @@ export const createCtrl = (initial: State): [Store, any] => { } } - // FIXME - // eslint-disable-next-line unicorn/consistent-function-scoping - const onQuit = () => { - if (!isTauri) { - console.debug('quit') - // return - } - // remote.quit() - } - - const onNew = () => { - newFile() - - return true - } - const onDiscard = () => { discard() return true } - const onFullscreen = () => { - if (!isTauri) return - - ctrl.setFullscreen(!store.fullscreen) - - return true - } - const onToggleMarkdown = () => toggleMarkdown() const onUndo = () => { @@ -173,11 +117,7 @@ export const createCtrl = (initial: State): [Store, any] => { } const keymap = { - [`${mod}-q`]: onQuit, - [`${mod}-n`]: onNew, [`${mod}-w`]: onDiscard, - 'Cmd-Enter': onFullscreen, - 'Alt-Enter': onFullscreen, [`${mod}-z`]: onUndo, [`Shift-${mod}-z`]: onRedo, [`${mod}-y`]: onRedo, @@ -205,76 +145,61 @@ export const createCtrl = (initial: State): [Store, any] => { } } - // FIXME - // eslint-disable-next-line sonarjs/cognitive-complexity const fetchData = async (): Promise => { - let args = {} // await remote.getArgs().catch(() => undefined) const state: State = unwrap(store) + const room = window.location.pathname?.slice(1).trim() + const args = { room: room || undefined } + if (isServer) return - if (!isTauri) { - const room = window.location.pathname?.slice(1).trim() + const { default: db } = await import('../db') + const data: string = await db.get('state') + let parsed - args = { room: room || undefined } + if (data !== undefined) { + try { + parsed = JSON.parse(data) + if (!parsed) return { ...state, args } + } catch { + throw new ServiceError('invalid_state', data) + } } - if (!isServer) { - const { default: db } = await import('../db') - const data: string = await db.get('state') - let parsed: any - - if (data !== undefined) { - try { - parsed = JSON.parse(data) - } catch { - throw new ServiceError('invalid_state', data) - } + let text = state.text + if (parsed.text) { + if (!isText(parsed.text)) { + throw new ServiceError('invalid_state', parsed.text) } - - if (!parsed) { - return { ...state, args } - } - - let text = state.text - - if (parsed.text) { - if (!isText(parsed.text)) { - throw new ServiceError('invalid_state', parsed.text) - } - - text = parsed.text - } - - const extensions = createExtensions({ - path: parsed.path, - markdown: parsed.markdown, - keymap, - config: {} as Config - }) - - const nState = { - ...parsed, - text, - extensions, - // config, - args - } - - if (nState.lastModified) { - nState.lastModified = new Date(nState.lastModified) - } - - for (const file of parsed.files) { - if (!isFile(file)) { - throw new ServiceError('invalid_file', file) - } - } - if (!isState(nState)) { - throw new ServiceError('invalid_state', nState) - } - - return nState - } else { - return + text = parsed.text } + + const extensions = createExtensions({ + path: parsed.path, + markdown: parsed.markdown, + keymap, + config: {} as Config + }) + + const nState = { + ...parsed, + text, + extensions, + // config, + args + } + + if (nState.lastModified) { + nState.lastModified = new Date(nState.lastModified) + } + + for (const file of parsed.files) { + if (!isFile(file)) { + throw new ServiceError('invalid_file', file) + } + } + if (!isState(nState)) { + throw new ServiceError('invalid_state', nState) + } + + return nState } const getTheme = (state: State) => ({ theme: state.config.theme }) @@ -293,21 +218,12 @@ export const createCtrl = (initial: State): [Store, any] => { const init = async () => { let data = await fetchData() - try { if (data.args?.room) { data = doStartCollab(data) } else if (data.args?.text) { data = await doOpenFile(data, { text: JSON.parse(data.args?.text) }) - } /* else if (data.args?.file) { - const file = await loadFile(data.config, data.args?.file) - - data = await doOpenFile(data, file) - } else if (data.path) { - const file = await loadFile(data.config, data.path) - - data = await doOpenFile(data, file) - } */ else if (!data.text) { + } else if (!data.text) { const text = createEmptyText() const extensions = createExtensions({ config: data.config, @@ -317,55 +233,15 @@ export const createCtrl = (initial: State): [Store, any] => { data = { ...data, text, extensions } } - } catch (error: any) { - data = { ...data, error: error.errorObject } + } catch (error) { + data = { ...data, error } } - setState({ ...data, config: { ...data.config, ...getTheme(data) }, loading: 'initialized' }) } - /* - const loadFile = async (config: Config, path: string): Promise => { - try { - const fileContent = await remote.readFile(path) - const lastModified = await remote.getFileLastModified(path) - const schema = createSchema({ - config, - markdown: false, - path, - keymap - }) - - const parser = createMarkdownParser(schema) - const doc = parser.parse(fileContent).toJSON() - const text = { - doc, - selection: { - type: 'text', - anchor: 1, - head: 1 - } - } - - return { - text, - lastModified: lastModified.toISOString(), - path - } - } catch (e) { - throw new ServiceError('file_permission_denied', { error: e }) - } - } - */ - const openFile = async (file: File) => { - const state: State = unwrap(store) - const update = await doOpenFile(state, file) - - setState(update) - } const doOpenFile = async (state: State, file: File): Promise => { const findIndexOfFile = (f: File) => { @@ -398,9 +274,8 @@ export const createCtrl = (initial: State): [Store, any] => { } } - // eslint-disable-next-line solid/reactivity const saveState = debounce(async (state: State) => { - const data: any = { + const data = { lastModified: state.lastModified, files: state.files, config: state.config, @@ -408,17 +283,14 @@ export const createCtrl = (initial: State): [Store, any] => { markdown: state.markdown, collab: { room: state.collab?.room - } + }, + text: '' } if (isInitialized(state.text)) { - //if (state.path) { - // const text = serialize(store.editorView.state) - // await remote.writeFile(state.path, text) - //} data.text = store.editorView.state.toJSON() } else if (state.text) { - data.text = state.text + data.text = state.text as string } if (!isServer) { const { default: db } = await import('../db') @@ -426,11 +298,6 @@ export const createCtrl = (initial: State): [Store, any] => { } }, 200) - const setFullscreen = (fullscreen: boolean) => { - // remote.setFullscreen(fullscreen) - setState({ fullscreen }) - } - const startCollab = () => { const state: State = unwrap(store) const update = doStartCollab(state) @@ -442,15 +309,15 @@ export const createCtrl = (initial: State): [Store, any] => { const backup = state.args?.room && state.collab?.room !== state.args.room const room = state.args?.room ?? uuidv4() const username = '' // FIXME: use authenticated user name - const [type, provider] = roomConnect(room, username) + const [payload, provider] = roomConnect(room, username) - const extensions = createExtensions({ + const extensions: ProseMirrorExtension[] = createExtensions({ config: state.config, markdown: state.markdown, path: state.path, keymap, - y: { type, provider } - }) + y: { payload, provider } as PeerData + } as InitOpts) let nState = state @@ -473,7 +340,7 @@ export const createCtrl = (initial: State): [Store, any] => { return { ...nState, extensions, - collab: { started: true, room, y: { type, provider } } + collab: { started: true, room, y: { payload, provider } } } } @@ -495,7 +362,7 @@ export const createCtrl = (initial: State): [Store, any] => { const editorState = store.text as EditorState const markdown = !state.markdown const selection = { type: 'text', anchor: 1, head: 1 } - let doc: any + let doc if (markdown) { const lines = serialize(editorState).split('\n') @@ -570,11 +437,7 @@ export const createCtrl = (initial: State): [Store, any] => { discard, getTheme, init, - // loadFile, - newFile, - openFile, saveState, - setFullscreen, setState, startCollab, stopCollab, diff --git a/src/components/Editor/prosemirror/extension/collab.ts b/src/components/Editor/prosemirror/extension/collab.ts index a5444659..56750de7 100644 --- a/src/components/Editor/prosemirror/extension/collab.ts +++ b/src/components/Editor/prosemirror/extension/collab.ts @@ -1,30 +1,28 @@ import { ySyncPlugin, yCursorPlugin, yUndoPlugin } from 'y-prosemirror' import type { ProseMirrorExtension } from '../state' -import type { YOptions } from '../../store' +import type { PeerData } from '../context' -export const cursorBuilder = (user: any): HTMLElement => { +export const cursorBuilder = (user: { + name: string + foreground: string + background: string +}): HTMLElement => { const cursor = document.createElement('span') - + const userDiv = document.createElement('span') cursor.classList.add('ProseMirror-yjs-cursor') cursor.setAttribute('style', `border-color: ${user.background}`) - const userDiv = document.createElement('span') - userDiv.setAttribute('style', `background-color: ${user.background}; color: ${user.foreground}`) userDiv.textContent = user.name cursor.append(userDiv) - return cursor } -export default (y: YOptions): ProseMirrorExtension => ({ +export default (y: PeerData): ProseMirrorExtension => ({ plugins: (prev) => y ? [ ...prev, - ySyncPlugin(y.type), - // FIXME - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore + ySyncPlugin(y.payload), yCursorPlugin(y.provider.awareness, { cursorBuilder }), yUndoPlugin() ] diff --git a/src/components/Editor/prosemirror/index.tsx b/src/components/Editor/prosemirror/index.tsx index 9cb7b6b4..25258f39 100644 --- a/src/components/Editor/prosemirror/index.tsx +++ b/src/components/Editor/prosemirror/index.tsx @@ -5,7 +5,7 @@ import { EditorView } from 'prosemirror-view' import { Schema } from 'prosemirror-model' import type { NodeViewFn, ProseMirrorExtension, ProseMirrorState } from './state' -interface Props { +interface ProseMirrorProps { style?: string class?: string text?: Store @@ -54,7 +54,7 @@ const createEditorState = ( return { editorState, nodeViews } } -export const ProseMirror = (props: Props) => { +export const ProseMirror = (props: ProseMirrorProps) => { let editorRef: HTMLDivElement const editorView = () => untrack(() => unwrap(props.editorView)) diff --git a/src/utils/p2p.ts b/src/components/Editor/prosemirror/p2p.ts similarity index 61% rename from src/utils/p2p.ts rename to src/components/Editor/prosemirror/p2p.ts index 06d04889..dce80ea5 100644 --- a/src/utils/p2p.ts +++ b/src/components/Editor/prosemirror/p2p.ts @@ -2,21 +2,19 @@ import { uniqueNamesGenerator, adjectives, animals } from 'unique-names-generato import { Awareness } from 'y-protocols/awareness' import { WebrtcProvider } from 'y-webrtc' import * as Y from 'yjs' -// import type { Reaction } from '../graphql/types.gen' +import type { Reaction } from '../../../graphql/types.gen' +import { setReactions } from '../../../stores/editor' -export const roomConnect = ( - room, - username = '', - keyname = 'reactions' -): [Y.XmlFragment, WebrtcProvider] => { +export const roomConnect = (room, username = '', keyname = 'collab'): [Y.XmlFragment, WebrtcProvider] => { const ydoc = new Y.Doc() - const yxmlfrag = ydoc.getXmlFragment(keyname) // TODO: use ydoc.getArray(keyname) as Reactions[] + const yarr = ydoc.getArray(keyname + '-reactions') + const yXmlFragment = ydoc.getXmlFragment(keyname) const webrtcOptions = { awareness: new Awareness(ydoc), filterBcConns: true, maxConns: 33, signaling: [ - 'wss://signaling.discours.io', + // 'wss://signaling.discours.io', // 'wss://stun.l.google.com:19302', 'wss://y-webrtc-signaling-eu.herokuapp.com', 'wss://signaling.yjs.dev' @@ -27,6 +25,11 @@ export const roomConnect = ( const provider = new WebrtcProvider(room, ydoc, webrtcOptions) let name = username + yarr.observeDeep(() => { + console.debug('yarray updated:', yarr.toArray()) + setReactions(yarr.toArray() as Reaction[]) + }) + if (Boolean(name) === false) { name = uniqueNamesGenerator({ dictionaries: [adjectives, animals], @@ -37,5 +40,5 @@ export const roomConnect = ( } provider.awareness.setLocalStateField('user', { name }) - return [yxmlfrag, provider] + return [yXmlFragment, provider] } diff --git a/src/components/Editor/prosemirror/setup.ts b/src/components/Editor/prosemirror/setup.ts index 311ccd18..1493c591 100644 --- a/src/components/Editor/prosemirror/setup.ts +++ b/src/components/Editor/prosemirror/setup.ts @@ -15,20 +15,20 @@ import dragHandle from './extension/drag-handle' import pasteMarkdown from './extension/paste-markdown' import table from './extension/table' import collab from './extension/collab' -import type { Config, YOptions } from '../store' +import type { Config, PeerData } from './context' import selectionMenu from './extension/selection' -interface Opts { +export interface InitOpts { data?: unknown keymap?: any config: Config markdown: boolean path?: string - y?: YOptions + y?: PeerData schema?: Schema } -const customKeymap = (opts: Opts): ProseMirrorExtension => ({ +const customKeymap = (opts: InitOpts): ProseMirrorExtension => ({ plugins: (prev) => (opts.keymap ? [...prev, keymap(opts.keymap)] : prev) }) @@ -43,7 +43,7 @@ const codeMirrorKeymap = (props: Props) => { } */ -export const createExtensions = (opts: Opts): ProseMirrorExtension[] => { +export const createExtensions = (opts: InitOpts): ProseMirrorExtension[] => { return opts.markdown ? [ placeholder('Просто начните...'), @@ -91,7 +91,7 @@ export const createEmptyText = () => ({ } }) -export const createSchema = (opts: Opts) => { +export const createSchema = (opts: InitOpts) => { const extensions = createExtensions(opts) let schemaSpec = { nodes: {} } diff --git a/src/components/Feed/Beside.tsx b/src/components/Feed/Beside.tsx index e4f894f8..20da9f2b 100644 --- a/src/components/Feed/Beside.tsx +++ b/src/components/Feed/Beside.tsx @@ -11,7 +11,7 @@ import { t } from '../../utils/intl' interface BesideProps { title?: string - values: any[] + values: (Shout | User | Topic | Author)[] beside: Shout wrapper: 'topic' | 'author' | 'article' | 'top-article' isTopicCompact?: boolean @@ -40,7 +40,7 @@ export default (props: BesideProps) => {
@@ -79,24 +82,33 @@ export const AuthorView = (props: AuthorProps) => {
-
-

{title()}

-
- 0}> - {/*FIXME*/} - {/**/} - +

{title()}

+
+ 0}> + + + + 4}> + + + 6}> + + + 9}> -
+
diff --git a/src/components/Views/Topic.tsx b/src/components/Views/Topic.tsx index 727c31f5..40412244 100644 --- a/src/components/Views/Topic.tsx +++ b/src/components/Views/Topic.tsx @@ -32,14 +32,6 @@ export const TopicView = (props: TopicProps) => { const topic = createMemo(() => topicEntities()[props.topicSlug]) - /* - const slug = createMemo(() => { - let slug = props?.slug - if (props?.slug.startsWith('@')) slug = slug.replace('@', '') - return slug - }) - */ - const title = createMemo(() => { const m = getSearchParams().by if (m === 'viewed') return t('Top viewed') @@ -60,21 +52,22 @@ export const TopicView = (props: TopicProps) => { {t('Recent')} -
  • - -
  • -
  • - -
  • -
  • - -
  • + {/*TODO: server sort*/} + {/*
  • */} + {/* */} + {/*
  • */} + {/*
  • */} + {/* */} + {/*
  • */} + {/*
  • */} + {/* */} + {/*
  • */}
    @@ -92,7 +85,10 @@ export const TopicView = (props: TopicProps) => { {(article) => (
    - +
    )}
    @@ -108,11 +104,11 @@ export const TopicView = (props: TopicProps) => { beside={sortedArticles()[6]} wrapper={'author'} /> - - - - - + + + + +
    diff --git a/src/graphql/publicGraphQLClient.ts b/src/graphql/publicGraphQLClient.ts index 7bb5d2c1..2d421737 100644 --- a/src/graphql/publicGraphQLClient.ts +++ b/src/graphql/publicGraphQLClient.ts @@ -2,8 +2,8 @@ import { ClientOptions, dedupExchange, fetchExchange, createClient, Exchange } f import { devtoolsExchange } from '@urql/devtools' import { isDev } from '../utils/config' -export const baseUrl = 'https://newapi.discours.io' -// export const baseUrl = 'http://localhost:8000' +// export const baseUrl = 'https://newapi.discours.io' +export const baseUrl = 'http://localhost:8000' const exchanges: Exchange[] = [dedupExchange, fetchExchange] diff --git a/src/graphql/query/article-reactions.ts b/src/graphql/query/article-reactions.ts deleted file mode 100644 index c04d6bc8..00000000 --- a/src/graphql/query/article-reactions.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { gql } from '@urql/core' - -export default gql` - query ReactionsByShoutQuery($slug: String!, $limit: Int!, $offset: Int!) { - reactionsByShout(slug: $slug, limit: $limit, offset: $offset) { - id - body - createdAt - createdBy { - _id: slug - name - slug - userpic - } - updatedAt - replyTo { - id - } - kind - range - stat { - _id: viewed - viewed - reacted - rating - } - } - } -` diff --git a/src/graphql/types.gen.ts b/src/graphql/types.gen.ts index 3ef4b19c..59b0f1b2 100644 --- a/src/graphql/types.gen.ts +++ b/src/graphql/types.gen.ts @@ -347,7 +347,6 @@ export type Query = { markdownBody: Scalars['String'] myChats: Array> reactionsByAuthor: Array> - reactionsByShout: Array> reactionsForShouts: Array> recentAll: Array> recentCandidates: Array> @@ -422,12 +421,6 @@ export type QueryReactionsByAuthorArgs = { slug: Scalars['String'] } -export type QueryReactionsByShoutArgs = { - limit: Scalars['Int'] - offset: Scalars['Int'] - slug: Scalars['String'] -} - export type QueryReactionsForShoutsArgs = { limit: Scalars['Int'] offset: Scalars['Int'] diff --git a/src/locales/ru.json b/src/locales/ru.json index 1bc3b78b..a78d8c06 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -20,6 +20,7 @@ "Comments": "Комментарии", "Communities": "Сообщества", "Create account": "Создать аккаунт", + "Copy link": "Скопировать ссылку", "Delete": "Удалить", "Discours": "Дискурс", "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": "Дискурс — это интеллектуальная среда, веб-пространство и инструменты, которые позволяют авторам сотрудничать с читателями и объединяться для совместного создания публикаций и медиапроектов", diff --git a/src/stores/ui.ts b/src/stores/ui.ts index 912b2535..39d4c717 100644 --- a/src/stores/ui.ts +++ b/src/stores/ui.ts @@ -19,8 +19,9 @@ const modal = atom(null) const warnings = atom([]) export const showModal = (modalType: ModalType) => modal.set(modalType) - export const hideModal = () => modal.set(null) +export const toggleModal = (modalType) => modal.get() ? hideModal() : showModal(modalType) + export const clearWarns = () => warnings.set([]) export const warn = (warning: Warning) => warnings.set([...warnings.get(), warning]) diff --git a/src/styles/app.scss b/src/styles/app.scss index 276c43dd..63660d45 100644 --- a/src/styles/app.scss +++ b/src/styles/app.scss @@ -38,6 +38,7 @@ body { &.fixed { overflow: hidden; position: fixed; + width: 100%; } } @@ -210,7 +211,7 @@ button { font-weight: 400; margin-top: 0.6rem; - padding: 0.6rem 1.2rem 1rem 1rem; + padding: 0.6rem 1.2rem 0.6rem 1rem; } form { @@ -460,29 +461,48 @@ figcaption { } } +.floor--7 { + @include media-breakpoint-down(md) { + margin-bottom: 1em; + } + + .col-md-6 { + margin-bottom: 1.6em; + + @include media-breakpoint-down(md) { + margin-right: 0; + } + } +} + .floor--important { background: #000; color: #fff; padding: $grid-gutter-width 0; + padding-bottom: $container-padding-x; + padding-top: $container-padding-x; + + @include media-breakpoint-up(md) { + padding-bottom: $grid-gutter-width; + padding-top: $grid-gutter-width; + } + + h2 { + @include font-size(4.4rem); + + text-align: center; + } @include media-breakpoint-down(md) { margin-bottom: 5rem; } - .all-materials, - .shout-card__title { + .all-materials { a { color: #fff; } } - .shout__topic, - .shout__author { - a { - color: rgb(255 255 255 / 50%); - } - } - a:hover { background: #fff; color: #000 !important; @@ -503,25 +523,6 @@ figcaption { } } -.shout__topic { - font-size: 1.2rem; - letter-spacing: 0.08em; - margin-bottom: 0.8rem; - text-transform: uppercase; - transition: background-color 0.2s; - - a { - background: transparent; - border: none; - color: $link-color; - - &:hover { - background: $link-color; - color: #fff !important; - } - } -} - /* stylelint-disable-next-line */ astro-island { display: flex !important; @@ -533,7 +534,9 @@ astro-island { .main-content { flex: 1 100%; + min-height: 300px; padding-top: 100px; + position: relative; transition: all 1s ease; } diff --git a/src/utils/apiClient.ts b/src/utils/apiClient.ts index cfd359fc..5fd1739c 100644 --- a/src/utils/apiClient.ts +++ b/src/utils/apiClient.ts @@ -2,7 +2,6 @@ import type { Reaction, Shout, FollowingEntity, AuthResult } from '../graphql/ty import { publicGraphQLClient } from '../graphql/publicGraphQLClient' import { privateGraphQLClient } from '../graphql/privateGraphQLClient' import articleBySlug from '../graphql/query/article-by-slug' -import articleReactions from '../graphql/query/article-reactions' import articlesRecentAll from '../graphql/query/articles-recent-all' import articlesRecentPublished from '../graphql/query/articles-recent-published' import topicsAll from '../graphql/query/topics-all' @@ -281,14 +280,14 @@ export const apiClient = { offset: number }): Promise => { const response = await publicGraphQLClient - .query(articleReactions, { - slug: articleSlug, + .query(reactionsForShouts, { + shouts: [articleSlug], limit, offset }) .toPromise() - return response.data?.reactionsByShout + return response.data?.reactionsForShouts }, getAuthorsBySlugs: async ({ slugs }) => { const response = await publicGraphQLClient.query(authorsBySlugs, { slugs }).toPromise() From 634c8ae59c8ffa310edc95edaa35857815b3f079 Mon Sep 17 00:00:00 2001 From: tonyrewin Date: Wed, 19 Oct 2022 18:56:29 +0300 Subject: [PATCH 21/56] postmerge, unstore drafts --- src/components/Editor/components/Editor.tsx | 1 - src/components/Editor/components/Error.tsx | 11 +++++------ src/components/Editor/components/Layout.tsx | 2 +- src/components/Editor/components/Sidebar.tsx | 2 +- src/components/Editor/markdown.ts | 2 +- .../Editor/prosemirror/extension/todo-list.ts | 2 +- src/components/Editor/prosemirror/p2p.ts | 5 ++++- src/components/Editor/prosemirror/setup.ts | 6 ++++-- src/components/Editor/store/actions.ts | 11 ++++------- src/components/Editor/store/context.ts | 10 +++++----- src/components/Views/Create.tsx | 3 +-- src/locales/ru.json | 3 ++- 12 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/components/Editor/components/Editor.tsx b/src/components/Editor/components/Editor.tsx index 584fa0ea..af8f97bc 100644 --- a/src/components/Editor/components/Editor.tsx +++ b/src/components/Editor/components/Editor.tsx @@ -9,7 +9,6 @@ export const Editor = () => { const onInit = (text: EditorState, editorView: EditorView) => ctrl.setState({ editorView, text }) const onReconfigure = (text: EditorState) => ctrl.setState({ text }) const onChange = (text: EditorState) => ctrl.setState({ text, lastModified: new Date() }) - // const editorCss = (config) => css`` const style = () => { if (store.error) { return `display: none;` diff --git a/src/components/Editor/components/Error.tsx b/src/components/Editor/components/Error.tsx index 106dce79..01c65e2b 100644 --- a/src/components/Editor/components/Error.tsx +++ b/src/components/Editor/components/Error.tsx @@ -1,6 +1,7 @@ import { Switch, Match } from 'solid-js' -import { useState } from '../store/context' import '../styles/Button.scss' +import { ErrorObject, useState } from '../store/context' +import { t } from '../../../utils/intl' export default () => { const [store] = useState() @@ -28,15 +29,13 @@ const InvalidState = (props: { title: string }) => {

    {props.title}

    - There is an error with the editor state. This is probably due to an old version in which the data - structure has changed. Automatic data migrations may be supported in the future. To fix this now, - you can copy important notes from below, clean the state and paste it again. + {t('Editing conflict, please copy your notes and refresh page')}

               {JSON.stringify(store.error.props)}
             
    @@ -48,7 +47,7 @@ const Other = () => { const onClick = () => ctrl.discard() const getMessage = () => { - const err = (store.error.props as any).error + const err = (store.error.props as ErrorObject['props']).error return typeof err === 'string' ? err : err.message } diff --git a/src/components/Editor/components/Layout.tsx b/src/components/Editor/components/Layout.tsx index f8fe6a3b..29bd2532 100644 --- a/src/components/Editor/components/Layout.tsx +++ b/src/components/Editor/components/Layout.tsx @@ -7,7 +7,7 @@ export type Styled = { config?: Config 'data-testid'?: string onClick?: () => void - onMouseEnter?: (e: any) => void + onMouseEnter?: (ev: MouseEvent) => void } export const Layout = (props: Styled) => { diff --git a/src/components/Editor/components/Sidebar.tsx b/src/components/Editor/components/Sidebar.tsx index 5c81ba6c..95389275 100644 --- a/src/components/Editor/components/Sidebar.tsx +++ b/src/components/Editor/components/Sidebar.tsx @@ -1,7 +1,7 @@ import { For, Show, createEffect, createSignal, onCleanup, onMount } from 'solid-js' import { unwrap } from 'solid-js/store' import { undo, redo } from 'prosemirror-history' -import { Draft, useState /*, Config, PrettierConfig */ } from '../store/context' +import { Draft, useState } from '../store/context' import * as remote from '../remote' import { isEmpty /*, isInitialized*/ } from '../prosemirror/helpers' import type { Styled } from './Layout' diff --git a/src/components/Editor/markdown.ts b/src/components/Editor/markdown.ts index b6ca7161..faa80056 100644 --- a/src/components/Editor/markdown.ts +++ b/src/components/Editor/markdown.ts @@ -122,7 +122,7 @@ export const markdownSerializer = new MarkdownSerializer( } ) -function listIsTight(tokens: any, idx: number) { +function listIsTight(tokens: any[], idx: number) { let i = idx while (++i < tokens.length) { if (tokens[i].type !== 'list_item_open') return tokens[i].hidden diff --git a/src/components/Editor/prosemirror/extension/todo-list.ts b/src/components/Editor/prosemirror/extension/todo-list.ts index d0a56a24..21057782 100644 --- a/src/components/Editor/prosemirror/extension/todo-list.ts +++ b/src/components/Editor/prosemirror/extension/todo-list.ts @@ -1,8 +1,8 @@ import { DOMSerializer, Node as ProsemirrorNode, NodeType, Schema } from 'prosemirror-model' -import type { EditorView } from 'prosemirror-view' import { inputRules, wrappingInputRule } from 'prosemirror-inputrules' import { splitListItem } from 'prosemirror-schema-list' import { keymap } from 'prosemirror-keymap' +import type { EditorView } from 'prosemirror-view' import type { ProseMirrorExtension } from '../helpers' const todoListRule = (nodeType: NodeType) => diff --git a/src/components/Editor/prosemirror/p2p.ts b/src/components/Editor/prosemirror/p2p.ts index 3c9d1f80..e878586a 100644 --- a/src/components/Editor/prosemirror/p2p.ts +++ b/src/components/Editor/prosemirror/p2p.ts @@ -5,13 +5,16 @@ import { Doc, XmlFragment } from 'yjs' import type { Reaction } from '../../../graphql/types.gen' import { setReactions } from '../../../stores/editor' -export const roomConnect = (room, 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 yarr.observeDeep(() => { console.debug('[p2p] yarray updated', yarr.toArray()) setReactions(yarr.toArray() as Reaction[]) }) + const yXmlFragment = ydoc.getXmlFragment(keyname) const webrtcOptions = { awareness: new Awareness(ydoc), diff --git a/src/components/Editor/prosemirror/setup.ts b/src/components/Editor/prosemirror/setup.ts index fbda49e4..fcc099c9 100644 --- a/src/components/Editor/prosemirror/setup.ts +++ b/src/components/Editor/prosemirror/setup.ts @@ -19,6 +19,7 @@ import type { Command } from 'prosemirror-state' import placeholder from './extension/placeholder' import todoList from './extension/todo-list' import strikethrough from './extension/strikethrough' +import scrollPlugin from './extension/scroll' interface ExtensionsProps { data?: unknown @@ -29,6 +30,7 @@ interface ExtensionsProps { y?: YOptions schema?: Schema collab?: boolean + typewriterMode?: boolean } const customKeymap = (props: ExtensionsProps): ProseMirrorExtension => ({ @@ -37,11 +39,11 @@ const customKeymap = (props: ExtensionsProps): ProseMirrorExtension => ({ export const createExtensions = (props: ExtensionsProps): ProseMirrorExtension[] => { const eee = [ - // scroll(props.config.typewriterMode), placeholder(t('Just start typing...')), customKeymap(props), base(props.markdown), - selectionMenu() + selectionMenu(), + scrollPlugin(props.config?.typewriterMode) ] if (props.markdown) { eee.push( diff --git a/src/components/Editor/store/actions.ts b/src/components/Editor/store/actions.ts index de661f95..49e290df 100644 --- a/src/components/Editor/store/actions.ts +++ b/src/components/Editor/store/actions.ts @@ -10,9 +10,7 @@ import { State, Draft, Config, ServiceError, newState } from './context' import { serialize, createMarkdownParser } from '../markdown' import db from '../db' import { isEmpty, isInitialized } from '../prosemirror/helpers' -import { drafts as draftsatom } from '../../../stores/editor' -import { useStore } from '@nanostores/solid' -import { createMemo } from 'solid-js' +import { createSignal } from 'solid-js' const isText = (x) => x && x.doc && x.selection const isDraft = (x): boolean => x && (x.text || x.path) @@ -239,8 +237,7 @@ export const createCtrl = (initial): [Store, { [key: string]: any }] => { } const loadDraft = async (config: Config, path: string): Promise => { - const draftstore = useStore(draftsatom) - const draft = createMemo(() => draftstore()[path]) + const [draft, setDraft] = createSignal() const schema = createSchema({ config, markdown: false, @@ -334,9 +331,9 @@ export const createCtrl = (initial): [Store, { [key: string]: any }] => { if (isInitialized(state.text as EditorState)) { if (state.path) { - // const text = serialize(store.editorView.state) + const text = serialize(store.editorView.state) + // TODO: saving draft logix here // await remote.writeDraft(state.path, text) - draftsatom.setKey(state.path, store.editorView.state) } else { data.text = store.editorView.state.toJSON() } diff --git a/src/components/Editor/store/context.ts b/src/components/Editor/store/context.ts index 069ee18e..3f2d5961 100644 --- a/src/components/Editor/store/context.ts +++ b/src/components/Editor/store/context.ts @@ -9,7 +9,7 @@ export interface Args { cwd?: string draft?: string room?: string - text?: any + text?: string } export interface PrettierConfig { @@ -23,17 +23,17 @@ export interface PrettierConfig { export interface Config { theme: string // codeTheme: string; + // alwaysOnTop: boolean; font: string fontSize: number contentWidth: number - // alwaysOnTop: boolean; - // typewriterMode: boolean; + typewriterMode?: boolean; prettier: PrettierConfig } export interface ErrorObject { id: string - props?: unknown + props?: any } export interface YOptions { @@ -78,7 +78,7 @@ export interface Draft { export class ServiceError extends Error { public errorObject: ErrorObject - constructor(id: string, props: unknown) { + constructor(id: string, props: any) { super(id) this.errorObject = { id, props } } diff --git a/src/components/Views/Create.tsx b/src/components/Views/Create.tsx index 982bfc79..1239260b 100644 --- a/src/components/Views/Create.tsx +++ b/src/components/Views/Create.tsx @@ -11,6 +11,7 @@ const matchDark = () => window.matchMedia('(prefers-color-scheme: dark)') export const CreateView = (props: { state: State }) => { let isMac = false + const onChangeTheme = () => ctrl.updateTheme() onMount(() => { isMac = window?.navigator.platform.includes('Mac') matchDark().addEventListener('change', onChangeTheme) @@ -34,8 +35,6 @@ export const CreateView = (props: { state: State }) => { await ctrl.init() }) - const onChangeTheme = () => ctrl.updateTheme() - onError((error) => { console.error('[create] error:', error) ctrl.setState({ error: { id: 'exception', props: { error } } }) diff --git a/src/locales/ru.json b/src/locales/ru.json index 61d707b2..27eaf740 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -162,5 +162,6 @@ "Restart collab": "Перезапустить коллаборацию", "Start collab": "Коллаборативный режим", "Clear": "Сбросить", - "Режим": "Theme" + "Theme": "Режим", + "Editing conflict, please copy your version and refresh page": "Конфликт редактирования, пожалуйста, скопируйте вашу версию текста и обновите страницу" } From 1d7a71ae3acb82bd9170983ea51be1e84ba0661f Mon Sep 17 00:00:00 2001 From: tonyrewin Date: Wed, 19 Oct 2022 19:35:24 +0300 Subject: [PATCH 22/56] no reactions in p2p --- src/components/Editor/prosemirror/p2p.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/components/Editor/prosemirror/p2p.ts b/src/components/Editor/prosemirror/p2p.ts index e878586a..09cdfe81 100644 --- a/src/components/Editor/prosemirror/p2p.ts +++ b/src/components/Editor/prosemirror/p2p.ts @@ -2,19 +2,17 @@ import { uniqueNamesGenerator, adjectives, animals } from 'unique-names-generato import { Awareness } from 'y-protocols/awareness' import { WebrtcProvider } from 'y-webrtc' import { Doc, XmlFragment } from 'yjs' -import type { Reaction } from '../../../graphql/types.gen' -import { setReactions } from '../../../stores/editor' +// import type { Reaction } from '../../../graphql/types.gen' +// import { setReactions } from '../../../stores/editor' export const roomConnect = (room: string, username = '', keyname = 'collab'): [XmlFragment, WebrtcProvider] => { const ydoc = new Doc() - const yarr = ydoc.getArray(keyname + '-reactions') - + // const yarr = ydoc.getArray(keyname + '-reactions') // TODO: use reactions - yarr.observeDeep(() => { - console.debug('[p2p] yarray updated', yarr.toArray()) - setReactions(yarr.toArray() as Reaction[]) - }) - + // yarr.observeDeep(() => { + // console.debug('[p2p] yarray updated', yarr.toArray()) + // setReactions(yarr.toArray() as Reaction[]) + // }) const yXmlFragment = ydoc.getXmlFragment(keyname) const webrtcOptions = { awareness: new Awareness(ydoc), From 642d8b9dd1e6ce0e88dbe86bffd2a22294d6b891 Mon Sep 17 00:00:00 2001 From: tonyrewin Date: Wed, 19 Oct 2022 20:26:07 +0300 Subject: [PATCH 23/56] demo --- .eslintrc.js | 1 + src/components/Editor/components/Editor.tsx | 16 +- src/components/Editor/components/Error.tsx | 31 +- src/components/Editor/components/Layout.tsx | 21 +- .../Editor/components/ProseMirror.tsx | 94 ++-- src/components/Editor/components/Sidebar.tsx | 146 +++--- src/components/Editor/env.ts | 3 + src/components/Editor/markdown.ts | 39 +- .../Editor/prosemirror/extension/base.ts | 20 +- .../Editor/prosemirror/extension/code.ts | 46 +- .../Editor/prosemirror/extension/collab.ts | 23 +- .../prosemirror/extension/drag-handle.ts | 10 +- .../Editor/prosemirror/extension/image.ts | 30 +- .../Editor/prosemirror/extension/link.ts | 14 +- .../prosemirror/extension/mark-input-rule.ts | 8 +- .../Editor/prosemirror/extension/markdown.ts | 10 +- .../Editor/prosemirror/extension/menu.ts | 161 ++++--- .../prosemirror/extension/paste-markdown.ts | 8 +- .../prosemirror/extension/placeholder.ts | 2 +- .../Editor/prosemirror/extension/prompt.ts | 56 +-- .../Editor/prosemirror/extension/scroll.ts | 4 +- .../Editor/prosemirror/extension/selection.ts | 75 ++-- .../prosemirror/extension/strikethrough.ts | 6 +- .../Editor/prosemirror/extension/table.ts | 10 +- .../Editor/prosemirror/extension/todo-list.ts | 15 +- src/components/Editor/prosemirror/helpers.ts | 14 +- src/components/Editor/prosemirror/setup.ts | 105 ++--- src/components/Editor/remote.ts | 2 +- src/components/Editor/store/actions.ts | 415 +++++++----------- src/components/Editor/store/context.ts | 101 ++--- .../Editor/styles/ArticlesList.scss | 2 - src/components/Editor/styles/Button.scss | 49 +-- src/components/Editor/styles/Editor.scss | 149 +++---- src/components/Editor/styles/Error.scss | 3 +- src/components/Editor/styles/Index.scss | 3 + src/components/Editor/styles/Layout.scss | 7 +- src/components/Editor/styles/Sidebar.scss | 56 ++- 37 files changed, 810 insertions(+), 945 deletions(-) create mode 100644 src/components/Editor/env.ts create mode 100644 src/components/Editor/styles/Index.scss diff --git a/.eslintrc.js b/.eslintrc.js index 13d10867..6b46108b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -27,6 +27,7 @@ module.exports = { // 'plugin:@typescript-eslint/recommended-requiring-type-checking' ], rules: { + 'no-nested-ternary': 'off', '@typescript-eslint/no-unused-vars': [ 'warn', { diff --git a/src/components/Editor/components/Editor.tsx b/src/components/Editor/components/Editor.tsx index af8f97bc..2266b8c1 100644 --- a/src/components/Editor/components/Editor.tsx +++ b/src/components/Editor/components/Editor.tsx @@ -1,5 +1,5 @@ -import type { EditorView } from 'prosemirror-view' -import type { EditorState } from 'prosemirror-state' +import { EditorView } from 'prosemirror-view' +import { EditorState } from 'prosemirror-state' import { useState } from '../store/context' import { ProseMirror } from './ProseMirror' import '../styles/Editor.scss' @@ -9,17 +9,11 @@ export const Editor = () => { const onInit = (text: EditorState, editorView: EditorView) => ctrl.setState({ editorView, text }) const onReconfigure = (text: EditorState) => ctrl.setState({ text }) const onChange = (text: EditorState) => ctrl.setState({ text, lastModified: new Date() }) - const style = () => { - if (store.error) { - return `display: none;` - } else { - return store.markdown ? `white-space: pre-wrap;` : '' - } - } + // const editorCss = (config) => css`` + const style = () => (store.error ? `display: none;` : store.markdown ? `white-space: pre-wrap;` : '') return ( { const [store] = useState() return ( }> - + - + - - + + ) @@ -25,17 +24,19 @@ const InvalidState = (props: { title: string }) => { const onClick = () => ctrl.clean() return ( -
    -
    +
    +

    {props.title}

    - {t('Editing conflict, please copy your notes and refresh page')} + There is an error with the editor state. This is probably due to an old version in which the data + structure has changed. Automatic data migrations may be supported in the future. To fix this now, + you can copy important notes from below, clean the state and paste it again.

               {JSON.stringify(store.error.props)}
             
    -
    @@ -47,18 +48,18 @@ const Other = () => { const onClick = () => ctrl.discard() const getMessage = () => { - const err = (store.error.props as ErrorObject['props']).error + const err = (store.error.props as any).error return typeof err === 'string' ? err : err.message } return ( -
    -
    +
    +

    An error occurred.

               {getMessage()}
             
    -
    diff --git a/src/components/Editor/components/Layout.tsx b/src/components/Editor/components/Layout.tsx index 29bd2532..c1742db3 100644 --- a/src/components/Editor/components/Layout.tsx +++ b/src/components/Editor/components/Layout.tsx @@ -1,19 +1,16 @@ -import type { JSX } from 'solid-js/jsx-runtime' -import type { Config } from '../store/context' +import { Config } from '../store/context' import '../styles/Layout.scss' export type Styled = { - children: JSX.Element - config?: Config - 'data-testid'?: string - onClick?: () => void - onMouseEnter?: (ev: MouseEvent) => void + children: any; + config?: Config; + 'data-testid'?: string; + onClick?: () => void; + onMouseEnter?: (e: any) => void; } export const Layout = (props: Styled) => { - return ( -
    - {props.children} -
    - ) + return (
    + {props.children} +
    ) } diff --git a/src/components/Editor/components/ProseMirror.tsx b/src/components/Editor/components/ProseMirror.tsx index b85af777..dcf23d8a 100644 --- a/src/components/Editor/components/ProseMirror.tsx +++ b/src/components/Editor/components/ProseMirror.tsx @@ -1,19 +1,19 @@ import { createEffect, untrack } from 'solid-js' import { Store, unwrap } from 'solid-js/store' -import { EditorState, EditorStateConfig, Transaction } from 'prosemirror-state' +import { EditorState, Transaction } from 'prosemirror-state' import { EditorView } from 'prosemirror-view' import { Schema } from 'prosemirror-model' -import type { NodeViewFn, ProseMirrorExtension, ProseMirrorState } from '../prosemirror/helpers' +import { NodeViewFn, ProseMirrorExtension, ProseMirrorState } from '../prosemirror/helpers' interface Props { - style?: string - className?: string - text?: Store - editorView?: Store - extensions?: Store - onInit: (s: EditorState, v: EditorView) => void - onReconfigure: (s: EditorState) => void - onChange: (s: EditorState) => void + style?: string; + className?: string; + text?: Store; + editorView?: Store; + extensions?: Store; + onInit: (s: EditorState, v: EditorView) => void; + onReconfigure: (s: EditorState) => void; + onChange: (s: EditorState) => void; } export const ProseMirror = (props: Props) => { @@ -28,39 +28,45 @@ export const ProseMirror = (props: Props) => { 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) { - 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] - } - + createEffect((payload: [EditorState, ProseMirrorExtension[]]) => { + const [prevText, prevExtensions] = payload + const text: EditorState = unwrap(props.text) + const extensions: ProseMirrorExtension[] = unwrap(props.extensions) + if (!text || !extensions?.length) { return [text, extensions] - }, - [props.text, props.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] ) - return
    + return ( +
    + ) } const createEditorState = ( @@ -68,8 +74,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: {} } @@ -95,10 +101,10 @@ const createEditorState = ( let editorState: EditorState if (reconfigure) { - editorState = text.reconfigure({ schema, plugins } as EditorStateConfig) + editorState = text.reconfigure({ schema, plugins }) } 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) } diff --git a/src/components/Editor/components/Sidebar.tsx b/src/components/Editor/components/Sidebar.tsx index 95389275..899dfbd2 100644 --- a/src/components/Editor/components/Sidebar.tsx +++ b/src/components/Editor/components/Sidebar.tsx @@ -1,23 +1,23 @@ -import { For, Show, createEffect, createSignal, onCleanup, onMount } from 'solid-js' +import { For, Show, createEffect, createSignal, onCleanup } from 'solid-js' import { unwrap } from 'solid-js/store' import { undo, redo } from 'prosemirror-history' -import { Draft, useState } from '../store/context' +import { File, useState } from '../store/context' +import { mod } from '../env' import * as remote from '../remote' -import { isEmpty /*, isInitialized*/ } from '../prosemirror/helpers' -import type { Styled } from './Layout' +import { isEmpty } from '../prosemirror/helpers' +import { Styled } from './Layout' import '../styles/Sidebar.scss' -import { t } from '../../../utils/intl' -const Off = (props) => +const Off = ({ children }: Styled) => -const Label = (props: Styled) => +const Label = (props: Styled) => const Link = ( props: Styled & { withMargin?: boolean; disabled?: boolean; title?: string; className?: string } ) => ( -
    -
    - ) -} diff --git a/src/components/Nav/AuthModal/ConfirmEmail.module.scss b/src/components/Nav/AuthModal/EmailConfirm.module.scss similarity index 100% rename from src/components/Nav/AuthModal/ConfirmEmail.module.scss rename to src/components/Nav/AuthModal/EmailConfirm.module.scss diff --git a/src/components/Nav/AuthModal/ConfirmEmail.tsx b/src/components/Nav/AuthModal/EmailConfirm.tsx similarity index 92% rename from src/components/Nav/AuthModal/ConfirmEmail.tsx rename to src/components/Nav/AuthModal/EmailConfirm.tsx index 41922f01..6588285e 100644 --- a/src/components/Nav/AuthModal/ConfirmEmail.tsx +++ b/src/components/Nav/AuthModal/EmailConfirm.tsx @@ -1,4 +1,4 @@ -import styles from './ConfirmEmail.module.scss' +import styles from './EmailConfirm.module.scss' import authModalStyles from './AuthModal.module.scss' import { clsx } from 'clsx' import { t } from '../../../utils/intl' @@ -11,7 +11,7 @@ type ConfirmEmailSearchParams = { token: string } -export const ConfirmEmail = () => { +export const EmailConfirm = () => { const confirmedEmail = 'test@test.com' const { searchParams } = useRouter() diff --git a/src/components/Nav/AuthModal/index.tsx b/src/components/Nav/AuthModal/index.tsx index 686ffecd..91480993 100644 --- a/src/components/Nav/AuthModal/index.tsx +++ b/src/components/Nav/AuthModal/index.tsx @@ -1,5 +1,5 @@ import { Show } from 'solid-js/web' -import { createEffect, createMemo } from 'solid-js' +import { createEffect, createMemo, onMount } from 'solid-js' import { t } from '../../../utils/intl' import { hideModal } from '../../../stores/ui' import { handleClientRouteLinkClick, useRouter } from '../../../stores/router' @@ -8,15 +8,25 @@ import styles from './AuthModal.module.scss' import { LoginForm } from './LoginForm' import { RegisterForm } from './RegisterForm' import { ForgotPasswordForm } from './ForgotPasswordForm' -import { ConfirmEmail } from './ConfirmEmail' +import { EmailConfirm } from './EmailConfirm' import type { AuthModalMode, AuthModalSearchParams } from './types' -import { ConfirmOAuth } from './ConfirmOAuth' +const AUTH_MODAL_MODES: Record = { + login: 'login', + register: 'register', + 'forgot-password': 'forgot-password', + // eslint-disable-next-line sonarjs/no-duplicate-string + 'confirm-email': 'confirm-email' +} export const AuthModal = () => { let rootRef: HTMLDivElement + const { searchParams } = useRouter() - const mode = createMemo(() => searchParams().mode || 'login') + + const mode = createMemo(() => { + return AUTH_MODAL_MODES[searchParams().mode] || 'login' + }) createEffect((oldMode) => { if (oldMode !== mode()) { @@ -28,12 +38,12 @@ export const AuthModal = () => {

    {t('Discours')}

    {t(`Join the global community of authors!`)}

    @@ -70,10 +80,7 @@ export const AuthModal = () => { - - - - +
    diff --git a/src/components/Nav/AuthModal/types.ts b/src/components/Nav/AuthModal/types.ts index 4bccde25..e7aeed0e 100644 --- a/src/components/Nav/AuthModal/types.ts +++ b/src/components/Nav/AuthModal/types.ts @@ -1,4 +1,4 @@ -export type AuthModalMode = 'login' | 'register' | 'confirm-email' | 'confirm-oauth' | 'forgot-password' +export type AuthModalMode = 'login' | 'register' | 'confirm-email' | 'forgot-password' export type AuthModalSearchParams = { mode: AuthModalMode diff --git a/src/components/Nav/Confirmed.scss b/src/components/Nav/Confirmed.scss new file mode 100644 index 00000000..4307f056 --- /dev/null +++ b/src/components/Nav/Confirmed.scss @@ -0,0 +1,6 @@ +.center { + display: flex; + justify-content: center; + align-items: center; + height: 420px; +} diff --git a/src/components/Nav/Confirmed.tsx b/src/components/Nav/Confirmed.tsx new file mode 100644 index 00000000..f870c83d --- /dev/null +++ b/src/components/Nav/Confirmed.tsx @@ -0,0 +1,18 @@ +import './Confirmed.scss' +import { onMount } from 'solid-js' +import { t } from '../../utils/intl' + +export const Confirmed = (props: { token?: string }) => { + onMount(() => { + const token = props.token ?? document.cookie.split(';').at(0).replace('token=', '') + window.addEventListener('mousemove', () => window.close()) + window.addEventListener('keydown', () => window.close()) + window.addEventListener('click', () => window.close()) + localStorage.setItem('token', token) + }) + return ( + <> +
    {t('You was successfully authorized')}
    + + ) +} diff --git a/src/components/Nav/Popup.tsx b/src/components/Nav/Popup.tsx index 5afc9237..40a25429 100644 --- a/src/components/Nav/Popup.tsx +++ b/src/components/Nav/Popup.tsx @@ -1,11 +1,11 @@ -import { createEffect, createSignal, JSX, onMount, Show } from 'solid-js' +import { createEffect, createSignal, onMount, Show } from 'solid-js' import style from './Popup.module.scss' import { hideModal, useModalStore } from '../../stores/ui' import { clsx } from 'clsx' interface PopupProps { name: string - children: JSX.Element + children: any class?: string } diff --git a/src/graphql/types.gen.ts b/src/graphql/types.gen.ts index 0a520088..5b233587 100644 --- a/src/graphql/types.gen.ts +++ b/src/graphql/types.gen.ts @@ -171,7 +171,7 @@ export type Mutation = { } export type MutationConfirmEmailArgs = { - token: Scalars['String'] + code: Scalars['String'] } export type MutationCreateChatArgs = { diff --git a/src/locales/ru.json b/src/locales/ru.json index 7fea97ff..2f7a8b1d 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -157,7 +157,6 @@ "Restore password": "Восстановить пароль", "Hooray! Welcome!": "Ура! Добро пожаловать!", "You've confirmed email": "Вы подтвердили почту", - "You've confirmed your account": "Вы подтвердили свою учётную запись", "This email is already taken. If it's you": "Такой email уже зарегистрирован. Если это вы", "enter": "войдите" } diff --git a/src/pages/confirm/[token].astro b/src/pages/confirm/[token].astro index cd8ff21f..89528424 100644 --- a/src/pages/confirm/[token].astro +++ b/src/pages/confirm/[token].astro @@ -1,4 +1,12 @@ --- +import { Confirmed } from '../../components/Nav/Confirmed' +import { t } from '../../utils/intl' + const token = Astro.params.token?.toString() || '' -return Astro.redirect('/?modal=confirm-oauth&token=' + token) --- + +{t('Discours')} + + + + diff --git a/src/pages/confirm/index.astro b/src/pages/confirm/index.astro new file mode 100644 index 00000000..1edb4589 --- /dev/null +++ b/src/pages/confirm/index.astro @@ -0,0 +1,10 @@ +--- +import { t } from '../../utils/intl' +import { Confirmed } from '../../components/Nav/Confirmed' +--- + +{t('Discours')} + + + + diff --git a/src/utils/config.ts b/src/utils/config.ts index 748d491e..75f03f39 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -1,4 +1,4 @@ export const isDev = import.meta.env.MODE === 'development' -// export const apiBaseUrl = 'https://newapi.discours.io' -export const apiBaseUrl = 'http://localhost:8000' +export const apiBaseUrl = 'https://newapi.discours.io' +// export const apiBaseUrl = 'http://localhost:8000' From 4d69749f55fa952a91d4d94586c5fa568160b0e0 Mon Sep 17 00:00:00 2001 From: Igor Lobanov Date: Mon, 31 Oct 2022 13:49:41 +0100 Subject: [PATCH 44/56] logger restored --- package.json | 2 ++ src/utils/logger.ts | 9 +++++++++ 2 files changed, 11 insertions(+) create mode 100644 src/utils/logger.ts diff --git a/package.json b/package.json index b3854825..e32a446f 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,8 @@ "idb": "^7.1.0", "jest": "^29.2.1", "lint-staged": "^13.0.3", + "loglevel": "^1.8.0", + "loglevel-plugin-prefix": "^0.8.4", "markdown-it": "^13.0.1", "markdown-it-container": "^3.0.0", "markdown-it-implicit-figures": "^0.10.0", diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 00000000..14ce2b34 --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,9 @@ +import loglevel from 'loglevel' +import prefix from 'loglevel-plugin-prefix' +import { isDev } from './config' + +prefix.reg(loglevel) +prefix.apply(loglevel, { template: '[%n]' }) +loglevel.setLevel(isDev ? loglevel.levels.TRACE : loglevel.levels.ERROR) + +export const getLogger = (name: string) => loglevel.getLogger(name) From 190a3226d54a0029290d9e409be9b3f77cde154f Mon Sep 17 00:00:00 2001 From: Igor Lobanov Date: Mon, 31 Oct 2022 13:51:52 +0100 Subject: [PATCH 45/56] prettier --- src/components/Discours/Banner.tsx | 2 +- src/components/Discours/Footer.tsx | 2 +- src/components/Editor/components/Editor.tsx | 4 +- src/components/Editor/components/Error.tsx | 18 ++-- src/components/Editor/components/Layout.tsx | 20 +++-- .../Editor/components/ProseMirror.tsx | 88 +++++++++---------- src/components/Editor/components/Sidebar.tsx | 59 +++++++------ src/components/Editor/markdown.ts | 8 +- .../Editor/prosemirror/extension/base.ts | 12 +-- .../Editor/prosemirror/extension/code.ts | 34 +++---- .../Editor/prosemirror/extension/collab.ts | 16 ++-- .../Editor/prosemirror/extension/menu.ts | 40 +++++---- .../Editor/prosemirror/extension/selection.ts | 68 +++++++------- .../Editor/prosemirror/extension/table.ts | 2 +- .../Editor/prosemirror/extension/todo-list.ts | 17 ++-- src/components/Editor/prosemirror/helpers.ts | 6 +- src/components/Editor/prosemirror/p2p.ts | 6 +- src/components/Editor/store/actions.ts | 44 +++++----- src/components/Editor/store/context.ts | 76 ++++++++-------- src/components/Editor/styles/Layout.scss | 2 +- src/components/Editor/styles/Sidebar.scss | 16 ++-- src/components/Feed/Row1.tsx | 2 +- .../Nav/AuthModal/AuthModal.module.scss | 6 +- src/components/Nav/Header.tsx | 6 +- src/components/Pages/about/ManifestPage.tsx | 83 +++++++++-------- src/components/Root.tsx | 1 - src/graphql/mutation/article-create.ts | 1 - src/graphql/mutation/article-destroy.ts | 1 - src/graphql/mutation/reaction-destroy.ts | 1 - src/styles/app.scss | 2 +- 30 files changed, 330 insertions(+), 313 deletions(-) diff --git a/src/components/Discours/Banner.tsx b/src/components/Discours/Banner.tsx index 17c8875a..f14d2a8e 100644 --- a/src/components/Discours/Banner.tsx +++ b/src/components/Discours/Banner.tsx @@ -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 ( diff --git a/src/components/Discours/Footer.tsx b/src/components/Discours/Footer.tsx index ed0a50f7..941c5d3a 100644 --- a/src/components/Discours/Footer.tsx +++ b/src/components/Discours/Footer.tsx @@ -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' : 'Русский')) diff --git a/src/components/Editor/components/Editor.tsx b/src/components/Editor/components/Editor.tsx index b2ee8aca..7df08ee5 100644 --- a/src/components/Editor/components/Editor.tsx +++ b/src/components/Editor/components/Editor.tsx @@ -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 ( { return ( }> - + - + - + ) @@ -24,8 +24,8 @@ const InvalidState = (props: { title: string }) => { const onClick = () => ctrl.clean() return ( -
    -
    +
    +

    {props.title}

    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 }) => {

               {JSON.stringify(store.error.props)}
             
    -
    @@ -53,13 +53,13 @@ const Other = () => { } return ( -
    -
    +
    +

    An error occurred.

               {getMessage()}
             
    -
    diff --git a/src/components/Editor/components/Layout.tsx b/src/components/Editor/components/Layout.tsx index 2d9a35e0..eeea10a8 100644 --- a/src/components/Editor/components/Layout.tsx +++ b/src/components/Editor/components/Layout.tsx @@ -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 (
    - {props.children} -
    ) + return ( +
    + {props.children} +
    + ) } diff --git a/src/components/Editor/components/ProseMirror.tsx b/src/components/Editor/components/ProseMirror.tsx index da06ba55..573cbac3 100644 --- a/src/components/Editor/components/ProseMirror.tsx +++ b/src/components/Editor/components/ProseMirror.tsx @@ -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; - editorView?: Store; - extensions?: Store; - onInit: (s: EditorState, v: EditorView) => void; - onReconfigure: (s: EditorState) => void; - onChange: (s: EditorState) => void; + style?: string + className?: string + text?: Store + editorView?: Store + extensions?: Store + 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 ( -
    - ) + return
    } 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) } diff --git a/src/components/Editor/components/Sidebar.tsx b/src/components/Editor/components/Sidebar.tsx index ecb392c1..33c7e2dd 100644 --- a/src/components/Editor/components/Sidebar.tsx +++ b/src/components/Editor/components/Sidebar.tsx @@ -8,16 +8,16 @@ import { isEmpty } from '../prosemirror/helpers' import type { Styled } from './Layout' import '../styles/Sidebar.scss' -const Off = (props) => +const Off = (props) => -const Label = (props: Styled) => +const Label = (props: Styled) => const Link = ( props: Styled & { withMargin?: boolean; disabled?: boolean; title?: string; className?: string } ) => (