experimental annotations

commit 41c0fe487b78fdabac4fc0abd922fc6b23b87821
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Tue Jan 19 14:55:36 2021 +0100

    move to the new experiments structure

commit 5b22dcaf042e247b138fc00ccaea1f1baa52b7a4
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Tue Jan 19 14:50:21 2021 +0100

    enable typescript checks again

commit 50d566f72c1eda9175075173e9b11c125fb0d767
Merge: 5352c488 a7d52bb0
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Tue Jan 19 14:49:47 2021 +0100

    Merge branch 'feature/annotations' of github.com:ueberdosis/tiptap-next into feature/annotations

commit 5352c4889f7d443148f6507bd0c372eec0b0a1dc
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Fri Jan 15 22:44:52 2021 +0100

    more fiddling with Y.js

commit e7c7fb70e7724ac3134de0aa47db06bc72a7925a
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Fri Jan 15 21:42:51 2021 +0100

    fiddle around with Y.js

commit a8b8268d6f3025a407caefed22c9db5657ca04f5
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Fri Jan 15 20:18:21 2021 +0100

    refactoring

commit 6bbc94ff417323bedac6c9cbcb541cbfdb471090
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Fri Jan 15 20:15:57 2021 +0100

    refactoring

commit dbdb3d3039bb818a973bb07c1c05c14ec73f6110
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Fri Jan 15 14:59:56 2021 +0100

    refactoring

commit 1d8038dd6b27f00d723b547d4ebb75351608b2d4
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Fri Jan 15 11:07:17 2021 +0100

    clean up

commit 4024ceaa7afddf890dd89610f487bd58231f1e09
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 22:01:09 2021 +0100

    refactoring

commit 4659583eee59571716cadaa821200f9cded5e2a0
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 21:49:08 2021 +0100

    disable typescript errors for now

commit 4a30fd13e4f91dd740fecd98e45972712ad9b742
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 21:48:23 2021 +0100

    code style

commit 1209ebafb21ab94f287d3c81db72dcb0d66d02d6
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 21:45:20 2021 +0100

    add a comment, set default class

commit 3a4394e4f107ad6df58d66eb690e25a4e58f9176
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 21:41:03 2021 +0100

    strange refactoring (wip)

commit 32e2d8a29bde0e282092799c69b1f32a85eb1251
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 17:19:12 2021 +0100

    add extension documentation page, refactoring

commit 4f9460895fce2c91399d230aa28d34d22bd11f8f
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 15:23:16 2021 +0100

    refactoring

commit 59d23958d524eb6055772880a525ec6828b93e35
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 12:54:25 2021 +0100

    refactoring

commit 66ea1cd22634af1d00b19e95274ffa60bcbd5506
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 12:52:49 2021 +0100

    clean up

commit 0d6a624029ef2318c5567db0de776343beefeb27
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 12:33:54 2021 +0100

    refactoring

commit 887767f78da94cd15387b581cf6b5c0f565f4b12
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 12:12:58 2021 +0100

    refactoring

commit c15bda12bdc76d7f901ed27740d85467b07e372b
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Wed Jan 13 22:23:57 2021 +0100

    refactoring

commit 97e7d1b527cde1382ee74e6b8494aca140b66f02
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Wed Jan 13 22:18:14 2021 +0100

    clean up

commit 2b28e35902209b95fa6beb12c7e64cb9efbb47ad
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Wed Jan 13 22:15:18 2021 +0100

    refactoring

commit 8612666b567ef8d4d8497d3b4dc063ecab7d85ed
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Wed Jan 13 22:11:15 2021 +0100

    remove version

commit f3169a29ea78a0b566a49c7885e687d9ecc09787
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Wed Jan 13 22:09:41 2021 +0100

    code style

commit 4d1c13ef4c00ddb49ad645983a092e7b4dbff60d
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Wed Jan 13 22:07:05 2021 +0100

    refactoring

commit 393e05278ab26b1d516404949ac6b6557e138085
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Wed Jan 13 21:47:16 2021 +0100

    refactoring

commit 65ee8f272578bbc172a5a82f634cc483d1314e58
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Wed Jan 13 21:32:24 2021 +0100

    add crappy styling

commit 816f031d5903ae2cb12f8e216f9f459e1716ec12
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Wed Jan 13 21:24:29 2021 +0100

    add basic annotation plugin

commit fa5ef2334a05a3ff06242f2e4f9288fe8aa405ce
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Wed Jan 13 16:19:15 2021 +0100

    init new package

commit a7d52bb0d4e2f7979ef87a341da0d15b68213f6a
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Fri Jan 15 22:44:52 2021 +0100

    more fiddling with Y.js

commit 2ad9d5047e27bb6fe1dbe398f4b3ccface028d6b
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Fri Jan 15 21:42:51 2021 +0100

    fiddle around with Y.js

commit 315dc512af2c26dd4f9d2c55b3b629530c60da8e
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Fri Jan 15 20:18:21 2021 +0100

    refactoring

commit 058e79f7efc2385910846a93a6af7e4f40f0d976
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Fri Jan 15 20:15:57 2021 +0100

    refactoring

commit 576e645797a923aee43e059e7b3485bf924683a4
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Fri Jan 15 14:59:56 2021 +0100

    refactoring

commit 46798f194fdbb6b5781818e6386efc5c089b46cc
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Fri Jan 15 11:07:17 2021 +0100

    clean up

commit ed7ebd39e13a1ed63931d19a08c2cc7a030f0e0a
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 22:01:09 2021 +0100

    refactoring

commit 3d61a206c8f70601845da191e92693baf4cc7f28
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 21:49:08 2021 +0100

    disable typescript errors for now

commit cc2286d82ade7356fa998bfcd2dea2eafaa2f122
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 21:48:23 2021 +0100

    code style

commit bb9fb292693f929793663b83853083157c384136
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 21:45:20 2021 +0100

    add a comment, set default class

commit d547e74f092de6457a574b3d6b852bde3b408c8d
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 21:41:03 2021 +0100

    strange refactoring (wip)

commit 99e415b4eba9e4fa704cbe77f053860bd7ae8b1b
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 17:19:12 2021 +0100

    add extension documentation page, refactoring

commit ba585e6abe1b56c8c1fcd4e68057d967c23ad67e
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 15:23:16 2021 +0100

    refactoring

commit 2981591b8b568e2b41f4f81930d67f75b0ffcd6d
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 12:54:25 2021 +0100

    refactoring

commit 4083f3e3ac3f0d8ff54bae78ebb3e957f2f20309
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 12:52:49 2021 +0100

    clean up

commit 409a060be38d50fda542f988780481cffc94c54e
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 12:33:54 2021 +0100

    refactoring

commit fb1d0dc46af4a067165dcca19c9b1ba8c7a5cfc9
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Thu Jan 14 12:12:58 2021 +0100

    refactoring

commit 6cba6e0d098c0f3d3d4ec1f0e74ab50aa7966d31
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Wed Jan 13 22:23:57 2021 +0100

    refactoring

commit 9f8b6ef0f5455e5c8b331258d9168d01c67e29b6
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Wed Jan 13 22:18:14 2021 +0100

    clean up

commit 514c4d08039352d9c96212116fc59cb676f86e0b
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Wed Jan 13 22:15:18 2021 +0100

    refactoring

commit 3b0b99d0029130696b7651a9a417bcbee5a35b6d
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Wed Jan 13 22:11:15 2021 +0100

    remove version

commit 64fc138d73d9981b8e187c703c68146be6276664
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Wed Jan 13 22:09:41 2021 +0100

    code style

commit cb42c8c504bc02c929bd041a1f1b87c3a8f068f8
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Wed Jan 13 22:07:05 2021 +0100

    refactoring

commit 4203615a35d34cd6988ac0377507f32f197860af
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Wed Jan 13 21:47:16 2021 +0100

    refactoring

commit cf476d899193540013da22c4d078c1daca86f2d3
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Wed Jan 13 21:32:24 2021 +0100

    add crappy styling

commit 59a7639ee9c9fb7f3f57c376220a20baec93bba2
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Wed Jan 13 21:24:29 2021 +0100

    add basic annotation plugin

commit 8d2d9158cc1611da18432d76925c002731d51d09
Author: Hans Pagel <hans.pagel@ueber.io>
Date:   Wed Jan 13 16:19:15 2021 +0100

    init new package
This commit is contained in:
Hans Pagel 2021-01-19 14:57:41 +01:00
parent 5452095343
commit 7f63a0b2e0
14 changed files with 486 additions and 0 deletions

View File

@ -0,0 +1,10 @@
export class AnnotationItem {
public id!: number
public text!: string
constructor(id: number, text: string) {
this.id = id
this.text = text
}
}

View File

@ -0,0 +1,33 @@
import { Plugin, PluginKey } from 'prosemirror-state'
import { AnnotationState } from './AnnotationState'
export const AnnotationPluginKey = new PluginKey('annotation')
export const AnnotationPlugin = (options: any) => new Plugin({
key: AnnotationPluginKey,
state: {
init: AnnotationState.init,
apply(transaction, prevState) {
return prevState.apply(transaction)
},
},
props: {
decorations(state) {
const { decorations } = this.getState(state)
const { selection } = state
if (!selection.empty) {
return decorations
}
const annotations = this
.getState(state)
.annotationsAt(selection.from)
options.onUpdate(annotations)
return decorations
},
},
})

View File

@ -0,0 +1,95 @@
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)
}
}

View File

@ -0,0 +1,70 @@
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?]) => {},
}
export const Annotation = Extension.create({
name: 'annotation',
defaultOptions: <AnnotationOptions>{
HTMLAttributes: {
class: 'annotation',
},
onUpdate: decorations => decorations,
},
addCommands() {
return {
addAnnotation: (content: any): Command => ({ 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): Command => ({ dispatch, state }) => {
if (dispatch) {
dispatch(state.tr.setMeta(AnnotationPluginKey, { type: 'deleteAnnotation', id }))
}
return true
},
}
},
addProseMirrorPlugins() {
return [
AnnotationPlugin(this.options),
]
},
})
declare module '@tiptap/core' {
interface AllExtensions {
Annotation: typeof Annotation,
}
}

View File

@ -0,0 +1,5 @@
import { Annotation } from './annotation'
export * from './annotation'
export default Annotation

View File

@ -0,0 +1,7 @@
context('/api/extensions/annotations', () => {
before(() => {
cy.visit('/api/extensions/annotations')
})
// TODO: Write tests
})

View File

@ -0,0 +1,87 @@
<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 youre 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>

View File

@ -0,0 +1,7 @@
context('/examples/annotations', () => {
before(() => {
cy.visit('/examples/annotations')
})
// TODO: Write tests
})

View File

@ -0,0 +1,145 @@
<template>
<div>
<div v-if="editor">
<h2>
Original
</h2>
<button @click="addComment" :disabled="!editor.can().addAnnotation()">
comment
</button>
<editor-content :editor="editor" />
<div v-for="comment in comments" :key="comment.type.spec.data.id">
{{ comment.type.spec.data }}
<button @click="deleteComment(comment.type.spec.data.id)">
remove
</button>
</div>
<!-- <br>
<h2>
ProseMirror JSON from Y.js document
</h2>
{{ rawDocument }} -->
<br>
<h2>
Y.js document
</h2>
{{ json }}
<br>
<h2>
Mirror
</h2>
<editor-content :editor="anotherEditor" />
</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 Collaboration from '@tiptap/extension-collaboration'
import Bold from '@tiptap/extension-bold'
import Heading from '@tiptap/extension-heading'
import * as Y from 'yjs'
import { yDocToProsemirrorJSON } from 'y-prosemirror'
import Annotation from '../Annotation/extension'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
anotherEditor: null,
comments: [],
ydoc: null,
}
},
mounted() {
this.ydoc = new Y.Doc()
this.editor = new Editor({
extensions: [
Document,
Paragraph,
Text,
Bold,
Heading,
Annotation.configure({
onUpdate: items => { this.comments = items },
}),
Collaboration.configure({
document: this.ydoc,
}),
],
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 youre free to add more complex data, for example JSON from another tiptap instance. :-)
</p>
`,
})
this.anotherEditor = new Editor({
extensions: [
Document,
Paragraph,
Text,
// Annotation.configure({
// onUpdate: items => { this.comments = items },
// }),
Collaboration.configure({
document: this.ydoc,
}),
],
})
},
methods: {
addComment() {
const content = prompt('Comment', '')
this.editor.commands.addAnnotation(content)
},
deleteComment(id) {
this.editor.commands.deleteAnnotation(id)
},
},
computed: {
rawDocument() {
return yDocToProsemirrorJSON(this.ydoc, 'default')
},
json() {
return this.ydoc.toJSON()
},
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
<style lang="scss">
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
}
.annotation {
background: #9DEF8F;
}
</style>

View File

@ -0,0 +1,8 @@
# Annotation
TODO
## Source code
[packages/extension-annotation/](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-annotation/)
## Usage
<demo name="Extensions/Annotation" highlight="24,44-46,60-67" />

View File

@ -2,3 +2,5 @@
Congratulations! Youve found our secret playground with a list of experiments. Be aware, that nothing here is ready to use. Feel free to play around, but please, dont open an issue for a bug youve found here or send pull requests. :-)
* [Linter](/experiments/linter)
* [Annotation](/experiments/annotation)
* [Comments](/experiments/comments)

View File

@ -0,0 +1,5 @@
# Annotation
⚠️ Experiment
<demo name="Experiments/Annotation" />

View File

@ -0,0 +1,5 @@
# Comments
⚠️ Experiment
<demo name="Experiments/Comments" />

View File

@ -35,6 +35,10 @@
link: /examples/drawing
- title: Multiple editors
link: /examples/multiple-editors
- title: Comments
link: /examples/comments
draft: true
- title: Guide
items:
@ -151,6 +155,9 @@
- title: Extensions
link: /api/extensions
items:
- title: Annotation
link: /api/extensions/annotation
draft: true
- title: Collaboration
link: /api/extensions/collaboration
type: pro