mirror of
https://github.com/ueberdosis/tiptap.git
synced 2025-06-07 17:43:49 +08:00
Merge branch 'main' of github.com:ueberdosis/tiptap
This commit is contained in:
commit
51aa314034
@ -65,7 +65,6 @@ const getInitialUser = () => {
|
|||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const [status, setStatus] = useState('connecting')
|
const [status, setStatus] = useState('connecting')
|
||||||
const [users, setUsers] = useState([])
|
|
||||||
const [currentUser, setCurrentUser] = useState(getInitialUser)
|
const [currentUser, setCurrentUser] = useState(getInitialUser)
|
||||||
|
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
@ -84,9 +83,6 @@ export default () => {
|
|||||||
}),
|
}),
|
||||||
CollaborationCursor.configure({
|
CollaborationCursor.configure({
|
||||||
provider: websocketProvider,
|
provider: websocketProvider,
|
||||||
onUpdate: updatedUsers => {
|
|
||||||
setUsers(updatedUsers)
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@ -96,7 +92,7 @@ export default () => {
|
|||||||
const indexeddbProvider = new IndexeddbPersistence(room, ydoc)
|
const indexeddbProvider = new IndexeddbPersistence(room, ydoc)
|
||||||
|
|
||||||
indexeddbProvider.on('synced', () => {
|
indexeddbProvider.on('synced', () => {
|
||||||
console.log('Loaded content from database …')
|
console.log('Loaded content from database …')
|
||||||
})
|
})
|
||||||
|
|
||||||
// Update status changes
|
// Update status changes
|
||||||
@ -109,7 +105,7 @@ export default () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editor && currentUser) {
|
if (editor && currentUser) {
|
||||||
localStorage.setItem('currentUser', JSON.stringify(currentUser))
|
localStorage.setItem('currentUser', JSON.stringify(currentUser))
|
||||||
editor.chain().focus().user(currentUser).run()
|
editor.chain().focus().updateUser(currentUser).run()
|
||||||
}
|
}
|
||||||
}, [editor, currentUser])
|
}, [editor, currentUser])
|
||||||
|
|
||||||
@ -128,7 +124,7 @@ export default () => {
|
|||||||
<div className="editor__footer">
|
<div className="editor__footer">
|
||||||
<div className={`editor__status editor__status--${status}`}>
|
<div className={`editor__status editor__status--${status}`}>
|
||||||
{status === 'connected'
|
{status === 'connected'
|
||||||
? `${users.length} user${users.length === 1 ? '' : 's'} online in ${room}`
|
? `${editor.storage.collaborationCursor.users.length} user${editor.storage.collaborationCursor.users.length === 1 ? '' : 's'} online in ${room}`
|
||||||
: 'offline'}
|
: 'offline'}
|
||||||
</div>
|
</div>
|
||||||
<div className="editor__name">
|
<div className="editor__name">
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<div class="editor__footer">
|
<div class="editor__footer">
|
||||||
<div :class="`editor__status editor__status--${status}`">
|
<div :class="`editor__status editor__status--${status}`">
|
||||||
<template v-if="status === 'connected'">
|
<template v-if="status === 'connected'">
|
||||||
{{ users.length }} user{{ users.length === 1 ? '' : 's' }} online in {{ room }}
|
{{ editor.storage.collaborationCursor.users.length }} user{{ editor.storage.collaborationCursor.users.length === 1 ? '' : 's' }} online in {{ room }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
offline
|
offline
|
||||||
@ -61,7 +61,6 @@ export default {
|
|||||||
provider: null,
|
provider: null,
|
||||||
indexdb: null,
|
indexdb: null,
|
||||||
editor: null,
|
editor: null,
|
||||||
users: [],
|
|
||||||
status: 'connecting',
|
status: 'connecting',
|
||||||
room: getRandomRoom(),
|
room: getRandomRoom(),
|
||||||
}
|
}
|
||||||
@ -74,8 +73,6 @@ export default {
|
|||||||
this.status = event.status
|
this.status = event.status
|
||||||
})
|
})
|
||||||
|
|
||||||
window.ydoc = ydoc
|
|
||||||
|
|
||||||
this.indexdb = new IndexeddbPersistence(this.room, ydoc)
|
this.indexdb = new IndexeddbPersistence(this.room, ydoc)
|
||||||
|
|
||||||
this.editor = new Editor({
|
this.editor = new Editor({
|
||||||
@ -92,9 +89,6 @@ export default {
|
|||||||
CollaborationCursor.configure({
|
CollaborationCursor.configure({
|
||||||
provider: this.provider,
|
provider: this.provider,
|
||||||
user: this.currentUser,
|
user: this.currentUser,
|
||||||
onUpdate: users => {
|
|
||||||
this.users = users
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
CharacterCount.configure({
|
CharacterCount.configure({
|
||||||
limit: 10000,
|
limit: 10000,
|
||||||
@ -120,7 +114,7 @@ export default {
|
|||||||
|
|
||||||
updateCurrentUser(attributes) {
|
updateCurrentUser(attributes) {
|
||||||
this.currentUser = { ...this.currentUser, ...attributes }
|
this.currentUser = { ...this.currentUser, ...attributes }
|
||||||
this.editor.chain().focus().user(this.currentUser).run()
|
this.editor.chain().focus().updateUser(this.currentUser).run()
|
||||||
|
|
||||||
localStorage.setItem('currentUser', JSON.stringify(this.currentUser))
|
localStorage.setItem('currentUser', JSON.stringify(this.currentUser))
|
||||||
},
|
},
|
||||||
|
@ -43,11 +43,15 @@ A render function for the cursor, look at [the extension source code](https://gi
|
|||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
### user()
|
### updateUser()
|
||||||
An object with the attributes of the current user. It expects a `name` and a `color`, but you can add additional fields, too.
|
Pass an object with updated attributes of the current user. It expects a `name` and a `color`, but you can add additional fields, too.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
editor.commands.user({ name: 'Philipp Kühn', color: '#000000' })
|
editor.commands.updateUser({
|
||||||
|
name: 'Philipp Kühn',
|
||||||
|
color: '#000000',
|
||||||
|
avatar: 'https://unavatar.io/github/philippkuehn',
|
||||||
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
## Source code
|
## Source code
|
||||||
|
@ -7,7 +7,7 @@ tableOfContents: true
|
|||||||
## Introduction
|
## Introduction
|
||||||
The whole Tiptap is code base is written in TypeScript. If you haven’t heard of it or never used it, no worries. You don’t have to.
|
The whole Tiptap is code base is written in TypeScript. If you haven’t heard of it or never used it, no worries. You don’t have to.
|
||||||
|
|
||||||
TypeScript extends JavaScript by adding types (hence the name). It adds new syntax, which doesn’t exist in Vanilla JavaScript. It’s actually removed before running in the browser, but this step – the compilation – is important to find bugs early. It checks if you passe the right types of data to functions. For a big and complex project, that’s very valuable. It means we’ll get notified of lot of bugs, before shipping code to you.
|
TypeScript extends JavaScript by adding types (hence the name). It adds new syntax, which doesn’t exist in Vanilla JavaScript. It’s actually removed before running in the browser, but this step – the compilation – is important to find bugs early. It checks if you pass the right types of data to functions. For a big and complex project, that’s very valuable. It means we’ll get notified of lot of bugs, before shipping code to you.
|
||||||
|
|
||||||
**Anyway, if you don’t use TypeScript in your project, that’s fine.** You will still be able to use Tiptap and nevertheless get a nice autocomplete for the Tiptap API (if your editor supports it, but most do).
|
**Anyway, if you don’t use TypeScript in your project, that’s fine.** You will still be able to use Tiptap and nevertheless get a nice autocomplete for the Tiptap API (if your editor supports it, but most do).
|
||||||
|
|
||||||
|
10
docs/jobs.md
10
docs/jobs.md
@ -1,13 +1,13 @@
|
|||||||
# Jobs
|
# Jobs
|
||||||
Some great companies are looking for developers right now. If you’re looking for a job to work with Tiptap and/or Hocuspocus, consider applying:
|
You enjoy to work with Tiptap? You are not alone. Some amazing companies are looking for developers with some Tiptap and/or [Hocuspocus](https://hocuspocus.dev) experience.
|
||||||
|
|
||||||
**[Software Engineer](https://gamma.app/docs/Software-Engineer-6s0e0grm9zk9w5s) @ Gamma**<br>
|
- **[Software Engineer](https://gamma.app/docs/Software-Engineer-6s0e0grm9zk9w5s) @ Gamma**<br>
|
||||||
React · Tiptap · San Francisco, CA
|
React · Tiptap · San Francisco, CA
|
||||||
|
|
||||||
**[Frontend Engineer](https://birdeatsbug.recruitee.com/o/frontend-engineer-vuejs-graphql-browser-extension) @ Bird Eats Bug**<br>
|
- **[Javascript Engineer, Software Engineer, …](https://birdeatsbug.recruitee.com/) @ Bird Eats Bug**<br>
|
||||||
Vue.js · Tiptap · Remote · Berlin, Germany
|
Vue.js · Tiptap · Remote · Berlin, Germany
|
||||||
|
|
||||||
**[Frontend Developer](https://bitcrowd.net/jobs) @ bitcrowd**<br>
|
- **[Frontend Developer](https://bitcrowd.net/jobs) @ bitcrowd**<br>
|
||||||
Tiptap · Remote · Berlin, Germany
|
Tiptap · Remote · Berlin, Germany
|
||||||
|
|
||||||
Is your company hiring, too? We have more than 200,000 page views/month from people who love to work with Tiptap. Maybe we can find someone for you. Reach out to [humans@tiptap.dev](mailto:humans@tiptap.dev) with a link to your job description!
|
Is your company hiring, too? We have [200,000 page views/month](https://plausible.io/tiptap.dev?period=30d) from people who love to work with Tiptap. Maybe we can help you find the right person. Reach out to [humans@tiptap.dev](mailto:humans@tiptap.dev) with a link to your job description!
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
import { Extension } from '@tiptap/core'
|
import { Extension } from '@tiptap/core'
|
||||||
import { yCursorPlugin } from 'y-prosemirror'
|
import { yCursorPlugin } from 'y-prosemirror'
|
||||||
|
|
||||||
|
type CollaborationCursorStorage = {
|
||||||
|
users: { clientId: number, [key: string]: any }[],
|
||||||
|
}
|
||||||
|
|
||||||
export interface CollaborationCursorOptions {
|
export interface CollaborationCursorOptions {
|
||||||
provider: any,
|
provider: any,
|
||||||
user: Record<string, any>,
|
user: Record<string, any>,
|
||||||
render (user: Record<string, any>): HTMLElement,
|
render (user: Record<string, any>): HTMLElement,
|
||||||
|
/**
|
||||||
|
* @deprecated The "onUpdate" option is deprecated. Please use `editor.storage.collaborationCursor.users` instead. Read more: https://tiptap.dev/api/extensions/collaboration-cursor
|
||||||
|
*/
|
||||||
onUpdate: (users: { clientId: number, [key: string]: any }[]) => null,
|
onUpdate: (users: { clientId: number, [key: string]: any }[]) => null,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -14,6 +21,12 @@ declare module '@tiptap/core' {
|
|||||||
/**
|
/**
|
||||||
* Update details of the current user
|
* Update details of the current user
|
||||||
*/
|
*/
|
||||||
|
updateUser: (attributes: Record<string, any>) => ReturnType,
|
||||||
|
/**
|
||||||
|
* Update details of the current user
|
||||||
|
*
|
||||||
|
* @deprecated The "user" command is deprecated. Please use "updateUser" instead. Read more: https://tiptap.dev/api/extensions/collaboration-cursor
|
||||||
|
*/
|
||||||
user: (attributes: Record<string, any>) => ReturnType,
|
user: (attributes: Record<string, any>) => ReturnType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,7 +41,9 @@ const awarenessStatesToArray = (states: Map<number, Record<string, any>>) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CollaborationCursor = Extension.create<CollaborationCursorOptions>({
|
const defaultOnUpdate = () => null
|
||||||
|
|
||||||
|
export const CollaborationCursor = Extension.create<CollaborationCursorOptions, CollaborationCursorStorage>({
|
||||||
name: 'collaborationCursor',
|
name: 'collaborationCursor',
|
||||||
|
|
||||||
addOptions() {
|
addOptions() {
|
||||||
@ -51,19 +66,36 @@ export const CollaborationCursor = Extension.create<CollaborationCursorOptions>(
|
|||||||
|
|
||||||
return cursor
|
return cursor
|
||||||
},
|
},
|
||||||
onUpdate: () => null,
|
onUpdate: defaultOnUpdate,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onCreate() {
|
||||||
|
if (this.options.onUpdate !== defaultOnUpdate) {
|
||||||
|
console.warn('[tiptap warn]: DEPRECATED: The "onUpdate" option is deprecated. Please use `editor.storage.collaborationCursor.users` instead. Read more: https://tiptap.dev/api/extensions/collaboration-cursor')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addStorage() {
|
||||||
|
return {
|
||||||
|
users: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
addCommands() {
|
addCommands() {
|
||||||
return {
|
return {
|
||||||
user: attributes => () => {
|
updateUser: attributes => () => {
|
||||||
this.options.user = attributes
|
this.options.user = attributes
|
||||||
|
|
||||||
this.options.provider.awareness.setLocalStateField('user', this.options.user)
|
this.options.provider.awareness.setLocalStateField('user', this.options.user)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
user: attributes => ({ editor }) => {
|
||||||
|
console.warn('[tiptap warn]: DEPRECATED: The "user" command is deprecated. Please use "updateUser" instead. Read more: https://tiptap.dev/api/extensions/collaboration-cursor')
|
||||||
|
|
||||||
|
return editor.commands.updateUser(attributes)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -72,22 +104,12 @@ export const CollaborationCursor = Extension.create<CollaborationCursorOptions>(
|
|||||||
yCursorPlugin((() => {
|
yCursorPlugin((() => {
|
||||||
this.options.provider.awareness.setLocalStateField('user', this.options.user)
|
this.options.provider.awareness.setLocalStateField('user', this.options.user)
|
||||||
|
|
||||||
this.options.provider.awareness.on('change', () => {
|
this.storage.users = awarenessStatesToArray(this.options.provider.awareness.states)
|
||||||
this.options.onUpdate(awarenessStatesToArray(this.options.provider.awareness.states))
|
|
||||||
})
|
|
||||||
|
|
||||||
this.options.provider.awareness.on('update', () => {
|
this.options.provider.awareness.on('update', () => {
|
||||||
this.options.onUpdate(awarenessStatesToArray(this.options.provider.awareness.states))
|
this.storage.users = awarenessStatesToArray(this.options.provider.awareness.states)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.options.provider.on('status', (event: { status: string }) => {
|
|
||||||
if (event.status === 'connected') {
|
|
||||||
this.options.provider.awareness.setLocalStateField('user', this.options.user)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.options.onUpdate(awarenessStatesToArray(this.options.provider.awareness.states))
|
|
||||||
|
|
||||||
return this.options.provider.awareness
|
return this.options.provider.awareness
|
||||||
})(),
|
})(),
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
Loading…
Reference in New Issue
Block a user