mirror of
https://github.com/ueberdosis/tiptap.git
synced 2024-12-18 13:17:49 +08:00
Merge branch 'main' of https://github.com/ueberdosis/tiptap-next into feature/remove-inferred-commands
# Conflicts: # docs/src/demos/Experiments/Annotation/extension/annotation.ts # docs/src/demos/Experiments/Color/extension/Color.ts # docs/src/demos/Experiments/Details/details.ts
This commit is contained in:
commit
e4cb53eab7
@ -1,4 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<client-only>
|
||||||
<demo-frame v-if="inline && mainFile" v-bind="props" />
|
<demo-frame v-if="inline && mainFile" v-bind="props" />
|
||||||
<div class="demo" v-else>
|
<div class="demo" v-else>
|
||||||
<template v-if="mainFile">
|
<template v-if="mainFile">
|
||||||
@ -29,13 +30,11 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<div v-else class="demo__error">
|
||||||
<div v-if="mainFile === false" class="demo__error">
|
|
||||||
Could not find a demo called “{{ name }}”.
|
Could not find a demo called “{{ name }}”.
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="demo__skeleton" />
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
|
</client-only>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -89,16 +89,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__error {
|
&__error {
|
||||||
padding: 1rem 1.5rem;
|
padding: 1rem 1.25rem;
|
||||||
color: $colorRed;
|
border-radius: 0.75rem;
|
||||||
background-color: rgba($colorRed, 0.1);
|
border: 3px solid $colorBlack;
|
||||||
}
|
background-color: $colorRed;
|
||||||
|
font-size: 1.1rem;
|
||||||
&__skeleton {
|
font-weight: 700;
|
||||||
border-top-left-radius: inherit;
|
margin-bottom: 0.25rem;
|
||||||
border-top-right-radius: inherit;
|
|
||||||
background-color: $colorWhite;
|
|
||||||
min-height: 20rem;
|
|
||||||
opacity: 0.1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,11 +23,6 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
default: 'vue',
|
|
||||||
},
|
|
||||||
|
|
||||||
inline: {
|
inline: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@ -52,7 +47,7 @@ export default {
|
|||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
query() {
|
query() {
|
||||||
return `mode=${this.mode}&inline=${this.inline}&highlight=${this.highlight}&showSource=${this.showSource}`
|
return `inline=${this.inline}&highlight=${this.highlight}&showSource=${this.showSource}`
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -7,11 +7,6 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
default: 'vue',
|
|
||||||
},
|
|
||||||
|
|
||||||
inline: {
|
inline: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@ -41,7 +36,6 @@ export default {
|
|||||||
props() {
|
props() {
|
||||||
return {
|
return {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
mode: this.mode,
|
|
||||||
inline: this.inline,
|
inline: this.inline,
|
||||||
highlight: this.highlight,
|
highlight: this.highlight,
|
||||||
showSource: this.showSource,
|
showSource: this.showSource,
|
||||||
@ -49,14 +43,27 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
mainFile() {
|
mainFile() {
|
||||||
const file = this.files
|
if (!this.mainFilePath) {
|
||||||
.find(item => item.path.endsWith('index.vue') || item.path.endsWith('index.jsx'))
|
|
||||||
|
|
||||||
if (!file) {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return require(`~/demos/${file.path}`).default
|
return require(`~/demos/${this.mainFilePath}`).default
|
||||||
|
},
|
||||||
|
|
||||||
|
mainFilePath() {
|
||||||
|
const file = this.files.find(item => item.path.endsWith('index.vue') || item.path.endsWith('index.jsx'))
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
return file.path
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mode() {
|
||||||
|
if (this.mainFilePath?.endsWith('.jsx')) {
|
||||||
|
return 'react'
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'vue'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -67,6 +67,8 @@ export default {
|
|||||||
this.status = event.status
|
this.status = event.status
|
||||||
})
|
})
|
||||||
|
|
||||||
|
window.ydoc = ydoc
|
||||||
|
|
||||||
this.indexdb = new IndexeddbPersistence('tiptap-collaboration-example', ydoc)
|
this.indexdb = new IndexeddbPersistence('tiptap-collaboration-example', ydoc)
|
||||||
|
|
||||||
this.editor = new Editor({
|
this.editor = new Editor({
|
||||||
@ -141,7 +143,7 @@ export default {
|
|||||||
.editor {
|
.editor {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
max-height: 90vh;
|
max-height: 400px;
|
||||||
color: #0D0D0D;
|
color: #0D0D0D;
|
||||||
background-color: $colorWhite;
|
background-color: $colorWhite;
|
||||||
border: 3px solid #0D0D0D;
|
border: 3px solid #0D0D0D;
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
export class AnnotationItem {
|
|
||||||
public id!: number
|
|
||||||
|
|
||||||
public text!: string
|
|
||||||
|
|
||||||
constructor(id: number, text: string) {
|
|
||||||
this.id = id
|
|
||||||
this.text = text
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
import { Decoration, DecorationSet } from 'prosemirror-view'
|
|
||||||
import { ySyncPluginKey } from 'y-prosemirror'
|
|
||||||
import { AnnotationPluginKey } from './AnnotationPlugin'
|
|
||||||
|
|
||||||
export class AnnotationState {
|
|
||||||
private decorations: any
|
|
||||||
|
|
||||||
constructor(decorations: any) {
|
|
||||||
this.decorations = decorations
|
|
||||||
}
|
|
||||||
|
|
||||||
findAnnotation(id: number) {
|
|
||||||
const current = this.decorations.find()
|
|
||||||
|
|
||||||
for (let i = 0; i < current.length; i += 1) {
|
|
||||||
if (current[i].spec.data.id === id) {
|
|
||||||
return current[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
annotationsAt(position: number) {
|
|
||||||
return this.decorations.find(position, position)
|
|
||||||
}
|
|
||||||
|
|
||||||
apply(transaction: any) {
|
|
||||||
console.log('transaction', transaction.meta, transaction.docChanged, transaction)
|
|
||||||
|
|
||||||
const yjsTransaction = transaction.getMeta(ySyncPluginKey)
|
|
||||||
if (yjsTransaction) {
|
|
||||||
// TODO: Map positions
|
|
||||||
// absolutePositionToRelativePosition(state.selection.anchor, pmbinding.type, pmbinding.mapping)
|
|
||||||
console.log('map positions', transaction, yjsTransaction)
|
|
||||||
|
|
||||||
return this
|
|
||||||
|
|
||||||
// const { binding } = yjsTransaction
|
|
||||||
// console.log({ binding }, { transaction }, transaction.docChanged)
|
|
||||||
// console.log('yjsTransaction.isChangeOrigin', yjsTransaction.isChangeOrigin)
|
|
||||||
|
|
||||||
// console.log('yjs mapping', yjsTransaction.binding?.mapping)
|
|
||||||
// console.log('all decorations', this.decorations.find())
|
|
||||||
// console.log('original prosemirror mapping', this.decorations.map(transaction.mapping, transaction.doc))
|
|
||||||
// console.log('difference between ProseMirror & Y.js', transaction.mapping, yjsTransaction.binding?.mapping)
|
|
||||||
|
|
||||||
// Code to sync the selection:
|
|
||||||
// export const getRelativeSelection = (pmbinding, state) => ({
|
|
||||||
// anchor: absolutePositionToRelativePosition(state.selection.anchor, pmbinding.type, pmbinding.mapping),
|
|
||||||
// head: absolutePositionToRelativePosition(state.selection.head, pmbinding.type, pmbinding.mapping)
|
|
||||||
// })
|
|
||||||
|
|
||||||
// console.log(yjsTransaction.binding.mapping, transaction.curSelection.anchor)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transaction.docChanged) {
|
|
||||||
// TODO: Fixes the initial load (complete replace of the document)
|
|
||||||
// return this
|
|
||||||
|
|
||||||
// TODO: Fixes later changes (typing before the annotation)
|
|
||||||
const decorations = this.decorations.map(transaction.mapping, transaction.doc)
|
|
||||||
|
|
||||||
return new AnnotationState(decorations)
|
|
||||||
}
|
|
||||||
|
|
||||||
const action = transaction.getMeta(AnnotationPluginKey)
|
|
||||||
const actionType = action && action.type
|
|
||||||
|
|
||||||
if (action) {
|
|
||||||
let { decorations } = this
|
|
||||||
|
|
||||||
if (actionType === 'addAnnotation') {
|
|
||||||
decorations = decorations.add(transaction.doc, [
|
|
||||||
Decoration.inline(action.from, action.to, { class: 'annotation' }, { data: action.data }),
|
|
||||||
])
|
|
||||||
} else if (actionType === 'deleteAnnotation') {
|
|
||||||
decorations = decorations.remove([
|
|
||||||
this.findAnnotation(action.id),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AnnotationState(decorations)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
static init(config: any, state: any) {
|
|
||||||
// TODO: Load initial decorations from Y.js?
|
|
||||||
const decorations = DecorationSet.create(state.doc, [
|
|
||||||
Decoration.inline(105, 190, { class: 'annotation' }, { data: { id: 123, content: 'foobar' } }),
|
|
||||||
])
|
|
||||||
|
|
||||||
return new AnnotationState(decorations)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
import { Extension, Command } from '@tiptap/core'
|
|
||||||
import { AnnotationItem } from './AnnotationItem'
|
|
||||||
import { AnnotationPlugin, AnnotationPluginKey } from './AnnotationPlugin'
|
|
||||||
|
|
||||||
function randomId() {
|
|
||||||
return Math.floor(Math.random() * 0xffffffff)
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AnnotationOptions {
|
|
||||||
HTMLAttributes: {
|
|
||||||
[key: string]: any
|
|
||||||
},
|
|
||||||
onUpdate: (items: [any?]) => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '@tiptap/core' {
|
|
||||||
interface AllCommands {
|
|
||||||
annotation: {
|
|
||||||
addAnnotation: (content: any) => Command,
|
|
||||||
deleteAnnotation: (id: number) => Command,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Annotation = Extension.create({
|
|
||||||
name: 'annotation',
|
|
||||||
|
|
||||||
defaultOptions: <AnnotationOptions>{
|
|
||||||
HTMLAttributes: {
|
|
||||||
class: 'annotation',
|
|
||||||
},
|
|
||||||
onUpdate: decorations => decorations,
|
|
||||||
},
|
|
||||||
|
|
||||||
addCommands() {
|
|
||||||
return {
|
|
||||||
addAnnotation: (content: any) => ({ dispatch, state }) => {
|
|
||||||
const { selection } = state
|
|
||||||
|
|
||||||
if (selection.empty) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dispatch && content) {
|
|
||||||
dispatch(state.tr.setMeta(AnnotationPluginKey, {
|
|
||||||
type: 'addAnnotation',
|
|
||||||
from: selection.from,
|
|
||||||
to: selection.to,
|
|
||||||
data: new AnnotationItem(
|
|
||||||
randomId(),
|
|
||||||
content,
|
|
||||||
),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
deleteAnnotation: (id: number) => ({ dispatch, state }) => {
|
|
||||||
if (dispatch) {
|
|
||||||
dispatch(state.tr.setMeta(AnnotationPluginKey, { type: 'deleteAnnotation', id }))
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
addProseMirrorPlugins() {
|
|
||||||
return [
|
|
||||||
AnnotationPlugin(this.options),
|
|
||||||
]
|
|
||||||
},
|
|
||||||
})
|
|
@ -1,5 +0,0 @@
|
|||||||
import { Annotation } from './annotation'
|
|
||||||
|
|
||||||
export * from './annotation'
|
|
||||||
|
|
||||||
export default Annotation
|
|
@ -1,7 +0,0 @@
|
|||||||
context('/demos/Extensions/Annotations', () => {
|
|
||||||
before(() => {
|
|
||||||
cy.visit('/demos/Extensions/Annotations')
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO: Write tests
|
|
||||||
})
|
|
@ -1,87 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div v-if="editor">
|
|
||||||
<button @click="addAnnotation" :disabled="!editor.can().addAnnotation()">
|
|
||||||
add annotation
|
|
||||||
</button>
|
|
||||||
<editor-content :editor="editor" />
|
|
||||||
<div v-for="comment in comments" :key="comment.type.spec.data.id">
|
|
||||||
{{ comment.type.spec.data }}
|
|
||||||
|
|
||||||
<button @click="deleteAnnotation(comment.type.spec.data.id)">
|
|
||||||
remove
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { Editor, EditorContent } from '@tiptap/vue-starter-kit'
|
|
||||||
import Document from '@tiptap/extension-document'
|
|
||||||
import Paragraph from '@tiptap/extension-paragraph'
|
|
||||||
import Text from '@tiptap/extension-text'
|
|
||||||
import Annotation from './extension'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
EditorContent,
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
editor: null,
|
|
||||||
comments: [],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.editor = new Editor({
|
|
||||||
extensions: [
|
|
||||||
Document,
|
|
||||||
Paragraph,
|
|
||||||
Text,
|
|
||||||
Annotation.configure({
|
|
||||||
onUpdate: items => { this.comments = items },
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
content: `
|
|
||||||
<p>
|
|
||||||
Annotations can be used to add additional information to the content, for example comments. They live on a different level than the actual editor content.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
This example allows you to add plain text, but you’re free to add more complex data, for example JSON from another tiptap instance. :-)
|
|
||||||
</p>
|
|
||||||
`,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
addAnnotation() {
|
|
||||||
const content = prompt('Annotation', '')
|
|
||||||
|
|
||||||
this.editor.commands.addAnnotation(content)
|
|
||||||
},
|
|
||||||
deleteAnnotation(id) {
|
|
||||||
this.editor.commands.deleteAnnotation(id)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeDestroy() {
|
|
||||||
this.editor.destroy()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
/* Basic editor styles */
|
|
||||||
.ProseMirror {
|
|
||||||
> * + * {
|
|
||||||
margin-top: 0.75em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.annotation {
|
|
||||||
background: #9DEF8F;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -0,0 +1,37 @@
|
|||||||
|
export class AnnotationItem {
|
||||||
|
private decoration!: any
|
||||||
|
|
||||||
|
constructor(decoration: any) {
|
||||||
|
this.decoration = decoration
|
||||||
|
}
|
||||||
|
|
||||||
|
get id() {
|
||||||
|
return this.decoration.type.spec.id
|
||||||
|
}
|
||||||
|
|
||||||
|
get from() {
|
||||||
|
return this.decoration.from
|
||||||
|
}
|
||||||
|
|
||||||
|
get to() {
|
||||||
|
return this.decoration.to
|
||||||
|
}
|
||||||
|
|
||||||
|
get data() {
|
||||||
|
return this.decoration.type.spec.data
|
||||||
|
}
|
||||||
|
|
||||||
|
get HTMLAttributes() {
|
||||||
|
return this.decoration.type.attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return JSON.stringify({
|
||||||
|
id: this.id,
|
||||||
|
data: this.data,
|
||||||
|
from: this.from,
|
||||||
|
to: this.to,
|
||||||
|
HTMLAttributes: this.HTMLAttributes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,34 @@
|
|||||||
|
import * as Y from 'yjs'
|
||||||
import { Plugin, PluginKey } from 'prosemirror-state'
|
import { Plugin, PluginKey } from 'prosemirror-state'
|
||||||
import { AnnotationState } from './AnnotationState'
|
import { AnnotationState } from './AnnotationState'
|
||||||
|
|
||||||
export const AnnotationPluginKey = new PluginKey('annotation')
|
export const AnnotationPluginKey = new PluginKey('annotation')
|
||||||
|
|
||||||
export const AnnotationPlugin = (options: any) => new Plugin({
|
export interface AnnotationPluginOptions {
|
||||||
|
HTMLAttributes: {
|
||||||
|
[key: string]: any
|
||||||
|
},
|
||||||
|
onUpdate: (items: [any?]) => {},
|
||||||
|
map: Y.Map<any>,
|
||||||
|
instance: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AnnotationPlugin = (options: AnnotationPluginOptions) => new Plugin({
|
||||||
key: AnnotationPluginKey,
|
key: AnnotationPluginKey,
|
||||||
|
|
||||||
state: {
|
state: {
|
||||||
init: AnnotationState.init,
|
init() {
|
||||||
apply(transaction, oldState) {
|
return new AnnotationState({
|
||||||
return oldState.apply(transaction)
|
HTMLAttributes: options.HTMLAttributes,
|
||||||
|
map: options.map,
|
||||||
|
instance: options.instance,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
apply(transaction, pluginState, oldState, newState) {
|
||||||
|
return pluginState.apply(transaction, newState)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
decorations(state) {
|
decorations(state) {
|
||||||
const { decorations } = this.getState(state)
|
const { decorations } = this.getState(state)
|
||||||
@ -28,6 +46,5 @@ export const AnnotationPlugin = (options: any) => new Plugin({
|
|||||||
|
|
||||||
return decorations
|
return decorations
|
||||||
},
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
})
|
})
|
@ -0,0 +1,151 @@
|
|||||||
|
import * as Y from 'yjs'
|
||||||
|
import { EditorState, Transaction } from 'prosemirror-state'
|
||||||
|
import { Decoration, DecorationSet } from 'prosemirror-view'
|
||||||
|
import { ySyncPluginKey, relativePositionToAbsolutePosition, absolutePositionToRelativePosition } from 'y-prosemirror'
|
||||||
|
import { AddAnnotationAction, DeleteAnnotationAction, UpdateAnnotationAction } from './collaboration-annotation'
|
||||||
|
import { AnnotationPluginKey } from './AnnotationPlugin'
|
||||||
|
import { AnnotationItem } from './AnnotationItem'
|
||||||
|
|
||||||
|
export interface AnnotationStateOptions {
|
||||||
|
HTMLAttributes: {
|
||||||
|
[key: string]: any
|
||||||
|
},
|
||||||
|
map: Y.Map<any>,
|
||||||
|
instance: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AnnotationState {
|
||||||
|
options: AnnotationStateOptions
|
||||||
|
|
||||||
|
decorations = DecorationSet.empty
|
||||||
|
|
||||||
|
constructor(options: AnnotationStateOptions) {
|
||||||
|
this.options = options
|
||||||
|
}
|
||||||
|
|
||||||
|
randomId() {
|
||||||
|
// TODO: That seems … to simple.
|
||||||
|
return Math.floor(Math.random() * 0xffffffff).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
findAnnotation(id: string) {
|
||||||
|
const current = this.decorations.find()
|
||||||
|
|
||||||
|
for (let i = 0; i < current.length; i += 1) {
|
||||||
|
if (current[i].spec.id === id) {
|
||||||
|
return current[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addAnnotation(action: AddAnnotationAction, state: EditorState) {
|
||||||
|
const ystate = ySyncPluginKey.getState(state)
|
||||||
|
const { type, binding } = ystate
|
||||||
|
const { map } = this.options
|
||||||
|
const { from, to, data } = action
|
||||||
|
const absoluteFrom = absolutePositionToRelativePosition(from, type, binding.mapping)
|
||||||
|
const absoluteTo = absolutePositionToRelativePosition(to, type, binding.mapping)
|
||||||
|
|
||||||
|
map.set(this.randomId(), {
|
||||||
|
from: absoluteFrom,
|
||||||
|
to: absoluteTo,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAnnotation(action: UpdateAnnotationAction) {
|
||||||
|
const { map } = this.options
|
||||||
|
|
||||||
|
const annotation = map.get(action.id)
|
||||||
|
|
||||||
|
map.set(action.id, {
|
||||||
|
from: annotation.from,
|
||||||
|
to: annotation.to,
|
||||||
|
data: action.data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteAnnotation(id: string) {
|
||||||
|
const { map } = this.options
|
||||||
|
|
||||||
|
map.delete(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
annotationsAt(position: number) {
|
||||||
|
return this.decorations.find(position, position).map(decoration => {
|
||||||
|
return new AnnotationItem(decoration)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
createDecorations(state: EditorState) {
|
||||||
|
const { map, HTMLAttributes } = this.options
|
||||||
|
const ystate = ySyncPluginKey.getState(state)
|
||||||
|
const { doc, type, binding } = ystate
|
||||||
|
const decorations: Decoration[] = []
|
||||||
|
|
||||||
|
map.forEach((annotation, id) => {
|
||||||
|
const from = relativePositionToAbsolutePosition(doc, type, annotation.from, binding.mapping)
|
||||||
|
const to = relativePositionToAbsolutePosition(doc, type, annotation.to, binding.mapping)
|
||||||
|
|
||||||
|
if (!from || !to) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[${this.options.instance}] Decoration.inline()`, from, to, HTMLAttributes, { id, data: annotation.data })
|
||||||
|
|
||||||
|
if (from === to) {
|
||||||
|
console.warn(`[${this.options.instance}] corrupt decoration `, annotation.from, from, annotation.to, to)
|
||||||
|
}
|
||||||
|
|
||||||
|
decorations.push(
|
||||||
|
Decoration.inline(from, to, HTMLAttributes, { id, data: annotation.data, inclusiveEnd: true }),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.decorations = DecorationSet.create(state.doc, decorations)
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(transaction: Transaction, state: EditorState) {
|
||||||
|
// Add/Remove annotations
|
||||||
|
const action = transaction.getMeta(AnnotationPluginKey) as AddAnnotationAction | UpdateAnnotationAction | DeleteAnnotationAction
|
||||||
|
|
||||||
|
if (action && action.type) {
|
||||||
|
console.log(`[${this.options.instance}] action: ${action.type}`)
|
||||||
|
|
||||||
|
if (action.type === 'addAnnotation') {
|
||||||
|
this.addAnnotation(action, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.type === 'updateAnnotation') {
|
||||||
|
this.updateAnnotation(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.type === 'deleteAnnotation') {
|
||||||
|
this.deleteAnnotation(action.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
if (action.type === 'createDecorations') {
|
||||||
|
this.createDecorations(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use Y.js to update positions
|
||||||
|
const ystate = ySyncPluginKey.getState(state)
|
||||||
|
|
||||||
|
if (ystate.isChangeOrigin) {
|
||||||
|
console.log(`[${this.options.instance}] isChangeOrigin: true → createDecorations`)
|
||||||
|
this.createDecorations(state)
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use ProseMirror to update positions
|
||||||
|
console.log(`[${this.options.instance}] isChangeOrigin: false → ProseMirror mapping`)
|
||||||
|
this.decorations = this.decorations.map(transaction.mapping, transaction.doc)
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,144 @@
|
|||||||
|
import * as Y from 'yjs'
|
||||||
|
import { Extension, Command } from '@tiptap/core'
|
||||||
|
import { AnnotationPlugin, AnnotationPluginKey } from './AnnotationPlugin'
|
||||||
|
|
||||||
|
export interface AddAnnotationAction {
|
||||||
|
type: 'addAnnotation',
|
||||||
|
data: any,
|
||||||
|
from: number,
|
||||||
|
to: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateAnnotationAction {
|
||||||
|
type: 'updateAnnotation',
|
||||||
|
id: string,
|
||||||
|
data: any,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeleteAnnotationAction {
|
||||||
|
type: 'deleteAnnotation',
|
||||||
|
id: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AnnotationOptions {
|
||||||
|
HTMLAttributes: {
|
||||||
|
[key: string]: any
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* An event listener which receives annotations for the current selection.
|
||||||
|
*/
|
||||||
|
onUpdate: (items: [any?]) => {},
|
||||||
|
/**
|
||||||
|
* An initialized Y.js document.
|
||||||
|
*/
|
||||||
|
document: Y.Doc | null,
|
||||||
|
/**
|
||||||
|
* Name of a Y.js map, can be changed to sync multiple fields with one Y.js document.
|
||||||
|
*/
|
||||||
|
field: string,
|
||||||
|
/**
|
||||||
|
* A raw Y.js map, can be used instead of `document` and `field`.
|
||||||
|
*/
|
||||||
|
map: Y.Map<any> | null,
|
||||||
|
instance: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMapFromOptions(options: AnnotationOptions): Y.Map<any> {
|
||||||
|
return options.map
|
||||||
|
? options.map
|
||||||
|
: options.document?.getMap(options.field) as Y.Map<any>
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@tiptap/core' {
|
||||||
|
interface AllCommands {
|
||||||
|
annotation: {
|
||||||
|
addAnnotation: (data: any) => Command,
|
||||||
|
updateAnnotation: (id: string, data: any) => Command,
|
||||||
|
deleteAnnotation: (id: string) => Command,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CollaborationAnnotation = Extension.create({
|
||||||
|
name: 'annotation',
|
||||||
|
|
||||||
|
defaultOptions: <AnnotationOptions>{
|
||||||
|
HTMLAttributes: {
|
||||||
|
class: 'annotation',
|
||||||
|
},
|
||||||
|
onUpdate: decorations => decorations,
|
||||||
|
document: null,
|
||||||
|
field: 'annotations',
|
||||||
|
map: null,
|
||||||
|
instance: '',
|
||||||
|
},
|
||||||
|
|
||||||
|
onCreate() {
|
||||||
|
const map = getMapFromOptions(this.options)
|
||||||
|
|
||||||
|
map.observe(() => {
|
||||||
|
console.log(`[${this.options.instance}] map updated → createDecorations`)
|
||||||
|
|
||||||
|
const transaction = this.editor.state.tr.setMeta(AnnotationPluginKey, {
|
||||||
|
type: 'createDecorations',
|
||||||
|
})
|
||||||
|
|
||||||
|
this.editor.view.dispatch(transaction)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
addCommands() {
|
||||||
|
return {
|
||||||
|
addAnnotation: (data: any) => ({ dispatch, state }) => {
|
||||||
|
const { selection } = state
|
||||||
|
|
||||||
|
if (selection.empty) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dispatch && data) {
|
||||||
|
state.tr.setMeta(AnnotationPluginKey, <AddAnnotationAction>{
|
||||||
|
type: 'addAnnotation',
|
||||||
|
from: selection.from,
|
||||||
|
to: selection.to,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
updateAnnotation: (id: string, data: any) => ({ dispatch, state }) => {
|
||||||
|
if (dispatch) {
|
||||||
|
state.tr.setMeta(AnnotationPluginKey, <UpdateAnnotationAction>{
|
||||||
|
type: 'updateAnnotation',
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
deleteAnnotation: id => ({ dispatch, state }) => {
|
||||||
|
if (dispatch) {
|
||||||
|
state.tr.setMeta(AnnotationPluginKey, <DeleteAnnotationAction>{
|
||||||
|
type: 'deleteAnnotation',
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addProseMirrorPlugins() {
|
||||||
|
return [
|
||||||
|
AnnotationPlugin({
|
||||||
|
HTMLAttributes: this.options.HTMLAttributes,
|
||||||
|
onUpdate: this.options.onUpdate,
|
||||||
|
map: getMapFromOptions(this.options),
|
||||||
|
instance: this.options.instance,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
})
|
@ -0,0 +1,5 @@
|
|||||||
|
import { CollaborationAnnotation } from './collaboration-annotation'
|
||||||
|
|
||||||
|
export * from './collaboration-annotation'
|
||||||
|
|
||||||
|
export default CollaborationAnnotation
|
@ -0,0 +1,7 @@
|
|||||||
|
context('/demos/Experiments/Annotation', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/demos/Experiments/Annotation')
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: Write tests
|
||||||
|
})
|
@ -2,36 +2,30 @@
|
|||||||
<div>
|
<div>
|
||||||
<div v-if="editor">
|
<div v-if="editor">
|
||||||
<h2>
|
<h2>
|
||||||
Original
|
Original Editor
|
||||||
</h2>
|
</h2>
|
||||||
<button @click="addComment" :disabled="!editor.can().addAnnotation()">
|
<button @click="addComment" :disabled="!editor.can().addAnnotation()">
|
||||||
comment
|
comment
|
||||||
</button>
|
</button>
|
||||||
<editor-content :editor="editor" />
|
<editor-content :editor="editor" />
|
||||||
<div v-for="comment in comments" :key="comment.type.spec.data.id">
|
<div v-for="comment in comments" :key="comment.id">
|
||||||
{{ comment.type.spec.data }}
|
{{ comment }}
|
||||||
|
|
||||||
<button @click="deleteComment(comment.type.spec.data.id)">
|
<button @click="updateComment(comment.id)">
|
||||||
|
update
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button @click="deleteComment(comment.id)">
|
||||||
remove
|
remove
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <br>
|
|
||||||
<h2>
|
<h2>
|
||||||
ProseMirror JSON from Y.js document
|
Another Editor
|
||||||
</h2>
|
|
||||||
{{ rawDocument }} -->
|
|
||||||
|
|
||||||
<br>
|
|
||||||
<h2>
|
|
||||||
Y.js document
|
|
||||||
</h2>
|
|
||||||
{{ json }}
|
|
||||||
|
|
||||||
<br>
|
|
||||||
<h2>
|
|
||||||
Mirror
|
|
||||||
</h2>
|
</h2>
|
||||||
|
<button @click="addAnotherComment" :disabled="!anotherEditor.can().addAnnotation()">
|
||||||
|
comment
|
||||||
|
</button>
|
||||||
<editor-content :editor="anotherEditor" />
|
<editor-content :editor="anotherEditor" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -46,8 +40,7 @@ import Collaboration from '@tiptap/extension-collaboration'
|
|||||||
import Bold from '@tiptap/extension-bold'
|
import Bold from '@tiptap/extension-bold'
|
||||||
import Heading from '@tiptap/extension-heading'
|
import Heading from '@tiptap/extension-heading'
|
||||||
import * as Y from 'yjs'
|
import * as Y from 'yjs'
|
||||||
import { yDocToProsemirrorJSON } from 'y-prosemirror'
|
import CollaborationAnnotation from './extension'
|
||||||
import Annotation from '../Annotation/extension'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -73,8 +66,10 @@ export default {
|
|||||||
Text,
|
Text,
|
||||||
Bold,
|
Bold,
|
||||||
Heading,
|
Heading,
|
||||||
Annotation.configure({
|
CollaborationAnnotation.configure({
|
||||||
|
document: this.ydoc,
|
||||||
onUpdate: items => { this.comments = items },
|
onUpdate: items => { this.comments = items },
|
||||||
|
instance: 'editor1',
|
||||||
}),
|
}),
|
||||||
Collaboration.configure({
|
Collaboration.configure({
|
||||||
document: this.ydoc,
|
document: this.ydoc,
|
||||||
@ -95,9 +90,12 @@ export default {
|
|||||||
Document,
|
Document,
|
||||||
Paragraph,
|
Paragraph,
|
||||||
Text,
|
Text,
|
||||||
// Annotation.configure({
|
Bold,
|
||||||
// onUpdate: items => { this.comments = items },
|
Heading,
|
||||||
// }),
|
CollaborationAnnotation.configure({
|
||||||
|
document: this.ydoc,
|
||||||
|
instance: 'editor2',
|
||||||
|
}),
|
||||||
Collaboration.configure({
|
Collaboration.configure({
|
||||||
document: this.ydoc,
|
document: this.ydoc,
|
||||||
}),
|
}),
|
||||||
@ -107,26 +105,32 @@ export default {
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
addComment() {
|
addComment() {
|
||||||
const content = prompt('Comment', '')
|
const data = prompt('Comment', '')
|
||||||
|
|
||||||
this.editor.commands.addAnnotation(content)
|
this.editor.commands.addAnnotation(data)
|
||||||
|
},
|
||||||
|
updateComment(id) {
|
||||||
|
const comment = this.comments.find(item => {
|
||||||
|
return id === item.id
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = prompt('Comment', comment.data)
|
||||||
|
|
||||||
|
this.editor.commands.updateAnnotation(id, data)
|
||||||
},
|
},
|
||||||
deleteComment(id) {
|
deleteComment(id) {
|
||||||
this.editor.commands.deleteAnnotation(id)
|
this.editor.commands.deleteAnnotation(id)
|
||||||
},
|
},
|
||||||
},
|
addAnotherComment() {
|
||||||
|
const data = prompt('Comment', '')
|
||||||
|
|
||||||
computed: {
|
this.anotherEditor.commands.addAnnotation(data)
|
||||||
rawDocument() {
|
|
||||||
return yDocToProsemirrorJSON(this.ydoc, 'default')
|
|
||||||
},
|
|
||||||
json() {
|
|
||||||
return this.ydoc.toJSON()
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.editor.destroy()
|
this.editor.destroy()
|
||||||
|
this.anotherEditor.destroy()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
@ -1,62 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import { Extension } from '@tiptap/core'
|
|
||||||
import { Decoration, DecorationSet } from 'prosemirror-view'
|
|
||||||
import { Plugin } from 'prosemirror-state'
|
|
||||||
|
|
||||||
function detectColors(doc) {
|
|
||||||
const hexColor = /(#[0-9a-f]{3,6})\b/ig
|
|
||||||
const results = []
|
|
||||||
const decorations: [any?] = []
|
|
||||||
|
|
||||||
doc.descendants((node: any, position: any) => {
|
|
||||||
if (!node.isText) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let matches
|
|
||||||
|
|
||||||
// eslint-disable-next-line
|
|
||||||
while (matches = hexColor.exec(node.text)) {
|
|
||||||
results.push({
|
|
||||||
color: matches[0],
|
|
||||||
from: position + matches.index,
|
|
||||||
to: position + matches.index + matches[0].length,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
results.forEach(issue => {
|
|
||||||
decorations.push(Decoration.inline(issue.from, issue.to, {
|
|
||||||
class: 'color',
|
|
||||||
style: `--color: ${issue.color}`,
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
return DecorationSet.create(doc, decorations)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Color = Extension.create({
|
|
||||||
name: 'color',
|
|
||||||
|
|
||||||
addProseMirrorPlugins() {
|
|
||||||
return [
|
|
||||||
new Plugin({
|
|
||||||
state: {
|
|
||||||
init(_, { doc }) {
|
|
||||||
return detectColors(doc)
|
|
||||||
},
|
|
||||||
apply(transaction, oldState) {
|
|
||||||
return transaction.docChanged
|
|
||||||
? detectColors(transaction.doc)
|
|
||||||
: oldState
|
|
||||||
},
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
decorations(state) {
|
|
||||||
return this.getState(state)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
},
|
|
||||||
})
|
|
@ -1,4 +0,0 @@
|
|||||||
import { Color } from './Color'
|
|
||||||
|
|
||||||
export * from './Color'
|
|
||||||
export default Color
|
|
@ -1,76 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<editor-content :editor="editor" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { Editor, EditorContent } from '@tiptap/vue-starter-kit'
|
|
||||||
import Document from '@tiptap/extension-document'
|
|
||||||
import Text from '@tiptap/extension-text'
|
|
||||||
import Paragraph from '@tiptap/extension-paragraph'
|
|
||||||
import Heading from '@tiptap/extension-heading'
|
|
||||||
import Color from './extension'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
EditorContent,
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
editor: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.editor = new Editor({
|
|
||||||
extensions: [
|
|
||||||
Document,
|
|
||||||
Paragraph,
|
|
||||||
Heading,
|
|
||||||
Text,
|
|
||||||
Color,
|
|
||||||
],
|
|
||||||
content: `
|
|
||||||
<p>
|
|
||||||
For triplets with repeated values, you can eliminate the repetition by writing in shorthand, for instance, #00FFFF becomes #0FF. This system is easy for computers to understand, and it pretty short to write, which makes it useful for quick copy paste and designation in programming. If you’re going to work with colors in a more involved way, though, HSL is a little bit more human-readable.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
A few more examples: #FFF, #0D0D0D, #616161, #A975FF, #FB5151, #FD9170, #FFCB6B, #68CEF8, #80cbc4, #9DEF8F
|
|
||||||
</p>
|
|
||||||
`,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeDestroy() {
|
|
||||||
this.editor.destroy()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
/* Basic editor styles */
|
|
||||||
.ProseMirror {
|
|
||||||
> * + * {
|
|
||||||
margin-top: 0.75em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.color {
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: ' ';
|
|
||||||
display: inline-block;
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
border: 1px solid rgba(128, 128, 128, 0.3);
|
|
||||||
vertical-align: middle;
|
|
||||||
margin-right: 0.1em;
|
|
||||||
margin-bottom: 0.15em;
|
|
||||||
border-radius: 2px;
|
|
||||||
background-color: var(--color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,7 +0,0 @@
|
|||||||
context('/demos/Examples/Annotations', () => {
|
|
||||||
before(() => {
|
|
||||||
cy.visit('/demos/Examples/Annotations')
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO: Write tests
|
|
||||||
})
|
|
@ -9,9 +9,13 @@ export interface DetailsSummaryOptions {
|
|||||||
export default Node.create<DetailsSummaryOptions>({
|
export default Node.create<DetailsSummaryOptions>({
|
||||||
name: 'detailsSummary',
|
name: 'detailsSummary',
|
||||||
|
|
||||||
content: 'inline*',
|
content: 'text*',
|
||||||
|
|
||||||
// group: 'block',
|
marks: '',
|
||||||
|
|
||||||
|
group: 'block',
|
||||||
|
|
||||||
|
isolating: true,
|
||||||
|
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
HTMLAttributes: {},
|
HTMLAttributes: {},
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Node, mergeAttributes } from '@tiptap/core'
|
import { Node, mergeAttributes, Command } from '@tiptap/core'
|
||||||
|
|
||||||
export interface DetailsOptions {
|
export interface DetailsOptions {
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
@ -6,6 +6,25 @@ export interface DetailsOptions {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '@tiptap/core' {
|
||||||
|
interface AllCommands {
|
||||||
|
details: {
|
||||||
|
/**
|
||||||
|
* Set a details node
|
||||||
|
*/
|
||||||
|
setDetails: () => Command,
|
||||||
|
/**
|
||||||
|
* Toggle a details node
|
||||||
|
*/
|
||||||
|
toggleDetails: () => Command,
|
||||||
|
/**
|
||||||
|
* Unset a details node
|
||||||
|
*/
|
||||||
|
unsetDetails: () => Command,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default Node.create<DetailsOptions>({
|
export default Node.create<DetailsOptions>({
|
||||||
name: 'details',
|
name: 'details',
|
||||||
|
|
||||||
@ -13,36 +32,21 @@ export default Node.create<DetailsOptions>({
|
|||||||
|
|
||||||
group: 'block',
|
group: 'block',
|
||||||
|
|
||||||
|
// defining: true,
|
||||||
|
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
HTMLAttributes: {},
|
HTMLAttributes: {},
|
||||||
},
|
},
|
||||||
|
|
||||||
addAttributes() {
|
|
||||||
return {
|
|
||||||
open: {
|
|
||||||
default: true,
|
|
||||||
parseHTML: element => {
|
|
||||||
return {
|
|
||||||
open: element.hasAttribute('open'),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
renderHTML: attributes => {
|
|
||||||
if (!attributes.open) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
open: 'open',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
parseHTML() {
|
parseHTML() {
|
||||||
return [{
|
return [
|
||||||
|
{
|
||||||
tag: 'details',
|
tag: 'details',
|
||||||
}]
|
},
|
||||||
|
{
|
||||||
|
tag: 'div[data-type="details"]',
|
||||||
|
},
|
||||||
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
renderHTML({ HTMLAttributes }) {
|
renderHTML({ HTMLAttributes }) {
|
||||||
@ -50,32 +54,25 @@ export default Node.create<DetailsOptions>({
|
|||||||
},
|
},
|
||||||
|
|
||||||
addNodeView() {
|
addNodeView() {
|
||||||
return ({
|
return ({ HTMLAttributes }) => {
|
||||||
node,
|
const item = document.createElement('div')
|
||||||
HTMLAttributes,
|
item.setAttribute('data-type', 'details')
|
||||||
getPos,
|
|
||||||
editor,
|
|
||||||
}) => {
|
|
||||||
const { view } = editor
|
|
||||||
const item = document.createElement('details')
|
|
||||||
|
|
||||||
item.addEventListener('click', event => {
|
const toggle = document.createElement('div')
|
||||||
// @ts-ignore
|
toggle.setAttribute('data-type', 'detailsToggle')
|
||||||
const { open } = event.target.parentElement as HTMLElement
|
item.append(toggle)
|
||||||
// @ts-ignore
|
|
||||||
const { localName } = event.target
|
|
||||||
|
|
||||||
if (typeof getPos === 'function' && localName === 'summary') {
|
const content = document.createElement('div')
|
||||||
view.dispatch(view.state.tr.setNodeMarkup(getPos(), undefined, {
|
content.setAttribute('data-type', 'detailsContent')
|
||||||
open: !open,
|
item.append(content)
|
||||||
}))
|
|
||||||
editor.commands.focus()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (node.attrs.open) {
|
toggle.addEventListener('click', () => {
|
||||||
|
if (item.hasAttribute('open')) {
|
||||||
|
item.removeAttribute('open')
|
||||||
|
} else {
|
||||||
item.setAttribute('open', 'open')
|
item.setAttribute('open', 'open')
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
Object.entries(HTMLAttributes).forEach(([key, value]) => {
|
Object.entries(HTMLAttributes).forEach(([key, value]) => {
|
||||||
item.setAttribute(key, value)
|
item.setAttribute(key, value)
|
||||||
@ -83,21 +80,28 @@ export default Node.create<DetailsOptions>({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
dom: item,
|
dom: item,
|
||||||
contentDOM: item,
|
contentDOM: content,
|
||||||
update: updatedNode => {
|
ignoreMutation: (mutation: MutationRecord) => {
|
||||||
if (updatedNode.type !== this.type) {
|
return !item.contains(mutation.target) || item === mutation.target
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updatedNode.attrs.open) {
|
|
||||||
item.setAttribute('open', 'open')
|
|
||||||
} else {
|
|
||||||
item.removeAttribute('open')
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
addCommands() {
|
||||||
|
return {
|
||||||
|
setDetails: () => ({ commands }) => {
|
||||||
|
// TODO: Doesn’t work
|
||||||
|
return commands.wrapIn('details')
|
||||||
|
},
|
||||||
|
toggleDetails: () => ({ commands }) => {
|
||||||
|
// TODO: Doesn’t work
|
||||||
|
return commands.toggleWrap('details')
|
||||||
|
},
|
||||||
|
unsetDetails: () => ({ commands }) => {
|
||||||
|
// TODO: Doesn’t work
|
||||||
|
return commands.lift('details')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="editor">
|
<div v-if="editor">
|
||||||
|
<button @click="editor.chain().focus().toggleDetails().run()" :class="{ 'is-active': editor.isActive('details') }">
|
||||||
|
details
|
||||||
|
</button>
|
||||||
|
|
||||||
<editor-content :editor="editor" />
|
<editor-content :editor="editor" />
|
||||||
|
|
||||||
|
<h2>HTML</h2>
|
||||||
|
{{ editor.getHTML() }}
|
||||||
|
|
||||||
|
<h2>Issues</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Commands don’t work</li>
|
||||||
|
<li>Fails to open nested details</li>
|
||||||
|
<li>Node can’t be deleted (if it’s the last node)</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -31,7 +45,7 @@ export default {
|
|||||||
],
|
],
|
||||||
content: `
|
content: `
|
||||||
<p>Here is a details list:</p>
|
<p>Here is a details list:</p>
|
||||||
<details open>
|
<details>
|
||||||
<summary>An open details tag</summary>
|
<summary>An open details tag</summary>
|
||||||
<p>More info about the details.</p>
|
<p>More info about the details.</p>
|
||||||
</details>
|
</details>
|
||||||
@ -39,6 +53,7 @@ export default {
|
|||||||
<summary>A closed details tag</summary>
|
<summary>A closed details tag</summary>
|
||||||
<p>More info about the details.</p>
|
<p>More info about the details.</p>
|
||||||
</details>
|
</details>
|
||||||
|
<p>That’s it.</p>
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -54,5 +69,31 @@ export default {
|
|||||||
> * + * {
|
> * + * {
|
||||||
margin-top: 0.75em;
|
margin-top: 0.75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
details,
|
||||||
|
[data-type="details"] {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
[data-type="detailsContent"] > *:not(summary) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-type="detailsToggle"]::before {
|
||||||
|
cursor: pointer;
|
||||||
|
content: '▸';
|
||||||
|
display: inline-block;
|
||||||
|
width: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[open] {
|
||||||
|
[data-type="detailsContent"] > *:not(summary) {
|
||||||
|
display: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-type="detailsToggle"]::before {
|
||||||
|
content: '▾';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -186,6 +186,29 @@ Have a look at all of the core commands listed below. They should give you a goo
|
|||||||
| .selectNodeForward() | Select a node forward. |
|
| .selectNodeForward() | Select a node forward. |
|
||||||
| .selectParentNode() | Select the parent node. |
|
| .selectParentNode() | Select the parent node. |
|
||||||
|
|
||||||
|
## Example use cases
|
||||||
|
|
||||||
|
### Quote a text
|
||||||
|
TODO
|
||||||
|
|
||||||
|
Add a blockquote, with a specified text, add a paragraph below, set the cursor there.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Untested, work in progress, likely to change
|
||||||
|
this.editor
|
||||||
|
.chain()
|
||||||
|
.focus()
|
||||||
|
.createParagraphNear()
|
||||||
|
.insertText(text)
|
||||||
|
.setBlockquote()
|
||||||
|
.insertHTML('<p></p>')
|
||||||
|
.createParagraphNear()
|
||||||
|
.unsetBlockquote()
|
||||||
|
.createParagraphNear()
|
||||||
|
.insertHTML('<p></p>')
|
||||||
|
.run()
|
||||||
|
```
|
||||||
|
|
||||||
## Add your own commands
|
## Add your own commands
|
||||||
All extensions can add additional commands (and most do), check out the specific [documentation for the provided nodes](/api/nodes), [marks](/api/marks), and [extensions](/api/extensions) to learn more about those.
|
All extensions can add additional commands (and most do), check out the specific [documentation for the provided nodes](/api/nodes), [marks](/api/marks), and [extensions](/api/extensions) to learn more about those.
|
||||||
|
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
## toc
|
## toc
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
Extensions add new capabilities to tiptap. [Nodes](/api/nodes) and [marks](/api/marks) are rendered in HTML. Extensions can’t add to the schema, but can add functionality or change the behaviour of the editor.
|
Extensions add new capabilities to tiptap and you’ll read the word extension here very often. Actually, there are literal Extensions. Those can’t add to the schema, but can add functionality or change the behaviour of the editor.
|
||||||
|
|
||||||
|
There are also some extensions with more capabilities. We call them [nodes](/api/nodes) and [marks](/api/marks) which can render content in the editor.
|
||||||
|
|
||||||
## List of provided extensions
|
## List of provided extensions
|
||||||
| Title | Default Extension | Source Code |
|
| Title | Default Extension | Source Code |
|
||||||
@ -19,7 +21,7 @@ Extensions add new capabilities to tiptap. [Nodes](/api/nodes) and [marks](/api/
|
|||||||
| [TextAlign](/api/extensions/text-align) | – | [GitHub](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-text-align/) |
|
| [TextAlign](/api/extensions/text-align) | – | [GitHub](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-text-align/) |
|
||||||
| [Typography](/api/extensions/typography) | – | [GitHub](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-typography/) |
|
| [Typography](/api/extensions/typography) | – | [GitHub](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-typography/) |
|
||||||
|
|
||||||
You don’t have to use it, but we prepared a `@tiptap/starter-kit` which includes the most common extensions. See an example on [how to use `defaultExtensions()`](/examples/default).
|
You don’t have to use it, but we prepared a `@tiptap/starter-kit` which includes the most common extensions. Read more about [`defaultExtensions()`](/guide/configuration#default-extensions).
|
||||||
|
|
||||||
## How extensions work
|
## How extensions work
|
||||||
Although tiptap tries to hide most of the complexity of ProseMirror, it’s built on top of its APIs and we recommend you to read through the [ProseMirror Guide](https://ProseMirror.net/docs/guide/) for advanced usage. You’ll have a better understanding of how everything works under the hood and get more familiar with many terms and jargon used by tiptap.
|
Although tiptap tries to hide most of the complexity of ProseMirror, it’s built on top of its APIs and we recommend you to read through the [ProseMirror Guide](https://ProseMirror.net/docs/guide/) for advanced usage. You’ll have a better understanding of how everything works under the hood and get more familiar with many terms and jargon used by tiptap.
|
||||||
@ -50,7 +52,7 @@ const editor = new Editor({
|
|||||||
],
|
],
|
||||||
```
|
```
|
||||||
|
|
||||||
Learn [more about custom extensions in our guide](/guide/build-extensions).
|
Learn [more about custom extensions in our guide](/guide/extend-extensions).
|
||||||
|
|
||||||
### ProseMirror plugins
|
### ProseMirror plugins
|
||||||
ProseMirror has a fantastic eco system with many amazing plugins. If you want to use one of them, you can register them with tiptap like that:
|
ProseMirror has a fantastic eco system with many amazing plugins. If you want to use one of them, you can register them with tiptap like that:
|
||||||
|
@ -10,10 +10,6 @@ Open this page in multiple browser windows to test it.
|
|||||||
We kindly ask you to [sponsor our work](/sponsor) when using this extension in production.
|
We kindly ask you to [sponsor our work](/sponsor) when using this extension in production.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
::: warning Use with Collaboration
|
|
||||||
This extension requires the [`Collaboration`](/api/extensions/collaboration) extension.
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
```bash
|
```bash
|
||||||
# with npm
|
# with npm
|
||||||
@ -23,6 +19,8 @@ npm install @tiptap/extension-collaboration-cursor
|
|||||||
yarn add @tiptap/extension-collaboration-cursor
|
yarn add @tiptap/extension-collaboration-cursor
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This extension requires the [`Collaboration`](/api/extensions/collaboration) extension.
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------- | ---------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| -------- | ---------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
@ -42,4 +40,6 @@ yarn add @tiptap/extension-collaboration-cursor
|
|||||||
:::warning Public
|
:::warning Public
|
||||||
The content of this editor is shared with other users.
|
The content of this editor is shared with other users.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
<demo name="Extensions/CollaborationCursor" :show-source="false" />
|
||||||
<demo name="Extensions/CollaborationCursor" highlight="11,39-45" />
|
<demo name="Extensions/CollaborationCursor" highlight="11,39-45" />
|
||||||
|
@ -48,4 +48,5 @@ yarn add @tiptap/extension-collaboration yjs y-websocket
|
|||||||
:::warning Public
|
:::warning Public
|
||||||
The content of this editor is shared with other users.
|
The content of this editor is shared with other users.
|
||||||
:::
|
:::
|
||||||
|
<demo name="Extensions/Collaboration" :show-source="false" />
|
||||||
<demo name="Extensions/Collaboration" highlight="10,27-28,35-37,44" />
|
<demo name="Extensions/Collaboration" highlight="10,27-28,35-37,44" />
|
||||||
|
@ -5,10 +5,6 @@
|
|||||||
This extension enables you to set the font family in the editor. It uses the [`TextStyle`](/api/marks/text-style) mark, which renders a `<span>` tag. The font family is applied as inline style, for example `<span style="font-family: Arial">`.
|
This extension enables you to set the font family in the editor. It uses the [`TextStyle`](/api/marks/text-style) mark, which renders a `<span>` tag. The font family is applied as inline style, for example `<span style="font-family: Arial">`.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
::: warning Use with TextStyle
|
|
||||||
This extension requires the [`TextStyle`](/api/marks/text-style) mark.
|
|
||||||
:::
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# with npm
|
# with npm
|
||||||
npm install @tiptap/extension-text-style @tiptap/extension-font-family
|
npm install @tiptap/extension-text-style @tiptap/extension-font-family
|
||||||
@ -17,6 +13,8 @@ npm install @tiptap/extension-text-style @tiptap/extension-font-family
|
|||||||
yarn add @tiptap/extension-text-style @tiptap/extension-font-family
|
yarn add @tiptap/extension-text-style @tiptap/extension-font-family
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This extension requires the [`TextStyle`](/api/marks/text-style) mark.
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| ------ | ------- | --------------- | ------------------------------------------------------------------------ |
|
| ------ | ------- | --------------- | ------------------------------------------------------------------------ |
|
||||||
|
@ -7,10 +7,6 @@ This extension enables you to use bullet lists in the editor. They are rendered
|
|||||||
Type <code>* </code>, <code>- </code> or <code>+ </code> at the beginning of a new line and it will magically transform to a bullet list.
|
Type <code>* </code>, <code>- </code> or <code>+ </code> at the beginning of a new line and it will magically transform to a bullet list.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
::: warning Use with ListItem
|
|
||||||
This extension requires the [`ListItem`](/api/nodes/list-item) node.
|
|
||||||
:::
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# with npm
|
# with npm
|
||||||
npm install @tiptap/extension-bullet-list @tiptap/extension-list-item
|
npm install @tiptap/extension-bullet-list @tiptap/extension-list-item
|
||||||
@ -19,6 +15,8 @@ npm install @tiptap/extension-bullet-list @tiptap/extension-list-item
|
|||||||
yarn add @tiptap/extension-bullet-list @tiptap/extension-list-item
|
yarn add @tiptap/extension-bullet-list @tiptap/extension-list-item
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This extension requires the [`ListItem`](/api/nodes/list-item) node.
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------------- | -------- | ------- | --------------------------------------------------------------------- |
|
| -------------- | -------- | ------- | --------------------------------------------------------------------- |
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Emoji
|
# Emoji
|
||||||
|
|
||||||
:::pro Fund the development ♥
|
:::pro Fund the development ♥
|
||||||
We need your support to maintain, update, support and develop tiptap 2. If you’re waiting for this extension, [become a sponsor and fund open source](/sponsor).
|
We need your support to maintain, update, support and develop tiptap 2. If you’re waiting for this extension, [become a sponsor and fund open-source](/sponsor).
|
||||||
:::
|
:::
|
||||||
|
|
||||||
TODO
|
TODO
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Hashtag
|
# Hashtag
|
||||||
|
|
||||||
:::pro Fund the development ♥
|
:::pro Fund the development ♥
|
||||||
We need your support to maintain, update, support and develop tiptap 2. If you’re waiting for this extension, [become a sponsor and fund open source](/sponsor).
|
We need your support to maintain, update, support and develop tiptap 2. If you’re waiting for this extension, [become a sponsor and fund open-source](/sponsor).
|
||||||
:::
|
:::
|
||||||
|
|
||||||
TODO
|
TODO
|
||||||
|
@ -5,10 +5,6 @@
|
|||||||
The ListItem extension adds support for the `<li>` HTML tag. It’s used for bullet lists and ordered lists and can’t really be used without them.
|
The ListItem extension adds support for the `<li>` HTML tag. It’s used for bullet lists and ordered lists and can’t really be used without them.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
::: warning Use with BulletList and/or OrderedList
|
|
||||||
This extension requires the [`BulletList`](/api/nodes/bullet-list) or [`OrderedList`](/api/nodes/ordered-list) node.
|
|
||||||
:::
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# with npm
|
# with npm
|
||||||
npm install @tiptap/extension-list-item
|
npm install @tiptap/extension-list-item
|
||||||
@ -17,6 +13,8 @@ npm install @tiptap/extension-list-item
|
|||||||
yarn add @tiptap/extension-list-item
|
yarn add @tiptap/extension-list-item
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This extension requires the [`BulletList`](/api/nodes/bullet-list) or [`OrderedList`](/api/nodes/ordered-list) node.
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------------- | -------- | ------- | --------------------------------------------------------------------- |
|
| -------------- | -------- | ------- | --------------------------------------------------------------------- |
|
||||||
|
@ -7,10 +7,6 @@ This extension enables you to use ordered lists in the editor. They are rendered
|
|||||||
Type <code>1. </code> (or any other number followed by a dot) at the beginning of a new line and it will magically transform to a ordered list.
|
Type <code>1. </code> (or any other number followed by a dot) at the beginning of a new line and it will magically transform to a ordered list.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
::: warning Use with ListItem
|
|
||||||
This extension requires the [`ListItem`](/api/nodes/list-item) node.
|
|
||||||
:::
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# with npm
|
# with npm
|
||||||
npm install @tiptap/extension-ordered-list @tiptap/extension-list-item
|
npm install @tiptap/extension-ordered-list @tiptap/extension-list-item
|
||||||
@ -19,6 +15,8 @@ npm install @tiptap/extension-ordered-list @tiptap/extension-list-item
|
|||||||
yarn add @tiptap/extension-ordered-list @tiptap/extension-list-item
|
yarn add @tiptap/extension-ordered-list @tiptap/extension-list-item
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This extension requires the [`ListItem`](/api/nodes/list-item) node.
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------------- | -------- | ------- | --------------------------------------------------------------------- |
|
| -------------- | -------- | ------- | --------------------------------------------------------------------- |
|
||||||
|
@ -5,10 +5,6 @@
|
|||||||
Don’t try to use tables without table cells. It won’t be fun.
|
Don’t try to use tables without table cells. It won’t be fun.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
::: warning Use with Table, TableRow and TableHeader
|
|
||||||
This extension requires the [`Table`](/api/nodes/table), [`TableRow`](/api/nodes/table-row) and [`TableHeader`](/api/nodes/table-header) nodes.
|
|
||||||
:::
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# with npm
|
# with npm
|
||||||
npm install @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-header @tiptap/extension-table-cell
|
npm install @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-header @tiptap/extension-table-cell
|
||||||
@ -17,6 +13,8 @@ npm install @tiptap/extension-table @tiptap/extension-table-row @tiptap/extensio
|
|||||||
yarn add @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-header @tiptap/extension-table-cell
|
yarn add @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-header @tiptap/extension-table-cell
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This extension requires the [`Table`](/api/nodes/table), [`TableRow`](/api/nodes/table-row) and [`TableHeader`](/api/nodes/table-header) nodes.
|
||||||
|
|
||||||
## Source code
|
## Source code
|
||||||
[packages/extension-table-cell/](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-table-cell/)
|
[packages/extension-table-cell/](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-table-cell/)
|
||||||
|
|
||||||
|
@ -21,10 +21,6 @@ TableRow.extend({
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
::: warning Use with Table, TableRow and TableCell
|
|
||||||
This extension requires the [`Table`](/api/nodes/table), [`TableRow`](/api/nodes/table-row) and [`TableCell`](/api/nodes/table-cell) nodes.
|
|
||||||
:::
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# with npm
|
# with npm
|
||||||
npm install @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-header @tiptap/extension-table-cell
|
npm install @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-header @tiptap/extension-table-cell
|
||||||
@ -33,6 +29,8 @@ npm install @tiptap/extension-table @tiptap/extension-table-row @tiptap/extensio
|
|||||||
yarn add @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-header @tiptap/extension-table-cell
|
yarn add @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-header @tiptap/extension-table-cell
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This extension requires the [`Table`](/api/nodes/table), [`TableRow`](/api/nodes/table-row) and [`TableCell`](/api/nodes/table-cell) nodes.
|
||||||
|
|
||||||
## Source code
|
## Source code
|
||||||
[packages/extension-table-header/](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-table-header/)
|
[packages/extension-table-header/](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-table-header/)
|
||||||
|
|
||||||
|
@ -5,10 +5,6 @@
|
|||||||
What’s a table without rows? Add this extension to make your tables usable.
|
What’s a table without rows? Add this extension to make your tables usable.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
::: warning Use with Table, TableHeader and TableCell
|
|
||||||
This extension requires the [`Table`](/api/nodes/table), [`TableHeader`](/api/nodes/table-header) and [`TableCell`](/api/nodes/table-cell) nodes.
|
|
||||||
:::
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# with npm
|
# with npm
|
||||||
npm install @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-header @tiptap/extension-table-cell
|
npm install @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-header @tiptap/extension-table-cell
|
||||||
@ -17,6 +13,8 @@ npm install @tiptap/extension-table @tiptap/extension-table-row @tiptap/extensio
|
|||||||
yarn add @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-header @tiptap/extension-table-cell
|
yarn add @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-header @tiptap/extension-table-cell
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This extension requires the [`Table`](/api/nodes/table), [`TableHeader`](/api/nodes/table-header) and [`TableCell`](/api/nodes/table-cell) nodes.
|
||||||
|
|
||||||
## Source code
|
## Source code
|
||||||
[packages/extension-table-row/](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-table-row/)
|
[packages/extension-table-row/](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-table-row/)
|
||||||
|
|
||||||
|
@ -7,10 +7,6 @@ Nothing is as much fun as a good old HTML table. The `Table` extension enables y
|
|||||||
Don’t forget to add a `spacer.gif`. (Just joking. If you don’t know what that is, don’t listen.)
|
Don’t forget to add a `spacer.gif`. (Just joking. If you don’t know what that is, don’t listen.)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
::: warning Use with TableRow, TableHeader and TableCell
|
|
||||||
This extension requires the [`TableRow`](/api/nodes/table-row), [`TableHeader`](/api/nodes/table-header) and [`TableCell`](/api/nodes/table-cell) nodes.
|
|
||||||
:::
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# with npm
|
# with npm
|
||||||
npm install @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-header @tiptap/extension-table-cell
|
npm install @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-header @tiptap/extension-table-cell
|
||||||
@ -19,6 +15,8 @@ npm install @tiptap/extension-table @tiptap/extension-table-row @tiptap/extensio
|
|||||||
yarn add @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-header @tiptap/extension-table-cell
|
yarn add @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-header @tiptap/extension-table-cell
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This extension requires the [`TableRow`](/api/nodes/table-row), [`TableHeader`](/api/nodes/table-header) and [`TableCell`](/api/nodes/table-cell) nodes.
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| ----------------------- | --------- | ----------- | --------------------------------------------------------------------- |
|
| ----------------------- | --------- | ----------- | --------------------------------------------------------------------- |
|
||||||
|
@ -7,10 +7,6 @@ This extension renders a task item list element, which is a `<li>` tag with a `d
|
|||||||
This extension doesn’t require any JavaScript framework, it’s based on plain JavaScript.
|
This extension doesn’t require any JavaScript framework, it’s based on plain JavaScript.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
::: warning Use with TaskList
|
|
||||||
This extension requires the [`TaskList`](/api/nodes/task-list) node.
|
|
||||||
:::
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# With npm
|
# With npm
|
||||||
npm install @tiptap/extension-task-list @tiptap/extension-task-item
|
npm install @tiptap/extension-task-list @tiptap/extension-task-item
|
||||||
@ -19,6 +15,8 @@ npm install @tiptap/extension-task-list @tiptap/extension-task-item
|
|||||||
yarn add @tiptap/extension-task-list @tiptap/extension-task-item
|
yarn add @tiptap/extension-task-list @tiptap/extension-task-item
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This extension requires the [`TaskList`](/api/nodes/task-list) node.
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------------- | -------- | ------- | --------------------------------------------------------------------- |
|
| -------------- | -------- | ------- | --------------------------------------------------------------------- |
|
||||||
|
@ -7,10 +7,6 @@ This extension enables you to use task lists in the editor. They are rendered as
|
|||||||
Type <code>[ ] </code> or <code>[x] </code> at the beginning of a new line and it will magically transform to a task list.
|
Type <code>[ ] </code> or <code>[x] </code> at the beginning of a new line and it will magically transform to a task list.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
::: warning Use with TaskItem
|
|
||||||
This extension requires the [`TaskItem`](/api/nodes/task-item) extension.
|
|
||||||
:::
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# with npm
|
# with npm
|
||||||
npm install @tiptap/extension-task-list @tiptap/extension-task-item
|
npm install @tiptap/extension-task-list @tiptap/extension-task-item
|
||||||
@ -19,6 +15,8 @@ npm install @tiptap/extension-task-list @tiptap/extension-task-item
|
|||||||
yarn add @tiptap/extension-task-list @tiptap/extension-task-item
|
yarn add @tiptap/extension-task-list @tiptap/extension-task-item
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This extension requires the [`TaskItem`](/api/nodes/task-item) extension.
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------------- | -------- | ------- | --------------------------------------------------------------------- |
|
| -------------- | -------- | ------- | --------------------------------------------------------------------- |
|
||||||
|
@ -3,13 +3,11 @@ Congratulations! You’ve found our playground with a list of experiments. Be aw
|
|||||||
|
|
||||||
## New
|
## New
|
||||||
* [Linter](/experiments/linter)
|
* [Linter](/experiments/linter)
|
||||||
* [Annotation](/experiments/annotation)
|
|
||||||
* [Comments](/experiments/comments)
|
|
||||||
* [Color](/experiments/color)
|
|
||||||
* [Commands](/experiments/commands)
|
|
||||||
* [Embeds](/experiments/embeds)
|
|
||||||
* [Multiple editors](/experiments/multiple-editors)
|
* [Multiple editors](/experiments/multiple-editors)
|
||||||
* [Details](/experiments/details)
|
* [@tiptap/extension-slash-command?](/experiments/commands)
|
||||||
|
* [@tiptap/extension-iframe?](/experiments/embeds)
|
||||||
|
* [@tiptap/extension-toggle-list?](/experiments/details)
|
||||||
|
* [@tiptap/extension-collaboration-annotation](/experiments/collaboration-annotation)
|
||||||
|
|
||||||
## Waiting for approval
|
## Waiting for approval
|
||||||
* [@tiptap/extension-placeholder](/experiments/placeholder)
|
* [@tiptap/extension-placeholder](/experiments/placeholder)
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
# Annotation
|
|
||||||
|
|
||||||
⚠️ Experiment
|
|
||||||
|
|
||||||
<demo name="Experiments/Annotation" />
|
|
42
docs/src/docPages/experiments/collaboration-annotation.md
Normal file
42
docs/src/docPages/experiments/collaboration-annotation.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# CollaborationAnnotation
|
||||||
|
[![Version](https://img.shields.io/npm/v/@tiptap/extension-collaboration-annotation.svg?label=version)](https://www.npmjs.com/package/@tiptap/extension-collaboration-annotation)
|
||||||
|
[![Downloads](https://img.shields.io/npm/dm/@tiptap/extension-collaboration-annotation.svg)](https://npmcharts.com/compare/@tiptap/extension-collaboration-annotation?minimal=true)
|
||||||
|
|
||||||
|
⚠️ Experiment
|
||||||
|
|
||||||
|
Annotations can be used to add additional information to the content, for example comments. They live on a different level than the actual editor content.
|
||||||
|
|
||||||
|
<!-- :::pro Pro Extension
|
||||||
|
We kindly ask you to [sponsor our work](/sponsor) when using this extension in production.
|
||||||
|
::: -->
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
```bash
|
||||||
|
# with npm
|
||||||
|
npm install @tiptap/extension-collaboration-annotation
|
||||||
|
|
||||||
|
# with Yarn
|
||||||
|
yarn add @tiptap/extension-collaboration-annotation
|
||||||
|
```
|
||||||
|
|
||||||
|
This extension requires the [`Collaboration`](/api/extensions/collaboration) extension.
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
| Option | Type | Default | Description |
|
||||||
|
| -------- | -------- | ----------- | ---------------------------------------------------------------------------------- |
|
||||||
|
| document | `Object` | `null` | An initialized Y.js document. |
|
||||||
|
| field | `String` | `'default'` | Name of a Y.js map, can be changed to sync multiple fields with one Y.js document. |
|
||||||
|
| map | `Object` | `null` | A raw Y.js map, can be used instead of `document` and `field`. |
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
| Command | Parameters | Description |
|
||||||
|
| ---------------- | ---------- | ------------------------------------------------------------------------- |
|
||||||
|
| addAnnotation | data | Adds an annotation to the current selection, takes a string or an object. |
|
||||||
|
| updateAnnotation | id, data | Update the data that’s associated with an annotation. |
|
||||||
|
| deleteAnnotation | id | Remove an annotation. |
|
||||||
|
|
||||||
|
## Source code
|
||||||
|
[packages/extension-collaboration-annotation/](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-collaboration-annotation/)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
<demo name="Experiments/CollaborationAnnotation" />
|
@ -1,5 +0,0 @@
|
|||||||
# Color
|
|
||||||
|
|
||||||
⚠️ Experiment
|
|
||||||
|
|
||||||
<demo name="Experiments/Color" />
|
|
@ -1,5 +0,0 @@
|
|||||||
# Comments
|
|
||||||
|
|
||||||
⚠️ Experiment
|
|
||||||
|
|
||||||
<demo name="Experiments/Comments" />
|
|
@ -1,7 +1,7 @@
|
|||||||
# Accessibility
|
# Accessibility
|
||||||
|
|
||||||
:::pro Fund the development ♥
|
:::pro Fund the development ♥
|
||||||
We need your support to maintain, update, support and develop tiptap 2. If you’re waiting for progress here, [become a sponsor and fund open source](/sponsor).
|
We need your support to maintain, update, support and develop tiptap 2. If you’re waiting for progress here, [become a sponsor and fund open-source](/sponsor).
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## toc
|
## toc
|
||||||
|
@ -37,8 +37,27 @@ This will do the following:
|
|||||||
5. make the text editable (but that’s the default anyway), and
|
5. make the text editable (but that’s the default anyway), and
|
||||||
6. disable the loading of [the default CSS](https://github.com/ueberdosis/tiptap-next/tree/main/packages/core/src/style.ts) (which is not much anyway).
|
6. disable the loading of [the default CSS](https://github.com/ueberdosis/tiptap-next/tree/main/packages/core/src/style.ts) (which is not much anyway).
|
||||||
|
|
||||||
## Configure extensions
|
## Nodes, marks and extensions
|
||||||
A lot of the extension can be configured, too. Add an `.configure()` to the extension and pass an object to it. The following example will disable the default heading levels 4, 5 and 6:
|
Most features are packed into [nodes](/api/nodes), [marks](/api/marks) and [extensions](/api/extensions). Import what you need and pass them as an Array to the editor and you are good to go. Here is the minimal setup with only three extensions:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { Editor } from '@tiptap/core'
|
||||||
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Text from '@tiptap/extension-text'
|
||||||
|
|
||||||
|
new Editor({
|
||||||
|
element: document.querySelector('.element'),
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Paragraph,
|
||||||
|
Text,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configure an extensions
|
||||||
|
Most extensions can be configured. Add a `.configure()` to pass an object to it. The following example will disable the default heading levels 4, 5 and 6:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { Editor } from '@tiptap/core'
|
import { Editor } from '@tiptap/core'
|
||||||
@ -60,4 +79,57 @@ new Editor({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
Have a look at the documentation of the extension you’re using to learn more about their settings.
|
Have a look at the documentation of the extension you use to learn more about their settings.
|
||||||
|
|
||||||
|
### Default extensions
|
||||||
|
We have put together a few of the most common extensions and provide a `defaultExtensions()` helper to load them. Here is how you to use that:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { Editor, defaultExtensions } from '@tiptap/starter-kit'
|
||||||
|
|
||||||
|
new Editor({
|
||||||
|
extensions: defaultExtensions(),
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
And you can even pass configuration for all default extensions as an object. Just prefix the configuration with the extension name:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { Editor, defaultExtensions } from '@tiptap/starter-kit'
|
||||||
|
|
||||||
|
new Editor({
|
||||||
|
extensions: defaultExtensions({
|
||||||
|
heading: {
|
||||||
|
levels: [1, 2, 3]
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
The `defaultExtensions()` function returns an array, so if you want to load them and add some custom extensions you could write it like that:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { Editor, defaultExtensions } from '@tiptap/starter-kit'
|
||||||
|
import Strike from '@tiptap/extension-strike'
|
||||||
|
|
||||||
|
new Editor({
|
||||||
|
extensions: [
|
||||||
|
...defaultExtensions(),
|
||||||
|
Strike,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Don’t want to load a specific extension? Just filter it out:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { Editor, defaultExtensions } from '@tiptap/starter-kit'
|
||||||
|
|
||||||
|
new Editor({
|
||||||
|
extensions: [
|
||||||
|
...defaultExtensions().filter(extension => extension.config.name !== 'history'),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
You’ll probably see something like that in collaborative editing examples. The [`Collaboration`](/api/extensions/collaboration) comes with its own history extension, you need to remove the default [`History`](/api/extensions/history) extension to avoid conflicts.
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
One of the strength of tiptap is it’s extendability. You don’t depend on the provided extensions, it’s intended to extend the editor to your liking. With custom extensions you can add new content types and new functionalities, on top of what already exists or from scratch.
|
One of the strength of tiptap is it’s extendability. You don’t depend on the provided extensions, it’s intended to extend the editor to your liking. With custom extensions you can add new content types and new functionalities, on top of what already exists or from scratch.
|
||||||
|
|
||||||
## Customize existing extensions
|
## Customize existing extensions
|
||||||
Let’s say you want to change the keyboard shortcuts for the bullet list. You should start by looking at [the source code of the `BulletList` extension](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-bullet-list/index.ts) and find the part you would like to change. In that case, the keyboard shortcut, and just that.
|
Let’s say you want to change the keyboard shortcuts for the bullet list. You should start by looking at [the source code of the `BulletList` extension](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-bullet-list/src/bullet-list.ts) and find the part you would like to change. In that case, the keyboard shortcut, and just that.
|
||||||
|
|
||||||
Every extension has an `extend()` method, which takes an object with everything you want to change or add to it. For the bespoken example, your code could like that:
|
Every extension has an `extend()` method, which takes an object with everything you want to change or add to it. For the bespoken example, your code could like that:
|
||||||
|
|
||||||
|
@ -146,3 +146,32 @@ export default {
|
|||||||
</node-view-wrapper>
|
</node-view-wrapper>
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
### dom: ?dom.Node
|
||||||
|
> The outer DOM node that represents the document node. When not given, the default strategy is used to create a DOM node.
|
||||||
|
|
||||||
|
### contentDOM: ?dom.Node
|
||||||
|
> The DOM node that should hold the node's content. Only meaningful if the node view also defines a dom property and if its node type is not a leaf node type. When this is present, ProseMirror will take care of rendering the node's children into it. When it is not present, the node view itself is responsible for rendering (or deciding not to render) its child nodes.
|
||||||
|
|
||||||
|
### update: ?fn(node: Node, decorations: [Decoration]) → bool
|
||||||
|
> When given, this will be called when the view is updating itself. It will be given a node (possibly of a different type), and an array of active decorations (which are automatically drawn, and the node view may ignore if it isn't interested in them), and should return true if it was able to update to that node, and false otherwise. If the node view has a contentDOM property (or no dom property), updating its child nodes will be handled by ProseMirror.
|
||||||
|
|
||||||
|
### selectNode: ?fn()
|
||||||
|
> Can be used to override the way the node's selected status (as a node selection) is displayed.
|
||||||
|
|
||||||
|
### deselectNode: ?fn()
|
||||||
|
> When defining a selectNode method, you should also provide a deselectNode method to remove the effect again.
|
||||||
|
|
||||||
|
### setSelection: ?fn(anchor: number, head: number, root: dom.Document)
|
||||||
|
> This will be called to handle setting the selection inside the node. The anchor and head positions are relative to the start of the node. By default, a DOM selection will be created between the DOM positions corresponding to those positions, but if you override it you can do something else.
|
||||||
|
|
||||||
|
### stopEvent: ?fn(event: dom.Event) → bool
|
||||||
|
> Can be used to prevent the editor view from trying to handle some or all DOM events that bubble up from the node view. Events for which this returns true are not handled by the editor.
|
||||||
|
|
||||||
|
### ignoreMutation: ?fn(dom.MutationRecord) → bool
|
||||||
|
> Called when a DOM mutation or a selection change happens within the view. When the change is a selection change, the record will have a type property of "selection" (which doesn't occur for native mutation records). Return false if the editor should re-read the selection or re-parse the range around the mutation, true if it can safely be ignored.
|
||||||
|
|
||||||
|
### destroy: ?fn()
|
||||||
|
> Called when the node view is removed from the editor or the whole editor is destroyed.
|
||||||
|
@ -63,7 +63,7 @@ editor.isActive({ textAlign: 'right' })
|
|||||||
If your selection spans multiple nodes or marks, or only part of the selection has a mark, `isActive()` will return `false` and indicate nothing is active. That is how it is supposed to be, because it allows people to apply a new node or mark to that selection right-away.
|
If your selection spans multiple nodes or marks, or only part of the selection has a mark, `isActive()` will return `false` and indicate nothing is active. That is how it is supposed to be, because it allows people to apply a new node or mark to that selection right-away.
|
||||||
|
|
||||||
## Icons
|
## Icons
|
||||||
Most editor toolbars use icons for their buttons. In some of our demos, we use the open source icon set [Remix Icon](https://remixicon.com/), that’s free to use. But it’s totally up to you what you use. Here are a few icon sets you can consider:
|
Most editor toolbars use icons for their buttons. In some of our demos, we use the open-source icon set [Remix Icon](https://remixicon.com/), that’s free to use. But it’s totally up to you what you use. Here are a few icon sets you can consider:
|
||||||
|
|
||||||
* [Remix Icon](https://remixicon.com/#editor)
|
* [Remix Icon](https://remixicon.com/#editor)
|
||||||
* [Font Awesome](https://fontawesome.com/icons?c=editors)
|
* [Font Awesome](https://fontawesome.com/icons?c=editors)
|
||||||
|
@ -7,4 +7,4 @@ The following guide describes how to integrate tiptap with your [Next.js](https:
|
|||||||
|
|
||||||
TODO
|
TODO
|
||||||
|
|
||||||
<demo name="React" mode="react" />
|
<demo name="React" />
|
||||||
|
@ -7,4 +7,4 @@ The following guide describes how to integrate tiptap with your [React](https://
|
|||||||
|
|
||||||
TODO
|
TODO
|
||||||
|
|
||||||
<demo name="React" mode="react" />
|
<demo name="React" />
|
||||||
|
@ -10,7 +10,7 @@ title: Headless WYSIWYG Text Editor
|
|||||||
|
|
||||||
tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as *New York Times*, *The Guardian* or *Atlassian*.
|
tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as *New York Times*, *The Guardian* or *Atlassian*.
|
||||||
|
|
||||||
Create exactly the rich text editor you want out of customizable building blocks. tiptap comes with sensible defaults, a lot of extensions and a friendly API to customize every aspect. It’s backed by a welcoming community, open source, and free.
|
Create exactly the rich text editor you want out of customizable building blocks. tiptap comes with sensible defaults, a lot of extensions and a friendly API to customize every aspect. It’s backed by a welcoming community, open-source, and free.
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
<demo name="Examples/CollaborativeEditing" :show-source="false" inline />
|
<demo name="Examples/CollaborativeEditing" :show-source="false" inline />
|
||||||
|
@ -3,16 +3,16 @@
|
|||||||
## Introduction
|
## Introduction
|
||||||
To deliver a top-notch developer experience and user experience, we put ~~hundreds~~ thousands of hours of unpaid work into tiptap. Your funding helps us to make this work more and more financially sustainable. This enables us to provide helpful support, maintain all our packages, keep everything up to date, and develop new features and extensions for tiptap.
|
To deliver a top-notch developer experience and user experience, we put ~~hundreds~~ thousands of hours of unpaid work into tiptap. Your funding helps us to make this work more and more financially sustainable. This enables us to provide helpful support, maintain all our packages, keep everything up to date, and develop new features and extensions for tiptap.
|
||||||
|
|
||||||
Give back to the open source community and [sponsor us on GitHub](https://github.com/sponsors/ueberdosis)! ♥
|
Give back to the open-source community and [sponsor us on GitHub](https://github.com/sponsors/ueberdosis)! ♥
|
||||||
|
|
||||||
## Your benefits as a sponsor
|
## Your benefits as a sponsor
|
||||||
* Give back to the open source community
|
* Give back to the open-source community
|
||||||
* Get early access to private repositories
|
* Get early access to private repositories
|
||||||
* Ensure the further maintenace and development of tiptap
|
* Ensure the further maintenace and development of tiptap
|
||||||
* Your issues and pull requests get a `sponsor ♥` label
|
* Your issues and pull requests get a `sponsor ♥` label
|
||||||
* Get a sponsor badge in all your comments on GitHub
|
* Get a sponsor badge in all your comments on GitHub
|
||||||
* Show support in your GitHub profile
|
* Show support in your GitHub profile
|
||||||
* Receive monthly reports about our open source work
|
* Receive monthly reports about our open-source work
|
||||||
|
|
||||||
Does that sound good? [Sponsor us on GitHub!](https://github.com/sponsors/ueberdosis)
|
Does that sound good? [Sponsor us on GitHub!](https://github.com/sponsors/ueberdosis)
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
Headless
|
Headless
|
||||||
</h3>
|
</h3>
|
||||||
<p>
|
<p>
|
||||||
We don’t tell you what a menu should look like or where it should be rendered in the DOM. That’s why tiptap is headless and comes without any CSS. You are in full control over markup, styling and behaviour.
|
It’s headless and comes without any CSS. You are in full control over markup, styling and behaviour.
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<btn type="tertiary" icon="arrow-right" to="/guide/styling">
|
<btn type="tertiary" icon="arrow-right" to="/guide/styling">
|
||||||
@ -49,7 +49,7 @@
|
|||||||
Framework-agnostic
|
Framework-agnostic
|
||||||
</h3>
|
</h3>
|
||||||
<p>
|
<p>
|
||||||
No matter what framework you use, you’ll enjoy tiptap. Out of the box, it works with plain JavaScript and Vue.js, but it’s also possible to use it in <g-link to="/installation/react">React</g-link>, <g-link to="/installation/svelte">Svelte</g-link> and others.
|
Out of the box, tiptap works with plain JavaScript and Vue.js, but it’s also possible to use it in <g-link to="/installation/react">React</g-link>, <g-link to="/installation/svelte">Svelte</g-link> and others.
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<btn type="tertiary" icon="arrow-right" to="/installation">
|
<btn type="tertiary" icon="arrow-right" to="/installation">
|
||||||
@ -63,7 +63,7 @@
|
|||||||
TypeScript
|
TypeScript
|
||||||
</h3>
|
</h3>
|
||||||
<p>
|
<p>
|
||||||
tiptap 2 is written in TypeScript. That helps to find bugs early and gives a nice autocomplete for the API (if your IDE supports that) on top of the extensive human written documentation.
|
TypeScript helps to find bugs early and gives you a nice autocomplete for the API on top of the extensive human written documentation.
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<btn type="tertiary" icon="arrow-right" to="/guide/typescript">
|
<btn type="tertiary" icon="arrow-right" to="/guide/typescript">
|
||||||
@ -77,7 +77,7 @@
|
|||||||
Collaborative
|
Collaborative
|
||||||
</h3>
|
</h3>
|
||||||
<p>
|
<p>
|
||||||
Real-time collaboration, syncing between different devices and working offline used to be hard. We provide everything you need to keep everything in sync, conflict-free with the power of <g-link to="https://github.com/yjs/yjs">Y.js</g-link>.
|
Real-time collaboration, syncing between different devices and working offline isn’t hard anymore. Keep everything in sync with the magic of <g-link to="https://github.com/yjs/yjs">Y.js</g-link>.
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<btn type="tertiary" icon="arrow-right" to="/guide/collaborative-editing">
|
<btn type="tertiary" icon="arrow-right" to="/guide/collaborative-editing">
|
||||||
@ -91,7 +91,7 @@
|
|||||||
Community
|
Community
|
||||||
</h3>
|
</h3>
|
||||||
<p>
|
<p>
|
||||||
Over the years, a lovely community has grown around tiptap. There’s so much content shared, so many people helping out in issues and a ton of community extensions, you’ll be surprised how much that can help.
|
There’s so much content shared, so many people helping out in issues and a ton of community extensions, you’ll be surprised how much that all can help.
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<btn type="tertiary" icon="arrow-right" to="https://github.com/ueberdosis/tiptap-next">
|
<btn type="tertiary" icon="arrow-right" to="https://github.com/ueberdosis/tiptap-next">
|
||||||
@ -108,7 +108,7 @@
|
|||||||
Quickstart
|
Quickstart
|
||||||
</h2>
|
</h2>
|
||||||
<p>
|
<p>
|
||||||
For quick demos or to give it just a spin, grab the latest build from a CDN. Here is a quick example to get you started with tiptap:
|
For quick demos or to give it just a spin, grab the latest build from a CDN. Here is an example to get you started with tiptap:
|
||||||
</p>
|
</p>
|
||||||
<!-- eslint-disable -->
|
<!-- eslint-disable -->
|
||||||
<prism language="html"><!DOCTYPE html>
|
<prism language="html"><!DOCTYPE html>
|
||||||
@ -134,7 +134,7 @@
|
|||||||
<!-- eslint-enable -->
|
<!-- eslint-enable -->
|
||||||
<div>
|
<div>
|
||||||
<btn type="tertiary" icon="arrow-right" to="/installation">
|
<btn type="tertiary" icon="arrow-right" to="/installation">
|
||||||
Learn More
|
Learn more
|
||||||
</btn>
|
</btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,21 +18,21 @@ import OrderedList, { OrderedListOptions } from '@tiptap/extension-ordered-list'
|
|||||||
import ListItem, { ListItemOptions } from '@tiptap/extension-list-item'
|
import ListItem, { ListItemOptions } from '@tiptap/extension-list-item'
|
||||||
|
|
||||||
export function defaultExtensions(options?: Partial<{
|
export function defaultExtensions(options?: Partial<{
|
||||||
dropursor: DropcursorOptions,
|
dropursor: Partial<DropcursorOptions>,
|
||||||
paragraph: ParagraphOptions,
|
paragraph: Partial<ParagraphOptions>,
|
||||||
history: HistoryOptions,
|
history: Partial<HistoryOptions>,
|
||||||
bold: BoldOptions,
|
bold: Partial<BoldOptions>,
|
||||||
italic: ItalicOptions,
|
italic: Partial<ItalicOptions>,
|
||||||
code: CodeOptions,
|
code: Partial<CodeOptions>,
|
||||||
codeBlock: CodeBlockOptions,
|
codeBlock: Partial<CodeBlockOptions>,
|
||||||
heading: HeadingOptions,
|
heading: Partial<HeadingOptions>,
|
||||||
hardBreak: HardBreakOptions,
|
hardBreak: Partial<HardBreakOptions>,
|
||||||
strike: StrikeOptions,
|
strike: Partial<StrikeOptions>,
|
||||||
blockquote: BlockquoteOptions,
|
blockquote: Partial<BlockquoteOptions>,
|
||||||
horizontalRule: HorizontalRuleOptions,
|
horizontalRule: Partial<HorizontalRuleOptions>,
|
||||||
bulletList: BulletListOptions,
|
bulletList: Partial<BulletListOptions>,
|
||||||
orderedList: OrderedListOptions,
|
orderedList: Partial<OrderedListOptions>,
|
||||||
listItem: ListItemOptions,
|
listItem: Partial<ListItemOptions>,
|
||||||
}>) {
|
}>) {
|
||||||
return [
|
return [
|
||||||
Document,
|
Document,
|
||||||
|
Loading…
Reference in New Issue
Block a user