editor-ctrl-fixes
This commit is contained in:
parent
adc0fe6393
commit
a0a33087f6
36
package.json
36
package.json
|
@ -29,24 +29,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.186.0",
|
||||
"@nanostores/persistent": "^0.7.0",
|
||||
"@nanostores/router": "^0.7.0",
|
||||
"@nanostores/solid": "^0.3.0",
|
||||
"@solid-primitives/memo": "^1.0.2",
|
||||
"loglevel": "^1.8.0",
|
||||
"loglevel-plugin-prefix": "^0.8.4",
|
||||
"mailgun.js": "^8.0.1",
|
||||
"markdown-it": "^13.0.1",
|
||||
"markdown-it-container": "^3.0.0",
|
||||
"markdown-it-implicit-figures": "^0.10.0",
|
||||
"markdown-it-mark": "^3.0.1",
|
||||
"markdown-it-replace-link": "^1.1.0",
|
||||
"nanostores": "^0.7.0",
|
||||
"postcss-modules": "^5.0.0"
|
||||
"mailgun.js": "^8.0.1"
|
||||
},
|
||||
"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,9 +42,13 @@
|
|||
"@graphql-codegen/urql-introspection": "^2.2.1",
|
||||
"@graphql-tools/url-loader": "^7.16.4",
|
||||
"@graphql-typed-document-node/core": "^3.1.1",
|
||||
"@nanostores/persistent": "^0.7.0",
|
||||
"@nanostores/router": "^0.7.0",
|
||||
"@nanostores/solid": "^0.3.0",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"@solid-devtools/debugger": "^0.11.1",
|
||||
"@solid-devtools/logger": "^0.4.9",
|
||||
"@solid-primitives/memo": "^1.0.2",
|
||||
"@types/express": "^4.17.14",
|
||||
"@types/node": "^18.7.19",
|
||||
"@types/uuid": "^8.3.4",
|
||||
|
@ -69,7 +58,7 @@
|
|||
"@urql/devtools": "^2.0.3",
|
||||
"@urql/exchange-auth": "^1.0.0",
|
||||
"@urql/exchange-graphcache": "^5.0.0",
|
||||
"astro": "^1.1.1",
|
||||
"astro": "^1.4.6",
|
||||
"astro-eslint-parser": "^0.6.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bootstrap": "5.1.3",
|
||||
|
@ -94,7 +83,16 @@
|
|||
"idb": "^7.0.1",
|
||||
"jest": "^29.0.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",
|
||||
"markdown-it-mark": "^3.0.1",
|
||||
"markdown-it-replace-link": "^1.1.0",
|
||||
"nanostores": "^0.7.0",
|
||||
"postcss": "^8.4.16",
|
||||
"postcss-modules": "^5.0.0",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-eslint": "^15.0.1",
|
||||
"prosemirror-commands": "^1.3.1",
|
||||
|
@ -111,10 +109,6 @@
|
|||
"prosemirror-schema-list": "^1.2.2",
|
||||
"prosemirror-state": "^1.4.1",
|
||||
"prosemirror-view": "^1.28.1",
|
||||
"rehype-autolink-headings": "^6.1.1",
|
||||
"rehype-slug": "^5.0.1",
|
||||
"rehype-toc": "^3.0.2",
|
||||
"remark-code-titles": "^0.1.2",
|
||||
"rollup": "~2.79.1",
|
||||
"rollup-plugin-visualizer": "^5.8.2",
|
||||
"sass": "^1.55.0",
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { For, Show, createEffect, createSignal, onCleanup } from 'solid-js'
|
||||
import { 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 './prosemirror/context'
|
||||
import { Draft, useState } from './prosemirror/context'
|
||||
import { clsx } from 'clsx'
|
||||
import type { Styled } from './Layout'
|
||||
import { t } from '../../utils/intl'
|
||||
|
||||
// import type { EditorState } from 'prosemirror-state'
|
||||
// import { serialize } from './prosemirror/markdown'
|
||||
// import { baseUrl } from '../../graphql/client'
|
||||
|
@ -11,12 +13,10 @@ import type { Styled } from './Layout'
|
|||
|
||||
// const copy = async (text: string): Promise<void> => navigator.clipboard.writeText(text)
|
||||
// const copyAllAsMarkdown = async (state: EditorState): Promise<void> =>
|
||||
// !isServer && navigator.clipboard.writeText(serialize(state))
|
||||
// navigator.clipboard.writeText(serialize(state)) && !isServer
|
||||
|
||||
const Off = (props: any) => <div class="sidebar-off">{props.children}</div>
|
||||
|
||||
const Label = (props: Styled) => <h3 class="sidebar-label">{props.children}</h3>
|
||||
|
||||
const Link = (
|
||||
props: Styled & { withMargin?: boolean; disabled?: boolean; title?: string; className?: string }
|
||||
) => (
|
||||
|
@ -32,12 +32,12 @@ const Link = (
|
|||
</button>
|
||||
)
|
||||
|
||||
type FileLinkProps = {
|
||||
file: File
|
||||
onOpenFile: (file: File) => void
|
||||
type DraftLinkProps = {
|
||||
draft: Draft
|
||||
onOpenDraft: (draft: Draft) => void
|
||||
}
|
||||
|
||||
const FileLink = (props: FileLinkProps) => {
|
||||
const DraftLink = (props: DraftLinkProps) => {
|
||||
const length = 100
|
||||
let content = ''
|
||||
const getContent = (node: any) => {
|
||||
|
@ -65,14 +65,14 @@ const FileLink = (props: FileLinkProps) => {
|
|||
}
|
||||
|
||||
const text = () =>
|
||||
props.file.path
|
||||
? props.file.path.slice(Math.max(0, props.file.path.length - length))
|
||||
: getContent(props.file.text?.doc)
|
||||
props.draft.path
|
||||
? props.draft.path.slice(Math.max(0, props.draft.path.length - length))
|
||||
: getContent(props.draft.text?.doc)
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line solid/no-react-specific-props
|
||||
<Link className="file" onClick={() => props.onOpenFile(props.file)} data-testid="open">
|
||||
{text()} {props.file.path && '📎'}
|
||||
<Link className="draft" onClick={() => props.onOpenDraft(props.draft)} data-testid="open">
|
||||
{text()} {props.draft.path && '📎'}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
@ -88,14 +88,13 @@ export const Sidebar = () => {
|
|||
// const collabText = () => (store.collab?.started ? 'Stop' : store.collab?.error ? 'Restart 🚨' : 'Start')
|
||||
const editorView = () => unwrap(store.editorView)
|
||||
// const onToggleMarkdown = () => ctrl.toggleMarkdown()
|
||||
const onOpenFile = (file: File) => ctrl.openFile(unwrap(file))
|
||||
const onOpenDraft = (draft: Draft) => ctrl.openDraft(unwrap(draft))
|
||||
// const collabUsers = () => store.collab?.y?.provider.awareness.meta.size ?? 0
|
||||
// const onUndo = () => undo(editorView().state, editorView().dispatch)
|
||||
// const onRedo = () => redo(editorView().state, editorView().dispatch)
|
||||
// const onCopyAllAsMd = () => copyAllAsMarkdown(editorView().state).then(() => setLastAction('copy-md'))
|
||||
// const onToggleAlwaysOnTop = () => ctrl.updateConfig({ alwaysOnTop: !store.config.alwaysOnTop })
|
||||
// const onToggleFullscreen = () => ctrl.setFullscreen(!store.fullscreen)
|
||||
// const onNew = () => ctrl.newFile()
|
||||
// const onNew = () => ctrl.newDraft()
|
||||
// const onDiscard = () => ctrl.discard()
|
||||
const [isHidden, setIsHidden] = createSignal<boolean | false>()
|
||||
|
||||
|
@ -106,7 +105,7 @@ export const Sidebar = () => {
|
|||
toggleSidebar()
|
||||
|
||||
// const onSaveAs = async () => {
|
||||
// const path = 'test' // TODO: save filename await remote.save(editorView().state)
|
||||
// const path = 'test' // TODO: save draftname await remote.save(editorView().state)
|
||||
//
|
||||
// if (path) ctrl.updatePath(path)
|
||||
// }
|
||||
|
@ -189,27 +188,17 @@ export const Sidebar = () => {
|
|||
</div>
|
||||
|
||||
{/*
|
||||
<Show when={isTauri && !store.path}>
|
||||
<Link onClick={onSaveAs}>
|
||||
Save to file <Keys keys={[mod, 's']} />
|
||||
</Link>
|
||||
</Show>
|
||||
<Link onClick={onNew} data-testid='new'>
|
||||
New <Keys keys={[mod, 'n']} />
|
||||
</Link>
|
||||
<Link
|
||||
onClick={onDiscard}
|
||||
disabled={!store.path && store.files?.length === 0 && isEmpty(store.text)}
|
||||
disabled={!store.path && store.drafts?.length === 0 && isEmpty(store.text)}
|
||||
data-testid='discard'
|
||||
>
|
||||
{store.path ? 'Close' : store.files?.length > 0 && isEmpty(store.text) ? 'Delete ⚠️' : 'Clear'}{' '}
|
||||
{store.path ? 'Close' : store.drafts?.length > 0 && isEmpty(store.text) ? 'Delete ⚠️' : 'Clear'}{' '}
|
||||
<Keys keys={[mod, 'w']} />
|
||||
</Link>
|
||||
<Show when={isTauri}>
|
||||
<Link onClick={onToggleFullscreen}>
|
||||
Fullscreen {store.fullscreen && '✅'} <Keys keys={[alt, 'Enter']} />
|
||||
</Link>
|
||||
</Show>
|
||||
<Link onClick={onUndo}>
|
||||
Undo <Keys keys={[mod, 'z']} />
|
||||
</Link>
|
||||
|
@ -226,15 +215,15 @@ export const Sidebar = () => {
|
|||
Markdown mode {store.markdown && '✅'} <Keys keys={[mod, 'm']} />
|
||||
</Link>
|
||||
<Link onClick={onCopyAllAsMd}>Copy all as MD {lastAction() === 'copy-md' && '📋'}</Link>
|
||||
*/}
|
||||
<Show when={store.files?.length > 0}>
|
||||
<h4>Files:</h4>
|
||||
|
||||
<Show when={store.drafts?.length > 0}>
|
||||
<h4>t('Drafts'):</h4>
|
||||
<p>
|
||||
<For each={store.files}>{(file) => <FileLink file={file} onOpenFile={onOpenFile} />}</For>
|
||||
<For each={store.drafts}>{(draft) => <DraftLink draft={draft} onOpenDraft={onOpenDraft} />}</For>
|
||||
</p>
|
||||
</Show>
|
||||
|
||||
{/*
|
||||
|
||||
<Link onClick={onCollab} title={store.collab?.error ? 'Connection error' : ''}>
|
||||
Collab {collabText()}
|
||||
</Link>
|
||||
|
|
|
@ -55,10 +55,11 @@ export interface Collab {
|
|||
|
||||
export type LoadingType = 'loading' | 'initialized'
|
||||
|
||||
export interface File {
|
||||
// TODO: use this interface in prosemirror's context
|
||||
export interface Draft {
|
||||
path?: string // used by state
|
||||
text?: { [key: string]: string }
|
||||
lastModified?: string
|
||||
path?: string
|
||||
markdown?: boolean
|
||||
}
|
||||
|
||||
|
@ -68,11 +69,9 @@ export interface State {
|
|||
extensions?: ProseMirrorExtension[]
|
||||
markdown?: boolean
|
||||
lastModified?: Date
|
||||
files: File[]
|
||||
config: Config
|
||||
error?: ErrorObject
|
||||
loading: LoadingType
|
||||
fullscreen: boolean
|
||||
collab?: Collab
|
||||
path?: string
|
||||
args?: Args
|
||||
|
@ -103,15 +102,14 @@ const DEFAULT_CONFIG = {
|
|||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const StateContext = createContext<[Store<State>, any]>([{} as Store<State>, undefined])
|
||||
|
||||
export const useState = () => useContext(StateContext)
|
||||
|
||||
export const newState = (props: Partial<State> = {}): State => ({
|
||||
extensions: [],
|
||||
files: [],
|
||||
loading: 'loading',
|
||||
fullscreen: false,
|
||||
markdown: false,
|
||||
config: DEFAULT_CONFIG,
|
||||
...props
|
||||
|
|
|
@ -1,119 +1,116 @@
|
|||
import { Store, createStore, unwrap } from 'solid-js/store'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import type { EditorState } from 'prosemirror-state'
|
||||
import type { Command, 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 'lodash'
|
||||
import debounce from 'lodash/debounce'
|
||||
import { createSchema, createExtensions, createEmptyText, InitOpts } from '../prosemirror/setup'
|
||||
import { State, File, Config, ServiceError, newState, PeerData } from '../prosemirror/context'
|
||||
import { State, Config, ServiceError, newState, PeerData } from '../prosemirror/context'
|
||||
import { serialize, createMarkdownParser } from '../prosemirror/markdown'
|
||||
import { isEmpty, isInitialized, ProseMirrorExtension } from '../prosemirror/state'
|
||||
import { isServer } from 'solid-js/web'
|
||||
import { roomConnect } from '../prosemirror/p2p'
|
||||
|
||||
const mod = 'Ctrl'
|
||||
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)
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const createCtrl = (initial: State): [Store<State>, { [key: string]: any }] => {
|
||||
const [store, setState] = createStore(initial)
|
||||
|
||||
const discardText = async () => {
|
||||
const state = unwrap(store)
|
||||
const index = state.files.length - 1
|
||||
const file = index !== -1 ? state.files[index] : undefined
|
||||
|
||||
let next: Partial<State>
|
||||
|
||||
if (file) {
|
||||
next = await createTextFromFile(file)
|
||||
} else {
|
||||
const extensions = createExtensions({
|
||||
config: state.config ?? store.config,
|
||||
markdown: state.markdown && store.markdown,
|
||||
keymap
|
||||
})
|
||||
|
||||
next = {
|
||||
text: createEmptyText(),
|
||||
extensions,
|
||||
lastModified: undefined,
|
||||
path: undefined,
|
||||
markdown: state.markdown
|
||||
}
|
||||
}
|
||||
|
||||
const files = state.files.filter((f: File) => f !== file)
|
||||
const extensions = createExtensions({
|
||||
config: state.config ?? store.config,
|
||||
markdown: state.markdown && store.markdown,
|
||||
keymap
|
||||
})
|
||||
|
||||
setState({
|
||||
files,
|
||||
...next,
|
||||
collab: file ? undefined : state.collab,
|
||||
text: createEmptyText(),
|
||||
extensions,
|
||||
lastModified: undefined,
|
||||
path: undefined,
|
||||
markdown: state.markdown,
|
||||
collab: state.collab,
|
||||
error: undefined
|
||||
})
|
||||
}
|
||||
|
||||
const addToFiles = (files: File[], prev: State) => {
|
||||
const text = prev.path ? undefined : (prev.text as EditorState).toJSON()
|
||||
|
||||
return [
|
||||
...files,
|
||||
{
|
||||
text,
|
||||
lastModified: prev.lastModified?.toISOString(),
|
||||
path: prev.path,
|
||||
markdown: prev.markdown
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const discard = async () => {
|
||||
if (store.path) {
|
||||
await discardText()
|
||||
} else if (store.files?.length > 0 && isEmpty(store.text)) {
|
||||
await discardText()
|
||||
} else {
|
||||
selectAll(store.editorView.state, store.editorView.dispatch)
|
||||
deleteSelection(store.editorView.state, store.editorView.dispatch)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const onDiscard = () => {
|
||||
discard()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const onToggleMarkdown = () => toggleMarkdown()
|
||||
|
||||
const onUndo = () => {
|
||||
if (!isInitialized(store.text)) return
|
||||
|
||||
if (!isInitialized(store.text)) return false
|
||||
const text = store.text as EditorState
|
||||
|
||||
if (store.collab?.started) {
|
||||
yUndo(text)
|
||||
} else {
|
||||
undo(text, store.editorView.dispatch)
|
||||
}
|
||||
|
||||
if (store.collab?.started) yUndo(text)
|
||||
else undo(text, store.editorView.dispatch)
|
||||
return true
|
||||
}
|
||||
|
||||
const onRedo = () => {
|
||||
if (!isInitialized(store.text)) return
|
||||
|
||||
if (!isInitialized(store.text)) return false
|
||||
const text = store.text as EditorState
|
||||
if (store.collab?.started) yRedo(text)
|
||||
else redo(text, store.editorView.dispatch)
|
||||
return true
|
||||
}
|
||||
|
||||
if (store.collab?.started) {
|
||||
yRedo(text)
|
||||
const toggleMarkdown = () => {
|
||||
const state = unwrap(store)
|
||||
const editorState = store.text as EditorState
|
||||
const markdown = !state.markdown
|
||||
const selection = { type: 'text', anchor: 1, head: 1 }
|
||||
let doc
|
||||
if (markdown) {
|
||||
const lines = serialize(editorState).split('\n')
|
||||
const nodes = lines.map((text) => {
|
||||
return text ? { type: 'paragraph', content: [{ type: 'text', text }] } : { type: 'paragraph' }
|
||||
})
|
||||
doc = { type: 'doc', content: nodes }
|
||||
} else {
|
||||
redo(text, store.editorView.dispatch)
|
||||
const schema = createSchema({
|
||||
config: state.config,
|
||||
path: state.path,
|
||||
y: state.collab?.y,
|
||||
markdown,
|
||||
keymap
|
||||
})
|
||||
const parser = createMarkdownParser(schema)
|
||||
let textContent = ''
|
||||
editorState.doc.forEach((node) => {
|
||||
textContent += `${node.textContent}\n`
|
||||
})
|
||||
const text = parser.parse(textContent)
|
||||
doc = text?.toJSON()
|
||||
}
|
||||
|
||||
return true
|
||||
const extensions = createExtensions({
|
||||
config: state.config,
|
||||
markdown,
|
||||
path: state.path,
|
||||
keymap,
|
||||
y: state.collab?.y
|
||||
})
|
||||
|
||||
setState({
|
||||
text: { selection, doc },
|
||||
extensions,
|
||||
markdown
|
||||
})
|
||||
}
|
||||
|
||||
const keymap = {
|
||||
|
@ -121,85 +118,52 @@ export const createCtrl = (initial: State): [Store<State>, { [key: string]: any
|
|||
[`${mod}-z`]: onUndo,
|
||||
[`Shift-${mod}-z`]: onRedo,
|
||||
[`${mod}-y`]: onRedo,
|
||||
[`${mod}-m`]: onToggleMarkdown
|
||||
}
|
||||
|
||||
const createTextFromFile = async (file: File) => {
|
||||
const state = unwrap(store)
|
||||
|
||||
// if (file.path) file = await loadFile(state.config, file.path)
|
||||
|
||||
const extensions = createExtensions({
|
||||
config: state.config,
|
||||
markdown: file.markdown,
|
||||
path: file.path,
|
||||
keymap
|
||||
})
|
||||
|
||||
return {
|
||||
text: file.text,
|
||||
extensions,
|
||||
lastModified: file.lastModified ? new Date(file.lastModified) : undefined,
|
||||
path: file.path,
|
||||
markdown: file.markdown
|
||||
}
|
||||
}
|
||||
[`${mod}-m`]: toggleMarkdown
|
||||
} as unknown as { [key: string]: Command }
|
||||
|
||||
const fetchData = async (): Promise<State> => {
|
||||
const state: State = unwrap(store)
|
||||
const room = window.location.pathname?.slice(1).trim()
|
||||
const args = { room: room || undefined }
|
||||
if (isServer) return
|
||||
|
||||
const state: State = unwrap(store)
|
||||
const room = undefined // window.location.pathname?.slice(1) + uuidv4()
|
||||
// console.debug('[editor-ctrl] got unique room', room)
|
||||
const args = { room }
|
||||
const { default: db } = await import('../db')
|
||||
const data: string = await db.get('state')
|
||||
console.debug('[editor-ctrl] got stored state from idb')
|
||||
let parsed
|
||||
let text = state.text
|
||||
|
||||
if (data !== undefined) {
|
||||
try {
|
||||
parsed = JSON.parse(data)
|
||||
if (!parsed) return { ...state, args }
|
||||
|
||||
console.debug('[editor-ctrl] json state parsed successfully', parsed)
|
||||
if (parsed?.text) {
|
||||
if (!parsed.text || !parsed.text.doc || !parsed.text.selection) {
|
||||
throw new ServiceError('invalid_state', parsed.text)
|
||||
} else {
|
||||
text = parsed.text
|
||||
console.debug('[editor-ctrl] got text from stored json', parsed)
|
||||
}
|
||||
}
|
||||
return {
|
||||
...parsed,
|
||||
text,
|
||||
extensions: createExtensions({
|
||||
path: parsed.path,
|
||||
markdown: parsed.markdown,
|
||||
keymap,
|
||||
config: {} as Config
|
||||
}),
|
||||
args,
|
||||
lastModified: parsed.lastModified ? new Date(parsed.lastModified) : new Date()
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
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 })
|
||||
|
@ -208,8 +172,6 @@ export const createCtrl = (initial: State): [Store<State>, { [key: string]: any
|
|||
setState({
|
||||
...newState(),
|
||||
loading: 'initialized',
|
||||
files: [],
|
||||
fullscreen: store.fullscreen,
|
||||
lastModified: new Date(),
|
||||
error: undefined,
|
||||
text: undefined
|
||||
|
@ -217,67 +179,34 @@ export const createCtrl = (initial: State): [Store<State>, { [key: string]: any
|
|||
}
|
||||
|
||||
const init = async () => {
|
||||
let data = await fetchData()
|
||||
let state = await fetchData()
|
||||
console.debug('[editor-ctrl] state initiated', state)
|
||||
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.text) {
|
||||
if (state.args?.room) {
|
||||
state = doStartCollab(state)
|
||||
} else if (!state.text) {
|
||||
const text = createEmptyText()
|
||||
const extensions = createExtensions({
|
||||
config: data.config,
|
||||
markdown: data.markdown,
|
||||
config: state.config,
|
||||
markdown: state.markdown,
|
||||
keymap
|
||||
})
|
||||
|
||||
data = { ...data, text, extensions }
|
||||
state = { ...state, text, extensions }
|
||||
}
|
||||
} catch (error) {
|
||||
data = { ...data, error }
|
||||
state = { ...state, error }
|
||||
}
|
||||
setState({
|
||||
...data,
|
||||
config: { ...data.config, ...getTheme(data) },
|
||||
...state,
|
||||
config: { ...state.config, ...getTheme(state) },
|
||||
loading: 'initialized'
|
||||
})
|
||||
}
|
||||
|
||||
const doOpenFile = async (state: State, file: File): Promise<State> => {
|
||||
const findIndexOfFile = (f: File) => {
|
||||
for (let i = 0; i < state.files.length; i++) {
|
||||
if (state.files[i] === f) return i
|
||||
|
||||
if (f.path && state.files[i].path === f.path) return i
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
const index = findIndexOfFile(file)
|
||||
const item = index === -1 ? file : state.files[index]
|
||||
let files = state.files.filter((f) => f !== item)
|
||||
|
||||
if (!isEmpty(state.text) && state.lastModified) {
|
||||
files = addToFiles(files, state)
|
||||
}
|
||||
|
||||
file.lastModified = item.lastModified
|
||||
const next = await createTextFromFile(file)
|
||||
|
||||
return {
|
||||
...state,
|
||||
...next,
|
||||
files,
|
||||
collab: undefined,
|
||||
error: undefined
|
||||
}
|
||||
}
|
||||
|
||||
const saveState = debounce(async (state: State) => {
|
||||
const data = {
|
||||
lastModified: state.lastModified,
|
||||
files: state.files,
|
||||
config: state.config,
|
||||
path: state.path,
|
||||
markdown: state.markdown,
|
||||
|
@ -322,15 +251,8 @@ export const createCtrl = (initial: State): [Store<State>, { [key: string]: any
|
|||
let nState = state
|
||||
|
||||
if ((backup && !isEmpty(state.text)) || state.path) {
|
||||
let files = state.files
|
||||
|
||||
if (!state.error) {
|
||||
files = addToFiles(files, state)
|
||||
}
|
||||
|
||||
nState = {
|
||||
...state,
|
||||
files,
|
||||
lastModified: undefined,
|
||||
path: undefined,
|
||||
error: undefined
|
||||
|
@ -357,54 +279,6 @@ export const createCtrl = (initial: State): [Store<State>, { [key: string]: any
|
|||
window.history.replaceState(null, '', '/')
|
||||
}
|
||||
|
||||
const toggleMarkdown = () => {
|
||||
const state = unwrap(store)
|
||||
const editorState = store.text as EditorState
|
||||
const markdown = !state.markdown
|
||||
const selection = { type: 'text', anchor: 1, head: 1 }
|
||||
let doc
|
||||
|
||||
if (markdown) {
|
||||
const lines = serialize(editorState).split('\n')
|
||||
const nodes = lines.map((text) => {
|
||||
return text ? { type: 'paragraph', content: [{ type: 'text', text }] } : { type: 'paragraph' }
|
||||
})
|
||||
|
||||
doc = { type: 'doc', content: nodes }
|
||||
} else {
|
||||
const schema = createSchema({
|
||||
config: state.config,
|
||||
path: state.path,
|
||||
y: state.collab?.y,
|
||||
markdown,
|
||||
keymap
|
||||
})
|
||||
|
||||
const parser = createMarkdownParser(schema)
|
||||
let textContent = ''
|
||||
|
||||
editorState.doc.forEach((node) => {
|
||||
textContent += `${node.textContent}\n`
|
||||
})
|
||||
const text = parser.parse(textContent)
|
||||
doc = text?.toJSON()
|
||||
}
|
||||
|
||||
const extensions = createExtensions({
|
||||
config: state.config,
|
||||
markdown,
|
||||
path: state.path,
|
||||
keymap,
|
||||
y: state.collab?.y
|
||||
})
|
||||
|
||||
setState({
|
||||
text: { selection, doc },
|
||||
extensions,
|
||||
markdown
|
||||
})
|
||||
}
|
||||
|
||||
const updateConfig = (config: Partial<Config>) => {
|
||||
const state = unwrap(store)
|
||||
const extensions = createExtensions({
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { uniqueNamesGenerator, adjectives, animals } from 'unique-names-generator'
|
||||
import { Awareness } from 'y-protocols/awareness'
|
||||
import { WebrtcProvider } from 'y-webrtc'
|
||||
import * as Y from 'yjs'
|
||||
import { Doc, XmlFragment } from 'yjs'
|
||||
import type { Reaction } from '../../../graphql/types.gen'
|
||||
import { setReactions } from '../../../stores/editor'
|
||||
|
||||
export const roomConnect = (room, username = '', keyname = 'collab'): [Y.XmlFragment, WebrtcProvider] => {
|
||||
const ydoc = new Y.Doc()
|
||||
export const roomConnect = (room, username = '', keyname = 'collab'): [XmlFragment, WebrtcProvider] => {
|
||||
const ydoc = new Doc()
|
||||
const yarr = ydoc.getArray(keyname + '-reactions')
|
||||
const yXmlFragment = ydoc.getXmlFragment(keyname)
|
||||
const webrtcOptions = {
|
||||
|
|
|
@ -17,10 +17,11 @@ import table from './extension/table'
|
|||
import collab from './extension/collab'
|
||||
import type { Config, PeerData } from './context'
|
||||
import selectionMenu from './extension/selection'
|
||||
import type { Command } from 'prosemirror-state'
|
||||
|
||||
export interface InitOpts {
|
||||
data?: unknown
|
||||
keymap?: any
|
||||
keymap?: { [key: string]: Command }
|
||||
config: Config
|
||||
markdown: boolean
|
||||
path?: string
|
||||
|
|
91
yarn.lock
91
yarn.lock
|
@ -73,24 +73,6 @@
|
|||
vscode-languageserver-types "^3.17.1"
|
||||
vscode-uri "^3.0.3"
|
||||
|
||||
"@astrojs/language-server@^0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@astrojs/language-server/-/language-server-0.27.0.tgz#5182965a1158e77bfcd9211edf0f8a5fef3c2505"
|
||||
integrity sha512-4nT2KqAhxjjElATs/4Q8nkiUlu+YalJqZIEW4YOGEoSDbju/pw7fy8CJHFOhkPmGux8173N58i6l1cewGcxluw==
|
||||
dependencies:
|
||||
"@vscode/emmet-helper" "^2.8.4"
|
||||
events "^3.3.0"
|
||||
prettier "^2.7.1"
|
||||
prettier-plugin-astro "^0.5.3"
|
||||
source-map "^0.7.3"
|
||||
vscode-css-languageservice "^6.0.1"
|
||||
vscode-html-languageservice "^5.0.0"
|
||||
vscode-languageserver "^8.0.1"
|
||||
vscode-languageserver-protocol "^3.17.1"
|
||||
vscode-languageserver-textdocument "^1.0.4"
|
||||
vscode-languageserver-types "^3.17.1"
|
||||
vscode-uri "^3.0.3"
|
||||
|
||||
"@astrojs/markdown-remark@^1.1.3":
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@astrojs/markdown-remark/-/markdown-remark-1.1.3.tgz#9fa985a532622043f0863c20f01c6ed01eca31e2"
|
||||
|
@ -2307,11 +2289,6 @@
|
|||
"@jridgewell/resolve-uri" "^3.0.3"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
|
||||
"@jsdevtools/rehype-toc@3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@jsdevtools/rehype-toc/-/rehype-toc-3.0.2.tgz#29c32e6b40cd4b5dafd96cb90d5057ac5dab4a51"
|
||||
integrity sha512-n5JEf16Wr4mdkRMZ8wMP/wN9/sHmTjRPbouXjJH371mZ2LEGDl72t8tEsMRNFerQN/QJtivOxqK1frdGa4QK5Q==
|
||||
|
||||
"@ljharb/has-package-exports-patterns@^0.0.2":
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@ljharb/has-package-exports-patterns/-/has-package-exports-patterns-0.0.2.tgz#c1718939b65efa1f45f53686c2fcfa992b9fb68f"
|
||||
|
@ -5722,7 +5699,7 @@ git-hooks-list@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/git-hooks-list/-/git-hooks-list-3.0.0.tgz#6d888988bb445b34e7c2e1eb97cb88358153221e"
|
||||
integrity sha512-XDfdemBGJIMAsHHOONHQxEH5dX2kCpE6MGZ1IsNvBuDPBZM3p4EAwAC7ygMjn/1/x+BJX0TK1ara1Zrh7JCFdQ==
|
||||
|
||||
github-slugger@^1.1.1, github-slugger@^1.4.0:
|
||||
github-slugger@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.4.0.tgz#206eb96cdb22ee56fdc53a28d5a302338463444e"
|
||||
integrity sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ==
|
||||
|
@ -6001,13 +5978,6 @@ hast-util-has-property@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/hast-util-has-property/-/hast-util-has-property-2.0.0.tgz#c15cd6180f3e535540739fcc9787bcffb5708cae"
|
||||
integrity sha512-4Qf++8o5v14us4Muv3HRj+Er6wTNGA/N9uCaZMty4JWvyFKLdhULrv4KE1b65AthsSO9TXSZnjuxS8ecIyhb0w==
|
||||
|
||||
hast-util-heading-rank@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-heading-rank/-/hast-util-heading-rank-2.1.0.tgz#c39f34fa8330ebfec03a08b5d5019ed56122029c"
|
||||
integrity sha512-w+Rw20Q/iWp2Bcnr6uTrYU6/ftZLbHKhvc8nM26VIWpDqDMlku2iXUVTeOlsdoih/UKQhY7PHQ+vZ0Aqq8bxtQ==
|
||||
dependencies:
|
||||
"@types/hast" "^2.0.0"
|
||||
|
||||
hast-util-is-element@^2.0.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-is-element/-/hast-util-is-element-2.1.2.tgz#fc0b0dc7cef3895e839b8d66979d57b0338c68f3"
|
||||
|
@ -9312,19 +9282,6 @@ regexpp@^3.0.0, regexpp@^3.2.0:
|
|||
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
|
||||
integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==
|
||||
|
||||
rehype-autolink-headings@^6.1.1:
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/rehype-autolink-headings/-/rehype-autolink-headings-6.1.1.tgz#0cb874a56f3de6ead1c2268d7f0fc5006f244db5"
|
||||
integrity sha512-NMYzZIsHM3sA14nC5rAFuUPIOfg+DFmf9EY1YMhaNlB7+3kK/ZlE6kqPfuxr1tsJ1XWkTrMtMoyHosU70d35mA==
|
||||
dependencies:
|
||||
"@types/hast" "^2.0.0"
|
||||
extend "^3.0.0"
|
||||
hast-util-has-property "^2.0.0"
|
||||
hast-util-heading-rank "^2.0.0"
|
||||
hast-util-is-element "^2.0.0"
|
||||
unified "^10.0.0"
|
||||
unist-util-visit "^4.0.0"
|
||||
|
||||
rehype-parse@^8.0.0:
|
||||
version "8.0.4"
|
||||
resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-8.0.4.tgz#3d17c9ff16ddfef6bbcc8e6a25a99467b482d688"
|
||||
|
@ -9344,19 +9301,6 @@ rehype-raw@^6.1.1:
|
|||
hast-util-raw "^7.2.0"
|
||||
unified "^10.0.0"
|
||||
|
||||
rehype-slug@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/rehype-slug/-/rehype-slug-5.0.1.tgz#6e732d0c55b3b1e34187e74b7363fb53229e5f52"
|
||||
integrity sha512-X5v3wV/meuOX9NFcGhJvUpEjIvQl2gDvjg3z40RVprYFt7q3th4qMmYLULiu3gXvbNX1ppx+oaa6JyY1W67pTA==
|
||||
dependencies:
|
||||
"@types/hast" "^2.0.0"
|
||||
github-slugger "^1.1.1"
|
||||
hast-util-has-property "^2.0.0"
|
||||
hast-util-heading-rank "^2.0.0"
|
||||
hast-util-to-string "^2.0.0"
|
||||
unified "^10.0.0"
|
||||
unist-util-visit "^4.0.0"
|
||||
|
||||
rehype-stringify@^9.0.0, rehype-stringify@^9.0.3:
|
||||
version "9.0.3"
|
||||
resolved "https://registry.yarnpkg.com/rehype-stringify/-/rehype-stringify-9.0.3.tgz#70e3bd6d4d29e7acf36b802deed350305d2c3c17"
|
||||
|
@ -9366,13 +9310,6 @@ rehype-stringify@^9.0.0, rehype-stringify@^9.0.3:
|
|||
hast-util-to-html "^8.0.0"
|
||||
unified "^10.0.0"
|
||||
|
||||
rehype-toc@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/rehype-toc/-/rehype-toc-3.0.2.tgz#0373e2abafddeb0606ee38229ff6714da6d86d68"
|
||||
integrity sha512-DMt376+4i1KJGgHJL7Ezd65qKkJ7Eqp6JSB47BJ90ReBrohI9ufrornArM6f4oJjP2E2DVZZHufWucv/9t7GUQ==
|
||||
dependencies:
|
||||
"@jsdevtools/rehype-toc" "3.0.2"
|
||||
|
||||
rehype@^12.0.1:
|
||||
version "12.0.1"
|
||||
resolved "https://registry.yarnpkg.com/rehype/-/rehype-12.0.1.tgz#68a317662576dcaa2565a3952e149d6900096bf6"
|
||||
|
@ -9392,13 +9329,6 @@ relay-runtime@12.0.0:
|
|||
fbjs "^3.0.0"
|
||||
invariant "^2.2.4"
|
||||
|
||||
remark-code-titles@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/remark-code-titles/-/remark-code-titles-0.1.2.tgz#ae41b47c517eae4084c761a59a60df5f0bd54aa8"
|
||||
integrity sha512-KsHQbaI4FX8Ozxqk7YErxwmBiveUqloKuVqyPG2YPLHojpgomodWgRfG4B+bOtmn/5bfJ8khw4rR0lvgVFl2Uw==
|
||||
dependencies:
|
||||
unist-util-visit "^1.4.0"
|
||||
|
||||
remark-gfm@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-3.0.1.tgz#0b180f095e3036545e9dddac0e8df3fa5cfee54f"
|
||||
|
@ -10739,11 +10669,6 @@ unist-util-generated@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-2.0.0.tgz#86fafb77eb6ce9bfa6b663c3f5ad4f8e56a60113"
|
||||
integrity sha512-TiWE6DVtVe7Ye2QxOVW9kqybs6cZexNwTwSMVgkfjEReqy/xwGpAXb99OxktoWwmL+Z+Epb0Dn8/GNDYP1wnUw==
|
||||
|
||||
unist-util-is@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-3.0.0.tgz#d9e84381c2468e82629e4a5be9d7d05a2dd324cd"
|
||||
integrity sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==
|
||||
|
||||
unist-util-is@^5.0.0:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-5.1.1.tgz#e8aece0b102fa9bc097b0fef8f870c496d4a6236"
|
||||
|
@ -10797,13 +10722,6 @@ unist-util-visit-children@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/unist-util-visit-children/-/unist-util-visit-children-1.1.4.tgz#e8a087e58a33a2815f76ea1901c15dec2cb4b432"
|
||||
integrity sha512-sA/nXwYRCQVRwZU2/tQWUqJ9JSFM1X3x7JIOsIgSzrFHcfVt6NkzDtKzyxg2cZWkCwGF9CO8x4QNZRJRMK8FeQ==
|
||||
|
||||
unist-util-visit-parents@^2.0.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz#25e43e55312166f3348cae6743588781d112c1e9"
|
||||
integrity sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==
|
||||
dependencies:
|
||||
unist-util-is "^3.0.0"
|
||||
|
||||
unist-util-visit-parents@^5.0.0, unist-util-visit-parents@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.1.tgz#868f353e6fce6bf8fa875b251b0f4fec3be709bb"
|
||||
|
@ -10812,13 +10730,6 @@ unist-util-visit-parents@^5.0.0, unist-util-visit-parents@^5.1.1:
|
|||
"@types/unist" "^2.0.0"
|
||||
unist-util-is "^5.0.0"
|
||||
|
||||
unist-util-visit@^1.4.0:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.1.tgz#4724aaa8486e6ee6e26d7ff3c8685960d560b1e3"
|
||||
integrity sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==
|
||||
dependencies:
|
||||
unist-util-visit-parents "^2.0.0"
|
||||
|
||||
unist-util-visit@^4.0.0, unist-util-visit@^4.1.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-4.1.1.tgz#1c4842d70bd3df6cc545276f5164f933390a9aad"
|
||||
|
|
Loading…
Reference in New Issue
Block a user