re-integrated

This commit is contained in:
tonyrewin 2022-10-18 21:43:50 +03:00
parent 05cdf42c05
commit 24d5acabef
16 changed files with 200 additions and 177 deletions

View File

@ -1,9 +1,8 @@
import type { EditorView } from 'prosemirror-view' import type { EditorView } from 'prosemirror-view'
import type { EditorState } from 'prosemirror-state' import type { EditorState } from 'prosemirror-state'
import { useState } from '../store' import { useState } from '../store/context'
import { ProseMirror } from '../components/ProseMirror' import { ProseMirror } from './ProseMirror'
import '../styles/Editor.scss' import '../styles/Editor.scss'
import type { ProseMirrorExtension, ProseMirrorState } from '../prosemirror/helpers'
export const Editor = () => { export const Editor = () => {
const [store, ctrl] = useState() const [store, ctrl] = useState()
@ -23,9 +22,9 @@ export const Editor = () => {
// eslint-disable-next-line solid/no-react-specific-props // eslint-disable-next-line solid/no-react-specific-props
className="editor" className="editor"
style={style()} style={style()}
editorView={store.editorView as EditorView} editorView={store.editorView}
text={store.text as ProseMirrorState} text={store.text}
extensions={store.extensions as ProseMirrorExtension[]} extensions={store.extensions}
onInit={onInit} onInit={onInit}
onReconfigure={onReconfigure} onReconfigure={onReconfigure}
onChange={onChange} onChange={onChange}

View File

@ -1,5 +1,5 @@
import { Switch, Match } from 'solid-js' import { Switch, Match } from 'solid-js'
import { useState } from '../store' import { useState } from '../store/context'
import '../styles/Button.scss' import '../styles/Button.scss'
export default () => { export default () => {
@ -12,8 +12,8 @@ export default () => {
<Match when={store.error.id === 'invalid_config'}> <Match when={store.error.id === 'invalid_config'}>
<InvalidState title="Invalid Config" /> <InvalidState title="Invalid Config" />
</Match> </Match>
<Match when={store.error.id === 'invalid_file'}> <Match when={store.error.id === 'invalid_draft'}>
<InvalidState title="Invalid File" /> <InvalidState title="Invalid Draft" />
</Match> </Match>
</Switch> </Switch>
) )
@ -48,8 +48,8 @@ const Other = () => {
const onClick = () => ctrl.discard() const onClick = () => ctrl.discard()
const getMessage = () => { const getMessage = () => {
const { error } = store.error.props as any const err = (store.error.props as any).error
return typeof error === 'string' ? error : error.message return typeof err === 'string' ? err : err.message
} }
return ( return (

View File

@ -1,8 +1,9 @@
import type { Config } from '../store' import type { JSX } from 'solid-js/jsx-runtime'
import type { Config } from '../store/context'
import '../styles/Layout.scss' import '../styles/Layout.scss'
export type Styled = { export type Styled = {
children: any children: JSX.Element
config?: Config config?: Config
'data-testid'?: string 'data-testid'?: string
onClick?: () => void onClick?: () => void

View File

@ -1,16 +1,16 @@
import { EditorState, type Transaction } from 'prosemirror-state'
import { EditorView } from 'prosemirror-view'
import { unwrap } from 'solid-js/store'
import { createEffect, untrack } from 'solid-js' import { createEffect, untrack } from 'solid-js'
import { createEditorState } from '../prosemirror' import { Store, unwrap } from 'solid-js/store'
import type { ProseMirrorState, ProseMirrorExtension } from '../prosemirror/helpers' import { EditorState, EditorStateConfig, Transaction } from 'prosemirror-state'
import { EditorView } from 'prosemirror-view'
import { Schema } from 'prosemirror-model'
import type { NodeViewFn, ProseMirrorExtension, ProseMirrorState } from '../prosemirror/helpers'
interface Props { interface Props {
style?: string style?: string
className?: string className?: string
text?: ProseMirrorState text?: Store<ProseMirrorState>
editorView?: EditorView editorView?: Store<EditorView>
extensions?: ProseMirrorExtension[] extensions?: Store<ProseMirrorExtension[]>
onInit: (s: EditorState, v: EditorView) => void onInit: (s: EditorState, v: EditorView) => void
onReconfigure: (s: EditorState) => void onReconfigure: (s: EditorState) => void
onChange: (s: EditorState) => void onChange: (s: EditorState) => void
@ -19,6 +19,7 @@ interface Props {
export const ProseMirror = (props: Props) => { export const ProseMirror = (props: Props) => {
let editorRef: HTMLDivElement let editorRef: HTMLDivElement
const editorView = () => untrack(() => unwrap(props.editorView)) const editorView = () => untrack(() => unwrap(props.editorView))
const dispatchTransaction = (tr: Transaction) => { const dispatchTransaction = (tr: Transaction) => {
if (!editorView()) return if (!editorView()) return
const newState = editorView().state.apply(tr) const newState = editorView().state.apply(tr)
@ -28,10 +29,10 @@ export const ProseMirror = (props: Props) => {
} }
createEffect( createEffect(
(state: [EditorState, ProseMirrorExtension[]]) => { (payload: [EditorState, ProseMirrorExtension[]]) => {
const [prevText, prevExtensions] = state const [prevText, prevExtensions] = payload
const text = unwrap(props.text) const text = unwrap(props.text)
const extensions = unwrap(props.extensions) const extensions: ProseMirrorExtension[] = unwrap(props.extensions)
if (!text || !extensions?.length) { if (!text || !extensions?.length) {
return [text, extensions] return [text, extensions]
} }
@ -61,3 +62,46 @@ export const ProseMirror = (props: Props) => {
return <div style={props.style} ref={editorRef} class={props.className} spell-check={false} /> return <div style={props.style} ref={editorRef} class={props.className} spell-check={false} />
} }
const createEditorState = (
text: ProseMirrorState,
extensions: ProseMirrorExtension[],
prevText?: EditorState
): {
editorState: EditorState
nodeViews: { [key: string]: NodeViewFn }
} => {
const reconfigure = text instanceof EditorState && prevText?.schema
let schemaSpec = { nodes: {} }
let nodeViews = {}
let plugins = []
for (const extension of extensions) {
if (extension.schema) {
schemaSpec = extension.schema(schemaSpec)
}
if (extension.nodeViews) {
nodeViews = { ...nodeViews, ...extension.nodeViews }
}
}
const schema = reconfigure ? prevText.schema : new Schema(schemaSpec)
for (const extension of extensions) {
if (extension.plugins) {
plugins = extension.plugins(plugins, schema)
}
}
let editorState: EditorState
if (reconfigure) {
editorState = text.reconfigure({ schema, plugins } as EditorStateConfig)
} else if (text instanceof EditorState) {
editorState = EditorState.fromJSON({ schema, plugins }, text.toJSON())
} else if (text) {
console.debug(text)
editorState = EditorState.fromJSON({ schema, plugins }, text)
}
return { editorState, nodeViews }
}

View File

@ -1,14 +1,12 @@
import { Show, createEffect, createSignal, onCleanup, For } from 'solid-js' import { For, Show, createEffect, createSignal, onCleanup, onMount } from 'solid-js'
import { unwrap } from 'solid-js/store' import { unwrap } from 'solid-js/store'
import { undo, redo } from 'prosemirror-history' import { undo, redo } from 'prosemirror-history'
import { useState } from '../store' import { Draft, useState /*, Config, PrettierConfig */ } from '../store/context'
import * as remote from '../remote'
import { isEmpty /*, isInitialized*/ } from '../prosemirror/helpers'
import type { Styled } from './Layout' import type { Styled } from './Layout'
import '../styles/Sidebar.scss' import '../styles/Sidebar.scss'
import { router } from '../../../stores/router'
import { t } from '../../../utils/intl' import { t } from '../../../utils/intl'
import { isEmpty } from '../prosemirror/helpers'
import type { EditorState } from 'prosemirror-state'
const Off = (props) => <div class="sidebar-off">{props.children}</div> const Off = (props) => <div class="sidebar-off">{props.children}</div>
@ -29,23 +27,22 @@ const Link = (
</button> </button>
) )
const mod = 'Ctrl'
const Keys = (props) => ( const Keys = (props) => (
<span> <span>
<For each={props.keys}>{(k: string) => <i>{k}</i>}</For> <For each={props.keys}>{(k: Element) => <i>{k}</i>}</For>
</span> </span>
) )
interface SidebarProps { export const Sidebar = () => {
error?: string const [isMac, setIsMac] = createSignal(false)
} onMount(() => setIsMac(window?.navigator.platform.includes('Mac')))
// eslint-disable-next-line unicorn/consistent-function-scoping
// eslint-disable-next-line sonarjs/cognitive-complexity // const isDark = () => window.matchMedia('(prefers-color-scheme: dark)').matches
export const Sidebar = (_props: SidebarProps) => { const mod = isMac() ? 'Cmd' : 'Ctrl'
// const alt = isMac() ? 'Cmd' : 'Alt'
const [store, ctrl] = useState() const [store, ctrl] = useState()
const [lastAction, setLastAction] = createSignal<string | undefined>() const [lastAction, setLastAction] = createSignal<string | undefined>()
const toggleTheme = () => { const toggleTheme = () => {
// TODO: use dark/light toggle somewhere
document.body.classList.toggle('dark') document.body.classList.toggle('dark')
ctrl.updateConfig({ theme: document.body.className }) ctrl.updateConfig({ theme: document.body.className })
} }
@ -58,20 +55,51 @@ export const Sidebar = (_props: SidebarProps) => {
} }
const editorView = () => unwrap(store.editorView) const editorView = () => unwrap(store.editorView)
const onToggleMarkdown = () => ctrl.toggleMarkdown() const onToggleMarkdown = () => ctrl.toggleMarkdown()
const onOpenDraft = (draft: Draft) => ctrl.openDraft(unwrap(draft))
const collabUsers = () => store.collab?.y?.provider.awareness.meta.size ?? 0 const collabUsers = () => store.collab?.y?.provider.awareness.meta.size ?? 0
const onUndo = () => undo(editorView().state, editorView().dispatch) const onUndo = () => undo(editorView().state, editorView().dispatch)
const onRedo = () => redo(editorView().state, editorView().dispatch) const onRedo = () => redo(editorView().state, editorView().dispatch)
const onNew = () => ctrl.newFile() const onCopyAllAsMd = () =>
remote.copyAllAsMarkdown(editorView().state).then(() => setLastAction('copy-md'))
const onDiscard = () => ctrl.discard() const onDiscard = () => ctrl.discard()
const [isHidden, setIsHidden] = createSignal<boolean | false>() const [isHidden, setIsHidden] = createSignal<boolean | false>()
// eslint-disable-next-line unicorn/consistent-function-scoping
const onHistory = () => {
console.log('[editor.sidebar] implement history handling')
router.open('/create/settings')
}
const toggleSidebar = () => setIsHidden(!isHidden()) const toggleSidebar = () => setIsHidden(!isHidden())
toggleSidebar() toggleSidebar()
// eslint-disable-next-line sonarjs/cognitive-complexity
const DraftLink = (p: { draft: Draft }) => {
const length = 100
let content = ''
const getContent = (node: any) => {
if (node.text) content += node.text
if (content.length > length) {
content = content.slice(0, Math.max(0, length)) + '...'
return content
}
if (node.content) {
for (const child of node.content) {
if (content.length >= length) break
content = getContent(child)
}
}
return content
}
const text = () =>
p.draft.path
? p.draft.path.slice(Math.max(0, p.draft.path.length - length))
: getContent(p.draft.text?.doc)
return (
// eslint-disable-next-line solid/no-react-specific-props
<Link className="draft" onClick={() => onOpenDraft(p.draft)} data-testid="open">
{text()} {p.draft.path && '📎'}
</Link>
)
}
const onCollab = () => { const onCollab = () => {
const state = unwrap(store) const state = unwrap(store)
store.collab?.started ? ctrl.stopCollab(state) : ctrl.startCollab(state) store.collab?.started ? ctrl.stopCollab(state) : ctrl.startCollab(state)
@ -88,19 +116,11 @@ export const Sidebar = (_props: SidebarProps) => {
}, 1000) }, 1000)
onCleanup(() => clearTimeout(id)) onCleanup(() => clearTimeout(id))
}) })
const discardText = () => {
if (store.path) {
return t('Close')
} else if (store.drafts.length > 0 && isEmpty(store.text as EditorState)) {
return t('Delete')
} else {
return t('Clear')
}
}
return ( return (
<div class={'sidebar-container' + (isHidden() ? ' sidebar-container--hidden' : '')}> <div class={'sidebar-container' + (isHidden() ? ' sidebar-container--hidden' : '')}>
<span class="sidebar-opener" onClick={toggleSidebar}> <span class="sidebar-opener" onClick={toggleSidebar}>
{t('Tips and proposals')} Советы и&nbsp;предложения
</span> </span>
<Off onClick={() => editorView().focus()}> <Off onClick={() => editorView().focus()}>
@ -112,35 +132,44 @@ export const Sidebar = (_props: SidebarProps) => {
<i>({store.path.slice(Math.max(0, store.path.length - 24))})</i> <i>({store.path.slice(Math.max(0, store.path.length - 24))})</i>
</Label> </Label>
)} )}
<Link onClick={onNew}>{t('Tabula rasa')}</Link> <Link>Пригласить соавторов</Link>
<Link onClick={onCollab}>{t('Invite coauthors')}</Link> <Link>Настройки публикации</Link>
<Link onClick={() => router.open('/create/settings')}>{t('Publication settings')}</Link> <Link>История правок</Link>
<Link onClick={onHistory}>{t('History of changes')}</Link>
<div class="theme-switcher">
Ночная тема
<input type="checkbox" name="theme" id="theme" onClick={toggleTheme} />
<label for="theme">Ночная тема</label>
</div>
<Link <Link
onClick={onDiscard} onClick={onDiscard}
disabled={!store.path && store.drafts.length === 0 && isEmpty(store.text as EditorState)} disabled={!store.path && store.drafts.length === 0 && isEmpty(store.text)}
data-testid="discard" data-testid="discard"
> >
{discardText()} <Keys keys={[mod, 'w']} /> {/* eslint-disable-next-line no-nested-ternary */}
{store.path
? 'Close'
: (store.drafts.length > 0 && isEmpty(store.text)
? 'Delete ⚠️'
: 'Clear')}{' '}
<Keys keys={[mod, 'w']} />
</Link> </Link>
<Link onClick={onUndo}> <Link onClick={onUndo}>
{t('Undo')} <Keys keys={[mod, 'z']} /> Undo <Keys keys={[mod, 'z']} />
</Link> </Link>
<Link onClick={onRedo}> <Link onClick={onRedo}>
{t('Redo')} <Keys keys={[mod, 'Shift+z']} /> Redo <Keys keys={[mod, ...(isMac() ? ['Shift', 'z'] : ['y'])]} />
</Link> </Link>
<Link onClick={onToggleMarkdown} data-testid="markdown"> <Link onClick={onToggleMarkdown} data-testid="markdown">
Markdown {store.markdown && '✅'} <Keys keys={[mod, 'm']} /> Markdown mode {store.markdown && '✅'} <Keys keys={[mod, 'm']} />
</Link> </Link>
<Link onClick={onCopyAllAsMd}>Copy all as MD {lastAction() === 'copy-md' && '📋'}</Link>
<Show when={store.drafts.length > 0}> <Show when={store.drafts.length > 0}>
<h4>{t('Drafts')}:</h4> <h4>Drafts:</h4>
<p> <p>
<For each={store.drafts}> <For each={store.drafts}>{(draft: Draft) => <DraftLink draft={draft} />}</For>
{(draft) => <Link onClick={() => router.open(draft.path)}>{draft.path}</Link>}
</For>
</p> </p>
</Show> </Show>
<Link onClick={onCollab} title={store.collab?.error ? 'Connection error' : ''}> <Link onClick={onCollab} title={store.collab?.error ? 'Connection error' : ''}>
{collabText()} {collabText()}
</Link> </Link>

View File

@ -1,34 +1,38 @@
import markdownit from 'markdown-it' import markdownit from 'markdown-it'
import { import { MarkdownSerializer, MarkdownParser, defaultMarkdownSerializer } from 'prosemirror-markdown'
MarkdownSerializer,
MarkdownParser,
defaultMarkdownSerializer,
MarkdownSerializerState
} from 'prosemirror-markdown'
import type { Node, Schema } from 'prosemirror-model' import type { Node, Schema } from 'prosemirror-model'
import type { EditorState } from 'prosemirror-state' import type { EditorState } from 'prosemirror-state'
export const serialize = (state: EditorState) => { export const serialize = (state: EditorState) => {
let text = markdownSerializer.serialize(state.doc) let text = markdownSerializer.serialize(state.doc)
if (text.charAt(text.length - 1) !== '\n') text += '\n' if (text.charAt(text.length - 1) !== '\n') {
text += '\n'
}
return text return text
} }
const findAlignment = (cell: Node) => { const findAlignment = (cell: Node): string | null => {
const alignment = cell.attrs.style as string const alignment = cell.attrs.style as string
if (!alignment) return null if (!alignment) {
return null
}
const match = alignment.match(/text-align: ?(left|right|center)/) const match = alignment.match(/text-align: ?(left|right|center)/)
if (match && match[1]) return match[1] if (match && match[1]) {
return match[1]
}
return null return null
} }
export const markdownSerializer = new MarkdownSerializer( export const markdownSerializer = new MarkdownSerializer(
{ {
...defaultMarkdownSerializer.nodes, ...defaultMarkdownSerializer.nodes,
image(state: MarkdownSerializerState, node) { image(state, node) {
const alt = state.esc(node.attrs.alt || '') const alt = state.esc(node.attrs.alt || '')
const src = node.attrs.path ?? node.attrs.src const src = node.attrs.path ?? node.attrs.src
const title = node.attrs.title || '' // ? state.quote(node.attrs.title) : undefined const title = node.attrs.title ? state.quote(node.attrs.title) : undefined
state.write(`![${alt}](${src}${title ? ' ' + title : ''})\n`) state.write(`![${alt}](${src}${title ? ' ' + title : ''})\n`)
}, },
code_block(state, node) { code_block(state, node) {
@ -118,8 +122,8 @@ export const markdownSerializer = new MarkdownSerializer(
} }
) )
function listIsTight(tokens, i: number) { function listIsTight(tokens: any, idx: number) {
// eslint-disable-next-line no-param-reassign let i = idx
while (++i < tokens.length) { while (++i < tokens.length) {
if (tokens[i].type !== 'list_item_open') return tokens[i].hidden if (tokens[i].type !== 'list_item_open') return tokens[i].hidden
} }

View File

@ -1,7 +1,7 @@
import { Plugin } from 'prosemirror-state' import { Plugin } from 'prosemirror-state'
import { Fragment, Node, Schema, Slice } from 'prosemirror-model' import { Fragment, Node, Schema, Slice } from 'prosemirror-model'
import type { ProseMirrorExtension } from '../helpers' import type { ProseMirrorExtension } from '../helpers'
import { createMarkdownParser } from '../markdown' import { createMarkdownParser } from '../../markdown'
const URL_REGEX = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:\d+)?(\/|\/([\w!#%&+./:=?@-]))?/g const URL_REGEX = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:\d+)?(\/|\/([\w!#%&+./:=?@-]))?/g

View File

@ -17,13 +17,11 @@ export type NodeViewFn = (
decorations: Decoration[] decorations: Decoration[]
) => NodeView ) => NodeView
export const isInitialized = (state: EditorState) => state !== undefined && state instanceof EditorState export const isInitialized = (state: any) => state !== undefined && state instanceof EditorState
export const isEmpty = (state: EditorState) => export const isEmpty = (state: any) =>
!isInitialized(state) || !isInitialized(state) ||
(state.doc.childCount === 1 && (state.doc.childCount === 1 &&
!state.doc.firstChild.type.spec.code && !state.doc.firstChild.type.spec.code &&
state.doc.firstChild.isTextblock && state.doc.firstChild.isTextblock &&
state.doc.firstChild.content.size === 0) state.doc.firstChild.content.size === 0)
export const isText = (x) => x && x.doc && x.selection

View File

@ -1,46 +0,0 @@
import { EditorState } from 'prosemirror-state'
import { Schema } from 'prosemirror-model'
import type { NodeViewFn, ProseMirrorExtension, ProseMirrorState } from './helpers'
export const createEditorState = (
text: ProseMirrorState,
extensions: ProseMirrorExtension[],
prevText?: EditorState
): {
editorState: EditorState
nodeViews: { [key: string]: NodeViewFn }
} => {
const reconfigure = text instanceof EditorState && prevText?.schema
let schemaSpec = { nodes: {} }
let nodeViews = {}
let plugins = []
for (const extension of extensions) {
if (extension.schema) {
schemaSpec = extension.schema(schemaSpec)
}
if (extension.nodeViews) {
nodeViews = { ...nodeViews, ...extension.nodeViews }
}
}
const schema = reconfigure ? prevText.schema : new Schema(schemaSpec)
for (const extension of extensions) {
if (extension.plugins) {
plugins = extension.plugins(plugins, schema)
}
}
let editorState: EditorState
if (reconfigure) {
editorState = text.reconfigure({ schema, plugins } as Partial<EditorState>)
} else if (text instanceof EditorState) {
editorState = EditorState.fromJSON({ schema, plugins }, text.toJSON())
} else if (text) {
console.debug(text)
editorState = EditorState.fromJSON({ schema, plugins }, text)
}
return { editorState, nodeViews }
}

View File

@ -17,7 +17,7 @@ import selectionMenu from './extension/selection'
import strikethrough from './extension/strikethrough' import strikethrough from './extension/strikethrough'
import table from './extension/table' import table from './extension/table'
import todoList from './extension/todo-list' import todoList from './extension/todo-list'
import type { Config, YOptions } from '../store' import type { Config, YOptions } from '../store/context'
import type { ProseMirrorExtension } from './helpers' import type { ProseMirrorExtension } from './helpers'
interface ExtensionsProps { interface ExtensionsProps {

View File

@ -0,0 +1,11 @@
import { EditorState } from 'prosemirror-state'
import { serialize } from './markdown'
export const copy = async (text: string): Promise<void> => {
navigator.clipboard.writeText(text)
}
export const copyAllAsMarkdown = async (state: EditorState): Promise<void> => {
const text = serialize(state)
navigator.clipboard.writeText(text)
}

View File

@ -6,8 +6,8 @@ import { selectAll, deleteSelection } from 'prosemirror-commands'
import { undo as yUndo, redo as yRedo } from 'y-prosemirror' import { undo as yUndo, redo as yRedo } from 'y-prosemirror'
import debounce from 'lodash/debounce' import debounce from 'lodash/debounce'
import { createSchema, createExtensions, createEmptyText } from '../prosemirror/setup' import { createSchema, createExtensions, createEmptyText } from '../prosemirror/setup'
import { State, Draft, Config, ServiceError, newState } from '.' import { State, Draft, Config, ServiceError, newState } from './context'
import { serialize, createMarkdownParser } from '../prosemirror/markdown' import { serialize, createMarkdownParser } from '../markdown'
import db from '../db' import db from '../db'
import { isEmpty, isInitialized } from '../prosemirror/helpers' import { isEmpty, isInitialized } from '../prosemirror/helpers'
import { drafts as draftsatom } from '../../../stores/editor' import { drafts as draftsatom } from '../../../stores/editor'
@ -91,7 +91,7 @@ export const createCtrl = (initial): [Store<State>, { [key: string]: any }] => {
...drafts, ...drafts,
{ {
body: text, body: text,
lastModified: prev.lastModified as Date, lastModified: prev.lastModified,
path: prev.path, path: prev.path,
markdown: prev.markdown markdown: prev.markdown
} as Draft } as Draft
@ -302,7 +302,7 @@ export const createCtrl = (initial): [Store<State>, { [key: string]: any }] => {
} }
const index = findIndexOfDraft(draft) const index = findIndexOfDraft(draft)
const item = index === -1 ? draft : state.drafts[index] const item = index === -1 ? draft : state.drafts[index]
let drafts = state.drafts.filter((f) => f !== item) let drafts = state.drafts.filter((d: Draft) => d !== item)
if (!isEmpty(state.text as EditorState) && state.lastModified) { if (!isEmpty(state.text as EditorState) && state.lastModified) {
drafts = addToDrafts(drafts, { lastModified: new Date(), text: state.text } as Draft) drafts = addToDrafts(drafts, { lastModified: new Date(), text: state.text } as Draft)
} }

View File

@ -4,14 +4,12 @@ import type { XmlFragment } from 'yjs'
import type { WebrtcProvider } from 'y-webrtc' import type { WebrtcProvider } from 'y-webrtc'
import type { ProseMirrorExtension, ProseMirrorState } from '../prosemirror/helpers' import type { ProseMirrorExtension, ProseMirrorState } from '../prosemirror/helpers'
import type { EditorView } from 'prosemirror-view' import type { EditorView } from 'prosemirror-view'
import { createEmptyText } from '../prosemirror/setup'
export interface Args { export interface Args {
draft: string // path to draft
cwd?: string cwd?: string
file?: string draft?: string
room?: string room?: string
text?: string text?: any
} }
export interface PrettierConfig { export interface PrettierConfig {
@ -28,7 +26,7 @@ export interface Config {
font: string font: string
fontSize: number fontSize: number
contentWidth: number contentWidth: number
alwaysOnTop: boolean // alwaysOnTop: boolean;
// typewriterMode: boolean; // typewriterMode: boolean;
prettier: PrettierConfig prettier: PrettierConfig
} }
@ -62,18 +60,20 @@ export interface State {
config: Config config: Config
error?: ErrorObject error?: ErrorObject
loading: LoadingType loading: LoadingType
fullscreen?: boolean
collab?: Collab collab?: Collab
path?: string path?: string
args?: Args args?: Args
isMac?: boolean
} }
export interface Draft { export interface Draft {
extensions?: ProseMirrorExtension[] text?: { [key: string]: any }
lastModified: Date
body?: string body?: string
text?: { doc: any; selection: { type: string; anchor: number; head: number } } lastModified?: Date
path?: string path?: string
markdown?: boolean markdown?: boolean
extensions?: ProseMirrorExtension[]
} }
export class ServiceError extends Error { export class ServiceError extends Error {
@ -92,6 +92,7 @@ export const newState = (props: Partial<State> = {}): State => ({
extensions: [], extensions: [],
drafts: [], drafts: [],
loading: 'loading', loading: 'loading',
fullscreen: false,
markdown: false, markdown: false,
config: { config: {
theme: undefined, theme: undefined,
@ -99,7 +100,6 @@ export const newState = (props: Partial<State> = {}): State => ({
font: 'muller', font: 'muller',
fontSize: 24, fontSize: 24,
contentWidth: 800, contentWidth: 800,
alwaysOnTop: false,
// typewriterMode: true, // typewriterMode: true,
prettier: { prettier: {
printWidth: 80, printWidth: 80,
@ -111,16 +111,3 @@ export const newState = (props: Partial<State> = {}): State => ({
}, },
...props ...props
}) })
export const addToDrafts = (drafts: Draft[], state: State): Draft[] => {
drafts.forEach((d) => {
if (!state.drafts.includes(d)) state.drafts.push(d)
})
return state.drafts
}
export const createTextFromDraft = async (draft: Draft) => {
const created = createEmptyText()
created.doc.content = Object.values(draft.text) // FIXME
return created
}

View File

@ -1,4 +1,4 @@
import { newState } from '../Editor/store' import { newState } from '../Editor/store/context'
import { MainLayout } from '../Layouts/MainLayout' import { MainLayout } from '../Layouts/MainLayout'
import { CreateView } from '../Views/Create' import { CreateView } from '../Views/Create'

View File

@ -1,7 +1,7 @@
import { Show, onCleanup, createEffect, onError, onMount, untrack } from 'solid-js' import { Show, onCleanup, createEffect, onError, onMount, untrack } from 'solid-js'
import { createMutable, unwrap } from 'solid-js/store' import { createMutable, unwrap } from 'solid-js/store'
import { State, StateContext } from '../Editor/store' import { State, StateContext } from '../Editor/store/context'
import { createCtrl } from '../Editor/store/ctrl' import { createCtrl } from '../Editor/store/actions'
import { Layout } from '../Editor/components/Layout' import { Layout } from '../Editor/components/Layout'
import { Editor } from '../Editor/components/Editor' import { Editor } from '../Editor/components/Editor'
import { Sidebar } from '../Editor/components/Sidebar' import { Sidebar } from '../Editor/components/Sidebar'
@ -10,7 +10,14 @@ import ErrorView from '../Editor/components/Error'
const matchDark = () => window.matchMedia('(prefers-color-scheme: dark)') const matchDark = () => window.matchMedia('(prefers-color-scheme: dark)')
export const CreateView = (props: { state: State }) => { export const CreateView = (props: { state: State }) => {
const [store, ctrl] = createCtrl(props.state) let isMac = false
onMount(() => {
isMac = window?.navigator.platform.includes('Mac')
matchDark().addEventListener('change', onChangeTheme)
onCleanup(() => matchDark().removeEventListener('change', onChangeTheme))
})
const [store, ctrl] = createCtrl({ ...props.state, isMac })
const mouseEnterCoords = createMutable({ x: 0, y: 0 }) const mouseEnterCoords = createMutable({ x: 0, y: 0 })
const onMouseEnter = (e: MouseEvent) => { const onMouseEnter = (e: MouseEvent) => {
@ -28,10 +35,6 @@ export const CreateView = (props: { state: State }) => {
}) })
const onChangeTheme = () => ctrl.updateTheme() const onChangeTheme = () => ctrl.updateTheme()
onMount(() => {
matchDark().addEventListener('change', onChangeTheme)
onCleanup(() => matchDark().removeEventListener('change', onChangeTheme))
})
onError((error) => { onError((error) => {
console.error('[create] error:', error) console.error('[create] error:', error)

View File

@ -2,14 +2,7 @@ import { persistentMap } from '@nanostores/persistent'
import type { Reaction } from '../graphql/types.gen' import type { Reaction } from '../graphql/types.gen'
import { atom } from 'nanostores' import { atom } from 'nanostores'
import { createSignal } from 'solid-js' import { createSignal } from 'solid-js'
import type { Draft } from '../components/Editor/store/context'
interface Draft {
createdAt: Date
topics?: string[]
lastModified: Date
body?: string
title?: string
}
interface Collab { interface Collab {
authors: string[] // slugs authors: string[] // slugs