Skip to content

Commit

Permalink
feat: support mark view (#103)
Browse files Browse the repository at this point in the history
  • Loading branch information
ocavue authored Dec 19, 2024
1 parent d5f9b9f commit 473af64
Show file tree
Hide file tree
Showing 70 changed files with 1,950 additions and 59 deletions.
10 changes: 10 additions & 0 deletions .changeset/blue-spiders-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@prosemirror-adapter/svelte": minor
"@prosemirror-adapter/react": minor
"@prosemirror-adapter/solid": minor
"@prosemirror-adapter/core": minor
"@prosemirror-adapter/lit": minor
"@prosemirror-adapter/vue": minor
---

Add API for mark view.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ You'll need this adapter if you want to use Prosemirror to build a rich text edi
- [ ] 🚀 coming soon...
- [x] Add out of box support for prosemirror features
- [x] [Prosemirror Node View](https://prosemirror.net/docs/ref/#view.NodeView)
- [x] [Prosemirror Mark View](https://prosemirror.net/docs/ref/#view.MarkView)
- [x] [Prosemirror Plugin View](https://prosemirror.net/docs/ref/#state.PluginView)
- [x] [Prosemirror Widget Decoration](https://prosemirror.net/docs/ref/#view.Decoration%5Ewidget)

Expand Down
15 changes: 12 additions & 3 deletions e2e/src/createEditorView.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import 'prosemirror-view/style/prosemirror.css'
import 'prosemirror-example-setup/style/style.css'
import 'prosemirror-menu/style/menu.css'
import 'prosemirror-view/style/prosemirror.css'

import { exampleSetup } from 'prosemirror-example-setup'
import { keymap } from 'prosemirror-keymap'
import { DOMParser } from 'prosemirror-model'
import { schema } from 'prosemirror-schema-basic'
import type { Plugin } from 'prosemirror-state'
import { EditorState } from 'prosemirror-state'
import type { NodeViewConstructor } from 'prosemirror-view'
import type {
MarkViewConstructor,
NodeViewConstructor,
} from 'prosemirror-view'
import { EditorView } from 'prosemirror-view'

export function createEditorView(element: HTMLElement | ShadowRoot, nodeViews: Record<string, NodeViewConstructor>, plugins: Plugin[]) {
export function createEditorView(
element: HTMLElement | ShadowRoot,
nodeViews: Record<string, NodeViewConstructor>,
markViews: Record<string, MarkViewConstructor>,
plugins: Plugin[],
) {
const content = document.querySelector('#content')
if (!content)
throw new Error('Content element not found')
Expand Down Expand Up @@ -48,5 +56,6 @@ export function createEditorView(element: HTMLElement | ShadowRoot, nodeViews: R
],
}),
nodeViews,
markViews,
})
}
8 changes: 8 additions & 0 deletions e2e/src/lit/components/Editor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
ShallowLitElement,
useMarkViewFactory,
useNodeViewFactory,
usePluginViewFactory,
useWidgetViewFactory,
Expand All @@ -16,10 +17,12 @@ import { Hashes } from './Hashes'
import { Heading } from './Heading'
import { Paragraph } from './Paragraph'
import { Size } from './Size'
import { Link } from './Link'

@customElement('my-editor')
export class MyEditor extends ShallowLitElement {
nodeViewFactory = useNodeViewFactory(this)
markViewFactory = useMarkViewFactory(this)
pluginViewFactory = usePluginViewFactory(this)
widgetViewFactory = useWidgetViewFactory(this)

Expand All @@ -30,6 +33,7 @@ export class MyEditor extends ShallowLitElement {
return

const nodeViewFactory = this.nodeViewFactory.value!
const markViewFactory = this.markViewFactory.value!
const pluginViewFactory = this.pluginViewFactory.value!
const widgetViewFactory = this.widgetViewFactory.value!
const getHashWidget = widgetViewFactory({
Expand All @@ -46,6 +50,10 @@ export class MyEditor extends ShallowLitElement {
heading: nodeViewFactory({
component: Heading,
}),
}, {
link: markViewFactory({
component: Link,
}),
}, [
new Plugin({
view: pluginViewFactory({
Expand Down
65 changes: 65 additions & 0 deletions e2e/src/lit/components/Link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { ShallowLitElement, useMarkViewContext } from '@prosemirror-adapter/lit'
import { html } from 'lit'
import { customElement, state } from 'lit/decorators.js'
import { ref } from 'lit/directives/ref.js'

const colors = [
'#f06292',
'#ba68c8',
'#9575cd',
'#7986cb',
'#64b5f6',
'#4fc3f7',
'#4dd0e1',
'#4db6ac',
'#81c784',
'#aed581',
'#ffb74d',
'#ffa726',
'#ff8a65',
'#d4e157',
'#ffd54f',
'#ffecb3',
]

function pickRandomColor() {
return colors[Math.floor(Math.random() * colors.length)]
}

@customElement('my-link')
export class Link extends ShallowLitElement {
markViewContext = useMarkViewContext(this)

@state()
color = colors[0]

timer: ReturnType<typeof setInterval> | null = null

override render() {
const ctx = this.markViewContext.value
if (!ctx)
return
const { contentRef } = ctx
return html`<a style="color: ${this.color}; transition: color 1s ease-in-out;" ${ref(contentRef)}></a>`
}

override connectedCallback() {
super.connectedCallback()
this.timer = setInterval(() => {
this.color = pickRandomColor()
}, 1000)
}

override disconnectedCallback() {
super.disconnectedCallback()
if (this.timer) {
clearInterval(this.timer)
}
}
}

declare global {
interface HTMLElementTagNameMap {
'my-link': Link
}
}
10 changes: 8 additions & 2 deletions e2e/src/react/components/Editor.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import './Editor.css'

import { useNodeViewFactory, usePluginViewFactory, useWidgetViewFactory } from '@prosemirror-adapter/react'
import { useMarkViewFactory, useNodeViewFactory, usePluginViewFactory, useWidgetViewFactory } from '@prosemirror-adapter/react'
import type { EditorView } from 'prosemirror-view'
import { DecorationSet } from 'prosemirror-view'
import type { FC } from 'react'
Expand All @@ -12,10 +12,12 @@ import { Hashes } from './Hashes'
import { Heading } from './Heading'
import { Paragraph } from './Paragraph'
import { Size } from './Size'
import { Link } from './Link'

export const Editor: FC = () => {
const viewRef = useRef<EditorView>()
const nodeViewFactory = useNodeViewFactory()
const markViewFactory = useMarkViewFactory()
const pluginViewFactory = usePluginViewFactory()
const widgetViewFactory = useWidgetViewFactory()
const editorRef = useRef<HTMLDivElement>(null)
Expand All @@ -42,6 +44,10 @@ export const Editor: FC = () => {
heading: nodeViewFactory({
component: Heading,
}),
}, {
link: markViewFactory({
component: Link,
}),
}, [
new Plugin({
view: pluginViewFactory({
Expand Down Expand Up @@ -69,7 +75,7 @@ export const Editor: FC = () => {
return () => {
viewRef.current?.destroy()
}
}, [nodeViewFactory, pluginViewFactory, widgetViewFactory])
}, [nodeViewFactory, markViewFactory, pluginViewFactory, widgetViewFactory])

return <div className="editor" ref={editorRef} />
}
49 changes: 49 additions & 0 deletions e2e/src/react/components/Link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useEffect, useState } from 'react'
import { useMarkViewContext } from '@prosemirror-adapter/react'

const colors = [
'#f06292',
'#ba68c8',
'#9575cd',
'#7986cb',
'#64b5f6',
'#4fc3f7',
'#4dd0e1',
'#4db6ac',
'#81c784',
'#aed581',
'#ffb74d',
'#ffa726',
'#ff8a65',
'#d4e157',
'#ffd54f',
'#ffecb3',
]

function pickRandomColor() {
return colors[Math.floor(Math.random() * colors.length)]
}

export function Link() {
const [color, setColor] = useState(colors[0])
const { mark, contentRef } = useMarkViewContext()
const href = mark.attrs.href as string
const title = mark.attrs.title as string | null

useEffect(() => {
const interval = setInterval(() => {
setColor(pickRandomColor())
}, 1000)
return () => clearInterval(interval)
}, [])

return (
<a
href={href}
ref={contentRef}
style={{ color, transition: 'color 1s ease-in-out' }}
title={title || undefined}
>
</a>
)
}
8 changes: 8 additions & 0 deletions e2e/src/solid/components/Editor.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DecorationSet } from 'prosemirror-view'
import {
useMarkViewFactory,
useNodeViewFactory,
usePluginViewFactory,
useWidgetViewFactory,
Expand All @@ -10,9 +11,11 @@ import { Paragraph } from './Paragraph'
import { Hashes } from './Hashes'
import { Heading } from './Heading'
import { Size } from './Size'
import { Link } from './Link'

export function Editor() {
const nodeViewFactory = useNodeViewFactory()
const markViewFactory = useMarkViewFactory()
const widgetViewFactory = useWidgetViewFactory()
const pluginViewFactory = usePluginViewFactory()

Expand All @@ -37,6 +40,11 @@ export function Editor() {
component: Heading,
}),
},
{
link: markViewFactory({
component: Link,
}),
},
[
new Plugin({
view: pluginViewFactory({
Expand Down
48 changes: 48 additions & 0 deletions e2e/src/solid/components/Link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useMarkViewContext } from '@prosemirror-adapter/solid'
import { createEffect, createMemo, createSignal, onCleanup } from 'solid-js'

const colors = [
'#f06292',
'#ba68c8',
'#9575cd',
'#7986cb',
'#64b5f6',
'#4fc3f7',
'#4dd0e1',
'#4db6ac',
'#81c784',
'#aed581',
'#ffb74d',
'#ffa726',
'#ff8a65',
'#d4e157',
'#ffd54f',
'#ffecb3',
]

function pickRandomColor() {
return colors[Math.floor(Math.random() * colors.length)]
}

export function Link() {
const [color, setColor] = createSignal(colors[0])
const context = useMarkViewContext()
const href = createMemo(() => context().mark.attrs.href as string)
const title = createMemo(() => context().mark.attrs.title as string | null)
createEffect(() => {
const interval = setInterval(() => {
setColor(pickRandomColor())
}, 1000)
return onCleanup(() => clearInterval(interval))
})

return (
<a
href={href()}
title={title() || undefined}
ref={context().contentRef}
style={{ color: color(), transition: 'color 1s ease-in-out' }}
>
</a>
)
}
11 changes: 10 additions & 1 deletion e2e/src/svelte/components/Editor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import {
useNodeViewFactory,
usePluginViewFactory,
useWidgetViewFactory
useWidgetViewFactory,
useMarkViewFactory,
} from "@prosemirror-adapter/svelte";
import { Plugin } from 'prosemirror-state'
import {DecorationSet, EditorView} from "prosemirror-view";
Expand All @@ -12,8 +13,10 @@
import Paragraph from "./Paragraph.svelte";
import Heading from "./Heading.svelte";
import Size from "./Size.svelte";
import Link from "./Link.svelte";
const nodeViewFactory = useNodeViewFactory()
const markViewFactory = useMarkViewFactory()
const pluginViewFactory = usePluginViewFactory()
const widgetViewFactory = useWidgetViewFactory()
Expand All @@ -34,6 +37,12 @@
heading: nodeViewFactory({
component: Heading
})
}, {
link: markViewFactory({
component: Link,
as: 'span',
contentAs: 'span'
})
}, [
new Plugin({
view: pluginViewFactory({
Expand Down
Loading

0 comments on commit 473af64

Please sign in to comment.