Merge branch 'main' of github.com:ueberdosis/tiptap

# Bitte geben Sie eine Commit-Beschreibung ein, um zu erklären, warum dieser
# Merge erforderlich ist, insbesondere wenn es einen aktualisierten
# Upstream-Branch mit einem Thema-Branch zusammenführt.
#
# Zeilen, die mit '#' beginnen, werden ignoriert,
# und eine leere Beschreibung bricht den Commit ab.
This commit is contained in:
Hans Pagel 2021-06-03 09:38:01 +02:00
commit af3f96a07c
21 changed files with 452 additions and 85 deletions

View File

@ -2,6 +2,7 @@ import {
Command,
Node,
nodeInputRule,
mergeAttributes,
} from '@tiptap/core'
export interface FigureOptions {
@ -35,6 +36,8 @@ export const Figure = Node.create<FigureOptions>({
draggable: true,
isolating: true,
addAttributes() {
return {
src: {
@ -76,7 +79,7 @@ export const Figure = Node.create<FigureOptions>({
renderHTML({ HTMLAttributes }) {
return [
'figure', this.options.HTMLAttributes,
['img', HTMLAttributes],
['img', mergeAttributes(HTMLAttributes, { draggable: false, contenteditable: false })],
['figcaption', 0],
]
},

View File

@ -0,0 +1,80 @@
import { Node, mergeAttributes } from '@tiptap/core'
import { VueNodeViewRenderer } from '@tiptap/vue-2'
import { Plugin } from 'prosemirror-state'
import CustomNodeView from './CustomNode.vue'
export default Node.create({
name: 'customNode',
isBlock: true,
inline: false,
group: 'block',
draggable: true,
isolating: true,
defining: true,
selectable: true,
addAttributes() {
return {
nest: { default: false },
}
},
parseHTML() {
return [
{
tag: 'custom-node',
},
]
},
renderHTML({ HTMLAttributes }) {
return ['custom-node', mergeAttributes(HTMLAttributes)]
},
addNodeView() {
return VueNodeViewRenderer(CustomNodeView)
},
addProseMirrorPlugins() {
return [
new Plugin({
props: {
handleKeyDown: (view, event) => {
// Prevent _any_ key from clearing block. As soon as you start typing,
// and a block is focused, it'll blast the block away.
view.state.typing = true
},
handlePaste: (view, event, slice) => {
// Prevent pasting overwriting block
view.state.pasting = true
},
},
filterTransaction: (transaction, state) => {
let result = true
// Check if our flags are set, and if the selected node is a custom node
if (state.typing || state.pasting) {
transaction.mapping.maps.forEach(map => {
map.forEach((oldStart, oldEnd, newStart, newEnd) => {
state.doc.nodesBetween(
oldStart,
oldEnd,
(node, number, pos, parent, index) => {
if (node.type.name === 'customNode') {
result = false
}
},
)
})
})
}
return result
},
}),
]
},
})

View File

@ -0,0 +1,106 @@
<template>
<node-view-wrapper class="draggable-item" :class="{ active: selected }">
<div
class="drag-handle"
contenteditable="false"
draggable="true"
data-drag-handle
/>
<div class="draggable-nested">
<tiptap-nested />
</div>
</node-view-wrapper>
</template>
<script>
import { NodeViewWrapper } from '@tiptap/vue-2'
import TiptapNested from './Nested.vue'
export default {
name: 'CustomNode',
components: {
NodeViewWrapper,
TiptapNested,
},
props: {
editor: {
type: Object,
default: () => {},
},
node: {
type: Object,
default: () => {},
},
decorations: {
type: Array,
default: () => [],
},
selected: {
type: Boolean,
default: false,
},
extension: {
type: Object,
default: () => {},
},
getPos: {
type: Function,
default: () => {},
},
updateAttributes: {
type: Function,
default: () => {},
},
},
}
</script>
<style lang="scss">
.draggable-item {
display: flex;
flex-wrap: wrap;
padding: 0.5rem;
margin: 0.5rem 0;
border-radius: 0.5rem;
background: white;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), 0px 10px 20px rgba(0, 0, 0, 0.1);
&.active {
border: 1px red solid;
}
input {
margin: 0 0 0.5rem 0;
}
.drag-handle {
flex: 0 0 auto;
position: relative;
width: 1rem;
height: 1rem;
top: 0.3rem;
margin-right: 0.5rem;
cursor: grab;
background-image: url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 16"><path fill-opacity="0.2" d="M4 14c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zM2 6C.9 6 0 6.9 0 8s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6C.9 0 0 .9 0 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" /></svg>');
background-repeat: no-repeat;
background-size: contain;
background-position: center;
}
.content {
flex: 1 1 auto;
}
}
.draggable-nested {
flex: 1
}
</style>

View File

@ -0,0 +1,45 @@
<template>
<div class="editor-nested" v-if="editor">
<editor-content :editor="editor" />
</div>
</template>
<script>
import { Editor, EditorContent } from '@tiptap/vue-2'
import StarterKit from '@tiptap/starter-kit'
import CustomNode from './CustomNode.js'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
content: `
Nested intro text`,
extensions: [StarterKit, CustomNode],
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
<style>
.editor-nested {
padding: 10px;
border: 1px gray solid;
}
.ProseMirror {
outline: none;
}
</style>

View File

@ -0,0 +1,49 @@
<template>
<div class="editor" v-if="editor">
<editor-content :editor="editor" />
</div>
</template>
<script>
import { Editor, EditorContent } from '@tiptap/vue-2'
import StarterKit from '@tiptap/starter-kit'
import CustomNode from './CustomNode.js'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
content: 'Testing<custom-node></custom-node>Text',
extensions: [StarterKit, CustomNode],
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
<style>
p {
margin: 0;
}
.editor {
padding: 10px;
border: 1px gray solid;
}
.ProseMirror {
outline: none;
}
</style>

View File

@ -0,0 +1,21 @@
# setMeta
Store a metadata property in the current transaction.
## Parameters
`key: string`
The name of your metadata. You can get its value at any time with [getMeta](https://prosemirror.net/docs/ref/#state.Transaction.getMeta).
`value: any`
Store any value within your metadata.
## Usage
```js
// Prevent the update event from being triggered
editor.commands.setMeta('preventUpdate', true)
// Store any value in the current transaction.
// You can get this value at any time with tr.getMeta('foo').
editor.commands.setMeta('foo', 'bar')
```

View File

@ -2,9 +2,6 @@
⚠️ Experiment
## Known issues
* Dragging should move the image, but duplicates it
## Tasks
* Add the caption as an optional parameter to the command
* Build commands to wrap an image into a figure + figcaption?

View File

@ -206,6 +206,8 @@
- title: setMark
link: /api/commands/set-mark
type: draft
- title: setMeta
link: /api/commands/set-meta
- title: setNode
link: /api/commands/set-node
type: draft

View File

@ -3,6 +3,47 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.0.0-beta.79](https://github.com/ueberdosis/tiptap/compare/@tiptap/core@2.0.0-beta.77...@tiptap/core@2.0.0-beta.79) (2021-06-02)
**Note:** Version bump only for package @tiptap/core
# [2.0.0-beta.77](https://github.com/ueberdosis/tiptap/compare/@tiptap/core@2.0.0-beta.76...@tiptap/core@2.0.0-beta.77) (2021-06-02)
### Features
* add setMeta command ([36dad2b](https://github.com/ueberdosis/tiptap/commit/36dad2bbae5ed66077822330133a9d8ee0d28747))
# [2.0.0-beta.76](https://github.com/ueberdosis/tiptap/compare/@tiptap/core@2.0.0-beta.75...@tiptap/core@2.0.0-beta.76) (2021-06-01)
### Bug Fixes
* remove custom coordsAtPos method, fix [#583](https://github.com/ueberdosis/tiptap/issues/583) ([485fb8c](https://github.com/ueberdosis/tiptap/commit/485fb8c74c831256aaa2cefdf130e05438c2e476))
# [2.0.0-beta.75](https://github.com/ueberdosis/tiptap/compare/@tiptap/core@2.0.0-beta.74...@tiptap/core@2.0.0-beta.75) (2021-06-01)
### Bug Fixes
* prevent dispatch empty fragment when parsing content with insertContent ([2a4e02a](https://github.com/ueberdosis/tiptap/commit/2a4e02ade3b74999a9632673a607568644d6d26c))
# [2.0.0-beta.74](https://github.com/ueberdosis/tiptap/compare/@tiptap/core@2.0.0-beta.73...@tiptap/core@2.0.0-beta.74) (2021-05-31)
**Note:** Version bump only for package @tiptap/core

View File

@ -1,7 +1,7 @@
{
"name": "@tiptap/core",
"description": "headless rich text editor",
"version": "2.0.0-beta.74",
"version": "2.0.0-beta.79",
"homepage": "https://tiptap.dev",
"keywords": [
"tiptap",

View File

@ -25,6 +25,12 @@ export const insertContentAt: RawCommands['insertContentAt'] = (position, value)
preserveWhitespace: 'full',
},
})
// dont dispatch an empty fragment because this can lead to strange errors
if (content.toString() === '<>') {
return true
}
const { from, to } = typeof position === 'number'
? { from: position, to: position }
: position

View File

@ -0,0 +1,18 @@
import { Command, RawCommands } from '../types'
declare module '@tiptap/core' {
interface Commands {
setMeta: {
/**
* Store a metadata property in the current transaction.
*/
setMeta: (key: string, value: any) => Command,
}
}
}
export const setMeta: RawCommands['setMeta'] = (key, value) => ({ tr }) => {
tr.setMeta(key, value)
return true
}

View File

@ -30,6 +30,7 @@ import * as selectNodeForward from '../commands/selectNodeForward'
import * as selectParentNode from '../commands/selectParentNode'
import * as setContent from '../commands/setContent'
import * as setMark from '../commands/setMark'
import * as setMeta from '../commands/setMeta'
import * as setNode from '../commands/setNode'
import * as setNodeSelection from '../commands/setNodeSelection'
import * as setTextSelection from '../commands/setTextSelection'
@ -78,6 +79,7 @@ export { selectNodeForward }
export { selectParentNode }
export { setContent }
export { setMark }
export { setMeta }
export { setNode }
export { setNodeSelection }
export { setTextSelection }
@ -131,6 +133,7 @@ export const Commands = Extension.create({
...selectParentNode,
...setContent,
...setMark,
...setMeta,
...setNode,
...setNodeSelection,
...setTextSelection,

View File

@ -1,72 +0,0 @@
import { EditorView } from 'prosemirror-view'
type DOMRectSide = 'bottom' | 'left' | 'right' | 'top';
function textRange(node: Node, from?: number, to?: number) {
const range = document.createRange()
range.setEnd(node, typeof to === 'number' ? to : (node.nodeValue || '').length)
range.setStart(node, Math.max(from || 0, 0))
return range
}
function singleRect(object: Range | Element, bias: number) {
const rects = object.getClientRects()
return !rects.length
? object.getBoundingClientRect()
: rects[bias < 0 ? 0 : rects.length - 1]
}
export default function coordsAtPos(view: EditorView, pos: number, end = false) {
const { node, offset } = view.domAtPos(pos) // view.docView.domFromPos(pos);
let side: DOMRectSide | null = null
let rect: DOMRect | null = null
if (node.nodeType === 3) {
const nodeValue = node.nodeValue || ''
if (end && offset < nodeValue.length) {
rect = singleRect(textRange(node, offset - 1, offset), -1)
side = 'right'
} else if (offset < nodeValue.length) {
rect = singleRect(textRange(node, offset, offset + 1), -1)
side = 'left'
}
} else if (node.firstChild) {
if (offset < node.childNodes.length) {
const child = node.childNodes[offset]
rect = singleRect(
child.nodeType === 3 ? textRange(child) : (child as Element),
-1,
)
side = 'left'
}
if ((!rect || rect.top === rect.bottom) && offset) {
const child = node.childNodes[offset - 1]
rect = singleRect(
child.nodeType === 3 ? textRange(child) : (child as Element),
1,
)
side = 'right'
}
} else {
const element = node as Element
rect = element.getBoundingClientRect()
side = 'left'
}
if (rect && side) {
const x = rect[side]
return {
top: rect.top,
bottom: rect.bottom,
left: x,
right: x,
}
}
return {
top: 0,
bottom: 0,
left: 0,
right: 0,
}
}

View File

@ -1,9 +1,8 @@
import { EditorView } from 'prosemirror-view'
import coordsAtPos from './coordsAtPos'
export default function posToDOMRect(view: EditorView, from: number, to: number): DOMRect {
const start = coordsAtPos(view, from)
const end = coordsAtPos(view, to, true)
const start = view.coordsAtPos(from)
const end = view.coordsAtPos(to, -1)
const top = Math.min(start.top, end.top)
const bottom = Math.max(start.bottom, end.bottom)
const left = Math.min(start.left, end.left)

View File

@ -12,7 +12,6 @@ export { default as markPasteRule } from './pasteRules/markPasteRule'
export { default as callOrReturn } from './utilities/callOrReturn'
export { default as mergeAttributes } from './utilities/mergeAttributes'
export { default as coordsAtPos } from './helpers/coordsAtPos'
export { default as getExtensionField } from './helpers/getExtensionField'
export { default as findChildren } from './helpers/findChildren'
export { default as findChildrenInRange } from './helpers/findChildrenInRange'

View File

@ -37,6 +37,12 @@ function getDecorations({ doc, name, lowlight }: { doc: ProsemirrorNode, name: s
? lowlight.highlight(language, block.node.textContent).children
: lowlight.highlightAuto(block.node.textContent).children
console.log({
lowlight,
languages,
nodes,
})
parseNodes(nodes).forEach(node => {
const to = from + node.text.length

View File

@ -3,6 +3,38 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.0.0-beta.78](https://github.com/ueberdosis/tiptap/compare/@tiptap/html@2.0.0-beta.77...@tiptap/html@2.0.0-beta.78) (2021-06-02)
**Note:** Version bump only for package @tiptap/html
# [2.0.0-beta.77](https://github.com/ueberdosis/tiptap/compare/@tiptap/html@2.0.0-beta.76...@tiptap/html@2.0.0-beta.77) (2021-06-02)
**Note:** Version bump only for package @tiptap/html
# [2.0.0-beta.76](https://github.com/ueberdosis/tiptap/compare/@tiptap/html@2.0.0-beta.75...@tiptap/html@2.0.0-beta.76) (2021-06-01)
**Note:** Version bump only for package @tiptap/html
# [2.0.0-beta.75](https://github.com/ueberdosis/tiptap/compare/@tiptap/html@2.0.0-beta.74...@tiptap/html@2.0.0-beta.75) (2021-06-01)
**Note:** Version bump only for package @tiptap/html
# [2.0.0-beta.74](https://github.com/ueberdosis/tiptap/compare/@tiptap/html@2.0.0-beta.73...@tiptap/html@2.0.0-beta.74) (2021-05-31)
**Note:** Version bump only for package @tiptap/html

View File

@ -1,7 +1,7 @@
{
"name": "@tiptap/html",
"description": "utility package to render tiptap JSON as HTML",
"version": "2.0.0-beta.74",
"version": "2.0.0-beta.78",
"homepage": "https://tiptap.dev",
"keywords": [
"tiptap",
@ -21,7 +21,7 @@
"dist"
],
"dependencies": {
"@tiptap/core": "^2.0.0-beta.74",
"@tiptap/core": "^2.0.0-beta.79",
"hostic-dom": "^0.8.6",
"prosemirror-model": "^1.14.1"
}

View File

@ -3,6 +3,38 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.0.0-beta.75](https://github.com/ueberdosis/tiptap/compare/@tiptap/starter-kit@2.0.0-beta.74...@tiptap/starter-kit@2.0.0-beta.75) (2021-06-02)
**Note:** Version bump only for package @tiptap/starter-kit
# [2.0.0-beta.74](https://github.com/ueberdosis/tiptap/compare/@tiptap/starter-kit@2.0.0-beta.73...@tiptap/starter-kit@2.0.0-beta.74) (2021-06-02)
**Note:** Version bump only for package @tiptap/starter-kit
# [2.0.0-beta.73](https://github.com/ueberdosis/tiptap/compare/@tiptap/starter-kit@2.0.0-beta.72...@tiptap/starter-kit@2.0.0-beta.73) (2021-06-01)
**Note:** Version bump only for package @tiptap/starter-kit
# [2.0.0-beta.72](https://github.com/ueberdosis/tiptap/compare/@tiptap/starter-kit@2.0.0-beta.71...@tiptap/starter-kit@2.0.0-beta.72) (2021-06-01)
**Note:** Version bump only for package @tiptap/starter-kit
# [2.0.0-beta.71](https://github.com/ueberdosis/tiptap/compare/@tiptap/starter-kit@2.0.0-beta.70...@tiptap/starter-kit@2.0.0-beta.71) (2021-05-31)
**Note:** Version bump only for package @tiptap/starter-kit

View File

@ -1,7 +1,7 @@
{
"name": "@tiptap/starter-kit",
"description": "starter kit for tiptap",
"version": "2.0.0-beta.71",
"version": "2.0.0-beta.75",
"homepage": "https://tiptap.dev",
"keywords": [
"tiptap",
@ -21,7 +21,7 @@
"dist"
],
"dependencies": {
"@tiptap/core": "^2.0.0-beta.74",
"@tiptap/core": "^2.0.0-beta.79",
"@tiptap/extension-blockquote": "^2.0.0-beta.13",
"@tiptap/extension-bold": "^2.0.0-beta.13",
"@tiptap/extension-bullet-list": "^2.0.0-beta.13",