Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .templates/template-nuxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"preview": "nuxt preview"
},
"dependencies": {
"nuxt": "^4.3.0",
"nuxt": "^4.3.1",
"vue": "^3.5.27"
},
"devDependencies": {
Expand Down
5 changes: 5 additions & 0 deletions lit-minimal/src/components/editor/examples/minimal/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export class LitEditor extends LitElement {
return this
}

override disconnectedCallback() {
this.editor.unmount()
super.disconnectedCallback()
}

override updated(changedProperties: PropertyValues) {
super.updated(changedProperties)
this.editor.mount(this.ref.value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ export class LitEditor extends LitElement {
return this
}

override disconnectedCallback() {
this.editor.unmount()
super.disconnectedCallback()
}

override updated(changedProperties: PropertyValues) {
super.updated(changedProperties)
this.editor.mount(this.ref.value)
Expand Down
4 changes: 4 additions & 0 deletions lit-toolbar/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
dist
.next
.svelte-kit
15 changes: 15 additions & 0 deletions lit-toolbar/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# lit-toolbar

A [ProseKit](https://prosekit.dev) example.

[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/prosekit/examples/tree/master/lit-toolbar)
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/prosekit/examples/tree/master/lit-toolbar)

Run the example locally with:

```bash
npx degit prosekit/examples/lit-toolbar lit-toolbar
cd lit-toolbar
npm install
npm run dev
```
13 changes: 13 additions & 0 deletions lit-toolbar/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ProseKit + Lit</title>
<script type="module" src="/src/app.ts"></script>
</head>

<body>
<my-app></my-app>
</body>
</html>
25 changes: 25 additions & 0 deletions lit-toolbar/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "example-lit-toolbar",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "tsc && vite build",
"dev": "vite",
"preview": "vite preview"
},
"dependencies": {
"lit": "^3.3.2",
"prosekit": "^0.17.1"
},
"devDependencies": {
"@egoist/tailwindcss-icons": "^1.9.2",
"@iconify-json/lucide": "^1.2.89",
"@tailwindcss/vite": "^4.1.18",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.18",
"tw-animate-css": "^1.4.0",
"typescript": "5.9.3",
"vite": "7.3.1"
}
}
13 changes: 13 additions & 0 deletions lit-toolbar/src/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@import 'tailwindcss';
@import 'tw-animate-css';

@plugin "@egoist/tailwindcss-icons";

body {
height: 100svh;
display: grid;
max-width: 900px;
padding: 16px;
margin-left: auto;
margin-right: auto;
}
18 changes: 18 additions & 0 deletions lit-toolbar/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import './app.css'
import './editor'

import { LitElement, html } from 'lit'
import { customElement } from 'lit/decorators.js'

@customElement('my-app')
export class MyApp extends LitElement {
createRenderRoot() {
return this
}

render() {
return html`
<my-editor></my-editor>
`
}
}
82 changes: 82 additions & 0 deletions lit-toolbar/src/components/editor/examples/toolbar/editor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import 'prosekit/basic/style.css'
import 'prosekit/basic/typography.css'

import '../../ui/toolbar/index'

import {
html,
LitElement,
type PropertyDeclaration,
type PropertyValues,
} from 'lit'
import { createRef, ref, type Ref } from 'lit/directives/ref.js'
import type { Editor } from 'prosekit/core'
import { createEditor } from 'prosekit/core'

import { sampleUploader } from '../../sample/sample-uploader'

import { defineExtension } from './extension'

export class LitEditor extends LitElement {
static override properties = {
editor: {
state: true,
attribute: false,
} satisfies PropertyDeclaration<Editor>,
}

private editor: Editor
private ref: Ref<HTMLDivElement>

constructor() {
super()

const extension = defineExtension()
this.editor = createEditor({ extension })
this.ref = createRef<HTMLDivElement>()
}

override createRenderRoot() {
return this
}

override disconnectedCallback() {
this.editor.unmount()
super.disconnectedCallback()
}

override updated(changedProperties: PropertyValues) {
super.updated(changedProperties)
this.editor.mount(this.ref.value)
}

override render() {
return html`
<div
class="box-border h-full w-full min-h-36 overflow-y-hidden overflow-x-hidden rounded-md border border-solid border-gray-200 dark:border-gray-700 shadow-sm flex flex-col bg-white dark:bg-gray-950 text-black dark:text-white"
>
<lit-editor-toolbar
.editor=${this.editor}
.uploader=${sampleUploader}
></lit-editor-toolbar>
<div class="relative w-full flex-1 box-border overflow-y-auto">
<div
${ref(this.ref)}
class="ProseMirror box-border min-h-full px-[max(4rem,calc(50%-20rem))] py-8 outline-hidden outline-0 [&_span[data-mention=user]]:text-blue-500 [&_span[data-mention=tag]]:text-violet-500"
></div>
</div>
</div>
`
}
}

export function registerLitEditor() {
if (customElements.get('lit-editor-example-toolbar')) return
customElements.define('lit-editor-example-toolbar', LitEditor)
}

declare global {
interface HTMLElementTagNameMap {
'lit-editor-example-toolbar': LitEditor
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineBasicExtension } from 'prosekit/basic'
import { union } from 'prosekit/core'

export function defineExtension() {
return union(defineBasicExtension())
}

export type EditorExtension = ReturnType<typeof defineExtension>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { LitEditor as ExampleEditor, registerLitEditor } from './editor'
54 changes: 54 additions & 0 deletions lit-toolbar/src/components/editor/sample/sample-uploader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { Uploader } from 'prosekit/extensions/file'

/**
* Uploads the given file to https://tmpfiles.org/ and returns the URL of the
* uploaded file.
*
* This function is only for demonstration purposes. All uploaded files will be
* deleted by the server after 1 hour.
*/
export const sampleUploader: Uploader<string> = ({
file,
onProgress,
}): Promise<string> => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
const formData = new FormData()
formData.append('file', file)

xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
onProgress({
loaded: event.loaded,
total: event.total,
})
}
})

xhr.addEventListener('load', () => {
if (xhr.status === 200) {
try {
const json = JSON.parse(xhr.responseText) as { data: { url: string } }
const url: string = json.data.url.replace(
'tmpfiles.org/',
'tmpfiles.org/dl/',
)

// Simulate a larger delay
setTimeout(() => resolve(url), 1000)
} catch (error) {
reject(new Error('Failed to parse response', { cause: error }))
}
} else {
reject(new Error(`Upload failed with status ${xhr.status}`))
}
})

xhr.addEventListener('error', () => {
reject(new Error('Upload failed'))
})

xhr.open('POST', 'https://tmpfiles.org/api/v1/upload', true)
xhr.send(formData)
})
}
76 changes: 76 additions & 0 deletions lit-toolbar/src/components/editor/ui/button/button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import 'prosekit/lit/tooltip'

import { html, LitElement, nothing, type PropertyDeclaration } from 'lit'

class LitButton extends LitElement {
static override properties = {
pressed: { type: Boolean },
disabled: { type: Boolean },
tooltip: { type: String },
icon: { type: String },
} satisfies Record<string, PropertyDeclaration>

pressed = false
disabled = false
tooltip = ''
icon = ''

override createRenderRoot() {
return this
}

override connectedCallback() {
super.connectedCallback()
this.classList.add('contents')
}

private handleMouseDown = (event: MouseEvent) => {
// Prevent the editor from being blurred when the button is clicked
event.preventDefault()
}

override render() {
const tooltip = this.tooltip

return html`
<prosekit-tooltip-root>
<prosekit-tooltip-trigger class="block">
<button
data-state=${this.pressed ? 'on' : 'off'}
class="outline-unset focus-visible:outline-unset flex items-center justify-center rounded-md p-2 font-medium transition focus-visible:ring-2 text-sm focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 disabled:pointer-events-none min-w-9 min-h-9 text-gray-900 dark:text-gray-50 disabled:text-gray-900/50 dark:disabled:text-gray-50/50 bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 data-[state=on]:bg-gray-200 dark:data-[state=on]:bg-gray-700"
?disabled=${this.disabled}
@mousedown=${this.handleMouseDown}
>
${this.icon
? html`
<div class="${this.icon}"></div>
`
: nothing}
${tooltip
? html`
<span class="sr-only">${tooltip}</span>
`
: nothing}
</button>
</prosekit-tooltip-trigger>
${tooltip
? html`
<prosekit-tooltip-content
class="z-50 overflow-hidden rounded-md border border-solid bg-gray-900 dark:bg-gray-50 px-3 py-1.5 text-xs text-gray-50 dark:text-gray-900 shadow-xs [&:not([data-state])]:hidden will-change-transform motion-safe:data-[state=open]:animate-in motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=open]:fade-in-0 motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=open]:zoom-in-95 motion-safe:data-[state=closed]:zoom-out-95 motion-safe:data-[state=open]:animate-duration-150 motion-safe:data-[state=closed]:animate-duration-200 motion-safe:data-[side=bottom]:slide-in-from-top-2 motion-safe:data-[side=bottom]:slide-out-to-top-2 motion-safe:data-[side=left]:slide-in-from-right-2 motion-safe:data-[side=left]:slide-out-to-right-2 motion-safe:data-[side=right]:slide-in-from-left-2 motion-safe:data-[side=right]:slide-out-to-left-2 motion-safe:data-[side=top]:slide-in-from-bottom-2 motion-safe:data-[side=top]:slide-out-to-bottom-2"
>
${tooltip}
</prosekit-tooltip-content>
`
: nothing}
</prosekit-tooltip-root>
`
}
}

customElements.define('lit-editor-button', LitButton)

declare global {
interface HTMLElementTagNameMap {
'lit-editor-button': LitButton
}
}
1 change: 1 addition & 0 deletions lit-toolbar/src/components/editor/ui/button/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import './button'
Loading