Skip to content

Commit

Permalink
feat: add solid support (#86)
Browse files Browse the repository at this point in the history
* feat: add `solid` support

* fix: 🐛 Adapting the reactive system for solidjs

* fix: 🐛 wrap nodeView.render with `runWithOwner``

* chore: 🤖 revert unnecessary modifications

* fix: 🐛 use `Dynamic` to render the UserComponent

* test: 💍 add cypress test for `solid-js` adapater

* fix: 🐛 fix export setting in `package.json` of `solid` pkg

* ci: 🎡 temporarily fix ci error

* fix: 🐛 fix e2e  bundle error
  • Loading branch information
shlroland authored Sep 17, 2024
1 parent e9d291c commit 7547fca
Show file tree
Hide file tree
Showing 55 changed files with 3,518 additions and 66 deletions.
5 changes: 3 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"prosemirror"
],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
"source.fixAll.eslint": "explicit"
},
"typescript.tsdk": "node_modules/typescript/lib"
}
34 changes: 34 additions & 0 deletions e2e/cypress/e2e/solid/node-view.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Cypress.config('baseUrl', `http://localhost:${Cypress.env('SERVER_PORT')}`)

beforeEach(() => {
cy.visit('/solid/')
})

it('heading node view render', () => {
cy.get('.editor')
.get('[data-node-view-root="true"]')
.get('h3')
.get('[data-node-view-content="true"]')
.contains('Hello ProseMirror')
})

it('paragraph node view render', () => {
cy.get('.editor')
.get('blockquote')
.get('[data-node-view-root="true"]')
.get('p')
.get('[data-node-view-content="true"]')
.contains('This is editable text')
})

it('heading node view update', () => {
const isMac = Cypress.platform === 'darwin'

const key = isMac ? '{cmd+[}' : '{ctrl+[}'

cy.get('.editor').type(`{selectAll}{leftArrow}${key}`, { delay: 500 })
cy.get('.editor').get('[data-node-view-root="true"]').get('h4').should('exist')

cy.get('.editor').type(`{selectAll}{leftArrow}${key}`, { delay: 500 })
cy.get('.editor').get('[data-node-view-root="true"]').get('h5').should('exist')
})
23 changes: 23 additions & 0 deletions e2e/cypress/e2e/solid/plugin-view.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Cypress.config('baseUrl', `http://localhost:${Cypress.env('SERVER_PORT')}`)

beforeEach(() => {
cy.visit('/solid/')
})

it('Size plugin view render', () => {
cy.get('.editor')
.get('[data-test-id="size-view-plugin"]')
.contains('Size for document:523')

cy.get('.editor').type('OK')

cy.get('.editor')
.get('[data-test-id="size-view-plugin"]')
.contains('Size for document:525')

cy.get('.editor').type('{backspace}')

cy.get('.editor')
.get('[data-test-id="size-view-plugin"]')
.contains('Size for document:524')
})
25 changes: 25 additions & 0 deletions e2e/cypress/e2e/solid/widget-view.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Cypress.config('baseUrl', `http://localhost:${Cypress.env('SERVER_PORT')}`)

beforeEach(() => {
cy.visit('/solid/')
})

it('Focus heading to render widget', () => {
cy.get('.editor')
.find('h3')
.click()

cy.get('i[data-widget-view-root="true"]')
.contains('###')

cy.get('.editor').click()

cy.get('i[data-widget-view-root="true"]').should('not.exist')

cy.get('.editor')
.find('h3')
.type('{enter}# Heading 1')

cy.get('i[data-widget-view-root="true"]')
.contains('#')
})
3 changes: 3 additions & 0 deletions e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@lit-labs/context": "^0.5.0",
"@prosemirror-adapter/lit": "workspace:*",
"@prosemirror-adapter/react": "workspace:*",
"@prosemirror-adapter/solid": "workspace:*",
"@prosemirror-adapter/svelte": "workspace:*",
"@prosemirror-adapter/vue": "workspace:*",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
Expand All @@ -30,8 +31,10 @@
"prosemirror-schema-basic": "^1.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"solid-js": "^1.8.11",
"start-server-and-test": "^2.0.0",
"svelte": "^4.0.0",
"vite-plugin-solid": "^2.8.2",
"vue": "3.5.3"
}
}
68 changes: 68 additions & 0 deletions e2e/src/solid/components/Editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { DecorationSet } from 'prosemirror-view'
import {
useNodeViewFactory,
usePluginViewFactory,
useWidgetViewFactory,
} from '@prosemirror-adapter/solid'
import { Plugin } from 'prosemirror-state'
import { createEditorView } from '../../createEditorView'
import { Paragraph } from './Paragraph'
import { Hashes } from './Hashes'
import { Heading } from './Heading'
import { Size } from './Size'

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

const editorRef = (element: HTMLDivElement) => {
if (element.firstChild)
return

const getHashWidget = widgetViewFactory({
as: 'i',
component: Hashes,
})

createEditorView(
element,
{
paragraph: nodeViewFactory({
component: Paragraph,
as: 'div',
contentAs: 'p',
}),
heading: nodeViewFactory({
component: Heading,
}),
},
[
new Plugin({
view: pluginViewFactory({
component: Size,
}),
}),
new Plugin({
props: {
decorations(state) {
const { $from } = state.selection
const node = $from.node()
if (node.type.name !== 'heading')
return DecorationSet.empty

const widget = getHashWidget($from.before() + 1, {
side: -1,
level: node.attrs.level,
})

return DecorationSet.create(state.doc, [widget])
},
},
}),
],
)
}

return <div class="editor" ref={editorRef}></div>
}
14 changes: 14 additions & 0 deletions e2e/src/solid/components/Hashes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useWidgetViewContext } from '@prosemirror-adapter/solid'
import { createMemo } from 'solid-js'

export function Hashes() {
const context = useWidgetViewContext()
const level = createMemo(() => context().spec?.level)
const hashes = createMemo(() => new Array(level() || 0).fill('#').join(''))

return (
<span style={{ 'color': 'blue', 'margin-right': '6px' }}>
{hashes()}
</span>
)
}
10 changes: 10 additions & 0 deletions e2e/src/solid/components/Heading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useNodeViewContext } from '@prosemirror-adapter/solid'
import { createMemo } from 'solid-js'
import { Dynamic } from 'solid-js/web'

export function Heading() {
const context = useNodeViewContext()
const Tag = createMemo(() => `h${context().node.attrs.level}` as 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6')

return <Dynamic component={Tag()} ref={context().contentRef} />
}
7 changes: 7 additions & 0 deletions e2e/src/solid/components/Paragraph.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { useNodeViewContext } from '@prosemirror-adapter/solid'

export function Paragraph() {
const context = useNodeViewContext()

return <div style={{ outline: context().selected ? 'blue solid 1px' : 'none' }} role="presentation" ref={context().contentRef} />
}
14 changes: 14 additions & 0 deletions e2e/src/solid/components/Size.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { usePluginViewContext } from '@prosemirror-adapter/solid'
import { createMemo } from 'solid-js'

export function Size() {
const context = usePluginViewContext()
const size = createMemo(() => context().view.state.doc.nodeSize)

return (
<div data-test-id="size-view-plugin">
Size for document:
{size()}
</div>
)
}
29 changes: 29 additions & 0 deletions e2e/src/solid/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.editor {
background: white;
color: black;
background-clip: padding-box;
border-radius: 4px;
border: 2px solid rgba(0, 0, 0, 0.2);
padding: 5px 0;
margin-bottom: 23px;
}

.ProseMirror p:first-child,
.ProseMirror h1:first-child,
.ProseMirror h2:first-child,
.ProseMirror h3:first-child,
.ProseMirror h4:first-child,
.ProseMirror h5:first-child,
.ProseMirror h6:first-child {
margin-top: 10px;
}

.ProseMirror {
padding: 4px 8px 4px 14px;
line-height: 1.2;
outline: none;
}

.ProseMirror p {
margin-bottom: 1em;
}
34 changes: 34 additions & 0 deletions e2e/src/solid/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Solid E2E</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<div style="display: none" id="content">
<h3>Hello ProseMirror</h3>

<blockquote>
<p>This is editable text. You can focus it and start typing.</p>
</blockquote>

<p>To apply styling, you can select a piece of text and manipulate
its styling from the menu. The basic schema
supports <em>emphasis</em>, <strong>strong
text</strong>, <a href="http://marijnhaverbeke.nl/blog">links</a>, <code>code
font</code>, and <img src="https://prosemirror.net/img/smiley.png"> images.</p>

<p>Block-level structure can be manipulated with key bindings (try
ctrl-shift-2 to create a level 2 heading, or enter in an empty
textblock to exit the parent block), or through the menu.</p>

<p>Try using the “list” item in the menu to wrap this paragraph in
a numbered list.</p>
</div>
<script src="/solid/index.tsx" type="module"></script>
</body>
</html>
27 changes: 27 additions & 0 deletions e2e/src/solid/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* @refresh reload */
import { render } from 'solid-js/web'

import './index.css'
import { ProsemirrorAdapterProvider } from '@prosemirror-adapter/solid'
import { Editor } from './components/Editor'

const root = document.getElementById('root')

if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
throw new Error(
'Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?',
)
}

render(
() => (
<>
<h1>Prosemirror Adapter React</h1>

<ProsemirrorAdapterProvider>
<Editor />
</ProsemirrorAdapterProvider>
</>
),
root!,
)
7 changes: 7 additions & 0 deletions e2e/src/solid/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "solid-js"
}
}
13 changes: 12 additions & 1 deletion e2e/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,21 @@ import { resolve } from 'node:path'
import react from '@vitejs/plugin-react'
import vue from '@vitejs/plugin-vue'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import solid from 'vite-plugin-solid'
import { defineConfig } from 'vite'

export default defineConfig({
root: 'src',
plugins: [react(), vue(), svelte()],
plugins: [
vue(),
svelte(),
react({
include: ['src/react/**/*'],
}),
solid({
include: ['src/solid/**/*'],
}),
],
optimizeDeps: {
exclude: ['svelte'],
},
Expand All @@ -20,6 +30,7 @@ export default defineConfig({
vue: resolve(__dirname, 'src/vue/index.html'),
svelte: resolve(__dirname, 'src/svelte/index.html'),
lit: resolve(__dirname, 'src/lit/index.html'),
solid: resolve(__dirname, 'src/solid/index.html'),
},
},
},
Expand Down
2 changes: 2 additions & 0 deletions examples/solid/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
dist
21 changes: 21 additions & 0 deletions examples/solid/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Example for @prosemirror-adapter/solid

A simple example for [`@prosemirror-adapter/solid`](../../packages/solid/).

[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/Saul-Mirone/prosemirror-adapter/tree/main/examples/solid)

## Getting Started

1. Clone the repo.

2. Install dependencies by `pnpm install`.

3. Run the example by `pnpm --filter=@examples/solid start`.

## Contributing

Follow our [contribution guide](../../CONTRIBUTING.md) to learn how to contribute to prosemirror-adapter.

## License

[MIT](../../LICENSE)
Loading

0 comments on commit 7547fca

Please sign in to comment.