re-integrated
This commit is contained in:
parent
05cdf42c05
commit
24d5acabef
|
@ -1,9 +1,8 @@
|
|||
import type { EditorView } from 'prosemirror-view'
|
||||
import type { EditorState } from 'prosemirror-state'
|
||||
import { useState } from '../store'
|
||||
import { ProseMirror } from '../components/ProseMirror'
|
||||
import { useState } from '../store/context'
|
||||
import { ProseMirror } from './ProseMirror'
|
||||
import '../styles/Editor.scss'
|
||||
import type { ProseMirrorExtension, ProseMirrorState } from '../prosemirror/helpers'
|
||||
|
||||
export const Editor = () => {
|
||||
const [store, ctrl] = useState()
|
||||
|
@ -23,9 +22,9 @@ export const Editor = () => {
|
|||
// eslint-disable-next-line solid/no-react-specific-props
|
||||
className="editor"
|
||||
style={style()}
|
||||
editorView={store.editorView as EditorView}
|
||||
text={store.text as ProseMirrorState}
|
||||
extensions={store.extensions as ProseMirrorExtension[]}
|
||||
editorView={store.editorView}
|
||||
text={store.text}
|
||||
extensions={store.extensions}
|
||||
onInit={onInit}
|
||||
onReconfigure={onReconfigure}
|
||||
onChange={onChange}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Switch, Match } from 'solid-js'
|
||||
import { useState } from '../store'
|
||||
import { useState } from '../store/context'
|
||||
import '../styles/Button.scss'
|
||||
|
||||
export default () => {
|
||||
|
@ -12,8 +12,8 @@ export default () => {
|
|||
<Match when={store.error.id === 'invalid_config'}>
|
||||
<InvalidState title="Invalid Config" />
|
||||
</Match>
|
||||
<Match when={store.error.id === 'invalid_file'}>
|
||||
<InvalidState title="Invalid File" />
|
||||
<Match when={store.error.id === 'invalid_draft'}>
|
||||
<InvalidState title="Invalid Draft" />
|
||||
</Match>
|
||||
</Switch>
|
||||
)
|
||||
|
@ -48,8 +48,8 @@ const Other = () => {
|
|||
const onClick = () => ctrl.discard()
|
||||
|
||||
const getMessage = () => {
|
||||
const { error } = store.error.props as any
|
||||
return typeof error === 'string' ? error : error.message
|
||||
const err = (store.error.props as any).error
|
||||
return typeof err === 'string' ? err : err.message
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -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'
|
||||
|
||||
export type Styled = {
|
||||
children: any
|
||||
children: JSX.Element
|
||||
config?: Config
|
||||
'data-testid'?: string
|
||||
onClick?: () => void
|
||||
|
|
|
@ -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 { createEditorState } from '../prosemirror'
|
||||
import type { ProseMirrorState, ProseMirrorExtension } from '../prosemirror/helpers'
|
||||
import { Store, unwrap } from 'solid-js/store'
|
||||
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 {
|
||||
style?: string
|
||||
className?: string
|
||||
text?: ProseMirrorState
|
||||
editorView?: EditorView
|
||||
extensions?: ProseMirrorExtension[]
|
||||
text?: Store<ProseMirrorState>
|
||||
editorView?: Store<EditorView>
|
||||
extensions?: Store<ProseMirrorExtension[]>
|
||||
onInit: (s: EditorState, v: EditorView) => void
|
||||
onReconfigure: (s: EditorState) => void
|
||||
onChange: (s: EditorState) => void
|
||||
|
@ -19,6 +19,7 @@ interface Props {
|
|||
export const ProseMirror = (props: Props) => {
|
||||
let editorRef: HTMLDivElement
|
||||
const editorView = () => untrack(() => unwrap(props.editorView))
|
||||
|
||||
const dispatchTransaction = (tr: Transaction) => {
|
||||
if (!editorView()) return
|
||||
const newState = editorView().state.apply(tr)
|
||||
|
@ -28,10 +29,10 @@ export const ProseMirror = (props: Props) => {
|
|||
}
|
||||
|
||||
createEffect(
|
||||
(state: [EditorState, ProseMirrorExtension[]]) => {
|
||||
const [prevText, prevExtensions] = state
|
||||
(payload: [EditorState, ProseMirrorExtension[]]) => {
|
||||
const [prevText, prevExtensions] = payload
|
||||
const text = unwrap(props.text)
|
||||
const extensions = unwrap(props.extensions)
|
||||
const extensions: ProseMirrorExtension[] = unwrap(props.extensions)
|
||||
if (!text || !extensions?.length) {
|
||||
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} />
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
|
|
@ -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 { 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 '../styles/Sidebar.scss'
|
||||
import { router } from '../../../stores/router'
|
||||
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>
|
||||
|
||||
|
@ -29,23 +27,22 @@ const Link = (
|
|||
</button>
|
||||
)
|
||||
|
||||
const mod = 'Ctrl'
|
||||
const Keys = (props) => (
|
||||
<span>
|
||||
<For each={props.keys}>{(k: string) => <i>{k}</i>}</For>
|
||||
<For each={props.keys}>{(k: Element) => <i>{k}</i>}</For>
|
||||
</span>
|
||||
)
|
||||
|
||||
interface SidebarProps {
|
||||
error?: string
|
||||
}
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
export const Sidebar = (_props: SidebarProps) => {
|
||||
export const Sidebar = () => {
|
||||
const [isMac, setIsMac] = createSignal(false)
|
||||
onMount(() => setIsMac(window?.navigator.platform.includes('Mac')))
|
||||
// eslint-disable-next-line unicorn/consistent-function-scoping
|
||||
// const isDark = () => window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
const mod = isMac() ? 'Cmd' : 'Ctrl'
|
||||
// const alt = isMac() ? 'Cmd' : 'Alt'
|
||||
const [store, ctrl] = useState()
|
||||
const [lastAction, setLastAction] = createSignal<string | undefined>()
|
||||
const toggleTheme = () => {
|
||||
// TODO: use dark/light toggle somewhere
|
||||
document.body.classList.toggle('dark')
|
||||
ctrl.updateConfig({ theme: document.body.className })
|
||||
}
|
||||
|
@ -58,20 +55,51 @@ export const Sidebar = (_props: SidebarProps) => {
|
|||
}
|
||||
const editorView = () => unwrap(store.editorView)
|
||||
const onToggleMarkdown = () => ctrl.toggleMarkdown()
|
||||
const onOpenDraft = (draft: Draft) => ctrl.openDraft(unwrap(draft))
|
||||
const collabUsers = () => store.collab?.y?.provider.awareness.meta.size ?? 0
|
||||
const onUndo = () => undo(editorView().state, editorView().dispatch)
|
||||
const onRedo = () => redo(editorView().state, editorView().dispatch)
|
||||
const onNew = () => ctrl.newFile()
|
||||
const onCopyAllAsMd = () =>
|
||||
remote.copyAllAsMarkdown(editorView().state).then(() => setLastAction('copy-md'))
|
||||
const onDiscard = () => ctrl.discard()
|
||||
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())
|
||||
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 state = unwrap(store)
|
||||
store.collab?.started ? ctrl.stopCollab(state) : ctrl.startCollab(state)
|
||||
|
@ -88,19 +116,11 @@ export const Sidebar = (_props: SidebarProps) => {
|
|||
}, 1000)
|
||||
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 (
|
||||
<div class={'sidebar-container' + (isHidden() ? ' sidebar-container--hidden' : '')}>
|
||||
<span class="sidebar-opener" onClick={toggleSidebar}>
|
||||
{t('Tips and proposals')}
|
||||
Советы и предложения
|
||||
</span>
|
||||
|
||||
<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>
|
||||
</Label>
|
||||
)}
|
||||
<Link onClick={onNew}>{t('Tabula rasa')}</Link>
|
||||
<Link onClick={onCollab}>{t('Invite coauthors')}</Link>
|
||||
<Link onClick={() => router.open('/create/settings')}>{t('Publication settings')}</Link>
|
||||
<Link onClick={onHistory}>{t('History of changes')}</Link>
|
||||
<Link>Пригласить соавторов</Link>
|
||||
<Link>Настройки публикации</Link>
|
||||
<Link>История правок</Link>
|
||||
|
||||
<div class="theme-switcher">
|
||||
Ночная тема
|
||||
<input type="checkbox" name="theme" id="theme" onClick={toggleTheme} />
|
||||
<label for="theme">Ночная тема</label>
|
||||
</div>
|
||||
<Link
|
||||
onClick={onDiscard}
|
||||
disabled={!store.path && store.drafts.length === 0 && isEmpty(store.text as EditorState)}
|
||||
disabled={!store.path && store.drafts.length === 0 && isEmpty(store.text)}
|
||||
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 onClick={onUndo}>
|
||||
{t('Undo')} <Keys keys={[mod, 'z']} />
|
||||
Undo <Keys keys={[mod, 'z']} />
|
||||
</Link>
|
||||
<Link onClick={onRedo}>
|
||||
{t('Redo')} <Keys keys={[mod, 'Shift+z']} />
|
||||
Redo <Keys keys={[mod, ...(isMac() ? ['Shift', 'z'] : ['y'])]} />
|
||||
</Link>
|
||||
<Link onClick={onToggleMarkdown} data-testid="markdown">
|
||||
Markdown {store.markdown && '✅'} <Keys keys={[mod, 'm']} />
|
||||
Markdown mode {store.markdown && '✅'} <Keys keys={[mod, 'm']} />
|
||||
</Link>
|
||||
<Link onClick={onCopyAllAsMd}>Copy all as MD {lastAction() === 'copy-md' && '📋'}</Link>
|
||||
<Show when={store.drafts.length > 0}>
|
||||
<h4>{t('Drafts')}:</h4>
|
||||
<h4>Drafts:</h4>
|
||||
<p>
|
||||
<For each={store.drafts}>
|
||||
{(draft) => <Link onClick={() => router.open(draft.path)}>{draft.path}</Link>}
|
||||
</For>
|
||||
<For each={store.drafts}>{(draft: Draft) => <DraftLink draft={draft} />}</For>
|
||||
</p>
|
||||
</Show>
|
||||
|
||||
<Link onClick={onCollab} title={store.collab?.error ? 'Connection error' : ''}>
|
||||
{collabText()}
|
||||
</Link>
|
||||
|
|
|
@ -1,34 +1,38 @@
|
|||
import markdownit from 'markdown-it'
|
||||
import {
|
||||
MarkdownSerializer,
|
||||
MarkdownParser,
|
||||
defaultMarkdownSerializer,
|
||||
MarkdownSerializerState
|
||||
} from 'prosemirror-markdown'
|
||||
import { MarkdownSerializer, MarkdownParser, defaultMarkdownSerializer } from 'prosemirror-markdown'
|
||||
import type { Node, Schema } from 'prosemirror-model'
|
||||
import type { EditorState } from 'prosemirror-state'
|
||||
|
||||
export const serialize = (state: EditorState) => {
|
||||
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
|
||||
}
|
||||
|
||||
const findAlignment = (cell: Node) => {
|
||||
const findAlignment = (cell: Node): string | null => {
|
||||
const alignment = cell.attrs.style as string
|
||||
if (!alignment) return null
|
||||
if (!alignment) {
|
||||
return null
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
export const markdownSerializer = new MarkdownSerializer(
|
||||
{
|
||||
...defaultMarkdownSerializer.nodes,
|
||||
image(state: MarkdownSerializerState, node) {
|
||||
image(state, node) {
|
||||
const alt = state.esc(node.attrs.alt || '')
|
||||
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(`\n`)
|
||||
},
|
||||
code_block(state, node) {
|
||||
|
@ -118,8 +122,8 @@ export const markdownSerializer = new MarkdownSerializer(
|
|||
}
|
||||
)
|
||||
|
||||
function listIsTight(tokens, i: number) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
function listIsTight(tokens: any, idx: number) {
|
||||
let i = idx
|
||||
while (++i < tokens.length) {
|
||||
if (tokens[i].type !== 'list_item_open') return tokens[i].hidden
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import { Plugin } from 'prosemirror-state'
|
||||
import { Fragment, Node, Schema, Slice } from 'prosemirror-model'
|
||||
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
|
||||
|
||||
|
|
|
@ -17,13 +17,11 @@ export type NodeViewFn = (
|
|||
decorations: Decoration[]
|
||||
) => 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) ||
|
||||
(state.doc.childCount === 1 &&
|
||||
!state.doc.firstChild.type.spec.code &&
|
||||
state.doc.firstChild.isTextblock &&
|
||||
state.doc.firstChild.content.size === 0)
|
||||
|
||||
export const isText = (x) => x && x.doc && x.selection
|
||||
|
|
|
@ -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 }
|
||||
}
|
|
@ -17,7 +17,7 @@ import selectionMenu from './extension/selection'
|
|||
import strikethrough from './extension/strikethrough'
|
||||
import table from './extension/table'
|
||||
import todoList from './extension/todo-list'
|
||||
import type { Config, YOptions } from '../store'
|
||||
import type { Config, YOptions } from '../store/context'
|
||||
import type { ProseMirrorExtension } from './helpers'
|
||||
|
||||
interface ExtensionsProps {
|
||||
|
|
11
src/components/Editor/remote.ts
Normal file
11
src/components/Editor/remote.ts
Normal 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)
|
||||
}
|
|
@ -6,8 +6,8 @@ import { selectAll, deleteSelection } from 'prosemirror-commands'
|
|||
import { undo as yUndo, redo as yRedo } from 'y-prosemirror'
|
||||
import debounce from 'lodash/debounce'
|
||||
import { createSchema, createExtensions, createEmptyText } from '../prosemirror/setup'
|
||||
import { State, Draft, Config, ServiceError, newState } from '.'
|
||||
import { serialize, createMarkdownParser } from '../prosemirror/markdown'
|
||||
import { State, Draft, Config, ServiceError, newState } from './context'
|
||||
import { serialize, createMarkdownParser } from '../markdown'
|
||||
import db from '../db'
|
||||
import { isEmpty, isInitialized } from '../prosemirror/helpers'
|
||||
import { drafts as draftsatom } from '../../../stores/editor'
|
||||
|
@ -91,7 +91,7 @@ export const createCtrl = (initial): [Store<State>, { [key: string]: any }] => {
|
|||
...drafts,
|
||||
{
|
||||
body: text,
|
||||
lastModified: prev.lastModified as Date,
|
||||
lastModified: prev.lastModified,
|
||||
path: prev.path,
|
||||
markdown: prev.markdown
|
||||
} as Draft
|
||||
|
@ -302,7 +302,7 @@ export const createCtrl = (initial): [Store<State>, { [key: string]: any }] => {
|
|||
}
|
||||
const index = findIndexOfDraft(draft)
|
||||
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) {
|
||||
drafts = addToDrafts(drafts, { lastModified: new Date(), text: state.text } as Draft)
|
||||
}
|
|
@ -4,14 +4,12 @@ import type { XmlFragment } from 'yjs'
|
|||
import type { WebrtcProvider } from 'y-webrtc'
|
||||
import type { ProseMirrorExtension, ProseMirrorState } from '../prosemirror/helpers'
|
||||
import type { EditorView } from 'prosemirror-view'
|
||||
import { createEmptyText } from '../prosemirror/setup'
|
||||
|
||||
export interface Args {
|
||||
draft: string // path to draft
|
||||
cwd?: string
|
||||
file?: string
|
||||
draft?: string
|
||||
room?: string
|
||||
text?: string
|
||||
text?: any
|
||||
}
|
||||
|
||||
export interface PrettierConfig {
|
||||
|
@ -28,7 +26,7 @@ export interface Config {
|
|||
font: string
|
||||
fontSize: number
|
||||
contentWidth: number
|
||||
alwaysOnTop: boolean
|
||||
// alwaysOnTop: boolean;
|
||||
// typewriterMode: boolean;
|
||||
prettier: PrettierConfig
|
||||
}
|
||||
|
@ -62,18 +60,20 @@ export interface State {
|
|||
config: Config
|
||||
error?: ErrorObject
|
||||
loading: LoadingType
|
||||
fullscreen?: boolean
|
||||
collab?: Collab
|
||||
path?: string
|
||||
args?: Args
|
||||
isMac?: boolean
|
||||
}
|
||||
|
||||
export interface Draft {
|
||||
extensions?: ProseMirrorExtension[]
|
||||
lastModified: Date
|
||||
text?: { [key: string]: any }
|
||||
body?: string
|
||||
text?: { doc: any; selection: { type: string; anchor: number; head: number } }
|
||||
lastModified?: Date
|
||||
path?: string
|
||||
markdown?: boolean
|
||||
extensions?: ProseMirrorExtension[]
|
||||
}
|
||||
|
||||
export class ServiceError extends Error {
|
||||
|
@ -92,6 +92,7 @@ export const newState = (props: Partial<State> = {}): State => ({
|
|||
extensions: [],
|
||||
drafts: [],
|
||||
loading: 'loading',
|
||||
fullscreen: false,
|
||||
markdown: false,
|
||||
config: {
|
||||
theme: undefined,
|
||||
|
@ -99,7 +100,6 @@ export const newState = (props: Partial<State> = {}): State => ({
|
|||
font: 'muller',
|
||||
fontSize: 24,
|
||||
contentWidth: 800,
|
||||
alwaysOnTop: false,
|
||||
// typewriterMode: true,
|
||||
prettier: {
|
||||
printWidth: 80,
|
||||
|
@ -111,16 +111,3 @@ export const newState = (props: Partial<State> = {}): State => ({
|
|||
},
|
||||
...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
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { newState } from '../Editor/store'
|
||||
import { newState } from '../Editor/store/context'
|
||||
import { MainLayout } from '../Layouts/MainLayout'
|
||||
import { CreateView } from '../Views/Create'
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Show, onCleanup, createEffect, onError, onMount, untrack } from 'solid-js'
|
||||
import { createMutable, unwrap } from 'solid-js/store'
|
||||
import { State, StateContext } from '../Editor/store'
|
||||
import { createCtrl } from '../Editor/store/ctrl'
|
||||
import { State, StateContext } from '../Editor/store/context'
|
||||
import { createCtrl } from '../Editor/store/actions'
|
||||
import { Layout } from '../Editor/components/Layout'
|
||||
import { Editor } from '../Editor/components/Editor'
|
||||
import { Sidebar } from '../Editor/components/Sidebar'
|
||||
|
@ -10,7 +10,14 @@ import ErrorView from '../Editor/components/Error'
|
|||
const matchDark = () => window.matchMedia('(prefers-color-scheme: dark)')
|
||||
|
||||
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 onMouseEnter = (e: MouseEvent) => {
|
||||
|
@ -28,10 +35,6 @@ export const CreateView = (props: { state: State }) => {
|
|||
})
|
||||
|
||||
const onChangeTheme = () => ctrl.updateTheme()
|
||||
onMount(() => {
|
||||
matchDark().addEventListener('change', onChangeTheme)
|
||||
onCleanup(() => matchDark().removeEventListener('change', onChangeTheme))
|
||||
})
|
||||
|
||||
onError((error) => {
|
||||
console.error('[create] error:', error)
|
||||
|
|
|
@ -2,14 +2,7 @@ import { persistentMap } from '@nanostores/persistent'
|
|||
import type { Reaction } from '../graphql/types.gen'
|
||||
import { atom } from 'nanostores'
|
||||
import { createSignal } from 'solid-js'
|
||||
|
||||
interface Draft {
|
||||
createdAt: Date
|
||||
topics?: string[]
|
||||
lastModified: Date
|
||||
body?: string
|
||||
title?: string
|
||||
}
|
||||
import type { Draft } from '../components/Editor/store/context'
|
||||
|
||||
interface Collab {
|
||||
authors: string[] // slugs
|
||||
|
|
Loading…
Reference in New Issue
Block a user