From adc0fe639342cd8d70803bcf9dd0a562ca97dcce Mon Sep 17 00:00:00 2001 From: tonyrewin Date: Fri, 7 Oct 2022 22:35:53 +0300 Subject: [PATCH] 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) => {