typing-fixes

This commit is contained in:
tonyrewin 2022-10-09 11:33:28 +03:00
parent d0e98bb525
commit ce9057d637
21 changed files with 242 additions and 257 deletions

View File

@ -89,6 +89,7 @@
"markdown-it-mark": "^3.0.1",
"markdown-it-replace-link": "^1.1.0",
"nanostores": "^0.7.0",
"orderedmap": "^2.1.0",
"postcss": "^8.4.16",
"postcss-modules": "^5.0.0",
"prettier": "^2.7.1",

3
src/assets/handle.svg Normal file
View File

@ -0,0 +1,3 @@
<svg viewBox="0 0 10 10" height="14" width="14">
<path d="M3 2a1 1 0 110-2 1 1 0 010 2zm0 4a1 1 0 110-2 1 1 0 010 2zm0 4a1 1 0 110-2 1 1 0 010 2zm4-8a1 1 0 110-2 1 1 0 010 2zm0 4a1 1 0 110-2 1 1 0 010 2zm0 4a1 1 0 110-2 1 1 0 010 2z"/>
</svg>

After

Width:  |  Height:  |  Size: 245 B

View File

@ -20,7 +20,8 @@ export default () => {
}
return (
<ProseMirror
class={'editor'}
// eslint-disable-next-line solid/no-react-specific-props
className="editor"
style={style()}
editorView={store.editorView as EditorView}
text={store.text as ProseMirrorState}

View File

@ -11,7 +11,8 @@ export const Editor = () => {
const onChange = (text: EditorState) => ctrl.setState({ text, lastModified: new Date() })
return (
<ProseMirror
class="editor"
// eslint-disable-next-line solid/no-react-specific-props
className="editor"
style={store.markdown && `white-space: pre-wrap;`}
editorView={store.editorView}
text={store.text}

View File

@ -1,5 +1,6 @@
import { schema as markdownSchema } from 'prosemirror-markdown'
import { Schema } from 'prosemirror-model'
import type OrderedMap from 'orderedmap'
import { NodeSpec, Schema } from 'prosemirror-model'
import { baseKeymap } from 'prosemirror-commands'
import { sinkListItem, liftListItem } from 'prosemirror-schema-list'
import { history } from 'prosemirror-history'
@ -39,7 +40,10 @@ export default (plain = false): ProseMirrorExtension => ({
marks: plainSchema.spec.marks
}
: {
nodes: (markdownSchema.spec.nodes as any).update('blockquote', blockquoteSchema),
nodes: (markdownSchema.spec.nodes as OrderedMap<NodeSpec>).update(
'blockquote',
blockquoteSchema as unknown as NodeSpec
),
marks: markdownSchema.spec.marks
},
plugins: (prev, schema) => [

View File

@ -2,7 +2,13 @@ import { ySyncPlugin, yCursorPlugin, yUndoPlugin } from 'y-prosemirror'
import type { YOptions } from '../../store'
import type { ProseMirrorExtension } from '../helpers'
export const cursorBuilder = (user: any): HTMLElement => {
export interface EditingProps {
name: string
foreground: string
background: string
}
export const cursorBuilder = (user: EditingProps): HTMLElement => {
const cursor = document.createElement('span')
cursor.classList.add('ProseMirror-yjs-cursor')
cursor.setAttribute('style', `border-color: ${user.background}`)

View File

@ -1,11 +1,7 @@
import { Plugin, NodeSelection } from 'prosemirror-state'
import { DecorationSet, Decoration } from 'prosemirror-view'
import type { ProseMirrorExtension } from '../helpers'
const handleIcon = `
<svg viewBox="0 0 10 10" height="14" width="14">
<path d="M3 2a1 1 0 110-2 1 1 0 010 2zm0 4a1 1 0 110-2 1 1 0 010 2zm0 4a1 1 0 110-2 1 1 0 010 2zm4-8a1 1 0 110-2 1 1 0 010 2zm0 4a1 1 0 110-2 1 1 0 010 2zm0 4a1 1 0 110-2 1 1 0 010 2z"/>
</svg>`
import handleIcon from '../../../../assets/handle.svg'
const createDragHandle = () => {
const handle = document.createElement('span')

View File

@ -1,7 +1,8 @@
import { Plugin } from 'prosemirror-state'
import type { Node, Schema } from 'prosemirror-model'
import type { Node, NodeSpec, Schema } from 'prosemirror-model'
import type { EditorView } from 'prosemirror-view'
import type { ProseMirrorExtension } from '../helpers'
import type { NodeViewFn, ProseMirrorExtension } from '../helpers'
import type OrderedMap from 'orderedmap'
const REGEX = /^!\[([^[\]]*?)]\((.+?)\)\s+/
const MAX_MATCH = 500
@ -17,7 +18,7 @@ const isUrl = (str: string) => {
const isBlank = (text: string) => text === ' ' || text === '\u00A0'
const imageInput = (schema: Schema, path?: string) =>
const imageInput = (schema: Schema, _path?: string) =>
new Plugin({
props: {
handleTextInput(view, from, to, text) {
@ -68,7 +69,7 @@ const imageSchema = {
src: dom.getAttribute('src'),
title: dom.getAttribute('title'),
alt: dom.getAttribute('alt'),
path: (dom as any).dataset.path
path: (dom as NodeSpec).dataset.path
})
}
],
@ -101,12 +102,12 @@ class ImageView {
contentDOM: Element
container: HTMLElement
handle: HTMLElement
onResizeFn: any
onResizeEndFn: any
onResizeFn: (e: Event) => void
onResizeEndFn: (e: Event) => void
width: number
updating: number
constructor(node: Node, view: EditorView, getPos: () => number, schema: Schema, path: string) {
constructor(node: Node, view: EditorView, getPos: () => number, schema: Schema, _path: string) {
this.node = node
this.view = view
this.getPos = getPos
@ -161,12 +162,12 @@ class ImageView {
export default (path?: string): ProseMirrorExtension => ({
schema: (prev) => ({
...prev,
nodes: (prev.nodes as any).update('image', imageSchema)
nodes: (prev.nodes as OrderedMap<NodeSpec>).update('image', imageSchema as unknown as NodeSpec)
}),
plugins: (prev, schema) => [...prev, imageInput(schema, path)],
nodeViews: {
image: (node, view, getPos) => {
return new ImageView(node, view, getPos, view.state.schema, path)
}
} as any
} as unknown as { [key: string]: NodeViewFn }
})

View File

@ -31,7 +31,6 @@ const markdownLinks = (schema: Schema) =>
if (action?.pos) {
(state as any).pos = action.pos
}
return state
}
},

View File

@ -16,48 +16,34 @@ import {
import type { MenuItemSpec, MenuElement } from 'prosemirror-menu'
import { wrapInList } from 'prosemirror-schema-list'
import { NodeSelection } from 'prosemirror-state'
import { Command, EditorState, NodeSelection, Transaction } from 'prosemirror-state'
import { TextField, openPrompt } from './prompt'
import type { ProseMirrorExtension } from '../helpers'
import type { Schema } from 'prosemirror-model'
import type { Attrs, MarkType, NodeType, Schema } from 'prosemirror-model'
import type { EditorView } from 'prosemirror-view'
// Helpers to create specific types of items
function canInsert(state: { selection: { $from: any } }, nodeType: any) {
function canInsert(state: EditorState, nodeType: NodeType) {
const $from = state.selection.$from
for (let d = $from.depth; d >= 0; d--) {
const index = $from.index(d)
if ($from.node(d).canReplaceWith(index, index, nodeType)) return true
}
return false
}
function insertImageItem(nodeType: { createAndFill: (arg0: any) => any }) {
function insertImageItem(nodeType: NodeType) {
return new MenuItem({
icon: icons.image,
label: 'image',
enable(state: any) {
enable(state) {
return canInsert(state, nodeType)
},
run(
state: {
selection: { node?: any; from?: any; to?: any }
doc: { textBetween: (arg0: any, arg1: any, arg2: string) => any }
},
_: any,
view: {
dispatch: (arg0: any) => void
state: { tr: { replaceSelectionWith: (arg0: any) => any } }
focus: () => void
}
) {
const { from, to, node } = state.selection
run(state: EditorState, _, view: EditorView) {
const { from, to, node } = state.selection as NodeSelection
let attrs = null
if (state.selection instanceof NodeSelection && node.type === nodeType) {
attrs = node.attrs
}
@ -77,7 +63,7 @@ function insertImageItem(nodeType: { createAndFill: (arg0: any) => any }) {
})
},
// eslint-disable-next-line no-shadow
callback(attrs: any) {
callback(attrs: Attrs) {
view.dispatch(view.state.tr.replaceSelectionWith(nodeType.createAndFill(attrs)))
view.focus()
}
@ -86,53 +72,31 @@ function insertImageItem(nodeType: { createAndFill: (arg0: any) => any }) {
})
}
function cmdItem(
cmd: (arg0: any) => any,
options: { [x: string]: any; active?: (state: any) => any; enable?: any; title?: any; select?: any }
) {
const passedOptions = {
label: options.title,
run: cmd
} as { [key: string]: any }
function cmdItem(cmd: Command, options: MenuItemSpec) {
const passedOptions = { label: options.title, run: cmd } as MenuItemSpec
Object.keys(options).forEach((prop) => (passedOptions[prop] = options[prop]))
if ((!options.enable || options.enable === true) && !options.select) {
passedOptions[options.enable ? 'enable' : 'select'] = (state: any) => cmd(state)
}
// TODO: enable/disable items logix
passedOptions.select = (state) => cmd(state)
return new MenuItem(passedOptions as MenuItemSpec)
}
function markActive(
state: {
selection: { from: any; $from: any; to: any; empty: any }
storedMarks: any
doc: { rangeHasMark: (arg0: any, arg1: any, arg2: any) => any }
},
type: { isInSet: (arg0: any) => any }
) {
function markActive(state: EditorState, type: MarkType) {
const { from, $from, to, empty } = state.selection
if (empty) return type.isInSet(state.storedMarks || $from.marks())
return state.doc.rangeHasMark(from, to, type)
}
function markItem(markType: any, options: { [x: string]: any; title?: string; icon?: any }) {
function markItem(markType: MarkType, options: MenuItemSpec) {
const passedOptions = {
active(state: any) {
active(state) {
return markActive(state, markType)
},
enable: true
} as { [key: string]: any }
}
} as MenuItemSpec
Object.keys(options).forEach((prop: string) => (passedOptions[prop] = options[prop]))
return cmdItem(toggleMark(markType), passedOptions)
}
function linkItem(markType: any) {
function linkItem(markType: MarkType) {
return new MenuItem({
title: 'Add or remove link',
icon: {
@ -140,19 +104,13 @@ function linkItem(markType: any) {
height: 18,
path: 'M3.27177 14.7277C2.06258 13.5186 2.06258 11.5527 3.27177 10.3435L6.10029 7.51502L4.75675 6.17148L1.92823 9C-0.0234511 10.9517 -0.0234511 14.1196 1.92823 16.0713C3.87991 18.023 7.04785 18.023 8.99952 16.0713L11.828 13.2428L10.4845 11.8992L7.65598 14.7277C6.44679 15.9369 4.48097 15.9369 3.27177 14.7277ZM6.87756 12.536L12.5346 6.87895L11.1203 5.46469L5.4633 11.1217L6.87756 12.536ZM6.17055 4.75768L8.99907 1.92916C10.9507 -0.0225206 14.1187 -0.0225201 16.0704 1.92916C18.022 3.88084 18.022 7.04878 16.0704 9.00046L13.2418 11.829L11.8983 10.4854L14.7268 7.65691C15.936 6.44772 15.936 4.4819 14.7268 3.27271C13.5176 2.06351 11.5518 2.06351 10.3426 3.2727L7.51409 6.10122L6.17055 4.75768Z'
},
active(state: any) {
return markActive(state, markType)
},
enable(state: { selection: { empty: any } }) {
return !state.selection.empty
},
run(state: any, dispatch: any, view: { state: any; dispatch: any; focus: () => void }) {
active: (state) => Boolean(markActive(state, markType)),
enable: (state: EditorState) => !state.selection.empty,
run(state: EditorState, dispatch: (t: Transaction) => void, view: EditorView) {
if (markActive(state, markType)) {
toggleMark(markType)(state, dispatch)
return true
}
openPrompt({
fields: {
href: new TextField({
@ -160,7 +118,7 @@ function linkItem(markType: any) {
required: true
})
},
callback(attrs: any) {
callback(attrs: Attrs) {
toggleMark(markType, attrs)(view.state, view.dispatch)
view.focus()
}
@ -169,14 +127,8 @@ function linkItem(markType: any) {
})
}
function wrapListItem(
nodeType: any,
options: {
title?: string
icon?: { width: number; height: number; path: string } | { width: number; height: number; path: string }
attrs?: any
}
) {
function wrapListItem(nodeType: NodeType, options: MenuItemSpec & { attrs: Attrs }) {
options.run = (_) => true
return cmdItem(wrapInList(nodeType, options.attrs), options)
}
@ -255,7 +207,7 @@ type BuildSchema = {
*/
export function buildMenuItems(schema: Schema) {
const r: { [key: string]: MenuItem | MenuItem[] } = {}
let type: any
let type: NodeType | MarkType
if ((type = schema.marks.strong)) {
r.toggleStrong = markItem(type, {
@ -265,7 +217,7 @@ export function buildMenuItems(schema: Schema) {
height: 16,
path: 'M9.82857 7.76C10.9371 6.99429 11.7143 5.73714 11.7143 4.57143C11.7143 1.98857 9.71428 0 7.14286 0H0V16H8.04571C10.4343 16 12.2857 14.0571 12.2857 11.6686C12.2857 9.93143 11.3029 8.44571 9.82857 7.76ZM3.42799 2.85708H6.85656C7.80513 2.85708 8.57085 3.6228 8.57085 4.57137C8.57085 5.51994 7.80513 6.28565 6.85656 6.28565H3.42799V2.85708ZM3.42799 13.1429H7.42799C8.37656 13.1429 9.14228 12.3772 9.14228 11.4286C9.14228 10.4801 8.37656 9.71434 7.42799 9.71434H3.42799V13.1429Z'
}
})
} as MenuItemSpec)
}
if ((type = schema.marks.em)) {
@ -276,14 +228,14 @@ export function buildMenuItems(schema: Schema) {
height: 16,
path: 'M4.39216 0V3.42857H6.81882L3.06353 12.5714H0V16H8.78431V12.5714H6.35765L10.1129 3.42857H13.1765V0H4.39216Z'
}
})
} as MenuItemSpec)
}
if ((type = schema.marks.code)) {
r.toggleCode = markItem(type, {
title: 'Toggle code font',
icon: icons.code
})
} as MenuItemSpec)
}
if ((type = schema.marks.link)) r.toggleLink = linkItem(type)
@ -298,7 +250,7 @@ export function buildMenuItems(schema: Schema) {
height: 16,
path: 'M0.000114441 1.6C0.000114441 0.714665 0.71478 0 1.60011 0C2.48544 0 3.20011 0.714665 3.20011 1.6C3.20011 2.48533 2.48544 3.19999 1.60011 3.19999C0.71478 3.19999 0.000114441 2.48533 0.000114441 1.6ZM0 8.00013C0 7.1148 0.714665 6.40014 1.6 6.40014C2.48533 6.40014 3.19999 7.1148 3.19999 8.00013C3.19999 8.88547 2.48533 9.60013 1.6 9.60013C0.714665 9.60013 0 8.88547 0 8.00013ZM1.6 12.8C0.714665 12.8 0 13.5254 0 14.4C0 15.2747 0.725332 16 1.6 16C2.47466 16 3.19999 15.2747 3.19999 14.4C3.19999 13.5254 2.48533 12.8 1.6 12.8ZM19.7333 15.4662H4.79999V13.3329H19.7333V15.4662ZM4.79999 9.06677H19.7333V6.93344H4.79999V9.06677ZM4.79999 2.66664V0.533307H19.7333V2.66664H4.79999Z'
}
})
} as MenuItemSpec & { attrs: Attrs })
}
if ((type = schema.nodes.ordered_list)) {
@ -309,7 +261,7 @@ export function buildMenuItems(schema: Schema) {
height: 16,
path: 'M2.00002 4.00003H1.00001V1.00001H0V0H2.00002V4.00003ZM2.00002 13.5V13H0V12H3.00003V16H0V15H2.00002V14.5H1.00001V13.5H2.00002ZM0 6.99998H1.80002L0 9.1V10H3.00003V9H1.20001L3.00003 6.89998V5.99998H0V6.99998ZM4.9987 2.99967V0.999648H18.9988V2.99967H4.9987ZM4.9987 15.0001H18.9988V13.0001H4.9987V15.0001ZM18.9988 8.99987H4.9987V6.99986H18.9988V8.99987Z'
}
})
} as MenuItemSpec & { attrs: Attrs })
}
if ((type = schema.nodes.blockquote)) {
@ -360,10 +312,8 @@ export function buildMenuItems(schema: Schema) {
r.insertHorizontalRule = new MenuItem({
label: '---',
icon: icons.horizontal_rule,
enable(state: any) {
return canInsert(state, hr)
},
run(state: { tr: { replaceSelectionWith: (arg0: any) => any } }, dispatch: (arg0: any) => void) {
enable: (state) => canInsert(state, hr),
run(state: EditorState, dispatch: (tr: Transaction) => void) {
dispatch(state.tr.replaceSelectionWith(hr.create()))
}
})
@ -404,7 +354,7 @@ export default (): ProseMirrorExtension => ({
...prev,
menuBar({
floating: true,
content: buildMenuItems(schema).fullMenu as any[]
content: buildMenuItems(schema).fullMenu as MenuItem | MenuItem[]
})
]
})

View File

@ -64,7 +64,7 @@ const pasteMarkdown = (schema: Schema) => {
event.preventDefault()
const paste = parser.parse(text)
const slice = paste as any
const slice = paste as Node & { openStart: number; openEnd: number }
const fragment = shiftKey ? slice.content : transform(schema, slice.content)
const tr = view.state.tr.replaceSelection(new Slice(fragment, slice.openStart, slice.openEnd))

View File

@ -1,12 +1,11 @@
const prefix = 'ProseMirror-prompt'
// eslint-disable-next-line sonarjs/cognitive-complexity
export function openPrompt(options: any) {
export function openPrompt(options) {
const wrapper = document.body.appendChild(document.createElement('div'))
wrapper.className = prefix
const mouseOutside = (e: any) => {
if (!wrapper.contains(e.target)) close()
const mouseOutside = (e: MouseEvent) => {
if (!wrapper.contains(e.target as Node)) close()
}
setTimeout(() => window.addEventListener('mousedown', mouseOutside), 50)
const close = () => {
@ -14,7 +13,7 @@ export function openPrompt(options: any) {
if (wrapper.parentNode) wrapper.remove()
}
const domFields: any = []
const domFields = []
options.fields.forEach((name) => {
domFields.push(options.fields[name].render())
})
@ -33,7 +32,7 @@ export function openPrompt(options: any) {
if (options.title) {
form.appendChild(document.createElement('h5')).textContent = options.title
}
domFields.forEach((field: any) => {
domFields.forEach((field) => {
form.appendChild(document.createElement('div')).appendChild(field)
})
const buttons = form.appendChild(document.createElement('div'))
@ -74,11 +73,11 @@ export function openPrompt(options: any) {
}
})
const input: any = form.elements[0]
if (input) input.focus()
const inpel = form.elements[0] as HTMLInputElement
if (inpel) inpel.focus()
}
function getValues(fields: any, domFields: any) {
function getValues(fields, domFields) {
const result = Object.create(null)
let i = 0
fields.forEarch((name) => {
@ -95,7 +94,7 @@ function getValues(fields: any, domFields: any) {
return result
}
function reportInvalid(dom: any, message: any) {
function reportInvalid(dom: HTMLElement, message: string) {
const parent = dom.parentNode
const msg = parent.appendChild(document.createElement('div'))
msg.style.left = dom.offsetLeft + dom.offsetWidth + 2 + 'px'
@ -106,13 +105,24 @@ function reportInvalid(dom: any, message: any) {
setTimeout(() => parent.removeChild(msg), 1500)
}
interface FieldOptions {
options: { value: string; label: string }[]
required: boolean
label: string
value: string
validateType: (v) => boolean
validate: (v) => boolean
read: (v) => string
clean: (v) => boolean
}
export class Field {
options: any
constructor(options: any) {
options: FieldOptions
constructor(options) {
this.options = options
}
read(dom: any) {
read(dom) {
return dom.value
}
// :: (any) → ?string
@ -121,13 +131,12 @@ export class Field {
return typeof _value === typeof ''
}
validate(value: any) {
validate(value) {
if (!value && this.options.required) return 'Required field'
return this.validateType(value) || (this.options.validate && this.options.validate(value))
}
clean(value: any) {
clean(value) {
return this.options.clean ? this.options.clean(value) : value
}
}
@ -147,7 +156,7 @@ export class TextField extends Field {
export class SelectField extends Field {
render() {
const select = document.createElement('select')
this.options.options.forEach((o: { value: string; label: string }) => {
this.options.options.forEach((o) => {
const opt = select.appendChild(document.createElement('option'))
opt.value = o.value
opt.selected = o.value === this.options.value

View File

@ -1,21 +1,22 @@
import { renderGrouped } from 'prosemirror-menu'
import { Plugin } from 'prosemirror-state'
import { EditorState, Plugin } from 'prosemirror-state'
import type { EditorView } from 'prosemirror-view'
import type { ProseMirrorExtension } from '../helpers'
import { buildMenuItems } from './menu'
export class SelectionTooltip {
tooltip: any
tooltip: HTMLElement
constructor(view: any, schema: any) {
constructor(view: EditorView, schema) {
this.tooltip = document.createElement('div')
this.tooltip.className = 'tooltip'
view.dom.parentNode.appendChild(this.tooltip)
const { dom } = renderGrouped(view, (buildMenuItems(schema) as any).fullMenu)
const { dom } = renderGrouped(view, buildMenuItems(schema).fullMenu as any)
this.tooltip.appendChild(dom)
this.update(view, null)
}
update(view: any, lastState: any) {
update(view: EditorView, lastState: EditorState) {
const state = view.state
if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) {
return
@ -41,9 +42,9 @@ export class SelectionTooltip {
}
}
export function toolTip(schema: any) {
export function toolTip(schema) {
return new Plugin({
view(editorView: any) {
view(editorView: EditorView) {
return new SelectionTooltip(editorView, schema)
}
})

View File

@ -1,8 +1,9 @@
import { EditorState, Selection } from 'prosemirror-state'
import type { Node, Schema, ResolvedPos } from 'prosemirror-model'
import type { Node, Schema, ResolvedPos, NodeSpec } from 'prosemirror-model'
import { InputRule, inputRules } from 'prosemirror-inputrules'
import { keymap } from 'prosemirror-keymap'
import type { ProseMirrorExtension } from '../helpers'
import type OrderedMap from 'orderedmap'
export const tableInputRule = (schema: Schema) =>
new InputRule(
@ -174,7 +175,7 @@ const getTextSize = (n: Node) => {
export default (): ProseMirrorExtension => ({
schema: (prev) => ({
...prev,
nodes: (prev.nodes as any).append(tableSchema)
nodes: (prev.nodes as OrderedMap<NodeSpec>).append(tableSchema as NodeSpec)
}),
// eslint-disable-next-line sonarjs/cognitive-complexity
plugins: (prev, schema) => [

View File

@ -1,80 +1,73 @@
import { keymap } from 'prosemirror-keymap'
import type { ProseMirrorExtension } from './helpers'
import { Schema } from 'prosemirror-model'
import base from './extension/base'
import markdown from './extension/markdown'
import link from './extension/link'
// import scroll from './prosemirror/extension/scroll'
import todoList from './extension/todo-list'
import code from './extension/code'
import strikethrough from './extension/strikethrough'
import placeholder from './extension/placeholder'
// import menu from './extension/menu'
import image from './extension/image'
import dragHandle from './extension/drag-handle'
import pasteMarkdown from './extension/paste-markdown'
import table from './extension/table'
// import scroll from './prosemirror/extension/scroll'
import { keymap } from 'prosemirror-keymap'
import { Schema } from 'prosemirror-model'
import type { Command } from 'prosemirror-state'
import { t } from '../../../utils/intl'
import base from './extension/base'
import code from './extension/code'
import collab from './extension/collab'
import type { Config, YOptions } from '../store'
import dragHandle from './extension/drag-handle'
import image from './extension/image'
import link from './extension/link'
import markdown from './extension/markdown'
import pasteMarkdown from './extension/paste-markdown'
import placeholder from './extension/placeholder'
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 { ProseMirrorExtension } from './helpers'
interface Props {
interface ExtensionsProps {
data?: unknown
keymap?: any
keymap?: { [key: string]: Command }
config: Config
markdown: boolean
path?: string
y?: YOptions
schema?: Schema
collab?: boolean
}
const customKeymap = (props: Props): ProseMirrorExtension => ({
const customKeymap = (props: ExtensionsProps): ProseMirrorExtension => ({
plugins: (prev) => (props.keymap ? [...prev, keymap(props.keymap)] : prev)
})
/*
const codeMirrorKeymap = (props: Props) => {
const keys = []
for (const key in props.keymap) {
keys.push({key: key, run: props.keymap[key]})
}
return cmKeymap.of(keys)
export const createExtensions = (props: ExtensionsProps): ProseMirrorExtension[] => {
const eee = [
// scroll(props.config.typewriterMode),
placeholder(t('Just start typing...')),
customKeymap(props),
base(props.markdown),
selectionMenu()
]
if (props.markdown) {
eee.push(
markdown(),
todoList(),
dragHandle(),
code(),
strikethrough(),
link(),
table(),
image(props.path),
pasteMarkdown()
/*
codeBlock({
theme: codeTheme(props.config),
typewriterMode: props.config.typewriterMode,
fontSize: props.config.fontSize,
prettier: props.config.prettier,
extensions: () => [codeMirrorKeymap(props)],
}),
*/
)
}
if (props.collab) eee.push(collab(props.y))
return eee
}
*/
export const createExtensions = (props: Props): ProseMirrorExtension[] =>
props.markdown
? [
placeholder('Просто начните...'),
customKeymap(props),
base(props.markdown),
collab(props.y),
selectionMenu()
]
: [
selectionMenu(),
customKeymap(props),
base(props.markdown),
markdown(),
todoList(),
dragHandle(),
code(),
strikethrough(),
link(),
table(),
image(props.path),
pasteMarkdown(),
collab(props.y)
// scroll(props.config.typewriterMode),
/*
codeBlock({
theme: codeTheme(props.config),
typewriterMode: props.config.typewriterMode,
fontSize: props.config.fontSize,
prettier: props.config.prettier,
extensions: () => [codeMirrorKeymap(props)],
}),
*/
]
export const createEmptyText = () => ({
doc: {
@ -88,7 +81,7 @@ export const createEmptyText = () => ({
}
})
export const createSchema = (props: Props) => {
export const createSchema = (props: ExtensionsProps) => {
const extensions = createExtensions({
config: props.config,
markdown: props.markdown,

View File

@ -1,6 +1,6 @@
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 * as Y from 'yjs'
@ -23,7 +23,7 @@ const isState = (x) => typeof x.lastModified !== 'string' && Array.isArray(x.dra
const isDraft = (x): boolean => x && (x.text || x.path)
const mod = 'Ctrl'
export const createCtrl = (initial): [Store<State>, any] => {
export const createCtrl = (initial): [Store<State>, { [key: string]: any }] => {
const [store, setState] = createStore(initial)
const onNew = () => {
@ -64,7 +64,7 @@ export const createCtrl = (initial): [Store<State>, any] => {
[`Shift-${mod}-z`]: onRedo,
[`${mod}-y`]: onRedo,
[`${mod}-m`]: onToggleMarkdown
}
} as { [key: string]: Command }
const createTextFromDraft = async (d: Draft): Promise<Draft> => {
let draft = d
@ -83,7 +83,7 @@ export const createCtrl = (initial): [Store<State>, any] => {
return {
text: draft.text,
extensions,
updatedAt: draft.updatedAt ? new Date(draft.updatedAt) : undefined,
lastModified: draft.lastModified ? new Date(draft.lastModified) : undefined,
path: draft.path,
markdown: draft.markdown
}
@ -96,7 +96,7 @@ export const createCtrl = (initial): [Store<State>, any] => {
...drafts,
{
body: text,
updatedAt: prev.updatedAt as Date,
lastModified: prev.lastModified as Date,
path: prev.path,
markdown: prev.markdown
} as Draft
@ -121,7 +121,7 @@ export const createCtrl = (initial): [Store<State>, any] => {
next = {
text: createEmptyText(),
extensions,
updatedAt: new Date(),
lastModified: new Date(),
path: undefined,
markdown: state.markdown
}
@ -227,7 +227,7 @@ export const createCtrl = (initial): [Store<State>, any] => {
} else if (data.args.text) {
data = await doOpenDraft(data, {
text: { ...JSON.parse(data.args.text) },
updatedAt: new Date()
lastModified: new Date()
})
} else if (data.args.draft) {
const draft = await loadDraft(data.config, data.args.draft)
@ -258,7 +258,7 @@ export const createCtrl = (initial): [Store<State>, any] => {
const loadDraft = async (config: Config, path: string): Promise<Draft> => {
const draftstore = useStore(draftsatom)
const draft = createMemo(() => draftstore()[path])
const lastModified = draft().updatedAt
const lastModified = draft().lastModified
const draftContent = draft().body
const schema = createSchema({
config,
@ -280,7 +280,7 @@ export const createCtrl = (initial): [Store<State>, any] => {
...draft(),
body: doc,
text,
updatedAt: lastModified.toISOString(),
lastModified: lastModified.toISOString(),
path
}
}
@ -327,9 +327,9 @@ export const createCtrl = (initial): [Store<State>, any] => {
const item = index === -1 ? draft : state.drafts[index]
let drafts = state.drafts.filter((f) => f !== item)
if (!isEmpty(state.text as EditorState) && state.lastModified) {
drafts = addToDrafts(drafts, { updatedAt: new Date(), text: state.text } as Draft)
drafts = addToDrafts(drafts, { lastModified: new Date(), text: state.text } as Draft)
}
draft.updatedAt = item.updatedAt
draft.lastModified = item.lastModified
const next = await createTextFromDraft(draft)
return {
@ -343,7 +343,8 @@ export const createCtrl = (initial): [Store<State>, any] => {
const saveState = () =>
debounce(async (state: State) => {
const data: any = {
const data: State = {
loading: 'initialized',
lastModified: state.lastModified,
drafts: state.drafts,
config: state.config,
@ -357,7 +358,8 @@ export const createCtrl = (initial): [Store<State>, any] => {
if (isInitialized(state.text as EditorState)) {
if (state.path) {
const text = serialize(store.editorView.state)
// TODO: await remote.writeDraft(state.path, text)
// await remote.writeDraft(state.path, text)
draftsatom.setKey(state.path, text)
} else {
data.text = store.editorView.state.toJSON()
}
@ -418,7 +420,7 @@ export const createCtrl = (initial): [Store<State>, any] => {
if ((backup && !isEmpty(state.text as EditorState)) || state.path) {
let drafts = state.drafts
if (!state.error) {
drafts = addToDrafts(drafts, { updatedAt: new Date(), text: state.text } as Draft)
drafts = addToDrafts(drafts, { lastModified: new Date(), text: state.text } as Draft)
}
newst = {
@ -455,7 +457,7 @@ export const createCtrl = (initial): [Store<State>, 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')
@ -495,6 +497,7 @@ export const createCtrl = (initial): [Store<State>, any] => {
extensions,
markdown
})
return true
}
const updateConfig = (config: Partial<Config>) => {

View File

@ -5,7 +5,6 @@ import type { WebrtcProvider } from 'y-webrtc'
import type { ProseMirrorExtension, ProseMirrorState } from '../prosemirror/helpers'
import type { EditorView } from 'prosemirror-view'
import { createEmptyText } from '../prosemirror/setup'
import type { Shout } from '../../../graphql/types.gen'
export interface Args {
draft: string // path to draft
@ -70,7 +69,7 @@ export interface State {
export interface Draft {
extensions?: ProseMirrorExtension[]
updatedAt: Date
lastModified: Date
body?: string
text?: { doc: any; selection: { type: string; anchor: number; head: number } }
path?: string

View File

@ -2,48 +2,64 @@
@import './Sidebar';
.editor {
flex: 1;
padding-top: 1em;
margin: 0.5em;
padding: 1em;
min-width: 50%;
min-height: fit-content;
display: inline-block;
border: 1px dotted rgb(0 0 0 / 80%);
}
label {
display: block;
}
a {
color: rgb(0 100 200);
text-decoration: none;
}
input,
button,
select,
textarea {
font-family: inherit;
font-size: inherit;
-webkit-padding: 0.4em 0;
padding: 0.4em;
margin: 0 0 0.5em;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 2px;
}
a:hover {
text-decoration: underline;
}
input:disabled {
color: #ccc;
}
a:visited {
color: rgb(0 100 200 / 70%);
}
button {
color: #333;
background-color: #f4f4f4;
outline: none;
}
label {
display: block;
}
button:disabled {
color: #999;
}
input,
button,
select,
textarea {
font-family: inherit;
font-size: inherit;
padding: 0.4em;
margin: 0 0 0.5em;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 2px;
}
button:not(:disabled):active {
background-color: #ddd;
}
input:disabled {
color: #ccc;
}
button:focus {
border-color: #666;
}
button {
color: #333;
background-color: #f4f4f4;
outline: none;
}
button:disabled {
color: #999;
}
button:not(:disabled):active {
background-color: #ddd;
}
button:focus {
border-color: #666;
}
.ProseMirror {
@ -104,17 +120,17 @@
}
blockquote {
border-left: 2px solid;
@include font-size(1.6rem);
margin: 1.5em 0;
border-left: 2px solid;
padding-left: 1.6em;
}
}
.ProseMirror-menuitem {
display: flex;
font-size: small;
display: flex;
&:hover {
> * {
@ -175,7 +191,7 @@
content: '';
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid currentcolor;
border-top: 4px solid draftcurrentcolor;
opacity: 0.6;
position: absolute;
right: 4px;
@ -215,7 +231,7 @@
content: '';
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
border-left: 4px solid currentcolor;
border-left: 4px solid draftcurrentcolor;
opacity: 0.6;
position: absolute;
right: 4px;
@ -270,7 +286,7 @@
}
.ProseMirror-icon svg {
fill: currentcolor;
fill: draftcurrentcolor;
height: 1em;
}
@ -333,7 +349,7 @@ li.ProseMirror-selectednode::after {
.ProseMirror-prompt {
background: #fff;
box-shadow: 0 4px 10px rgb(0 0 0 / 25%);
box-shadow: 0 4px 10px rgba(0 0 0 / 25%);
font-size: 0.7em;
position: absolute;
}
@ -378,7 +394,7 @@ li.ProseMirror-selectednode::after {
.tooltip {
background: var(--background);
box-shadow: 0 4px 10px rgb(0 0 0 / 25%);
box-shadow: 0 4px 10px rgba(0 0 0 / 25%);
color: #000;
display: flex;
position: absolute;

View File

@ -92,10 +92,11 @@
}
.sidebar-container {
color: rgb(255 255 255 / 50%);
font-family: Muller;
@include font-size(1.6rem);
color: rgb(255 255 255 / 50%);
font-family: Muller;
display: inline-flex;
overflow: hidden;
position: relative;
top: 0;

View File

@ -1,4 +1,4 @@
import { persistentAtom } from '@nanostores/persistent'
import { persistentAtom, persistentMap } from '@nanostores/persistent'
import type { Reaction } from '../graphql/types.gen'
import { atom } from 'nanostores'
import { createSignal } from 'solid-js'
@ -18,7 +18,7 @@ interface Collab {
title?: string
}
export const drafts = persistentAtom<{ [key: string]: Draft }>(
export const drafts = persistentMap<{ [key: string]: Draft }>(
'drafts',
{},
{

View File

@ -8553,7 +8553,7 @@ ora@^6.1.0:
strip-ansi "^7.0.1"
wcwidth "^1.0.1"
orderedmap@^2.0.0:
orderedmap@^2.0.0, orderedmap@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-2.1.0.tgz#819457082fa3a06abd316d83a281a1ca467437cd"
integrity sha512-/pIFexOm6S70EPdznemIz3BQZoJ4VTFrhqzu0ACBqBgeLsLxq8e6Jim63ImIfwW/zAD1AlXpRMlOv3aghmo4dA==