editor refactoring
This commit is contained in:
parent
3eeed11e76
commit
43fbe729f9
|
@ -83,8 +83,6 @@
|
||||||
"idb": "^7.0.1",
|
"idb": "^7.0.1",
|
||||||
"jest": "^29.0.1",
|
"jest": "^29.0.1",
|
||||||
"lint-staged": "^13.0.3",
|
"lint-staged": "^13.0.3",
|
||||||
"loglevel": "^1.8.0",
|
|
||||||
"loglevel-plugin-prefix": "^0.8.4",
|
|
||||||
"markdown-it": "^13.0.1",
|
"markdown-it": "^13.0.1",
|
||||||
"markdown-it-container": "^3.0.0",
|
"markdown-it-container": "^3.0.0",
|
||||||
"markdown-it-implicit-figures": "^0.10.0",
|
"markdown-it-implicit-figures": "^0.10.0",
|
||||||
|
|
|
@ -21,6 +21,7 @@ export const Donate = () => {
|
||||||
const [amount, setAmount] = createSignal(0)
|
const [amount, setAmount] = createSignal(0)
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const {
|
const {
|
||||||
cp: { CloudPayments }
|
cp: { CloudPayments }
|
||||||
} = window as any // Checkout(cpOptions)
|
} = window as any // Checkout(cpOptions)
|
||||||
|
@ -60,6 +61,7 @@ export const Donate = () => {
|
||||||
amountSwitchElement?.querySelector('input[type=radio]:checked')
|
amountSwitchElement?.querySelector('input[type=radio]:checked')
|
||||||
setAmount(Number.parseInt(customAmountElement?.value || choice?.value || '0'))
|
setAmount(Number.parseInt(customAmountElement?.value || choice?.value || '0'))
|
||||||
console.log('[donate] input amount ' + amount)
|
console.log('[donate] input amount ' + amount)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
;(widget() as any).charge(
|
;(widget() as any).charge(
|
||||||
{
|
{
|
||||||
// options
|
// options
|
||||||
|
@ -81,13 +83,13 @@ export const Donate = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(opts: any) => {
|
(opts) => {
|
||||||
// success
|
// success
|
||||||
// действие при успешной оплате
|
// действие при успешной оплате
|
||||||
console.debug('[donate] options', opts)
|
console.debug('[donate] options', opts)
|
||||||
showModal('thank')
|
showModal('thank')
|
||||||
},
|
},
|
||||||
function (reason: string, options: any) {
|
function (reason: string, options) {
|
||||||
// fail
|
// fail
|
||||||
// действие при неуспешной оплате
|
// действие при неуспешной оплате
|
||||||
console.debug('[donate] options', options)
|
console.debug('[donate] options', options)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import './ArticlesList.scss'
|
import '../styles/ArticlesList.scss'
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
return (
|
return (
|
|
@ -1,5 +1,5 @@
|
||||||
import { Switch, Match, createMemo } from 'solid-js'
|
import { Switch, Match, createMemo } from 'solid-js'
|
||||||
import { ErrorObject, useState } from './prosemirror/context'
|
import { ErrorObject, useState } from '../store/context'
|
||||||
|
|
||||||
const InvalidState = (props: { title: string }) => {
|
const InvalidState = (props: { title: string }) => {
|
||||||
const [store, ctrl] = useState()
|
const [store, ctrl] = useState()
|
|
@ -1,12 +1,13 @@
|
||||||
import type { Config } from './prosemirror/context'
|
import type { JSX } from 'solid-js/jsx-runtime'
|
||||||
import './Layout.scss'
|
import type { Config } from '../store/context'
|
||||||
|
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?: (e: MouseEvent) => void
|
||||||
onMouseEnter?: (e: any) => void
|
onMouseEnter?: (e: MouseEvent) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Layout = (props: Styled) => {
|
export const Layout = (props: Styled) => {
|
|
@ -1,10 +1,10 @@
|
||||||
import { Show, createEffect, createSignal, onCleanup } from 'solid-js'
|
import { Show, createEffect, createSignal, onCleanup, For } 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 { Draft, useState } from './prosemirror/context'
|
import { Draft, useState } from '../store/context'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import type { Styled } from './Layout'
|
import type { Styled } from './Layout'
|
||||||
import { t } from '../../utils/intl'
|
import { t } from '../../../utils/intl'
|
||||||
|
|
||||||
// import type { EditorState } from 'prosemirror-state'
|
// import type { EditorState } from 'prosemirror-state'
|
||||||
// import { serialize } from './prosemirror/markdown'
|
// import { serialize } from './prosemirror/markdown'
|
||||||
|
@ -15,7 +15,7 @@ import { t } from '../../utils/intl'
|
||||||
// const copyAllAsMarkdown = async (state: EditorState): Promise<void> =>
|
// const copyAllAsMarkdown = async (state: EditorState): Promise<void> =>
|
||||||
// navigator.clipboard.writeText(serialize(state)) && !isServer
|
// navigator.clipboard.writeText(serialize(state)) && !isServer
|
||||||
|
|
||||||
const Off = (props: any) => <div class="sidebar-off">{props.children}</div>
|
const Off = (props) => <div class="sidebar-off">{props.children}</div>
|
||||||
const Label = (props: Styled) => <h3 class="sidebar-label">{props.children}</h3>
|
const Label = (props: Styled) => <h3 class="sidebar-label">{props.children}</h3>
|
||||||
const Link = (
|
const Link = (
|
||||||
props: Styled & { withMargin?: boolean; disabled?: boolean; title?: string; className?: string }
|
props: Styled & { withMargin?: boolean; disabled?: boolean; title?: string; className?: string }
|
||||||
|
@ -85,13 +85,19 @@ export const Sidebar = () => {
|
||||||
ctrl.updateConfig({ theme: document.body.className })
|
ctrl.updateConfig({ theme: document.body.className })
|
||||||
}
|
}
|
||||||
|
|
||||||
// const collabText = () => (store.collab?.started ? 'Stop' : store.collab?.error ? 'Restart 🚨' : 'Start')
|
const collabText = () => {
|
||||||
|
if (store.collab?.started) {
|
||||||
|
return 'Stop'
|
||||||
|
} else {
|
||||||
|
return store.collab?.error ? 'Restart 🚨' : 'Start'
|
||||||
|
}
|
||||||
|
}
|
||||||
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 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 onCopyAllAsMd = () => copyAllAsMarkdown(editorView().state).then(() => setLastAction('copy-md'))
|
// const onCopyAllAsMd = () => copyAllAsMarkdown(editorView().state).then(() => setLastAction('copy-md'))
|
||||||
// const onToggleAlwaysOnTop = () => ctrl.updateConfig({ alwaysOnTop: !store.config.alwaysOnTop })
|
// const onToggleAlwaysOnTop = () => ctrl.updateConfig({ alwaysOnTop: !store.config.alwaysOnTop })
|
||||||
// const onNew = () => ctrl.newDraft()
|
// const onNew = () => ctrl.newDraft()
|
||||||
|
@ -110,11 +116,11 @@ export const Sidebar = () => {
|
||||||
// if (path) ctrl.updatePath(path)
|
// if (path) ctrl.updatePath(path)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// const onCollab = () => {
|
const onCollab = () => {
|
||||||
// const state = unwrap(store)
|
const state = unwrap(store)
|
||||||
//
|
|
||||||
// store.collab?.started ? ctrl.stopCollab(state) : console.log(state)
|
store.collab?.started ? ctrl.stopCollab(state) : console.log(state)
|
||||||
// }
|
}
|
||||||
//
|
//
|
||||||
// const onOpenInApp = () => {
|
// const onOpenInApp = () => {
|
||||||
// // if (isTauri) return
|
// // if (isTauri) return
|
||||||
|
@ -142,11 +148,11 @@ export const Sidebar = () => {
|
||||||
// })
|
// })
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// const Keys = (props: { keys: string[] }) => (
|
const Keys = (props: { keys: string[] }) => (
|
||||||
// <span>
|
<span>
|
||||||
// <For each={props.keys}>{(k: string) => <i>{k}</i>}</For>
|
<For each={props.keys}>{(k: string) => <i>{k}</i>}</For>
|
||||||
// </span>
|
</span>
|
||||||
// )
|
)
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
setLastAction()
|
setLastAction()
|
||||||
|
@ -165,7 +171,7 @@ export const Sidebar = () => {
|
||||||
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}>
|
||||||
Советы и предложения
|
Советы и предложения
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<Off onClick={() => editorView().focus()} data-tauri-drag-region="true">
|
<Off onClick={() => editorView().focus()} data-tauri-drag-region="true">
|
|
@ -1,7 +1,7 @@
|
||||||
import './Editor.scss'
|
import './styles/Editor.scss'
|
||||||
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 './prosemirror/context'
|
import { useState } from './store/context'
|
||||||
import { ProseMirror } from './prosemirror'
|
import { ProseMirror } from './prosemirror'
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { history } from 'prosemirror-history'
|
||||||
import { dropCursor } from 'prosemirror-dropcursor'
|
import { dropCursor } from 'prosemirror-dropcursor'
|
||||||
import { buildKeymap } from 'prosemirror-example-setup'
|
import { buildKeymap } from 'prosemirror-example-setup'
|
||||||
import { keymap } from 'prosemirror-keymap'
|
import { keymap } from 'prosemirror-keymap'
|
||||||
import type { ProseMirrorExtension } from '../state'
|
import type { ProseMirrorExtension } from '../../store/state'
|
||||||
|
|
||||||
const plainSchema = new Schema({
|
const plainSchema = new Schema({
|
||||||
nodes: {
|
nodes: {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import type { EditorState, Transaction } from 'prosemirror-state'
|
||||||
import type { EditorView } from 'prosemirror-view'
|
import type { EditorView } from 'prosemirror-view'
|
||||||
import { keymap } from 'prosemirror-keymap'
|
import { keymap } from 'prosemirror-keymap'
|
||||||
import { markInputRule } from './mark-input-rule'
|
import { markInputRule } from './mark-input-rule'
|
||||||
import type { ProseMirrorExtension } from '../state'
|
import type { ProseMirrorExtension } from '../../store/state'
|
||||||
|
|
||||||
const blank = '\u00A0'
|
const blank = '\u00A0'
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { ySyncPlugin, yCursorPlugin, yUndoPlugin } from 'y-prosemirror'
|
import { ySyncPlugin, yCursorPlugin, yUndoPlugin } from 'y-prosemirror'
|
||||||
import type { ProseMirrorExtension } from '../state'
|
import type { ProseMirrorExtension } from '../../store/state'
|
||||||
import type { PeerData } from '../context'
|
import type { PeerData } from '../../store/context'
|
||||||
|
|
||||||
export const cursorBuilder = (user: {
|
export const cursorBuilder = (user: {
|
||||||
name: string
|
name: string
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Plugin, NodeSelection } from 'prosemirror-state'
|
import { Plugin, NodeSelection } from 'prosemirror-state'
|
||||||
import { DecorationSet, Decoration } from 'prosemirror-view'
|
import { DecorationSet, Decoration } from 'prosemirror-view'
|
||||||
import type { ProseMirrorExtension } from '../state'
|
import type { ProseMirrorExtension } from '../../store/state'
|
||||||
|
|
||||||
const handleIcon = `
|
const handleIcon = `
|
||||||
<svg viewBox="0 0 10 10" height="14" width="14">
|
<svg viewBox="0 0 10 10" height="14" width="14">
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
import { Plugin } from 'prosemirror-state'
|
import { Plugin } from 'prosemirror-state'
|
||||||
import type { Node, Schema } from 'prosemirror-model'
|
import type { Node, Schema } from 'prosemirror-model'
|
||||||
import type { EditorView } from 'prosemirror-view'
|
import type { EditorView } from 'prosemirror-view'
|
||||||
// import { convertFileSrc } from '@tauri-apps/api/tauri'
|
import type { ProseMirrorExtension } from '../../store/state'
|
||||||
// import { resolvePath, dirname } from '../../remote'
|
|
||||||
// import { isTauri } from '../../env'
|
|
||||||
import type { ProseMirrorExtension } from '../state'
|
|
||||||
|
|
||||||
const REGEX = /^!\[([^[\]]*)]\((.+?)\)\s+/
|
const REGEX = /^!\[([^[\]]*)]\((.+?)\)\s+/
|
||||||
const MAX_MATCH = 500
|
const MAX_MATCH = 500
|
||||||
|
@ -139,8 +136,8 @@ class ImageView {
|
||||||
contentDOM: Element
|
contentDOM: Element
|
||||||
container: HTMLElement
|
container: HTMLElement
|
||||||
handle: HTMLElement
|
handle: HTMLElement
|
||||||
onResizeFn: any
|
onResizeFn: (e: Event) => void
|
||||||
onResizeEndFn: any
|
onResizeEndFn: (e: Event) => void
|
||||||
width: number
|
width: number
|
||||||
updating: number
|
updating: number
|
||||||
|
|
||||||
|
|
|
@ -1,33 +1,24 @@
|
||||||
import { Plugin, PluginKey, TextSelection, Transaction } from 'prosemirror-state'
|
import { Plugin, PluginKey, TextSelection, Transaction } from 'prosemirror-state'
|
||||||
import type { EditorView } from 'prosemirror-view'
|
import type { EditorView } from 'prosemirror-view'
|
||||||
import type { Mark, Node, ResolvedPos, Schema } from 'prosemirror-model'
|
import type { Mark, Node, ResolvedPos, Schema } from 'prosemirror-model'
|
||||||
import type { ProseMirrorExtension } from '../state'
|
import type { ProseMirrorExtension } from '../../store/state'
|
||||||
|
|
||||||
const REGEX = /(^|\s)\[(.+)]\(([^ ]+)(?: "(.+)")?\)/
|
const REGEX = /(^|\s)\[(.+)]\(([^ ]+)(?: "(.+)")?\)/
|
||||||
|
|
||||||
const findMarkPosition = (mark: Mark, doc: Node, from: number, to: number) => {
|
const findMarkPosition = (mark: Mark, doc: Node, from: number, to: number) => {
|
||||||
let markPos = { from: -1, to: -1 }
|
let markPos = { from: -1, to: -1 }
|
||||||
|
|
||||||
doc.nodesBetween(from, to, (node, pos) => {
|
doc.nodesBetween(from, to, (node, pos) => {
|
||||||
if (markPos.from > -1) return false
|
if (markPos.from > -1) return false
|
||||||
|
|
||||||
if (markPos.from === -1 && mark.isInSet(node.marks)) {
|
if (markPos.from === -1 && mark.isInSet(node.marks)) {
|
||||||
markPos = { from: pos, to: pos + Math.max(node.textContent.length, 1) }
|
markPos = { from: pos, to: pos + Math.max(node.textContent.length, 1) }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return markPos
|
return markPos
|
||||||
}
|
}
|
||||||
|
|
||||||
const pluginKey = new PluginKey('markdown-links')
|
const pluginKey = new PluginKey('markdown-links')
|
||||||
|
|
||||||
const resolvePos = (view: EditorView, pos: number) => {
|
const resolvePos = (view: EditorView, pos: number) => view.state?.doc?.resolve(pos)
|
||||||
try {
|
|
||||||
return view.state.doc.resolve(pos)
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME
|
// FIXME
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
|
@ -38,50 +29,35 @@ const toLink = (view: EditorView, tr: Transaction) => {
|
||||||
|
|
||||||
if (lastPos !== undefined) {
|
if (lastPos !== undefined) {
|
||||||
const $from = resolvePos(view, lastPos)
|
const $from = resolvePos(view, lastPos)
|
||||||
|
|
||||||
if (!$from || $from.depth === 0 || $from.parent.type.spec.code) {
|
if (!$from || $from.depth === 0 || $from.parent.type.spec.code) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const lineFrom = $from.before()
|
const lineFrom = $from.before()
|
||||||
const lineTo = $from.after()
|
const lineTo = $from.after()
|
||||||
|
|
||||||
const line = view.state.doc.textBetween(lineFrom, lineTo, '\0', '\0')
|
const line = view.state.doc.textBetween(lineFrom, lineTo, '\0', '\0')
|
||||||
const match = REGEX.exec(line)
|
const match = REGEX.exec(line)
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
const [full, , text, href] = match
|
const [full, , text, href] = match
|
||||||
const spaceLeft = full.indexOf(text) - 1
|
const spaceLeft = full.indexOf(text) - 1
|
||||||
const spaceRight = full.length - text.length - href.length - spaceLeft - 4
|
const spaceRight = full.length - text.length - href.length - spaceLeft - 4
|
||||||
const start = match.index + $from.start() + spaceLeft
|
const start = match.index + $from.start() + spaceLeft
|
||||||
const end = start + full.length - spaceLeft - spaceRight
|
const end = start + full.length - spaceLeft - spaceRight
|
||||||
|
|
||||||
if (sel.$from.pos >= start && sel.$from.pos <= end) {
|
if (sel.$from.pos >= start && sel.$from.pos <= end) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not convert md links if content has marks
|
// Do not convert md links if content has marks
|
||||||
const $startPos = resolvePos(view, start)
|
const $startPos = resolvePos(view, start)
|
||||||
|
|
||||||
if (($startPos as ResolvedPos).marks().length > 0) {
|
if (($startPos as ResolvedPos).marks().length > 0) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const textStart = start + 1
|
const textStart = start + 1
|
||||||
const textEnd = textStart + text.length
|
const textEnd = textStart + text.length
|
||||||
|
|
||||||
if (textEnd < end) tr.delete(textEnd, end)
|
if (textEnd < end) tr.delete(textEnd, end)
|
||||||
|
|
||||||
if (textStart > start) tr.delete(start, textStart)
|
if (textStart > start) tr.delete(start, textStart)
|
||||||
|
|
||||||
const to = start + text.length
|
const to = start + text.length
|
||||||
|
|
||||||
tr.addMark(start, to, state.schema.marks.link.create({ href }))
|
tr.addMark(start, to, state.schema.marks.link.create({ href }))
|
||||||
|
|
||||||
const sub = end - textEnd + textStart - start
|
const sub = end - textEnd + textStart - start
|
||||||
|
|
||||||
tr.setMeta(pluginKey, { pos: sel.$head.pos - sub })
|
tr.setMeta(pluginKey, { pos: sel.$head.pos - sub })
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,11 +68,7 @@ const toLink = (view: EditorView, tr: Transaction) => {
|
||||||
const toMarkdown = (view: EditorView, tr: Transaction) => {
|
const toMarkdown = (view: EditorView, tr: Transaction) => {
|
||||||
const { schema } = pluginKey.getState(view.state)
|
const { schema } = pluginKey.getState(view.state)
|
||||||
const sel = view.state.selection
|
const sel = view.state.selection
|
||||||
|
if (sel.$head.depth === 0 || sel.$head.parent.type.spec.code) return false
|
||||||
if (sel.$head.depth === 0 || sel.$head.parent.type.spec.code) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const mark = schema.marks.link.isInSet(sel.$head.marks())
|
const mark = schema.marks.link.isInSet(sel.$head.marks())
|
||||||
const textFrom = sel.$head.pos - sel.$head.textOffset
|
const textFrom = sel.$head.pos - sel.$head.textOffset
|
||||||
const textTo = sel.$head.after()
|
const textTo = sel.$head.after()
|
||||||
|
@ -105,11 +77,9 @@ const toMarkdown = (view: EditorView, tr: Transaction) => {
|
||||||
const { href } = mark.attrs
|
const { href } = mark.attrs
|
||||||
const range = findMarkPosition(mark, view.state.doc, textFrom, textTo)
|
const range = findMarkPosition(mark, view.state.doc, textFrom, textTo)
|
||||||
const text = view.state.doc.textBetween(range.from, range.to, '\0', '\0')
|
const text = view.state.doc.textBetween(range.from, range.to, '\0', '\0')
|
||||||
|
|
||||||
tr.replaceRangeWith(range.from, range.to, view.state.schema.text(`[${text}](${href})`))
|
tr.replaceRangeWith(range.from, range.to, view.state.schema.text(`[${text}](${href})`))
|
||||||
tr.setSelection(new TextSelection(tr.doc.resolve(sel.$head.pos + 1)))
|
tr.setSelection(new TextSelection(tr.doc.resolve(sel.$head.pos + 1)))
|
||||||
tr.setMeta(pluginKey, { pos: sel.$head.pos })
|
tr.setMeta(pluginKey, { pos: sel.$head.pos })
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,27 +88,19 @@ const toMarkdown = (view: EditorView, tr: Transaction) => {
|
||||||
|
|
||||||
const handleMove = (view: EditorView) => {
|
const handleMove = (view: EditorView) => {
|
||||||
const sel = view.state.selection
|
const sel = view.state.selection
|
||||||
|
|
||||||
if (!sel.empty || !sel.$head) return false
|
if (!sel.empty || !sel.$head) return false
|
||||||
|
|
||||||
const pos = sel.$head.pos
|
const pos = sel.$head.pos
|
||||||
const tr = view.state.tr
|
const tr = view.state.tr
|
||||||
|
|
||||||
if (toLink(view, tr)) {
|
if (toLink(view, tr)) {
|
||||||
view.dispatch(tr)
|
view.dispatch(tr)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toMarkdown(view, tr)) {
|
if (toMarkdown(view, tr)) {
|
||||||
view.dispatch(tr)
|
view.dispatch(tr)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.setMeta(pluginKey, { pos })
|
tr.setMeta(pluginKey, { pos })
|
||||||
view.dispatch(tr)
|
view.dispatch(tr)
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,14 +113,12 @@ const markdownLinks = (schema: Schema) =>
|
||||||
},
|
},
|
||||||
apply(tr, state) {
|
apply(tr, state) {
|
||||||
const action = tr.getMeta(this)
|
const action = tr.getMeta(this)
|
||||||
|
|
||||||
if (action?.pos) {
|
if (action?.pos) {
|
||||||
// FIXME
|
// FIXME
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
state.pos = action.pos
|
state.pos = action.pos
|
||||||
}
|
}
|
||||||
|
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -171,7 +131,6 @@ const markdownLinks = (schema: Schema) =>
|
||||||
if (handleMove(view)) {
|
if (handleMove(view)) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,31 +8,19 @@ export const markInputRule = (regexp: RegExp, nodeType: MarkType, getAttrs?) =>
|
||||||
const attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs
|
const attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs
|
||||||
const tr = state.tr
|
const tr = state.tr
|
||||||
let end = endArg
|
let end = endArg
|
||||||
|
|
||||||
if (match[1]) {
|
if (match[1]) {
|
||||||
const textStart = start + match[0].indexOf(match[1])
|
const textStart = start + match[0].indexOf(match[1])
|
||||||
const textEnd = textStart + match[1].length
|
const textEnd = textStart + match[1].length
|
||||||
let hasMarks = false
|
let hasMarks = false
|
||||||
|
|
||||||
state.doc.nodesBetween(textStart, textEnd, (node) => {
|
state.doc.nodesBetween(textStart, textEnd, (node) => {
|
||||||
if (node.marks.length > 0) {
|
hasMarks = node.marks.length > 0
|
||||||
hasMarks = true
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
if (hasMarks) return
|
||||||
if (hasMarks) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (textEnd < end) tr.delete(textEnd, end)
|
if (textEnd < end) tr.delete(textEnd, end)
|
||||||
|
|
||||||
if (textStart > start) tr.delete(start, textStart)
|
if (textStart > start) tr.delete(start, textStart)
|
||||||
|
|
||||||
end = start + match[1].length
|
end = start + match[1].length
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.addMark(start, end, nodeType.create(attrs))
|
tr.addMark(start, end, nodeType.create(attrs))
|
||||||
tr.removeStoredMark(nodeType)
|
tr.removeStoredMark(nodeType)
|
||||||
|
|
||||||
return tr
|
return tr
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
ellipsis
|
ellipsis
|
||||||
} from 'prosemirror-inputrules'
|
} from 'prosemirror-inputrules'
|
||||||
import type { NodeType, Schema } from 'prosemirror-model'
|
import type { NodeType, Schema } from 'prosemirror-model'
|
||||||
import type { ProseMirrorExtension } from '../state'
|
import type { ProseMirrorExtension } from '../../store/state'
|
||||||
|
|
||||||
const blockQuoteRule = (nodeType: NodeType) => wrappingInputRule(/^\s*>\s$/, nodeType)
|
const blockQuoteRule = (nodeType: NodeType) => wrappingInputRule(/^\s*>\s$/, nodeType)
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { wrapInList } from 'prosemirror-schema-list'
|
||||||
import { NodeSelection } from 'prosemirror-state'
|
import { NodeSelection } from 'prosemirror-state'
|
||||||
|
|
||||||
import { TextField, openPrompt } from './prompt'
|
import { TextField, openPrompt } from './prompt'
|
||||||
import type { ProseMirrorExtension } from '../state'
|
import type { ProseMirrorExtension } from '../../store/state'
|
||||||
import type { Schema } from 'prosemirror-model'
|
import type { Schema } from 'prosemirror-model'
|
||||||
|
|
||||||
// Helpers to create specific types of items
|
// Helpers to create specific types of items
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Plugin } from 'prosemirror-state'
|
import { Plugin } from 'prosemirror-state'
|
||||||
// import { Fragment, Node, Schema } from 'prosemirror-model'
|
// import { Fragment, Node, Schema } from 'prosemirror-model'
|
||||||
import type { Schema } from 'prosemirror-model'
|
import type { Schema } from 'prosemirror-model'
|
||||||
import type { ProseMirrorExtension } from '../state'
|
import type { ProseMirrorExtension } from '../../store/state'
|
||||||
// import { createMarkdownParser } from '../markdown'
|
// import { createMarkdownParser } from '../markdown'
|
||||||
|
|
||||||
// const URL_REGEX = /(ftp|http|https):\/\/(\w+(?::\w*)?@)?(\S+)(:\d+)?(\/|\/([\w!#%&+./:=?@-]))?/g
|
// const URL_REGEX = /(ftp|http|https):\/\/(\w+(?::\w*)?@)?(\S+)(:\d+)?(\/|\/([\w!#%&+./:=?@-]))?/g
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Plugin } from 'prosemirror-state'
|
import { Plugin } from 'prosemirror-state'
|
||||||
import { DecorationSet, Decoration } from 'prosemirror-view'
|
import { DecorationSet, Decoration } from 'prosemirror-view'
|
||||||
import { ProseMirrorExtension, isEmpty } from '../state'
|
import { ProseMirrorExtension, isEmpty } from '../../store/state'
|
||||||
|
|
||||||
const placeholder = (text: string) =>
|
const placeholder = (text: string) =>
|
||||||
new Plugin({
|
new Plugin({
|
||||||
|
|
|
@ -2,51 +2,42 @@ const prefix = 'ProseMirror-prompt'
|
||||||
|
|
||||||
// FIXME !!!
|
// FIXME !!!
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
export function openPrompt(options: any) {
|
export function openPrompt(options) {
|
||||||
|
const domFields = []
|
||||||
|
const submitButton = document.createElement('button')
|
||||||
|
const cancelButton = document.createElement('button')
|
||||||
const wrapper = document.body.appendChild(document.createElement('div'))
|
const wrapper = document.body.appendChild(document.createElement('div'))
|
||||||
|
const form = wrapper.appendChild(document.createElement('form'))
|
||||||
|
const buttons = form.appendChild(document.createElement('div'))
|
||||||
|
const box = wrapper.getBoundingClientRect()
|
||||||
wrapper.className = prefix
|
wrapper.className = prefix
|
||||||
|
const mouseOutside = (e: MouseEvent) => {
|
||||||
const mouseOutside = (e: any) => {
|
if (!wrapper.contains(e.target as Node)) close()
|
||||||
if (!wrapper.contains(e.target)) close()
|
|
||||||
}
|
}
|
||||||
setTimeout(() => window.addEventListener('mousedown', mouseOutside), 50)
|
setTimeout(() => window.addEventListener('mousedown', mouseOutside), 50) // FIXME
|
||||||
const close = () => {
|
const close = () => {
|
||||||
window.removeEventListener('mousedown', mouseOutside)
|
window.removeEventListener('mousedown', mouseOutside)
|
||||||
if (wrapper.parentNode) wrapper.remove()
|
if (wrapper.parentNode) wrapper.remove()
|
||||||
}
|
}
|
||||||
|
options.fields.forEach((name) => domFields.push(options.fields[name].render()))
|
||||||
const domFields: any = []
|
|
||||||
options.fields.forEach((name) => {
|
|
||||||
domFields.push(options.fields[name].render())
|
|
||||||
})
|
|
||||||
|
|
||||||
const submitButton = document.createElement('button')
|
|
||||||
submitButton.type = 'submit'
|
submitButton.type = 'submit'
|
||||||
submitButton.className = prefix + '-submit'
|
submitButton.className = prefix + '-submit'
|
||||||
submitButton.textContent = 'OK'
|
submitButton.textContent = 'OK'
|
||||||
const cancelButton = document.createElement('button')
|
|
||||||
cancelButton.type = 'button'
|
cancelButton.type = 'button'
|
||||||
cancelButton.className = prefix + '-cancel'
|
cancelButton.className = prefix + '-cancel'
|
||||||
cancelButton.textContent = 'Cancel'
|
cancelButton.textContent = 'Cancel'
|
||||||
cancelButton.addEventListener('click', close)
|
cancelButton.addEventListener('click', close)
|
||||||
|
|
||||||
const form = wrapper.appendChild(document.createElement('form'))
|
|
||||||
if (options.title) {
|
if (options.title) {
|
||||||
form.appendChild(document.createElement('h5')).textContent = options.title
|
const headel = form.appendChild(document.createElement('h5'))
|
||||||
|
headel.textContent = options.title
|
||||||
}
|
}
|
||||||
domFields.forEach((field: any) => {
|
domFields.forEach((fld) => form.appendChild(document.createElement('div')).append(fld))
|
||||||
form.appendChild(document.createElement('div')).append(field)
|
|
||||||
})
|
|
||||||
const buttons = form.appendChild(document.createElement('div'))
|
|
||||||
buttons.className = prefix + '-buttons'
|
buttons.className = prefix + '-buttons'
|
||||||
buttons.append(submitButton)
|
buttons.append(submitButton)
|
||||||
buttons.append(document.createTextNode(' '))
|
buttons.append(document.createTextNode(' '))
|
||||||
buttons.append(cancelButton)
|
buttons.append(cancelButton)
|
||||||
|
|
||||||
const box = wrapper.getBoundingClientRect()
|
|
||||||
wrapper.style.top = (window.innerHeight - box.height) / 2 + 'px'
|
wrapper.style.top = (window.innerHeight - box.height) / 2 + 'px'
|
||||||
wrapper.style.left = (window.innerWidth - box.width) / 2 + 'px'
|
wrapper.style.left = (window.innerWidth - box.width) / 2 + 'px'
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
const params = getValues(options.fields, domFields)
|
const params = getValues(options.fields, domFields)
|
||||||
if (params) {
|
if (params) {
|
||||||
|
@ -54,12 +45,10 @@ export function openPrompt(options: any) {
|
||||||
options.callback(params)
|
options.callback(params)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
form.addEventListener('submit', (e) => {
|
form.addEventListener('submit', (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
submit()
|
submit()
|
||||||
})
|
})
|
||||||
|
|
||||||
form.addEventListener('keydown', (e) => {
|
form.addEventListener('keydown', (e) => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@ -73,12 +62,11 @@ export function openPrompt(options: any) {
|
||||||
}, 500)
|
}, 500)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const input = form.elements[0] as HTMLInputElement
|
||||||
const input: any = form.elements[0]
|
|
||||||
if (input) input.focus()
|
if (input) input.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
function getValues(fields: any, domFields: any) {
|
function getValues(fields, domFields) {
|
||||||
const result = Object.create(null)
|
const result = Object.create(null)
|
||||||
let i = 0
|
let i = 0
|
||||||
fields.forEarch((name) => {
|
fields.forEarch((name) => {
|
||||||
|
@ -95,7 +83,7 @@ function getValues(fields: any, domFields: any) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
function reportInvalid(dom: any, message: any) {
|
function reportInvalid(dom, message) {
|
||||||
const parent = dom.parentNode
|
const parent = dom.parentNode
|
||||||
const msg = parent.appendChild(document.createElement('div'))
|
const msg = parent.appendChild(document.createElement('div'))
|
||||||
msg.style.left = dom.offsetLeft + dom.offsetWidth + 2 + 'px'
|
msg.style.left = dom.offsetLeft + dom.offsetWidth + 2 + 'px'
|
||||||
|
@ -106,12 +94,12 @@ function reportInvalid(dom: any, message: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Field {
|
export class Field {
|
||||||
options: any
|
options: { required: boolean; validate; clean; label: string; value: string }
|
||||||
constructor(options: any) {
|
constructor(options) {
|
||||||
this.options = options
|
this.options = options
|
||||||
}
|
}
|
||||||
|
|
||||||
read(dom: any) {
|
read(dom) {
|
||||||
return dom.value
|
return dom.value
|
||||||
}
|
}
|
||||||
// :: (any) → ?string
|
// :: (any) → ?string
|
||||||
|
@ -120,13 +108,12 @@ export class Field {
|
||||||
return typeof _value === typeof ''
|
return typeof _value === typeof ''
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(value: any) {
|
validate(value) {
|
||||||
if (!value && this.options.required) return 'Required field'
|
if (!value && this.options.required) return 'Required field'
|
||||||
|
|
||||||
return this.validateType(value) || (this.options.validate && this.options.validate(value))
|
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
|
return this.options.clean ? this.options.clean(value) : value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,7 +121,6 @@ export class Field {
|
||||||
export class TextField extends Field {
|
export class TextField extends Field {
|
||||||
render() {
|
render() {
|
||||||
const input: HTMLInputElement = document.createElement('input')
|
const input: HTMLInputElement = document.createElement('input')
|
||||||
|
|
||||||
input.type = 'text'
|
input.type = 'text'
|
||||||
input.placeholder = this.options.label
|
input.placeholder = this.options.label
|
||||||
input.value = this.options.value || ''
|
input.value = this.options.value || ''
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Plugin } from 'prosemirror-state'
|
import { Plugin } from 'prosemirror-state'
|
||||||
import type { EditorView } from 'prosemirror-view'
|
import type { EditorView } from 'prosemirror-view'
|
||||||
import type { ProseMirrorExtension } from '../state'
|
import type { ProseMirrorExtension } from '../../store/state'
|
||||||
|
|
||||||
const scroll = (view: EditorView) => {
|
const scroll = (view: EditorView) => {
|
||||||
if (!view.state.selection.empty) return false
|
if (!view.state.selection.empty) return false
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
import { /*MenuItem,*/ renderGrouped } from 'prosemirror-menu'
|
import { /*MenuItem,*/ MenuItem, renderGrouped } from 'prosemirror-menu'
|
||||||
import type { Schema } from 'prosemirror-model'
|
import type { Schema } from 'prosemirror-model'
|
||||||
import { Plugin } from 'prosemirror-state'
|
import { EditorState, Plugin } from 'prosemirror-state'
|
||||||
|
import type { EditorView } from 'prosemirror-view'
|
||||||
// import { EditorView } from 'prosemirror-view'
|
// import { EditorView } from 'prosemirror-view'
|
||||||
import type { ProseMirrorExtension } from '../state'
|
import type { ProseMirrorExtension } from '../../store/state'
|
||||||
import { buildMenuItems } from './menu'
|
import { buildMenuItems } from './menu'
|
||||||
|
|
||||||
const cut = (arr: any[] | any) => arr.filter((a: any) => !!a)
|
const cut = (arr) => arr.filter((a) => !!a)
|
||||||
|
|
||||||
export class SelectionTooltip {
|
export class SelectionTooltip {
|
||||||
tooltip: any
|
tooltip: HTMLElement
|
||||||
|
|
||||||
constructor(view: any, schema: Schema) {
|
constructor(view: EditorView, schema: Schema) {
|
||||||
this.tooltip = document.createElement('div')
|
this.tooltip = document.createElement('div')
|
||||||
this.tooltip.className = 'tooltip'
|
this.tooltip.className = 'tooltip'
|
||||||
view.dom.parentNode.append(this.tooltip)
|
view.dom.parentNode.append(this.tooltip)
|
||||||
const content = cut((buildMenuItems(schema) as { [key: string]: any })?.fullMenu)
|
const content = cut((buildMenuItems(schema) as { [key: string]: MenuItem })?.fullMenu)
|
||||||
|
|
||||||
console.debug(content)
|
console.debug(content)
|
||||||
const { dom } = renderGrouped(view, content)
|
const { dom } = renderGrouped(view, content)
|
||||||
|
@ -23,7 +24,7 @@ export class SelectionTooltip {
|
||||||
this.update(view, null)
|
this.update(view, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
update(view: any, lastState: any) {
|
update(view: EditorView, lastState: EditorState) {
|
||||||
const state = view.state
|
const state = view.state
|
||||||
|
|
||||||
if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) {
|
if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) {
|
||||||
|
@ -52,9 +53,9 @@ export class SelectionTooltip {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toolTip(schema: any) {
|
export function toolTip(schema: Schema) {
|
||||||
return new Plugin({
|
return new Plugin({
|
||||||
view(editorView: any) {
|
view(editorView: EditorView) {
|
||||||
return new SelectionTooltip(editorView, schema)
|
return new SelectionTooltip(editorView, schema)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { inputRules } from 'prosemirror-inputrules'
|
import { inputRules } from 'prosemirror-inputrules'
|
||||||
import type { MarkType } from 'prosemirror-model'
|
import type { MarkType } from 'prosemirror-model'
|
||||||
import { markInputRule } from './mark-input-rule'
|
import { markInputRule } from './mark-input-rule'
|
||||||
import type { ProseMirrorExtension } from '../state'
|
import type { ProseMirrorExtension } from '../../store/state'
|
||||||
|
|
||||||
const strikethroughRule = (nodeType: MarkType) => markInputRule(/~{2}(.+)~{2}$/, nodeType)
|
const strikethroughRule = (nodeType: MarkType) => markInputRule(/~{2}(.+)~{2}$/, nodeType)
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { EditorState, Selection } from 'prosemirror-state'
|
||||||
import type { Node, Schema, ResolvedPos } from 'prosemirror-model'
|
import type { Node, Schema, ResolvedPos } from 'prosemirror-model'
|
||||||
import { InputRule, inputRules } from 'prosemirror-inputrules'
|
import { InputRule, inputRules } from 'prosemirror-inputrules'
|
||||||
import { keymap } from 'prosemirror-keymap'
|
import { keymap } from 'prosemirror-keymap'
|
||||||
import type { ProseMirrorExtension } from '../state'
|
import type { ProseMirrorExtension } from '../../store/state'
|
||||||
|
|
||||||
export const tableInputRule = (schema: Schema) =>
|
export const tableInputRule = (schema: Schema) =>
|
||||||
new InputRule(
|
new InputRule(
|
||||||
|
|
|
@ -3,7 +3,7 @@ import type { EditorView } from 'prosemirror-view'
|
||||||
import { wrappingInputRule, inputRules } from 'prosemirror-inputrules'
|
import { wrappingInputRule, inputRules } from 'prosemirror-inputrules'
|
||||||
import { splitListItem } from 'prosemirror-schema-list'
|
import { splitListItem } from 'prosemirror-schema-list'
|
||||||
import { keymap } from 'prosemirror-keymap'
|
import { keymap } from 'prosemirror-keymap'
|
||||||
import type { ProseMirrorExtension } from '../state'
|
import type { ProseMirrorExtension } from '../../store/state'
|
||||||
|
|
||||||
const todoListRule = (nodeType: NodeType) =>
|
const todoListRule = (nodeType: NodeType) =>
|
||||||
wrappingInputRule(new RegExp('^\\[( |x)]\\s$'), nodeType, (match) => ({
|
wrappingInputRule(new RegExp('^\\[( |x)]\\s$'), nodeType, (match) => ({
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Store, unwrap } from 'solid-js/store'
|
||||||
import { EditorState, Plugin, Transaction } from 'prosemirror-state'
|
import { EditorState, Plugin, Transaction } from 'prosemirror-state'
|
||||||
import { EditorView } from 'prosemirror-view'
|
import { EditorView } from 'prosemirror-view'
|
||||||
import { Schema } from 'prosemirror-model'
|
import { Schema } from 'prosemirror-model'
|
||||||
import type { NodeViewFn, ProseMirrorExtension, ProseMirrorState } from './state'
|
import type { NodeViewFn, ProseMirrorExtension, ProseMirrorState } from '../store/state'
|
||||||
|
|
||||||
interface ProseMirrorProps {
|
interface ProseMirrorProps {
|
||||||
style?: string
|
style?: string
|
||||||
|
@ -38,7 +38,7 @@ const createEditorState = (
|
||||||
nodeViews = { ...nodeViews, ...extension.nodeViews }
|
nodeViews = { ...nodeViews, ...extension.nodeViews }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.debug('[editor] create state with extensions', extensions)
|
||||||
const schema = reconfigure ? prevText.schema : new Schema(schemaSpec)
|
const schema = reconfigure ? prevText.schema : new Schema(schemaSpec)
|
||||||
|
|
||||||
for (const extension of extensions) {
|
for (const extension of extensions) {
|
||||||
|
@ -66,15 +66,13 @@ export const ProseMirror = (props: ProseMirrorProps) => {
|
||||||
props.onChange(newState)
|
props.onChange(newState)
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line solid/reactivity
|
|
||||||
createEffect(
|
createEffect(
|
||||||
(state: [EditorState, ProseMirrorExtension[]]) => {
|
(state: [EditorState, ProseMirrorExtension[]]) => {
|
||||||
|
console.debug('[prosemirror] init editor with extensions', state)
|
||||||
const [prevText, prevExtensions] = state
|
const [prevText, prevExtensions] = state
|
||||||
const text = unwrap(props.text) as EditorState
|
const text = unwrap(props.text) as EditorState
|
||||||
const extensions: ProseMirrorExtension[] = unwrap(props.extensions)
|
const extensions: ProseMirrorExtension[] = unwrap(props.extensions)
|
||||||
|
|
||||||
if (!text || !extensions?.length) return [text, extensions]
|
if (!text || !extensions?.length) return [text, extensions]
|
||||||
|
|
||||||
if (!props.editorView) {
|
if (!props.editorView) {
|
||||||
const { editorState, nodeViews } = createEditorState(text, extensions)
|
const { editorState, nodeViews } = createEditorState(text, extensions)
|
||||||
const view = new EditorView(editorRef, {
|
const view = new EditorView(editorRef, {
|
||||||
|
@ -82,26 +80,19 @@ export const ProseMirror = (props: ProseMirrorProps) => {
|
||||||
nodeViews,
|
nodeViews,
|
||||||
dispatchTransaction
|
dispatchTransaction
|
||||||
})
|
})
|
||||||
|
|
||||||
view.focus()
|
view.focus()
|
||||||
props.onInit(editorState, view)
|
props.onInit(editorState, view)
|
||||||
|
|
||||||
return [editorState, extensions]
|
return [editorState, extensions]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extensions !== prevExtensions || (!(text instanceof EditorState) && text !== prevText)) {
|
if (extensions !== prevExtensions || (!(text instanceof EditorState) && text !== prevText)) {
|
||||||
const { editorState, nodeViews } = createEditorState(text, extensions, prevText)
|
const { editorState, nodeViews } = createEditorState(text, extensions, prevText)
|
||||||
|
|
||||||
if (!editorState) return
|
if (!editorState) return
|
||||||
|
|
||||||
editorView().updateState(editorState)
|
editorView().updateState(editorState)
|
||||||
editorView().setProps({ nodeViews, dispatchTransaction })
|
editorView().setProps({ nodeViews, dispatchTransaction })
|
||||||
props.onReconfigure(editorState)
|
props.onReconfigure(editorState)
|
||||||
editorView().focus()
|
editorView().focus()
|
||||||
|
|
||||||
return [editorState, extensions]
|
return [editorState, extensions]
|
||||||
}
|
}
|
||||||
|
|
||||||
return [text, extensions]
|
return [text, extensions]
|
||||||
},
|
},
|
||||||
[props.text, props.extensions]
|
[props.text, props.extensions]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { keymap } from 'prosemirror-keymap'
|
import { keymap } from 'prosemirror-keymap'
|
||||||
import type { ProseMirrorExtension } from './state'
|
import type { ProseMirrorExtension } from '../store/state'
|
||||||
import { Schema } from 'prosemirror-model'
|
import { Schema } from 'prosemirror-model'
|
||||||
import base from './extension/base'
|
import base from './extension/base'
|
||||||
import markdown from './extension/markdown'
|
import markdown from './extension/markdown'
|
||||||
|
@ -15,7 +15,7 @@ import dragHandle from './extension/drag-handle'
|
||||||
import pasteMarkdown from './extension/paste-markdown'
|
import pasteMarkdown from './extension/paste-markdown'
|
||||||
import table from './extension/table'
|
import table from './extension/table'
|
||||||
import collab from './extension/collab'
|
import collab from './extension/collab'
|
||||||
import type { Config, PeerData } from './context'
|
import type { Config, PeerData } from '../store/context'
|
||||||
import selectionMenu from './extension/selection'
|
import selectionMenu from './extension/selection'
|
||||||
import type { Command } from 'prosemirror-state'
|
import type { Command } from 'prosemirror-state'
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,12 @@ 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, InitOpts } from '../prosemirror/setup'
|
import { createSchema, createExtensions, createEmptyText, InitOpts } from '../prosemirror/setup'
|
||||||
import { State, Config, ServiceError, newState, PeerData } from '../prosemirror/context'
|
import { State, Config, ServiceError, newState, PeerData } from './context'
|
||||||
import { serialize, createMarkdownParser } from '../prosemirror/markdown'
|
import { serialize, createMarkdownParser } from '../prosemirror/markdown'
|
||||||
import { isEmpty, isInitialized, ProseMirrorExtension } from '../prosemirror/state'
|
import { isEmpty, isInitialized, ProseMirrorExtension } from './state'
|
||||||
import { isServer } from 'solid-js/web'
|
import { isServer } from 'solid-js/web'
|
||||||
import { roomConnect } from '../prosemirror/p2p'
|
import { roomConnect } from '../prosemirror/p2p'
|
||||||
|
|
||||||
const mod = 'Ctrl'
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export const createCtrl = (initial: State): [Store<State>, { [key: string]: any }] => {
|
export const createCtrl = (initial: State): [Store<State>, { [key: string]: any }] => {
|
||||||
const [store, setState] = createStore(initial)
|
const [store, setState] = createStore(initial)
|
||||||
|
@ -54,7 +52,7 @@ export const createCtrl = (initial: State): [Store<State>, { [key: string]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
const onUndo = () => {
|
const onUndo = () => {
|
||||||
if (!isInitialized(store.text)) return false
|
if (!isInitialized(store.text as EditorState)) return false
|
||||||
const text = store.text as EditorState
|
const text = store.text as EditorState
|
||||||
if (store.collab?.started) yUndo(text)
|
if (store.collab?.started) yUndo(text)
|
||||||
else undo(text, store.editorView.dispatch)
|
else undo(text, store.editorView.dispatch)
|
||||||
|
@ -62,7 +60,7 @@ export const createCtrl = (initial: State): [Store<State>, { [key: string]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
const onRedo = () => {
|
const onRedo = () => {
|
||||||
if (!isInitialized(store.text)) return false
|
if (!isInitialized(store.text as EditorState)) return false
|
||||||
const text = store.text as EditorState
|
const text = store.text as EditorState
|
||||||
if (store.collab?.started) yRedo(text)
|
if (store.collab?.started) yRedo(text)
|
||||||
else redo(text, store.editorView.dispatch)
|
else redo(text, store.editorView.dispatch)
|
||||||
|
@ -113,6 +111,7 @@ export const createCtrl = (initial: State): [Store<State>, { [key: string]: any
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mod = 'Ctrl'
|
||||||
const keymap = {
|
const keymap = {
|
||||||
[`${mod}-w`]: onDiscard,
|
[`${mod}-w`]: onDiscard,
|
||||||
[`${mod}-z`]: onUndo,
|
[`${mod}-z`]: onUndo,
|
||||||
|
@ -123,31 +122,27 @@ export const createCtrl = (initial: State): [Store<State>, { [key: string]: any
|
||||||
|
|
||||||
const fetchData = async (): Promise<State> => {
|
const fetchData = async (): Promise<State> => {
|
||||||
if (isServer) return
|
if (isServer) return
|
||||||
|
|
||||||
const state: State = unwrap(store)
|
const state: State = unwrap(store)
|
||||||
const room = undefined // window.location.pathname?.slice(1) + uuidv4()
|
console.debug('[editor] init state', state)
|
||||||
// console.debug('[editor-ctrl] got unique room', room)
|
|
||||||
const args = { room }
|
|
||||||
const { default: db } = await import('../db')
|
const { default: db } = await import('../db')
|
||||||
const data: string = await db.get('state')
|
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) {
|
if (data !== undefined) {
|
||||||
|
console.debug('[editor] state stored before', data)
|
||||||
try {
|
try {
|
||||||
parsed = JSON.parse(data)
|
const parsed = JSON.parse(data)
|
||||||
|
let text = state.text
|
||||||
|
const room = undefined // window.location.pathname?.slice(1) + uuidv4()
|
||||||
|
const args = { room }
|
||||||
if (!parsed) return { ...state, args }
|
if (!parsed) return { ...state, args }
|
||||||
|
|
||||||
console.debug('[editor-ctrl] json state parsed successfully', parsed)
|
|
||||||
if (parsed?.text) {
|
if (parsed?.text) {
|
||||||
if (!parsed.text || !parsed.text.doc || !parsed.text.selection) {
|
if (!parsed.text || !parsed.text.doc || !parsed.text.selection) {
|
||||||
throw new ServiceError('invalid_state', parsed.text)
|
throw new ServiceError('invalid_state', parsed.text)
|
||||||
} else {
|
} else {
|
||||||
text = parsed.text
|
text = parsed.text
|
||||||
console.debug('[editor-ctrl] got text from stored json', parsed)
|
console.debug('[editor] got text parsed')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.debug('[editor] json state parsed successfully', parsed)
|
||||||
return {
|
return {
|
||||||
...parsed,
|
...parsed,
|
||||||
text,
|
text,
|
||||||
|
@ -166,71 +161,77 @@ export const createCtrl = (initial: State): [Store<State>, { [key: string]: any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTheme = (state: State) => ({ theme: state.config.theme })
|
const getTheme = (state: State) => ({ theme: state.config?.theme || '' })
|
||||||
|
|
||||||
const clean = () => {
|
const clean = () => {
|
||||||
setState({
|
const s: State = {
|
||||||
...newState(),
|
...newState(),
|
||||||
loading: 'initialized',
|
loading: 'initialized',
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
error: undefined,
|
error: undefined,
|
||||||
text: undefined
|
text: undefined,
|
||||||
})
|
args: {}
|
||||||
|
}
|
||||||
|
setState(s)
|
||||||
|
console.debug('[editor] clean state', s)
|
||||||
}
|
}
|
||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
let state = await fetchData()
|
let state = await fetchData()
|
||||||
console.debug('[editor-ctrl] state initiated', state)
|
if (state) {
|
||||||
try {
|
console.debug('[editor] state initiated', state)
|
||||||
if (state.args?.room) {
|
try {
|
||||||
state = doStartCollab(state)
|
if (state.args?.room) {
|
||||||
} else if (!state.text) {
|
state = { ...doStartCollab(state) }
|
||||||
const text = createEmptyText()
|
} else if (!state.text) {
|
||||||
const extensions = createExtensions({
|
const text = createEmptyText()
|
||||||
config: state.config,
|
const extensions = createExtensions({
|
||||||
markdown: state.markdown,
|
config: state?.config || ({} as Config),
|
||||||
keymap
|
markdown: state.markdown,
|
||||||
})
|
keymap
|
||||||
|
})
|
||||||
state = { ...state, text, extensions }
|
state = { ...state, text, extensions }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
state = { ...state, error }
|
||||||
}
|
}
|
||||||
} catch (error) {
|
setState({
|
||||||
state = { ...state, error }
|
...state,
|
||||||
|
config: {
|
||||||
|
...state.config,
|
||||||
|
...getTheme(state)
|
||||||
|
},
|
||||||
|
loading: 'initialized'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
config: { ...state.config, ...getTheme(state) },
|
|
||||||
loading: 'initialized'
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveState = debounce(async (state: State) => {
|
const saveState = () =>
|
||||||
const data = {
|
debounce(async (state: State) => {
|
||||||
lastModified: state.lastModified,
|
const data = {
|
||||||
config: state.config,
|
lastModified: state.lastModified,
|
||||||
path: state.path,
|
config: state.config,
|
||||||
markdown: state.markdown,
|
path: state.path,
|
||||||
collab: {
|
markdown: state.markdown,
|
||||||
room: state.collab?.room
|
collab: {
|
||||||
},
|
room: state.collab?.room
|
||||||
text: ''
|
},
|
||||||
}
|
text: ''
|
||||||
|
}
|
||||||
if (isInitialized(state.text)) {
|
if (isInitialized(state.text as EditorState)) {
|
||||||
data.text = store.editorView.state.toJSON()
|
data.text = store.editorView.state.toJSON()
|
||||||
} else if (state.text) {
|
} else if (state.text) {
|
||||||
data.text = state.text as string
|
data.text = state.text as string
|
||||||
}
|
}
|
||||||
if (!isServer) {
|
if (!isServer) {
|
||||||
const { default: db } = await import('../db')
|
const { default: db } = await import('../db')
|
||||||
db.set('state', JSON.stringify(data))
|
db.set('state', JSON.stringify(data))
|
||||||
}
|
}
|
||||||
}, 200)
|
}, 200)
|
||||||
|
|
||||||
const startCollab = () => {
|
const startCollab = () => {
|
||||||
const state: State = unwrap(store)
|
const state: State = unwrap(store)
|
||||||
const update = doStartCollab(state)
|
const update = doStartCollab(state)
|
||||||
|
|
||||||
setState(update)
|
setState(update)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,7 +240,6 @@ export const createCtrl = (initial: State): [Store<State>, { [key: string]: any
|
||||||
const room = state.args?.room ?? uuidv4()
|
const room = state.args?.room ?? uuidv4()
|
||||||
const username = '' // FIXME: use authenticated user name
|
const username = '' // FIXME: use authenticated user name
|
||||||
const [payload, provider] = roomConnect(room, username)
|
const [payload, provider] = roomConnect(room, username)
|
||||||
|
|
||||||
const extensions: ProseMirrorExtension[] = createExtensions({
|
const extensions: ProseMirrorExtension[] = createExtensions({
|
||||||
config: state.config,
|
config: state.config,
|
||||||
markdown: state.markdown,
|
markdown: state.markdown,
|
||||||
|
@ -247,10 +247,8 @@ export const createCtrl = (initial: State): [Store<State>, { [key: string]: any
|
||||||
keymap,
|
keymap,
|
||||||
y: { payload, provider } as PeerData
|
y: { payload, provider } as PeerData
|
||||||
} as InitOpts)
|
} as InitOpts)
|
||||||
|
|
||||||
let nState = state
|
let nState = state
|
||||||
|
if ((backup && !isEmpty(state.text as EditorState)) || state.path) {
|
||||||
if ((backup && !isEmpty(state.text)) || state.path) {
|
|
||||||
nState = {
|
nState = {
|
||||||
...state,
|
...state,
|
||||||
lastModified: undefined,
|
lastModified: undefined,
|
||||||
|
@ -258,7 +256,6 @@ export const createCtrl = (initial: State): [Store<State>, { [key: string]: any
|
||||||
error: undefined
|
error: undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...nState,
|
...nState,
|
||||||
extensions,
|
extensions,
|
||||||
|
@ -274,7 +271,6 @@ export const createCtrl = (initial: State): [Store<State>, { [key: string]: any
|
||||||
path: state.path,
|
path: state.path,
|
||||||
keymap
|
keymap
|
||||||
})
|
})
|
||||||
|
|
||||||
setState({ collab: undefined, extensions })
|
setState({ collab: undefined, extensions })
|
||||||
window.history.replaceState(null, '', '/')
|
window.history.replaceState(null, '', '/')
|
||||||
}
|
}
|
||||||
|
@ -302,7 +298,6 @@ export const createCtrl = (initial: State): [Store<State>, { [key: string]: any
|
||||||
|
|
||||||
const updateTheme = () => {
|
const updateTheme = () => {
|
||||||
const { theme } = getTheme(unwrap(store))
|
const { theme } = getTheme(unwrap(store))
|
||||||
|
|
||||||
setState('config', { theme })
|
setState('config', { theme })
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,9 @@ export interface ProseMirrorExtension {
|
||||||
|
|
||||||
export type ProseMirrorState = EditorState | unknown
|
export type ProseMirrorState = EditorState | unknown
|
||||||
|
|
||||||
export const isInitialized = (state: any) => state !== undefined && state instanceof EditorState
|
export const isInitialized = (state: EditorState) => state !== undefined && state instanceof EditorState
|
||||||
|
|
||||||
export const isEmpty = (state: any) =>
|
export const isEmpty = (state: EditorState) =>
|
||||||
!isInitialized(state) ||
|
!isInitialized(state) ||
|
||||||
(state.doc.childCount === 1 &&
|
(state.doc.childCount === 1 &&
|
||||||
!state.doc.firstChild.type.spec.code &&
|
!state.doc.firstChild.type.spec.code &&
|
|
@ -8,9 +8,6 @@ import { Icon } from '../Nav/Icon'
|
||||||
import './Card.scss'
|
import './Card.scss'
|
||||||
import { locale } from '../../stores/ui'
|
import { locale } from '../../stores/ui'
|
||||||
import { handleClientRouteLinkClick } from '../../stores/router'
|
import { handleClientRouteLinkClick } from '../../stores/router'
|
||||||
import { getLogger } from '../../utils/logger'
|
|
||||||
|
|
||||||
const log = getLogger('card component')
|
|
||||||
|
|
||||||
interface ArticleCardProps {
|
interface ArticleCardProps {
|
||||||
settings?: {
|
settings?: {
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
import type { Shout } from '../../graphql/types.gen'
|
import type { Shout } from '../../graphql/types.gen'
|
||||||
import { ArticleCard } from './Card'
|
import { ArticleCard } from './Card'
|
||||||
import { getLogger } from '../../utils/logger'
|
|
||||||
|
|
||||||
const log = getLogger('Row5')
|
|
||||||
|
|
||||||
export const Row5 = (props: { articles: Shout[] }) => {
|
export const Row5 = (props: { articles: Shout[] }) => {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -11,11 +11,8 @@ import { handleClientRouteLinkClick, router, Routes, useRouter } from '../../sto
|
||||||
import styles from './Header.module.scss'
|
import styles from './Header.module.scss'
|
||||||
import privateStyles from './Private.module.scss'
|
import privateStyles from './Private.module.scss'
|
||||||
import { getPagePath } from '@nanostores/router'
|
import { getPagePath } from '@nanostores/router'
|
||||||
import { getLogger } from '../../utils/logger'
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
|
|
||||||
const log = getLogger('header')
|
|
||||||
|
|
||||||
const resources: { name: string; route: keyof Routes }[] = [
|
const resources: { name: string; route: keyof Routes }[] = [
|
||||||
{ name: t('zine'), route: 'home' },
|
{ name: t('zine'), route: 'home' },
|
||||||
{ name: t('feed'), route: 'feed' },
|
{ name: t('feed'), route: 'feed' },
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
import { createEffect, createSignal, JSX, onMount, Show } from 'solid-js'
|
import { createEffect, createSignal, JSX, onMount, Show } from 'solid-js'
|
||||||
import { getLogger } from '../../utils/logger'
|
|
||||||
import './Modal.scss'
|
import './Modal.scss'
|
||||||
import { hideModal, useModalStore } from '../../stores/ui'
|
import { hideModal, useModalStore } from '../../stores/ui'
|
||||||
|
|
||||||
const log = getLogger('modal')
|
|
||||||
|
|
||||||
interface ModalProps {
|
interface ModalProps {
|
||||||
name: string
|
name: string
|
||||||
children: JSX.Element
|
children: JSX.Element
|
||||||
|
@ -26,7 +23,7 @@ export const Modal = (props: ModalProps) => {
|
||||||
const [visible, setVisible] = createSignal(false)
|
const [visible, setVisible] = createSignal(false)
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
setVisible(getModal() === props.name)
|
setVisible(getModal() === props.name)
|
||||||
log.debug(`${props.name} is ${getModal() === props.name ? 'visible' : 'hidden'}`)
|
console.debug(`[modal] ${props.name} is ${getModal() === props.name ? 'visible' : 'hidden'}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { setLocale } from '../stores/ui'
|
||||||
import { Component, createEffect, createMemo } from 'solid-js'
|
import { Component, createEffect, createMemo } from 'solid-js'
|
||||||
import { Routes, useRouter } from '../stores/router'
|
import { Routes, useRouter } from '../stores/router'
|
||||||
import { Dynamic, isServer } from 'solid-js/web'
|
import { Dynamic, isServer } from 'solid-js/web'
|
||||||
import { getLogger } from '../utils/logger'
|
|
||||||
|
|
||||||
import type { PageProps } from './types'
|
import type { PageProps } from './types'
|
||||||
|
|
||||||
|
@ -48,8 +47,6 @@ import { CreatePage } from './Pages/CreatePage'
|
||||||
// const ThanksPage = lazy(() => import('./Pages/about/ThanksPage'))
|
// const ThanksPage = lazy(() => import('./Pages/about/ThanksPage'))
|
||||||
// const CreatePage = lazy(() => import('./Pages/about/CreatePage'))
|
// const CreatePage = lazy(() => import('./Pages/about/CreatePage'))
|
||||||
|
|
||||||
const log = getLogger('root')
|
|
||||||
|
|
||||||
const pagesMap: Record<keyof Routes, Component<PageProps>> = {
|
const pagesMap: Record<keyof Routes, Component<PageProps>> = {
|
||||||
create: CreatePage,
|
create: CreatePage,
|
||||||
home: HomePage,
|
home: HomePage,
|
||||||
|
|
|
@ -8,9 +8,6 @@ import { t } from '../../utils/intl'
|
||||||
import { locale } from '../../stores/ui'
|
import { locale } from '../../stores/ui'
|
||||||
import { useAuthStore } from '../../stores/auth'
|
import { useAuthStore } from '../../stores/auth'
|
||||||
import { follow, unfollow } from '../../stores/zine/common'
|
import { follow, unfollow } from '../../stores/zine/common'
|
||||||
import { getLogger } from '../../utils/logger'
|
|
||||||
|
|
||||||
const log = getLogger('TopicCard')
|
|
||||||
|
|
||||||
interface TopicProps {
|
interface TopicProps {
|
||||||
topic: Topic
|
topic: Topic
|
||||||
|
|
|
@ -6,11 +6,8 @@ import { t } from '../../utils/intl'
|
||||||
import { useAuthorsStore, setSortAllBy as setSortAllAuthorsBy } from '../../stores/zine/authors'
|
import { useAuthorsStore, setSortAllBy as setSortAllAuthorsBy } from '../../stores/zine/authors'
|
||||||
import { handleClientRouteLinkClick, useRouter } from '../../stores/router'
|
import { handleClientRouteLinkClick, useRouter } from '../../stores/router'
|
||||||
import { useAuthStore } from '../../stores/auth'
|
import { useAuthStore } from '../../stores/auth'
|
||||||
import { getLogger } from '../../utils/logger'
|
|
||||||
import '../../styles/AllTopics.scss'
|
import '../../styles/AllTopics.scss'
|
||||||
|
|
||||||
const log = getLogger('AllAuthorsView')
|
|
||||||
|
|
||||||
type AllAuthorsPageSearchParams = {
|
type AllAuthorsPageSearchParams = {
|
||||||
by: '' | 'name' | 'shouts' | 'rating'
|
by: '' | 'name' | 'shouts' | 'rating'
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,6 @@ import { handleClientRouteLinkClick, useRouter } from '../../stores/router'
|
||||||
import { TopicCard } from '../Topic/Card'
|
import { TopicCard } from '../Topic/Card'
|
||||||
import { useAuthStore } from '../../stores/auth'
|
import { useAuthStore } from '../../stores/auth'
|
||||||
import '../../styles/AllTopics.scss'
|
import '../../styles/AllTopics.scss'
|
||||||
import { getLogger } from '../../utils/logger'
|
|
||||||
|
|
||||||
const log = getLogger('AllTopicsView')
|
|
||||||
|
|
||||||
type AllTopicsPageSearchParams = {
|
type AllTopicsPageSearchParams = {
|
||||||
by: 'shouts' | 'authors' | 'title' | ''
|
by: 'shouts' | 'authors' | 'title' | ''
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
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, newState } from '../Editor/prosemirror/context'
|
import { State, StateContext, newState } from '../Editor/store/context'
|
||||||
import { createCtrl } from '../Editor/prosemirror/ctrl'
|
import { createCtrl } from '../Editor/store/ctrl'
|
||||||
import { Layout } from '../Editor/Layout'
|
import { Layout } from '../Editor/components/Layout'
|
||||||
import Editor from '../Editor'
|
import Editor from '../Editor'
|
||||||
import { Sidebar } from '../Editor/Sidebar'
|
import { Sidebar } from '../Editor/components/Sidebar'
|
||||||
import ErrorView from '../Editor/Error'
|
import ErrorView from '../Editor/components/Error'
|
||||||
import { getLogger } from '../../utils/logger'
|
|
||||||
|
|
||||||
const log = getLogger('CreateView')
|
|
||||||
|
|
||||||
export const CreateView = () => {
|
export const CreateView = () => {
|
||||||
const [store, ctrl] = createCtrl(newState())
|
const [store, ctrl] = createCtrl(newState())
|
||||||
|
|
|
@ -10,7 +10,6 @@ import Beside from '../Feed/Beside'
|
||||||
import RowShort from '../Feed/RowShort'
|
import RowShort from '../Feed/RowShort'
|
||||||
import Slider from '../Feed/Slider'
|
import Slider from '../Feed/Slider'
|
||||||
import Group from '../Feed/Group'
|
import Group from '../Feed/Group'
|
||||||
import { getLogger } from '../../utils/logger'
|
|
||||||
import type { Shout, Topic } from '../../graphql/types.gen'
|
import type { Shout, Topic } from '../../graphql/types.gen'
|
||||||
import { Icon } from '../Nav/Icon'
|
import { Icon } from '../Nav/Icon'
|
||||||
import { t } from '../../utils/intl'
|
import { t } from '../../utils/intl'
|
||||||
|
@ -25,8 +24,6 @@ import { useTopAuthorsStore } from '../../stores/zine/topAuthors'
|
||||||
import { locale } from '../../stores/ui'
|
import { locale } from '../../stores/ui'
|
||||||
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
|
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
|
||||||
|
|
||||||
const log = getLogger('home view')
|
|
||||||
|
|
||||||
type HomeProps = {
|
type HomeProps = {
|
||||||
randomTopics: Topic[]
|
randomTopics: Topic[]
|
||||||
recentPublishedArticles: Shout[]
|
recentPublishedArticles: Shout[]
|
||||||
|
|
|
@ -1,32 +1,29 @@
|
||||||
import { atom } from 'nanostores'
|
import { atom } from 'nanostores'
|
||||||
import type { AuthResult } from '../graphql/types.gen'
|
import type { AuthResult } from '../graphql/types.gen'
|
||||||
import { getLogger } from '../utils/logger'
|
|
||||||
import { resetToken, setToken } from '../graphql/privateGraphQLClient'
|
import { resetToken, setToken } from '../graphql/privateGraphQLClient'
|
||||||
import { apiClient } from '../utils/apiClient'
|
import { apiClient } from '../utils/apiClient'
|
||||||
import { createSignal } from 'solid-js'
|
import { createSignal } from 'solid-js'
|
||||||
|
|
||||||
const log = getLogger('auth-store')
|
|
||||||
|
|
||||||
const [session, setSession] = createSignal<AuthResult | null>(null)
|
const [session, setSession] = createSignal<AuthResult | null>(null)
|
||||||
|
|
||||||
export const signIn = async (params) => {
|
export const signIn = async (params) => {
|
||||||
const authResult = await apiClient.authLogin(params)
|
const authResult = await apiClient.authLogin(params)
|
||||||
setSession(authResult)
|
setSession(authResult)
|
||||||
setToken(authResult.token)
|
setToken(authResult.token)
|
||||||
log.debug('signed in')
|
console.debug('signed in')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const signUp = async (params) => {
|
export const signUp = async (params) => {
|
||||||
const authResult = await apiClient.authRegister(params)
|
const authResult = await apiClient.authRegister(params)
|
||||||
setSession(authResult)
|
setSession(authResult)
|
||||||
setToken(authResult.token)
|
setToken(authResult.token)
|
||||||
log.debug('signed up')
|
console.debug('signed up')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const signOut = () => {
|
export const signOut = () => {
|
||||||
setSession(null)
|
setSession(null)
|
||||||
resetToken()
|
resetToken()
|
||||||
log.debug('signed out')
|
console.debug('signed out')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const emailChecks = atom<{ [email: string]: boolean }>({})
|
export const emailChecks = atom<{ [email: string]: boolean }>({})
|
||||||
|
@ -44,7 +41,7 @@ export const register = async ({ email, password }: { email: string; password: s
|
||||||
})
|
})
|
||||||
|
|
||||||
if (authResult && !authResult.error) {
|
if (authResult && !authResult.error) {
|
||||||
log.debug('register session update', authResult)
|
console.debug('register session update', authResult)
|
||||||
setSession(authResult)
|
setSession(authResult)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,9 @@ import { apiClient } from '../../utils/apiClient'
|
||||||
import { addAuthorsByTopic } from './authors'
|
import { addAuthorsByTopic } from './authors'
|
||||||
import { addTopicsByAuthor } from './topics'
|
import { addTopicsByAuthor } from './topics'
|
||||||
import { byStat } from '../../utils/sortby'
|
import { byStat } from '../../utils/sortby'
|
||||||
|
|
||||||
import { getLogger } from '../../utils/logger'
|
|
||||||
import { createSignal } from 'solid-js'
|
import { createSignal } from 'solid-js'
|
||||||
import { createLazyMemo } from '@solid-primitives/memo'
|
import { createLazyMemo } from '@solid-primitives/memo'
|
||||||
|
|
||||||
const log = getLogger('articles store')
|
|
||||||
|
|
||||||
const [sortedArticles, setSortedArticles] = createSignal<Shout[]>([])
|
const [sortedArticles, setSortedArticles] = createSignal<Shout[]>([])
|
||||||
const [articleEntities, setArticleEntities] = createSignal<{ [articleSlug: string]: Shout }>({})
|
const [articleEntities, setArticleEntities] = createSignal<{ [articleSlug: string]: Shout }>({})
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
import { apiClient } from '../../utils/apiClient'
|
import { apiClient } from '../../utils/apiClient'
|
||||||
import type { Author } from '../../graphql/types.gen'
|
import type { Author } from '../../graphql/types.gen'
|
||||||
import { getLogger } from '../../utils/logger'
|
|
||||||
import { createSignal } from 'solid-js'
|
import { createSignal } from 'solid-js'
|
||||||
import { createLazyMemo } from '@solid-primitives/memo'
|
import { createLazyMemo } from '@solid-primitives/memo'
|
||||||
|
|
||||||
const log = getLogger('authors store')
|
|
||||||
|
|
||||||
export type AuthorsSortBy = 'shouts' | 'name' | 'rating'
|
export type AuthorsSortBy = 'shouts' | 'name' | 'rating'
|
||||||
|
|
||||||
const [sortAllBy, setSortAllBy] = createSignal<AuthorsSortBy>('shouts')
|
const [sortAllBy, setSortAllBy] = createSignal<AuthorsSortBy>('shouts')
|
||||||
|
@ -32,7 +29,7 @@ const sortedAuthors = createLazyMemo(() => {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'name': {
|
case 'name': {
|
||||||
log.debug('sorted by name')
|
console.debug('sorted by name')
|
||||||
authors.sort((a, b) => a.name.localeCompare(b.name))
|
authors.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import { createMemo, createSignal } from 'solid-js'
|
import { createMemo, createSignal } from 'solid-js'
|
||||||
import { apiClient } from '../../utils/apiClient'
|
import { apiClient } from '../../utils/apiClient'
|
||||||
import type { Topic } from '../../graphql/types.gen'
|
import type { Topic } from '../../graphql/types.gen'
|
||||||
import { byCreated, byTopicStatDesc } from '../../utils/sortby'
|
import { byTopicStatDesc } from '../../utils/sortby'
|
||||||
import { getLogger } from '../../utils/logger'
|
|
||||||
import { createLazyMemo } from '@solid-primitives/memo'
|
import { createLazyMemo } from '@solid-primitives/memo'
|
||||||
|
|
||||||
const log = getLogger('topics store')
|
export type TopicsSortBy = 'followers' | 'title' | 'authors' | 'shouts'
|
||||||
|
|
||||||
export type TopicsSortBy = 'created' | 'title' | 'authors' | 'shouts'
|
|
||||||
|
|
||||||
const [sortAllBy, setSortAllBy] = createSignal<TopicsSortBy>('shouts')
|
const [sortAllBy, setSortAllBy] = createSignal<TopicsSortBy>('shouts')
|
||||||
|
|
||||||
|
@ -21,9 +18,9 @@ const sortedTopics = createLazyMemo<Topic[]>(() => {
|
||||||
const topics = Object.values(topicEntities())
|
const topics = Object.values(topicEntities())
|
||||||
|
|
||||||
switch (sortAllBy()) {
|
switch (sortAllBy()) {
|
||||||
case 'created': {
|
case 'followers': {
|
||||||
// log.debug('sorted by created')
|
// console.debug('[store.topics] sorted by followers')
|
||||||
topics.sort(byCreated)
|
topics.sort(byTopicStatDesc('followers'))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'shouts': {
|
case 'shouts': {
|
||||||
|
@ -37,12 +34,12 @@ const sortedTopics = createLazyMemo<Topic[]>(() => {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'title': {
|
case 'title': {
|
||||||
// log.debug('sorted by title')
|
// console.debug('[store.topics] sorted by title')
|
||||||
topics.sort((a, b) => a.title.localeCompare(b.title))
|
topics.sort((a, b) => a.title.localeCompare(b.title))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
log.error(`Unknown sort: ${sortAllBy()}`)
|
console.error(`Unknown sort: ${sortAllBy()}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import type { Reaction, Shout, FollowingEntity, AuthResult } from '../graphql/types.gen'
|
import type { Reaction, Shout, FollowingEntity, AuthResult } from '../graphql/types.gen'
|
||||||
|
|
||||||
import { getLogger } from './logger'
|
|
||||||
import { publicGraphQLClient } from '../graphql/publicGraphQLClient'
|
import { publicGraphQLClient } from '../graphql/publicGraphQLClient'
|
||||||
import { privateGraphQLClient } from '../graphql/privateGraphQLClient'
|
import { privateGraphQLClient } from '../graphql/privateGraphQLClient'
|
||||||
import articleBySlug from '../graphql/query/article-by-slug'
|
import articleBySlug from '../graphql/query/article-by-slug'
|
||||||
|
@ -32,8 +30,6 @@ import authorsBySlugs from '../graphql/query/authors-by-slugs'
|
||||||
import incrementView from '../graphql/mutation/increment-view'
|
import incrementView from '../graphql/mutation/increment-view'
|
||||||
import myChats from '../graphql/query/my-chats'
|
import myChats from '../graphql/query/my-chats'
|
||||||
|
|
||||||
const log = getLogger('api-client')
|
|
||||||
|
|
||||||
const FEED_SIZE = 50
|
const FEED_SIZE = 50
|
||||||
const REACTIONS_PAGE_SIZE = 100
|
const REACTIONS_PAGE_SIZE = 100
|
||||||
|
|
||||||
|
@ -51,7 +47,7 @@ export class ApiError extends Error {
|
||||||
export const apiClient = {
|
export const apiClient = {
|
||||||
authLogin: async ({ email, password }): Promise<AuthResult> => {
|
authLogin: async ({ email, password }): Promise<AuthResult> => {
|
||||||
const response = await publicGraphQLClient.query(authLoginQuery, { email, password }).toPromise()
|
const response = await publicGraphQLClient.query(authLoginQuery, { email, password }).toPromise()
|
||||||
// log.debug('authLogin', { response })
|
// console.debug('[api-client] authLogin', { response })
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
if (response.error.message === '[GraphQL] User not found') {
|
if (response.error.message === '[GraphQL] User not found') {
|
||||||
throw new ApiError('user_not_found')
|
throw new ApiError('user_not_found')
|
||||||
|
@ -119,7 +115,7 @@ export const apiClient = {
|
||||||
const response = await publicGraphQLClient.query(topicsRandomQuery, { amount }).toPromise()
|
const response = await publicGraphQLClient.query(topicsRandomQuery, { amount }).toPromise()
|
||||||
|
|
||||||
if (!response.data) {
|
if (!response.data) {
|
||||||
log.error('getRandomTopics', response.error)
|
console.error('[api-client] getRandomTopics', response.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.data.topicsRandom
|
return response.data.topicsRandom
|
||||||
|
@ -177,7 +173,7 @@ export const apiClient = {
|
||||||
.toPromise()
|
.toPromise()
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
log.error('getArticlesForTopics', response.error)
|
console.error('[api-client] getArticlesForTopics', response.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.data.shoutsByTopics
|
return response.data.shoutsByTopics
|
||||||
|
@ -200,7 +196,7 @@ export const apiClient = {
|
||||||
.toPromise()
|
.toPromise()
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
log.error('getArticlesForAuthors', response.error)
|
console.error('[api-client] getArticlesForAuthors', response.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.data.shoutsByAuthors
|
return response.data.shoutsByAuthors
|
||||||
|
@ -226,7 +222,7 @@ export const apiClient = {
|
||||||
const response = await publicGraphQLClient.query(articlesRecentPublished, { limit, offset }).toPromise()
|
const response = await publicGraphQLClient.query(articlesRecentPublished, { limit, offset }).toPromise()
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
log.error('getPublishedArticles', response.error)
|
console.error('[api-client] getPublishedArticles', response.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.data.recentPublished
|
return response.data.recentPublished
|
||||||
|
@ -234,14 +230,14 @@ export const apiClient = {
|
||||||
getAllTopics: async () => {
|
getAllTopics: async () => {
|
||||||
const response = await publicGraphQLClient.query(topicsAll, {}).toPromise()
|
const response = await publicGraphQLClient.query(topicsAll, {}).toPromise()
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
log.debug('getAllTopics', response.error)
|
console.debug('[api-client] getAllTopics', response.error)
|
||||||
}
|
}
|
||||||
return response.data.topicsAll
|
return response.data.topicsAll
|
||||||
},
|
},
|
||||||
getAllAuthors: async () => {
|
getAllAuthors: async () => {
|
||||||
const response = await publicGraphQLClient.query(authorsAll, {}).toPromise()
|
const response = await publicGraphQLClient.query(authorsAll, {}).toPromise()
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
log.debug('getAllAuthors', response.error)
|
console.debug('[api-client] getAllAuthors', response.error)
|
||||||
}
|
}
|
||||||
return response.data.authorsAll
|
return response.data.authorsAll
|
||||||
},
|
},
|
||||||
|
@ -300,7 +296,7 @@ export const apiClient = {
|
||||||
},
|
},
|
||||||
createReaction: async ({ reaction }) => {
|
createReaction: async ({ reaction }) => {
|
||||||
const response = await privateGraphQLClient.mutation(reactionCreate, { reaction }).toPromise()
|
const response = await privateGraphQLClient.mutation(reactionCreate, { reaction }).toPromise()
|
||||||
log.debug('[api] create reaction mutation called')
|
console.debug('[api-client] [api] create reaction mutation called')
|
||||||
return response.data.createReaction
|
return response.data.createReaction
|
||||||
},
|
},
|
||||||
updateReaction: async ({ reaction }) => {
|
updateReaction: async ({ reaction }) => {
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
import loglevel from 'loglevel'
|
|
||||||
import prefix from 'loglevel-plugin-prefix'
|
|
||||||
import { isDev } from './config'
|
|
||||||
|
|
||||||
prefix.reg(loglevel)
|
|
||||||
prefix.apply(loglevel, { template: '[%n]' })
|
|
||||||
loglevel.setLevel(isDev ? loglevel.levels.TRACE : loglevel.levels.ERROR)
|
|
||||||
|
|
||||||
export const getLogger = (name: string) => loglevel.getLogger(name)
|
|
15
yarn.lock
15
yarn.lock
|
@ -3448,10 +3448,10 @@ astro-eslint-parser@^0.7.0:
|
||||||
eslint-visitor-keys "^3.0.0"
|
eslint-visitor-keys "^3.0.0"
|
||||||
espree "^9.0.0"
|
espree "^9.0.0"
|
||||||
|
|
||||||
astro@^1.1.1:
|
astro@^1.4.6:
|
||||||
version "1.4.5"
|
version "1.4.6"
|
||||||
resolved "https://registry.yarnpkg.com/astro/-/astro-1.4.5.tgz#6515d8d6edd0d73be80707408c345dfe8c8c7b9a"
|
resolved "https://registry.yarnpkg.com/astro/-/astro-1.4.6.tgz#8442efca97605caf6d44f036b25f155df2cafdbd"
|
||||||
integrity sha512-7uflNdFMsAONkEdtNRqP1XgtgdUhiiFfYd3DjtaoaskYhcnfKKYy8Lht0stIQaMtAqITIy0LLTDvDM/j8SfuUw==
|
integrity sha512-zGi3uCl+VwBYwbmccyo9M4xI68gx+M7jJdP7kFSR3UCdmDbCoCY6u85ixAAjbkMUSNsuxsrBrhH9HW/X5SGTzQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@astrojs/compiler" "^0.26.0"
|
"@astrojs/compiler" "^0.26.0"
|
||||||
"@astrojs/language-server" "^0.26.2"
|
"@astrojs/language-server" "^0.26.2"
|
||||||
|
@ -7417,12 +7417,7 @@ loglevel-colored-level-prefix@^1.0.0:
|
||||||
chalk "^1.1.3"
|
chalk "^1.1.3"
|
||||||
loglevel "^1.4.1"
|
loglevel "^1.4.1"
|
||||||
|
|
||||||
loglevel-plugin-prefix@^0.8.4:
|
loglevel@^1.4.1:
|
||||||
version "0.8.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/loglevel-plugin-prefix/-/loglevel-plugin-prefix-0.8.4.tgz#2fe0e05f1a820317d98d8c123e634c1bd84ff644"
|
|
||||||
integrity sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==
|
|
||||||
|
|
||||||
loglevel@^1.4.1, loglevel@^1.8.0:
|
|
||||||
version "1.8.0"
|
version "1.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.0.tgz#e7ec73a57e1e7b419cb6c6ac06bf050b67356114"
|
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.0.tgz#e7ec73a57e1e7b419cb6c6ac06bf050b67356114"
|
||||||
integrity sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==
|
integrity sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==
|
||||||
|
|
Loading…
Reference in New Issue
Block a user