diff --git a/docs/package.json b/docs/package.json index 6b5744c2..3c850ae1 100644 --- a/docs/package.json +++ b/docs/package.json @@ -29,6 +29,7 @@ "vue-github-button": "^1.1.2", "vue-live": "^1.16.0", "y-indexeddb": "^9.0.6", + "y-prosemirror": "^1.0.5", "y-webrtc": "^10.1.7", "y-websocket": "^1.3.8", "yjs": "^13.4.7" diff --git a/docs/src/demos/Examples/Drawing/index.vue b/docs/src/demos/Examples/Drawing/index.vue index ca37f4f7..ef1e3bcc 100644 --- a/docs/src/demos/Examples/Drawing/index.vue +++ b/docs/src/demos/Examples/Drawing/index.vue @@ -18,7 +18,6 @@ export default { data() { return { editor: null, - provider: null, } }, @@ -36,7 +35,6 @@ export default { beforeDestroy() { this.editor.destroy() - this.provider.destroy() }, } diff --git a/docs/src/demos/Examples/MultipleEditors/index.vue b/docs/src/demos/Examples/MultipleEditors/index.vue index 33c6d230..672a5a35 100644 --- a/docs/src/demos/Examples/MultipleEditors/index.vue +++ b/docs/src/demos/Examples/MultipleEditors/index.vue @@ -18,6 +18,12 @@
+
+ JSON +
+
+ {{ json }} +
@@ -31,7 +37,7 @@ import TaskList from '@tiptap/extension-task-list' import TaskItem from '@tiptap/extension-task-item' import Collaboration from '@tiptap/extension-collaboration' import * as Y from 'yjs' -import { WebsocketProvider } from 'y-websocket' +import { yDocToProsemirrorJSON } from 'y-prosemirror' const ParagraphDocument = Document.extend({ content: 'paragraph', @@ -55,13 +61,12 @@ export default { title: null, tasks: null, description: null, + ydoc: null, } }, mounted() { - const ydoc = new Y.Doc() - - this.provider = new WebsocketProvider('wss://websocket.tiptap.dev', 'tiptap-multiple-editors-example', ydoc) + this.ydoc = new Y.Doc() this.title = new Editor({ extensions: [ @@ -69,10 +74,11 @@ export default { Paragraph, Text, Collaboration.configure({ - document: ydoc, + document: this.ydoc, field: 'title', }), ], + content: '

No matter what you do, this’ll be a single paragraph.', }) this.tasks = new Editor({ @@ -83,10 +89,17 @@ export default { TaskList, CustomTaskItem, Collaboration.configure({ - document: ydoc, + document: this.ydoc, field: 'tasks', }), ], + content: ` +

+ `, }) this.description = new Editor({ @@ -95,13 +108,28 @@ export default { Paragraph, Text, Collaboration.configure({ - document: ydoc, + document: this.ydoc, field: 'description', }), ], + content: ` +

+ This can be lengthy text. +

+ `, }) }, + computed: { + json() { + return { + title: yDocToProsemirrorJSON(this.ydoc, 'title'), + tasks: yDocToProsemirrorJSON(this.ydoc, 'tasks'), + description: yDocToProsemirrorJSON(this.ydoc, 'description'), + } + }, + }, + beforeDestroy() { this.title.destroy() this.tasks.destroy() @@ -150,5 +178,23 @@ export default { &--title { font-size: 1.6rem; } + + &--json { + background: #0D0D0D; + color: #FFF; + font-size: 0.8rem; + } +} + +pre { + font-family: 'JetBrainsMono', monospace; + padding: 0.75rem 1rem; + border-radius: 0.5rem; + + code { + color: inherit; + background: none; + font-size: 0.8rem; + } } diff --git a/docs/src/demos/Examples/TodoApp/index.vue b/docs/src/demos/Examples/TodoApp/index.vue index b8551d06..0e9bb4cd 100644 --- a/docs/src/demos/Examples/TodoApp/index.vue +++ b/docs/src/demos/Examples/TodoApp/index.vue @@ -43,8 +43,13 @@ export default { ], content: ` `, }) @@ -70,5 +75,9 @@ ul[data-type="taskList"] { margin-right: 0.5rem; } } + + input[type="checkbox"] { + cursor: pointer; + } } diff --git a/docs/src/demos/Experiments/Annotation/extension/AnnotationItem.ts b/docs/src/demos/Experiments/Annotation/extension/AnnotationItem.ts new file mode 100644 index 00000000..56b85459 --- /dev/null +++ b/docs/src/demos/Experiments/Annotation/extension/AnnotationItem.ts @@ -0,0 +1,10 @@ +export class AnnotationItem { + public id!: number + + public text!: string + + constructor(id: number, text: string) { + this.id = id + this.text = text + } +} diff --git a/docs/src/demos/Experiments/Annotation/extension/AnnotationPlugin.ts b/docs/src/demos/Experiments/Annotation/extension/AnnotationPlugin.ts new file mode 100644 index 00000000..7542bc59 --- /dev/null +++ b/docs/src/demos/Experiments/Annotation/extension/AnnotationPlugin.ts @@ -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, oldState) { + return oldState.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 + }, + + }, +}) diff --git a/docs/src/demos/Experiments/Annotation/extension/AnnotationState.ts b/docs/src/demos/Experiments/Annotation/extension/AnnotationState.ts new file mode 100644 index 00000000..23f65823 --- /dev/null +++ b/docs/src/demos/Experiments/Annotation/extension/AnnotationState.ts @@ -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) + } +} diff --git a/docs/src/demos/Experiments/Annotation/extension/annotation.ts b/docs/src/demos/Experiments/Annotation/extension/annotation.ts new file mode 100644 index 00000000..06c666b6 --- /dev/null +++ b/docs/src/demos/Experiments/Annotation/extension/annotation.ts @@ -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: { + 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, + } +} diff --git a/docs/src/demos/Experiments/Annotation/extension/index.ts b/docs/src/demos/Experiments/Annotation/extension/index.ts new file mode 100644 index 00000000..7c86e27d --- /dev/null +++ b/docs/src/demos/Experiments/Annotation/extension/index.ts @@ -0,0 +1,5 @@ +import { Annotation } from './annotation' + +export * from './annotation' + +export default Annotation diff --git a/docs/src/demos/Experiments/Annotation/index.spec.js b/docs/src/demos/Experiments/Annotation/index.spec.js new file mode 100644 index 00000000..6a218007 --- /dev/null +++ b/docs/src/demos/Experiments/Annotation/index.spec.js @@ -0,0 +1,7 @@ +context('/api/extensions/annotations', () => { + before(() => { + cy.visit('/api/extensions/annotations') + }) + + // TODO: Write tests +}) diff --git a/docs/src/demos/Experiments/Annotation/index.vue b/docs/src/demos/Experiments/Annotation/index.vue new file mode 100644 index 00000000..9ab11811 --- /dev/null +++ b/docs/src/demos/Experiments/Annotation/index.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/docs/src/demos/Experiments/CharacterLimit/extension/CharacterLimit.ts b/docs/src/demos/Experiments/CharacterLimit/extension/CharacterLimit.ts new file mode 100644 index 00000000..6892f647 --- /dev/null +++ b/docs/src/demos/Experiments/CharacterLimit/extension/CharacterLimit.ts @@ -0,0 +1,64 @@ +// @ts-nocheck +import { Extension } from '@tiptap/core' +import { + Plugin, PluginKey, +} from 'prosemirror-state' + +export interface CharacterLimitOptions { + limit: number, +} + +export const CharacterLimit = Extension.create({ + name: 'characterLimit', + + defaultOptions: { + limit: 100, + }, + + addProseMirrorPlugins() { + const { options } = this + + return [ + new Plugin({ + + key: new PluginKey('characterLimit'), + + // state: { + // init(_, config) { + // // console.log(_, config) + // // const length = config.doc.content.size + + // // if (length > options.limit) { + // // console.log('too long', options.limit, config) + + // // const transaction = config.tr.insertText('', options.limit + 1, length) + + // // return config.apply(transaction) + // // } + // }, + // apply() { + // // + // }, + // }, + + appendTransaction: (transactions, oldState, newState) => { + const oldLength = oldState.doc.content.size + const newLength = newState.doc.content.size + + if (newLength > options.limit && newLength > oldLength) { + const newTr = newState.tr + newTr.insertText('', options.limit + 1, newLength) + + return newTr + } + }, + }), + ] + }, +}) + +declare module '@tiptap/core' { + interface AllExtensions { + CharacterLimit: typeof CharacterLimit, + } +} diff --git a/docs/src/demos/Experiments/CharacterLimit/extension/index.ts b/docs/src/demos/Experiments/CharacterLimit/extension/index.ts new file mode 100644 index 00000000..7770eb81 --- /dev/null +++ b/docs/src/demos/Experiments/CharacterLimit/extension/index.ts @@ -0,0 +1,4 @@ +import { CharacterLimit } from './CharacterLimit' + +export * from './CharacterLimit' +export default CharacterLimit diff --git a/docs/src/demos/Experiments/CharacterLimit/index.vue b/docs/src/demos/Experiments/CharacterLimit/index.vue new file mode 100644 index 00000000..16a4822c --- /dev/null +++ b/docs/src/demos/Experiments/CharacterLimit/index.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/docs/src/demos/Experiments/Comments/index.spec.js b/docs/src/demos/Experiments/Comments/index.spec.js new file mode 100644 index 00000000..6f90b595 --- /dev/null +++ b/docs/src/demos/Experiments/Comments/index.spec.js @@ -0,0 +1,7 @@ +context('/examples/annotations', () => { + before(() => { + cy.visit('/examples/annotations') + }) + + // TODO: Write tests +}) diff --git a/docs/src/demos/Experiments/Comments/index.vue b/docs/src/demos/Experiments/Comments/index.vue new file mode 100644 index 00000000..c2388200 --- /dev/null +++ b/docs/src/demos/Experiments/Comments/index.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/docs/src/demos/Experiments/Linter/extension/Linter.ts b/docs/src/demos/Experiments/Linter/extension/Linter.ts new file mode 100644 index 00000000..4307ed66 --- /dev/null +++ b/docs/src/demos/Experiments/Linter/extension/Linter.ts @@ -0,0 +1,98 @@ +// @ts-nocheck +import { Extension } from '@tiptap/core' +import { Decoration, DecorationSet } from 'prosemirror-view' +import { Plugin, PluginKey, TextSelection } from 'prosemirror-state' + +function renderIcon(issue) { + const icon = document.createElement('div') + + icon.className = 'lint-icon' + icon.title = issue.message + icon.issue = issue + + return icon +} + +function runAllLinterPlugins(doc, plugins) { + const decorations: [any?] = [] + + const results = plugins.map(LinterPlugin => { + return new LinterPlugin(doc).scan().getResults() + }).flat() + + results.forEach(issue => { + decorations.push(Decoration.inline(issue.from, issue.to, { + class: 'problem', + }), + Decoration.widget(issue.from, renderIcon(issue))) + }) + + return DecorationSet.create(doc, decorations) +} + +export interface LinterOptions { + plugins: [any], +} + +export const Linter = Extension.create({ + name: 'linter', + + defaultOptions: { + plugins: [], + }, + + addProseMirrorPlugins() { + const { plugins } = this.options + + return [ + new Plugin({ + key: new PluginKey('linter'), + state: { + init(_, { doc }) { + return runAllLinterPlugins(doc, plugins) + }, + apply(transaction, oldState) { + return transaction.docChanged + ? runAllLinterPlugins(transaction.doc, plugins) + : oldState + }, + }, + props: { + decorations(state) { + return this.getState(state) + }, + handleClick(view, _, event) { + if (/lint-icon/.test(event.target.className)) { + const { from, to } = event.target.issue + + view.dispatch( + view.state.tr + .setSelection(TextSelection.create(view.state.doc, from, to)) + .scrollIntoView(), + ) + + return true + } + }, + handleDoubleClick(view, _, event) { + if (/lint-icon/.test(event.target.className)) { + const prob = event.target.issue + + if (prob.fix) { + prob.fix(view) + view.focus() + return true + } + } + }, + }, + }), + ] + }, +}) + +declare module '@tiptap/core' { + interface AllExtensions { + Linter: typeof Linter, + } +} diff --git a/docs/src/demos/Experiments/Linter/extension/LinterPlugin.ts b/docs/src/demos/Experiments/Linter/extension/LinterPlugin.ts new file mode 100644 index 00000000..46fc867a --- /dev/null +++ b/docs/src/demos/Experiments/Linter/extension/LinterPlugin.ts @@ -0,0 +1,23 @@ +// @ts-nocheck +export default class LinterPlugin { + protected doc + + private results = [] + + constructor(doc: any) { + this.doc = doc + } + + record(message: string, from: number, to: number, fix?: null) { + this.results.push({ + message, + from, + to, + fix, + }) + } + + getResults() { + return this.results + } +} diff --git a/docs/src/demos/Experiments/Linter/extension/index.ts b/docs/src/demos/Experiments/Linter/extension/index.ts new file mode 100644 index 00000000..29d42a68 --- /dev/null +++ b/docs/src/demos/Experiments/Linter/extension/index.ts @@ -0,0 +1,8 @@ +import { Linter } from './Linter' + +export * from './Linter' +export default Linter + +export { BadWords } from './plugins/BadWords' +export { Punctuation } from './plugins/Punctuation' +export { HeadingLevel } from './plugins/HeadingLevel' diff --git a/docs/src/demos/Experiments/Linter/extension/plugins/BadWords.ts b/docs/src/demos/Experiments/Linter/extension/plugins/BadWords.ts new file mode 100644 index 00000000..59194c21 --- /dev/null +++ b/docs/src/demos/Experiments/Linter/extension/plugins/BadWords.ts @@ -0,0 +1,26 @@ +// @ts-nocheck +import LinterPlugin from '../LinterPlugin' + +export class BadWords extends LinterPlugin { + + public regex = /\b(obviously|clearly|evidently|simply)\b/ig + + scan() { + this.doc.descendants((node: any, position: any) => { + if (!node.isText) { + return + } + + const matches = this.regex.exec(node.text) + + if (matches) { + this.record( + `Try not to say '${matches[0]}'`, + position + matches.index, position + matches.index + matches[0].length, + ) + } + }) + + return this + } +} diff --git a/docs/src/demos/Experiments/Linter/extension/plugins/HeadingLevel.ts b/docs/src/demos/Experiments/Linter/extension/plugins/HeadingLevel.ts new file mode 100644 index 00000000..df78dacf --- /dev/null +++ b/docs/src/demos/Experiments/Linter/extension/plugins/HeadingLevel.ts @@ -0,0 +1,30 @@ +// @ts-nocheck +import LinterPlugin from '../LinterPlugin' + +export class HeadingLevel extends LinterPlugin { + fixHeader(level) { + return function ({ state, dispatch }) { + dispatch(state.tr.setNodeMarkup(this.from - 1, null, { level })) + } + } + + scan() { + let lastHeadLevel = null + + this.doc.descendants((node, position) => { + if (node.type.name === 'heading') { + // Check whether heading levels fit under the current level + const { level } = node.attrs + + if (lastHeadLevel != null && level > lastHeadLevel + 1) { + this.record(`Heading too small (${level} under ${lastHeadLevel})`, + position + 1, position + 1 + node.content.size, + this.fixHeader(lastHeadLevel + 1)) + } + lastHeadLevel = level + } + }) + + return this + } +} diff --git a/docs/src/demos/Experiments/Linter/extension/plugins/Punctuation.ts b/docs/src/demos/Experiments/Linter/extension/plugins/Punctuation.ts new file mode 100644 index 00000000..d3a228d7 --- /dev/null +++ b/docs/src/demos/Experiments/Linter/extension/plugins/Punctuation.ts @@ -0,0 +1,37 @@ +// @ts-nocheck +import LinterPlugin from '../LinterPlugin' + +export class Punctuation extends LinterPlugin { + public regex = / ([,.!?:]) ?/g + + fix(replacement: any) { + return function ({ state, dispatch }) { + dispatch( + state.tr.replaceWith( + this.from, this.to, + state.schema.text(replacement), + ), + ) + } + } + + scan() { + this.doc.descendants((node, position) => { + if (!node.isText) { + return + } + + const matches = this.regex.exec(node.text) + + if (matches) { + this.record( + 'Suspicious spacing around punctuation', + position + matches.index, position + matches.index + matches[0].length, + this.fix(`${matches[1]} `), + ) + } + }) + + return this + } +} diff --git a/docs/src/demos/Experiments/Linter/index.vue b/docs/src/demos/Experiments/Linter/index.vue new file mode 100644 index 00000000..b4c09f9c --- /dev/null +++ b/docs/src/demos/Experiments/Linter/index.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/docs/src/docPages/api/extensions/annotation.md b/docs/src/docPages/api/extensions/annotation.md new file mode 100644 index 00000000..c65fea66 --- /dev/null +++ b/docs/src/docPages/api/extensions/annotation.md @@ -0,0 +1,8 @@ +# Annotation +TODO + +## Source code +[packages/extension-annotation/](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-annotation/) + +## Usage + diff --git a/docs/src/docPages/api/extensions/suggestion.md b/docs/src/docPages/api/extensions/suggestion.md index c8c92987..3539f167 100644 --- a/docs/src/docPages/api/extensions/suggestion.md +++ b/docs/src/docPages/api/extensions/suggestion.md @@ -1,7 +1,7 @@ # Suggestion :::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 the 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 diff --git a/docs/src/docPages/api/nodes/emoji.md b/docs/src/docPages/api/nodes/emoji.md new file mode 100644 index 00000000..f8705b6a --- /dev/null +++ b/docs/src/docPages/api/nodes/emoji.md @@ -0,0 +1,7 @@ +# Emoji + +:::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). +::: + +TODO diff --git a/docs/src/docPages/api/nodes/hashtag.md b/docs/src/docPages/api/nodes/hashtag.md new file mode 100644 index 00000000..e0aeaf3a --- /dev/null +++ b/docs/src/docPages/api/nodes/hashtag.md @@ -0,0 +1,7 @@ +# Hashtag + +:::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). +::: + +TODO diff --git a/docs/src/docPages/api/nodes/mention.md b/docs/src/docPages/api/nodes/mention.md index 3d15ae56..3d1c2d72 100644 --- a/docs/src/docPages/api/nodes/mention.md +++ b/docs/src/docPages/api/nodes/mention.md @@ -1,5 +1,9 @@ # Mention +:::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). +::: + ## Installation ```bash # with npm diff --git a/docs/src/docPages/api/nodes/table-cell.md b/docs/src/docPages/api/nodes/table-cell.md index 17ee77ed..60f98068 100644 --- a/docs/src/docPages/api/nodes/table-cell.md +++ b/docs/src/docPages/api/nodes/table-cell.md @@ -1,7 +1,7 @@ # TableCell :::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 the 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 diff --git a/docs/src/docPages/api/nodes/table-row.md b/docs/src/docPages/api/nodes/table-row.md index cb997243..8e404803 100644 --- a/docs/src/docPages/api/nodes/table-row.md +++ b/docs/src/docPages/api/nodes/table-row.md @@ -1,7 +1,7 @@ # TableRow :::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 the 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 diff --git a/docs/src/docPages/api/nodes/table.md b/docs/src/docPages/api/nodes/table.md index 5e874697..9ffa2192 100644 --- a/docs/src/docPages/api/nodes/table.md +++ b/docs/src/docPages/api/nodes/table.md @@ -1,7 +1,7 @@ # Table :::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 the 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 diff --git a/docs/src/docPages/examples/multiple-editors.md b/docs/src/docPages/examples/multiple-editors.md index ccfe1c6e..da61e913 100644 --- a/docs/src/docPages/examples/multiple-editors.md +++ b/docs/src/docPages/examples/multiple-editors.md @@ -1,9 +1,5 @@ # Multiple editors -The following examples has three different instances of tiptap. The first is configured to have a single paragraph of text, the second to have a task list and the third to have text. All of them are stored in a single Y.js document, which is synced with other users. - -:::warning Shared Document -Be nice! The content of this editor is shared with other users from the Internet. -::: +The following example has three different instances of tiptap. The first is configured to have a single paragraph of text, the second to have a task list and the third to have text. All of them are stored in a single Y.js document, which can be synced with other users. diff --git a/docs/src/docPages/experiments.md b/docs/src/docPages/experiments.md new file mode 100644 index 00000000..262720b5 --- /dev/null +++ b/docs/src/docPages/experiments.md @@ -0,0 +1,7 @@ +# Experiments +Congratulations! You’ve 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, don’t open an issue for a bug you’ve found here or send pull requests. :-) + +* [Linter](/experiments/linter) +* [Annotation](/experiments/annotation) +* [Comments](/experiments/comments) +* [CharacterLimit](/experiments/character-limit) diff --git a/docs/src/docPages/experiments/annotation.md b/docs/src/docPages/experiments/annotation.md new file mode 100644 index 00000000..4d4ebbe5 --- /dev/null +++ b/docs/src/docPages/experiments/annotation.md @@ -0,0 +1,5 @@ +# Annotation + +⚠️ Experiment + + diff --git a/docs/src/docPages/experiments/character-limit.md b/docs/src/docPages/experiments/character-limit.md new file mode 100644 index 00000000..fa6a209a --- /dev/null +++ b/docs/src/docPages/experiments/character-limit.md @@ -0,0 +1,5 @@ +# CharacterLimit + +⚠️ Experiment + + diff --git a/docs/src/docPages/experiments/comments.md b/docs/src/docPages/experiments/comments.md new file mode 100644 index 00000000..42fda436 --- /dev/null +++ b/docs/src/docPages/experiments/comments.md @@ -0,0 +1,5 @@ +# Comments + +⚠️ Experiment + + diff --git a/docs/src/docPages/experiments/linter.md b/docs/src/docPages/experiments/linter.md new file mode 100644 index 00000000..37d083f8 --- /dev/null +++ b/docs/src/docPages/experiments/linter.md @@ -0,0 +1,5 @@ +# Linter + +⚠️ Experiment + + diff --git a/docs/src/docPages/guide/accessibility.md b/docs/src/docPages/guide/accessibility.md new file mode 100644 index 00000000..711ecc5c --- /dev/null +++ b/docs/src/docPages/guide/accessibility.md @@ -0,0 +1,30 @@ +# Accessibility + +:::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). +::: + +## toc + +## Introduction +We strive to make tiptap accessible to everyone, but to be honest, there’s not much work done now. From our current understanding, that’s what needs to be done: + +### Interface +An interface needs to have semantic markup, must be keyboard accessible and well documented. Currently, we don’t even provide an interface, so for now that’s totally up to you. But no worries, we’ll provide an interface soon and take accessibility into account early on. + +### Editor +The editor needs to produce semantic markup, must be keyboard accessible and well documented. The tiptap content is well structured so that’s a good foundation already. That said, we can add support and encourage the usage of additional attributes, for example the Alt-attribute for images. + +### Writing assistance (optional) +An optional writing assitance could help people writing content semanticly correct, for example pointing out an incorrect usage of heading levels. With that kind of assistance provided by the core developers, we could help to improve the content of a lot of applications. + +## Resources + +| Document | Section | Heading | +| -------- | ------- | -------------------------------------------------------------------------------------- | +| WCAG 2.1 | 1.1 | [Text Alternatives](https://www.w3.org/WAI/WCAG21/Understanding/text-alternatives) | +| WCAG 2.1 | 1.1.1 | [Non-text Content](https://www.w3.org/WAI/WCAG21/Understanding/non-text-content) | +| WCAG 2.1 | 2.1 | [Keyboard Accessible](https://www.w3.org/WAI/WCAG21/Understanding/keyboard-accessible) | +| WCAG 2.1 | 2.1.1 | [Keyboard](https://www.w3.org/WAI/WCAG21/Understanding/keyboard) | +| WCAG 2.1 | 4.1.1 | [Parsing](https://www.w3.org/WAI/WCAG21/Understanding/parsing) | +| WCAG 2.1 | 4.1.2 | [Name, Role, Value](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) | diff --git a/docs/src/docPages/guide/typescript.md b/docs/src/docPages/guide/typescript.md new file mode 100644 index 00000000..0e4a1a01 --- /dev/null +++ b/docs/src/docPages/guide/typescript.md @@ -0,0 +1,51 @@ +# Working with TypeScript + +## toc + +## Introduction +The whole tiptap is code base is written in TypeScript. If you haven’t heard of it or never used it, no worries. You don’t have to. + +TypeScript extends JavaScript by adding types (hence the name). It adds new syntax, which doesn’t exist in plain JavaScript. It’s actually removed before running in the browser, but this step – the compilation – is important to find bugs early. It checks if you passe the right types of data to functions. For a big and complex project, that’s very valuable. It means we’ll get notified of lot of bugs, before shipping code to you. + +Anyway, if you don’t use TypeScript in your project, that’s fine. You’ll still be able to use tiptap and even get a really nice autocomplete for the tiptap API (if your editor supports it, but most do). + +If you’re using TypeScript in your project and want to extend tiptap, there are two things that are good to know. + +## Options type +To extend or create default options for an extension, you’ll need to define a custom type, here is an example: + +```ts +import { Extension } from '@tiptap/core' + +export interface CustomExtensionOptions { + awesomeness: number, +} + +const CustomExtension = Extension.create({ + defaultOptions: { + awesomeness: 100, + }, +}) +``` + +## Command type +The core package also exports a `Command` type, which needs to be added to all commands that you specify in your code. Here is an example: + +```ts +import { Command, Extension } from '@tiptap/core' + +const CustomExtension = Extension.create({ + addCommands() { + return { + /** + * Comments will be added to the autocomplete. + */ + yourCommand: (): Command => ({ commands }) => { + // … + }, + } + }, +}) +``` + +That’s basically it. We’re doing all the rest automatically. diff --git a/docs/src/docPages/overview/feedback.md b/docs/src/docPages/overview/feedback.md deleted file mode 100644 index 8844146e..00000000 --- a/docs/src/docPages/overview/feedback.md +++ /dev/null @@ -1,7 +0,0 @@ -# Feedback - -We’re looking for your feedback to improve tiptap 2 before the first public release! Share everything that helps to make it better for everyone! - -* Create issues on GitHub! [Link](https://github.com/ueberdosis/tiptap-next/issues) -* Send an email! [humans@tiptap.dev](mailto:humans@tiptap.dev) -* Follow us on Twitter! [@hanspagel](https://twitter.com/hanspagel), [@_philippkuehn](https://twitter.com/_philippkuehn), or [@_ueberdosis](https://twitter.com/_ueberdosis) diff --git a/docs/src/docPages/sponsor.md b/docs/src/docPages/sponsor.md index 366806d0..31f4857f 100644 --- a/docs/src/docPages/sponsor.md +++ b/docs/src/docPages/sponsor.md @@ -1,11 +1,12 @@ # Become a sponsor -To deliver a top-notch developer experience and user experience, we put hundreds 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. -If you’re using tiptap in a commercial project or just want to give back to the open source community, you can [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 * Give back to the open source community * Get early access to private repositories +* Ensure the further maintenace and development of tiptap * Your issues and pull requests get a `sponsor 💖` label * Get a sponsor badge in all your comments on GitHub * Show support in your GitHub profile @@ -13,11 +14,28 @@ If you’re using tiptap in a commercial project or just want to give back to th Does that sound good? [Sponsor us on GitHub!](https://github.com/sponsors/ueberdosis) -## I can’t use GitHub. -If you’re a company, don’t want to use GitHub, don’t have a credit card or want a proper invoice form us, just reach out to us at [humans@tiptap.dev](mailto:humans@tiptap.dev). +## The maintainers of tiptap +If you’re thankful for tiptap, you should say thank you to all 12 lovely people of [überdosis](https://twitter.com/_ueberdosis). The amazing company we’re all building together and the amazing company that funded the initial development costs of tiptap 2. -## I want consulting. -We don’t do any calls, consulting or personal support. If you have an issue, a question, want to talk something through or anything else, [please use GitHub issues](https://github.com/ueberdosis/tiptap-next/issues), to keep everything accessible for the whole community. +AND you should definitely hire us if you want us to design und build an amazing digital product for you. Bonus points if it’s somehow text editing related. -## Can we have a call? +But here are the friendly faces of the two maintainer of tiptap, Philipp Kühn (left) and Hans Pagel (right). You’ve probably read our names in the thousands of commits, pull requests or Tweets already. + +![Philipp and Hans, the maintainers of tiptap, looking happy](/philipp-and-hans.jpg) + +## More peace of mind +Companies betting on tiptap probably want some peace of mind and ensure that we keep maintaining tiptap, but don’t forget that our work is based on the work of other lovely people that you should definitel sponsor too: + +* [Sponsor Marijn Haverbeke](https://marijnhaverbeke.nl/fund/) (ProseMirror) +* [Sponsor Kevin Jahns](https://github.com/sponsors/dmonad) (Y.js) + +## Frequently asked questions + +### I can’t use GitHub. How can I support you? +If you’re a company, don’t want to use GitHub, don’t have a credit card or want a proper invoice from us, just reach out to us at [humans@tiptap.dev](mailto:humans@tiptap.dev). + +### I want consulting. What’s your rate? +We don’t do any calls, consulting or personal support for tiptap. If you have an issue, a question, want to talk something through or anything else, [please use GitHub issues](https://github.com/ueberdosis/tiptap-next/issues) to keep everything accessible for the whole community. + +### Can we have a call? Nope, we are big fans of asynchronous communication. If you really need to reach out in private, send us an email to [humans@tiptap.dev](mailto:humans@tiptap.dev), but don’t expect technical email support. diff --git a/docs/src/layouts/App/index.vue b/docs/src/layouts/App/index.vue index 1ccb734f..3f5e4c82 100644 --- a/docs/src/layouts/App/index.vue +++ b/docs/src/layouts/App/index.vue @@ -65,10 +65,6 @@
Edit this page on GitHub · - Impressum - · - Privacy Policy - · Made with 🖤 by überdosis
@@ -87,9 +83,7 @@ 'app__link': true, 'app__link--exact': $router.currentRoute.path === item.link, 'app__link--active': $router.currentRoute.path.startsWith(item.link), - 'app__link--draft': item.draft === true, - 'app__link--pro': item.pro === true, - 'app__link--new': item.new === true, + [`app__link--${item.type}`]: item.type !== null, 'app__link--with-children': !!item.items }" :to="item.redirect || item.link" @@ -104,9 +98,7 @@ 'app__link': true, 'app__link--exact': $router.currentRoute.path === item.link, 'app__link--active': $router.currentRoute.path.startsWith(item.link), - 'app__link--draft': item.draft === true, - 'app__link--pro': item.pro === true, - 'app__link--new': item.new === true, + [`app__link--${item.type}`]: item.type !== null, }" :to="item.link" exact diff --git a/docs/src/layouts/App/style.scss b/docs/src/layouts/App/style.scss index 7212bd80..fd6c98d3 100644 --- a/docs/src/layouts/App/style.scss +++ b/docs/src/layouts/App/style.scss @@ -255,6 +255,18 @@ $menuBreakPoint: 800px; } } + &--sponsor { + color: $colorWhite; + + &::after { + content: '💖'; + font-family: 'JetBrainsMono', monospace; + text-transform: uppercase; + padding: 0 0.5em; + border-radius: 5px; + } + } + &--with-children::after { content: '↓'; color: rgba($colorWhite, 0.2); diff --git a/docs/src/links.yaml b/docs/src/links.yaml index f73d6a41..6fbf3c01 100644 --- a/docs/src/links.yaml +++ b/docs/src/links.yaml @@ -8,8 +8,9 @@ link: /overview/upgrade-guide - title: Contributing link: /overview/contributing - - title: Feedback - link: /overview/feedback + - title: Become a sponsor + link: /sponsor + type: sponsor - title: Examples link: /examples @@ -19,7 +20,7 @@ link: /examples/basic - title: Collaborative editing link: /examples/collaborative-editing - pro: true + type: pro - title: Markdown shortcuts link: /examples/markdown-shortcuts - title: Formatting @@ -34,6 +35,10 @@ link: /examples/drawing - title: Multiple editors link: /examples/multiple-editors + - title: Comments + link: /examples/comments + draft: true + - title: Guide items: @@ -48,11 +53,11 @@ skip: true - title: Alpine.js link: /guide/getting-started/alpinejs - draft: true + type: draft skip: true - title: Livewire link: /guide/getting-started/livewire - draft: true + type: draft skip: true - title: Configure the editor link: /guide/configuration @@ -66,12 +71,15 @@ link: /guide/build-extensions - title: Complex node views link: /guide/node-views - draft: true - - title: Working with TypeScript - link: /guide/working-with-typescript + type: draft - title: Collaborative editing link: /guide/collaborative-editing - pro: true + type: pro + - title: Accessibility + link: /guide/accessibility + type: draft + - title: Working with TypeScript + link: /guide/typescript - title: API items: @@ -90,8 +98,14 @@ link: /api/nodes/code-block - title: Document link: /api/nodes/document + - title: Emoji + link: /api/nodes/emoji + type: draft - title: HardBreak link: /api/nodes/hard-break + - title: Hashtag + link: /api/nodes/hashtag + type: draft - title: Heading link: /api/nodes/heading - title: HorizontalRule @@ -102,20 +116,20 @@ link: /api/nodes/list-item - title: Mention link: /api/nodes/mention - draft: true + type: draft - title: OrderedList link: /api/nodes/ordered-list - title: Paragraph link: /api/nodes/paragraph - title: Table link: /api/nodes/table - draft: true + type: draft - title: TableRow link: /api/nodes/table-row - draft: true + type: draft - title: TableCell link: /api/nodes/table-cell - draft: true + type: draft - title: TaskList link: /api/nodes/task-list - title: TaskItem @@ -144,12 +158,15 @@ - title: Extensions link: /api/extensions items: + - title: Annotation + link: /api/extensions/annotation + draft: true - title: Collaboration link: /api/extensions/collaboration - pro: true + type: pro - title: CollaborationCursor link: /api/extensions/collaboration-cursor - pro: true + type: pro - title: Dropcursor link: /api/extensions/dropcursor - title: Focus @@ -162,7 +179,7 @@ link: /api/extensions/history - title: Suggestion link: /api/extensions/suggestion - draft: true + type: draft - title: TextAlign link: /api/extensions/text-align - title: Typography @@ -176,14 +193,17 @@ - title: Keyboard Shortcuts link: /api/keyboard-shortcuts -- title: Sponsoring - items: - - title: Become a sponsor - link: /sponsor - - title: Monthly reports - link: /reports - title: Links items: + - title: 'Follow on Twitter' + link: https://twitter.com/tiptap_editor - title: Documentation for tiptap 1.x link: https://v1.tiptap.dev + +- title: Legal + items: + - title: Impressum + link: /impressum + - title: Privacy Policy + link: /privacy-policy diff --git a/docs/src/templates/DocPage/style.scss b/docs/src/templates/DocPage/style.scss index 69ce9963..f51005ca 100644 --- a/docs/src/templates/DocPage/style.scss +++ b/docs/src/templates/DocPage/style.scss @@ -67,6 +67,10 @@ } } + > p > img { + max-width: 100%; + } + :first-child { margin-top: 0; } diff --git a/docs/static/philipp-and-hans.jpg b/docs/static/philipp-and-hans.jpg new file mode 100644 index 00000000..81da5472 Binary files /dev/null and b/docs/static/philipp-and-hans.jpg differ