Merge branch 'master' into master

This commit is contained in:
Hans Pagel 2020-07-20 11:47:06 +02:00 committed by GitHub
commit 7dcc02b441
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 2759 additions and 2272 deletions

View File

@ -1,8 +1,9 @@
--- ---
name: Bug report name: Bug report
about: Create a report to help us improve about: Create a report to help us improve
url: https://github.com/ueberdosis/tiptap/issues/new
title: '' title: ''
labels: 'bug' labels: bug
assignees: '' assignees: ''
--- ---
@ -12,13 +13,13 @@ A clear and concise description of what the bug is.
**Steps to Reproduce / Codesandbox Example** **Steps to Reproduce / Codesandbox Example**
Steps to reproduce the behavior: Steps to reproduce the behavior:
1. Go to '...' 1. Go to ''
2. Click on '....' 2. Click on ''
3. Scroll down to '....' 3. Scroll down to ''
4. See error 4. See error
Fork this or create a new Codesandbox replicating your error Fork this or create a new Codesandbox replicating your error
https://codesandbox.io/s/qxv5m9y6oq?fontsize=14 https://codesandbox.io/s/vue-issue-template-h0g28
**Expected behavior** **Expected behavior**
A clear and concise description of what you expected to happen. A clear and concise description of what you expected to happen.

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Ask a Question
url: https://github.com/ueberdosis/tiptap/discussions/new
about: Ask the community for help

View File

@ -2,7 +2,7 @@
name: Feature request name: Feature request
about: Suggest an idea for this project about: Suggest an idea for this project
title: '' title: ''
labels: 'feature request' labels: feature request
assignees: '' assignees: ''
--- ---

55
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,55 @@
name: ci
on: [push, pull_request]
jobs:
audit:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
steps:
- uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: yarn install
- run: yarn build:packages
- run: yarn audit-ci
lint:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
steps:
- uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: yarn install
- run: yarn build:packages
- run: yarn lint
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 12.x, 14.x]
steps:
- uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: yarn install
- run: yarn build:packages
- run: yarn test

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.history
.DS_Store .DS_Store
node_modules node_modules
dist/ dist/

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2019, Scrumpy UG (limited liability) Copyright (c) 2020, überdosis GbR
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,26 +1,33 @@
> This repository has been migrated to a new organization. Read more: https://github.com/ueberdosis/tiptap/issues/759
# tiptap # tiptap
A renderless and extendable rich-text editor for [Vue.js](https://github.com/vuejs/vue) A renderless and extendable rich-text editor for [Vue.js](https://github.com/vuejs/vue)
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/scrumpy/tiptap) [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/ueberdosis/tiptap)
[![](https://img.shields.io/npm/v/tiptap.svg?label=version)](https://www.npmjs.com/package/tiptap) [![Version](https://img.shields.io/npm/v/tiptap.svg?label=version)](https://www.npmjs.com/package/tiptap)
[![](https://img.shields.io/npm/dm/tiptap.svg)](https://npmcharts.com/compare/tiptap?minimal=true) [![Downloads](https://img.shields.io/npm/dm/tiptap.svg)](https://npmcharts.com/compare/tiptap?minimal=true)
[![](https://img.shields.io/npm/l/tiptap.svg)](https://www.npmjs.com/package/tiptap) [![License](https://img.shields.io/npm/l/tiptap.svg)](https://www.npmjs.com/package/tiptap)
[![](https://img.badgesize.io/https://unpkg.com/tiptap/dist/tiptap.min.js?compression=gzip&label=size&colorB=000000)](https://www.npmjs.com/package/tiptap) [![Filesize](https://img.badgesize.io/https://unpkg.com/tiptap/dist/tiptap.min.js?compression=gzip&label=size&colorB=000000)](https://www.npmjs.com/package/tiptap)
[![Build Status](https://travis-ci.org/scrumpy/tiptap.svg?branch=master)](https://travis-ci.org/scrumpy/tiptap) [![Build Status](https://github.com/ueberdosis/tiptap/workflows/ci/badge.svg)](https://github.com/ueberdosis/tiptap/actions)
[![Sponsor](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub)](https://github.com/sponsors/ueberdosis)
## Why I built tiptap ## Why I built tiptap
I was looking for a text editor for [Vue.js](https://github.com/vuejs/vue) and found some solutions that didn't really satisfy me. The editor should be easy to extend and not based on old dependencies such as jQuery. For React there is already a great editor called [Slate.js](https://github.com/ianstormtaylor/slate), which impresses with its modularity. I came across [Prosemirror](https://github.com/prosemirror) and decided to build on it. Prosemirror is a toolkit for building rich-text editors that are already in use at many well-known companies such as *Atlassian* or *New York Times*. I was looking for a text editor for [Vue.js](https://github.com/vuejs/vue) and found some solutions that didn't really satisfy me. The editor should be easy to extend and not based on old dependencies such as jQuery. For React there is already a great editor called [Slate.js](https://github.com/ianstormtaylor/slate), which impresses with its modularity. I came across [Prosemirror](https://github.com/prosemirror) and decided to build on it. Prosemirror is a toolkit for building rich-text editors that are already in use at many well-known companies such as *Atlassian* or *New York Times*.
### What does `renderless` mean? ### What does `renderless` mean?
With renderless components you'll have (almost) full control over markup and styling. I don't want to tell you what a menu should look like or where it should be rendered in the DOM. That's all up to you. There is also a [good article](https://adamwathan.me/renderless-components-in-vuejs/) about renderless components by Adam Wathan. With renderless components you'll have (almost) full control over markup and styling. I don't want to tell you what a menu should look like or where it should be rendered in the DOM. That's all up to you. There is also a [good article about renderless components](https://adamwathan.me/renderless-components-in-vuejs/) by Adam Wathan.
### How is the data stored under the hood? ### How is the data stored under the hood?
You can save your data as a raw `HTML` string or can get a `JSON`-serializable representation of your document. And of course, you can pass these two types back to the editor. You can save your data as a raw `HTML` string or can get a `JSON`-serializable representation of your document. And of course, you can pass these two types back to the editor.
## 💖 Sponsor the development
Are you using tiptap in production? We need your sponsorship to maintain, update and develop tiptap. [Become a Sponsor now!](https://github.com/sponsors/ueberdosis)
## Examples ## Examples
To check out some live examples, visit [tiptap.scrumpy.io](https://tiptap.scrumpy.io/). To check out some live examples, visit [tiptap.dev](https://tiptap.dev/).
## Installation ## Installation
``` ```
@ -86,7 +93,7 @@ export default {
| `setContent` | `content, emitUpdate, parseOptions` | Replace the current content. You can pass an HTML string or a JSON document. `emitUpdate` defaults to `false`. `parseOptions` defaults to those provided in constructor. | | `setContent` | `content, emitUpdate, parseOptions` | Replace the current content. You can pass an HTML string or a JSON document. `emitUpdate` defaults to `false`. `parseOptions` defaults to those provided in constructor. |
| `clearContent` | `emitUpdate` | Clears the current content. `emitUpdate` defaults to `false`. | | `clearContent` | `emitUpdate` | Clears the current content. `emitUpdate` defaults to `false`. |
| `setOptions` | `options` | Overwrites the current editor properties. | | `setOptions` | `options` | Overwrites the current editor properties. |
| `registerPlugin` | `plugin` | Register a Prosemirror plugin. | | `registerPlugin` | `plugin`, `handlePlugins` | Register a Prosemirror plugin. You can pass a function `handlePlugins` with parameters `(plugin, oldPlugins)` to define an order in which `newPlugins` will be called. `handlePlugins` defaults to pushing `plugin` to front of `oldPlugins`. |
| `getJSON` | | Get the current content as JSON. | | `getJSON` | | Get the current content as JSON. |
| `getHTML` | | Get the current content as HTML. | | `getHTML` | | Get the current content as HTML. |
| `focus` | | Focus the editor. | | `focus` | | Focus the editor. |
@ -111,6 +118,7 @@ The `<editor-menu-bar />` component is renderless and will receive some properti
| `commands` | `Array` | A list of all commands. | | `commands` | `Array` | A list of all commands. |
| `isActive` | `Object` | An object of functions to check if your selected text is a node or mark. `isActive.{node|mark}(attrs)` | | `isActive` | `Object` | An object of functions to check if your selected text is a node or mark. `isActive.{node|mark}(attrs)` |
| `getMarkAttrs` | `Function` | A function to get all mark attributes of your selection. | | `getMarkAttrs` | `Function` | A function to get all mark attributes of your selection. |
| `getNodeAttrs` | `Function` | A function to get all node attributes of your selection. |
| `focused` | `Boolean` | Whether the editor is focused. | | `focused` | `Boolean` | Whether the editor is focused. |
| `focus` | `Function` | A function to focus the editor. | | `focus` | `Function` | A function to focus the editor. |
@ -140,6 +148,7 @@ The `<editor-menu-bubble />` component is renderless and will receive some prope
| `commands` | `Array` | A list of all commands. | | `commands` | `Array` | A list of all commands. |
| `isActive` | `Object` | An object of functions to check if your selected text is a node or mark. `isActive.{node|mark}(attrs)` | | `isActive` | `Object` | An object of functions to check if your selected text is a node or mark. `isActive.{node|mark}(attrs)` |
| `getMarkAttrs` | `Function` | A function to get all mark attributes of your selection. | | `getMarkAttrs` | `Function` | A function to get all mark attributes of your selection. |
| `getNodeAttrs` | `Function` | A function to get all node attributes of your selection. |
| `focused` | `Boolean` | Whether the editor is focused. | | `focused` | `Boolean` | Whether the editor is focused. |
| `focus` | `Function` | A function to focus the editor. | | `focus` | `Function` | A function to focus the editor. |
| `menu` | `Object` | An object for positioning your menu. | | `menu` | `Object` | An object for positioning your menu. |
@ -173,6 +182,7 @@ The `<editor-floating-menu />` component is renderless and will receive some pro
| `commands` | `Array` | A list of all commands. | | `commands` | `Array` | A list of all commands. |
| `isActive` | `Object` | An object of functions to check if your selected text is a node or mark. `isActive.{node|mark}(attrs)` | | `isActive` | `Object` | An object of functions to check if your selected text is a node or mark. `isActive.{node|mark}(attrs)` |
| `getMarkAttrs` | `Function` | A function to get all mark attributes of your selection. | | `getMarkAttrs` | `Function` | A function to get all mark attributes of your selection. |
| `getNodeAttrs` | `Function` | A function to get all node attributes of your selection. |
| `focused` | `Boolean` | Whether the editor is focused. | | `focused` | `Boolean` | Whether the editor is focused. |
| `focus` | `Function` | A function to focus the editor. | | `focus` | `Function` | A function to focus the editor. |
| `menu` | `Object` | An object for positioning your menu. | | `menu` | `Object` | An object for positioning your menu. |
@ -375,7 +385,7 @@ export default class BlockquoteNode extends Node {
### Create a Node as a Vue Component ### Create a Node as a Vue Component
The real power of the nodes comes in combination with Vue components. Let us build an iframe node, where you can change its URL (this can also be found in our [examples](https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/Embeds)). The real power of the nodes comes in combination with Vue components. Let us build an iframe node, where you can change its URL (this can also be found in our [examples](https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/Embeds)).
```js ```js
import { Node } from 'tiptap' import { Node } from 'tiptap'
@ -420,7 +430,10 @@ export default class IframeNode extends Node {
// `updateAttrs` is a function to update attributes defined in `schema` // `updateAttrs` is a function to update attributes defined in `schema`
// `view` is the ProseMirror view instance // `view` is the ProseMirror view instance
// `options` is an array of your extension options // `options` is an array of your extension options
// `selected` // `selected` is a boolean which is true when selected
// `editor` is a reference to the TipTap editor instance
// `getPos` is a function to retrieve the start position of the node
// `decorations` is an array of decorations around the node
props: ['node', 'updateAttrs', 'view'], props: ['node', 'updateAttrs', 'view'],
computed: { computed: {
src: { src: {
@ -497,20 +510,26 @@ Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
## Credits ## Credits
- [Philipp Kühn](https://github.com/philippkuehn) - [Philipp Kühn](https://github.com/philippkuehn)
- [Hans Pagel](https://github.com/hanspagel)
- [Christoph Flathmann](https://github.com/Chrissi2812) - [Christoph Flathmann](https://github.com/Chrissi2812)
- [Erick Wilder](https://github.com/erickwilder) - [Erick Wilder](https://github.com/erickwilder)
- [Marius Tolzmann](https://github.com/mariux) - [Marius Tolzmann](https://github.com/mariux)
- [All Contributors](../../contributors) - [All Contributors](../../contributors)
## Packages Using Tiptap ## Related projects
- [html-to-prosemirror](https://github.com/ueberdosis/html-to-prosemirror) by @hanspagel
- [prosemirror-to-html](https://github.com/ueberdosis/prosemirror-to-html) by @hanspagel
- [tiptap-svelte](https://github.com/andrewjk/tiptap-svelte) by @andrewjk
- [Laravel Nova Tiptap Editor Field](https://github.com/manogi/nova-tiptap) by @manogi - [Laravel Nova Tiptap Editor Field](https://github.com/manogi/nova-tiptap) by @manogi
- [WYSIWYG editor for Vuetify](https://github.com/iliyaZelenko/tiptap-vuetify) by @iliyaZelenko - [WYSIWYG editor for Vuetify](https://github.com/iliyaZelenko/tiptap-vuetify) by @iliyaZelenko
- [Quasar Tiptap Demo](https://github.com/kfields/quasar-tiptap-demo) @kfields - [Quasar Tiptap Demo](https://github.com/kfields/quasar-tiptap-demo) @kfields
- [Python Library that converts tiptap JSON](https://github.com/scrolltech/tiptapy) @scrolltech - [Python Library that converts tiptap JSON](https://github.com/scrolltech/tiptapy) @scrolltech
- [WYSIWYG editor for Element UI](https://github.com/Leecason/element-tiptap) by @Leecason
- [WYSIWYG editor for Quasar Framework](https://github.com/donotebase/quasar-tiptap) by @mekery
## Love our work? ## Love our work?
[Become a backer](https://www.paypal.me/philippkuehn) ❤️ [Sponsor us](https://github.com/sponsors/ueberdosis) ❤️
## License ## License

View File

@ -1,7 +0,0 @@
<template>
<a class="ad" href="https://scrumpy.io/" target="_blank">
<img class="ad__image" src="https://drop.philipp-kuehn.com/7RIu3ptVUQ.png" alt="Scrumpy. Agile, Made Simple. Get Started." />
</a>
</template>
<style lang="scss" src="./style.scss" scoped></style>

View File

@ -1,42 +0,0 @@
@import "~variables";
.ad {
display: block;
padding: 1rem;
transition: 0.2s transform;
margin: 3rem auto 0 auto;
width: 15rem;
@media (min-width: 1020px) {
position: fixed;
left: 0;
bottom: 0;
margin-top: 0;
}
&__image {
display: block;
width: 100%;
height: auto;
border-radius: 5px;
overflow: hidden;
transition: 0.2s box-shadow;
box-shadow:
0 2px 4px 0 rgba(black, 0.05),
0 2px 10px 0 rgba(black, 0.07)
;
}
&:hover {
transform: translateY(-5px);
}
&:hover &__image {
box-shadow:
0 2px 1px 0 rgba(black, 0.07),
0 5px 20px 0 rgba(black, 0.06),
0 8px 40px 0 rgba(black, 0.04)
;
}
}

View File

@ -20,8 +20,6 @@
</a> </a>
</div> </div>
<ad />
</div> </div>
</template> </template>
@ -30,7 +28,6 @@ import Navigation from 'Components/Navigation'
import Hero from 'Components/Hero' import Hero from 'Components/Hero'
import Subnavigation from 'Components/Subnavigation' import Subnavigation from 'Components/Subnavigation'
import Icon from 'Components/Icon' import Icon from 'Components/Icon'
import Ad from 'Components/Ad'
export default { export default {
components: { components: {
@ -38,7 +35,6 @@ export default {
Hero, Hero,
Subnavigation, Subnavigation,
Icon, Icon,
Ad,
}, },
} }
</script> </script>

View File

@ -7,20 +7,23 @@
</h1> </h1>
<github-button <github-button
class="navigation__count" class="navigation__count"
href="https://github.com/scrumpy/tiptap" href="https://github.com/ueberdosis/tiptap"
data-show-count="true" data-show-count="true"
aria-label="Star scrumpy/tiptap on GitHub" aria-label="Star ueberdosis/tiptap on GitHub"
/> />
</div> </div>
<div> <div>
<a class="navigation__link" href="https://tiptap.scrumpy.io/docs" target="_blank"> <a class="navigation__link" href="https://tiptap.dev/docs" target="_blank">
Documentation Documentation
</a> </a>
<a class="navigation__link" href="https://github.com/scrumpy/tiptap/blob/master/CONTRIBUTING.md" target="_blank"> <a class="navigation__link" href="https://github.com/ueberdosis/tiptap/blob/master/CONTRIBUTING.md" target="_blank">
Contribute Contribute
</a> </a>
<a class="navigation__github-link" href="https://github.com/scrumpy/tiptap" target="_blank"> <a class="navigation__link" href="https://github.com/sponsors/ueberdosis" target="_blank">
Sponsor
</a>
<a class="navigation__github-link" href="https://github.com/ueberdosis/tiptap" target="_blank">
<icon class="navigation__icon" name="github" /> <icon class="navigation__icon" name="github" />
</a> </a>
</div> </div>

View File

@ -35,7 +35,7 @@
<script> <script>
import Fuse from 'fuse.js' import Fuse from 'fuse.js'
import tippy from 'tippy.js' import tippy, { sticky } from 'tippy.js'
import Icon from 'Components/Icon' import Icon from 'Components/Icon'
import { Editor, EditorContent, EditorMenuBar } from 'tiptap' import { Editor, EditorContent, EditorMenuBar } from 'tiptap'
import { import {
@ -103,18 +103,17 @@ export default {
}, },
// is called on every keyDown event while a suggestion is active // is called on every keyDown event while a suggestion is active
onKeyDown: ({ event }) => { onKeyDown: ({ event }) => {
// pressing up arrow if (event.key === 'ArrowUp') {
if (event.keyCode === 38) {
this.upHandler() this.upHandler()
return true return true
} }
// pressing down arrow
if (event.keyCode === 40) { if (event.key === 'ArrowDown') {
this.downHandler() this.downHandler()
return true return true
} }
// pressing enter
if (event.keyCode === 13) { if (event.key === 'Enter') {
this.enterHandler() this.enterHandler()
return true return true
} }
@ -135,7 +134,7 @@ export default {
keys: ['name'], keys: ['name'],
}) })
return fuse.search(query) return fuse.search(query).map(item => item.item)
}, },
}), }),
new Code(), new Code(),
@ -159,7 +158,6 @@ export default {
filteredUsers: [], filteredUsers: [],
navigatedUserIndex: 0, navigatedUserIndex: 0,
insertMention: () => {}, insertMention: () => {},
observer: null,
} }
}, },
@ -217,44 +215,35 @@ export default {
return return
} }
this.popup = tippy(node, { // ref: https://atomiks.github.io/tippyjs/v6/all-props/
content: this.$refs.suggestions, this.popup = tippy('.page', {
trigger: 'mouseenter', getReferenceClientRect: node.getBoundingClientRect,
appendTo: () => document.body,
interactive: true, interactive: true,
sticky: true, // make sure position of tippy is updated when content changes
plugins: [sticky],
content: this.$refs.suggestions,
trigger: 'mouseenter', // manual
showOnCreate: true,
theme: 'dark', theme: 'dark',
placement: 'top-start', placement: 'top-start',
inertia: true, inertia: true,
duration: [400, 200], duration: [400, 200],
showOnInit: true,
arrow: true,
arrowType: 'round',
}) })
// we have to update tippy whenever the DOM is updated
if (MutationObserver) {
this.observer = new MutationObserver(() => {
this.popup.popperInstance.scheduleUpdate()
})
this.observer.observe(this.$refs.suggestions, {
childList: true,
subtree: true,
characterData: true,
})
}
}, },
destroyPopup() { destroyPopup() {
if (this.popup) { if (this.popup) {
this.popup.destroy() this.popup[0].destroy()
this.popup = null this.popup = null
} }
if (this.observer) {
this.observer.disconnect()
}
}, },
}, },
beforeDestroy() {
this.destroyPopup()
},
} }
</script> </script>
@ -306,36 +295,12 @@ export default {
} }
} }
.tippy-tooltip.dark-theme { .tippy-box[data-theme~=dark] {
background-color: $color-black; background-color: $color-black;
padding: 0; padding: 0;
font-size: 1rem; font-size: 1rem;
text-align: inherit; text-align: inherit;
color: $color-white; color: $color-white;
border-radius: 5px; border-radius: 5px;
.tippy-backdrop {
display: none;
}
.tippy-roundarrow {
fill: $color-black;
}
.tippy-popper[x-placement^=top] & .tippy-arrow {
border-top-color: $color-black;
}
.tippy-popper[x-placement^=bottom] & .tippy-arrow {
border-bottom-color: $color-black;
}
.tippy-popper[x-placement^=left] & .tippy-arrow {
border-left-color: $color-black;
}
.tippy-popper[x-placement^=right] & .tippy-arrow {
border-right-color: $color-black;
}
} }
</style> </style>

View File

@ -27,8 +27,7 @@
} }
p code { p code {
display: inline-block; padding: 0.2rem 0.4rem;
padding: 0 0.4rem;
border-radius: 5px; border-radius: 5px;
font-size: 0.8rem; font-size: 0.8rem;
font-weight: bold; font-weight: bold;

View File

@ -15,154 +15,154 @@ const routes = [
path: '/', path: '/',
component: () => import('Components/Routes/Basic'), component: () => import('Components/Routes/Basic'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/Basic', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/Basic',
}, },
}, },
{ {
path: '/menu-bubble', path: '/menu-bubble',
component: () => import('Components/Routes/MenuBubble'), component: () => import('Components/Routes/MenuBubble'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/MenuBubble', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/MenuBubble',
}, },
}, },
{ {
path: '/floating-menu', path: '/floating-menu',
component: () => import('Components/Routes/FloatingMenu'), component: () => import('Components/Routes/FloatingMenu'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/FloatingMenu', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/FloatingMenu',
}, },
}, },
{ {
path: '/links', path: '/links',
component: () => import('Components/Routes/Links'), component: () => import('Components/Routes/Links'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/Links', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/Links',
}, },
}, },
{ {
path: '/images', path: '/images',
component: () => import('Components/Routes/Images'), component: () => import('Components/Routes/Images'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/Images', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/Images',
}, },
}, },
{ {
path: '/hiding-menu-bar', path: '/hiding-menu-bar',
component: () => import('Components/Routes/HidingMenuBar'), component: () => import('Components/Routes/HidingMenuBar'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/HidingMenuBar', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/HidingMenuBar',
}, },
}, },
{ {
path: '/tables', path: '/tables',
component: () => import('Components/Routes/Tables'), component: () => import('Components/Routes/Tables'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/Tables', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/Tables',
}, },
}, },
{ {
path: '/todo-list', path: '/todo-list',
component: () => import('Components/Routes/TodoList'), component: () => import('Components/Routes/TodoList'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/TodoList', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/TodoList',
}, },
}, },
{ {
path: '/search-and-replace', path: '/search-and-replace',
component: () => import('Components/Routes/SearchAndReplace'), component: () => import('Components/Routes/SearchAndReplace'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/SearchAndReplace', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/SearchAndReplace',
}, },
}, },
{ {
path: '/suggestions', path: '/suggestions',
component: () => import('Components/Routes/Suggestions'), component: () => import('Components/Routes/Suggestions'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/Suggestions', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/Suggestions',
}, },
}, },
{ {
path: '/markdown-shortcuts', path: '/markdown-shortcuts',
component: () => import('Components/Routes/MarkdownShortcuts'), component: () => import('Components/Routes/MarkdownShortcuts'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/MarkdownShortcuts', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/MarkdownShortcuts',
}, },
}, },
{ {
path: '/code-highlighting', path: '/code-highlighting',
component: () => import('Components/Routes/CodeHighlighting'), component: () => import('Components/Routes/CodeHighlighting'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/CodeHighlighting', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/CodeHighlighting',
}, },
}, },
{ {
path: '/history', path: '/history',
component: () => import('Components/Routes/History'), component: () => import('Components/Routes/History'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/History', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/History',
}, },
}, },
{ {
path: '/read-only', path: '/read-only',
component: () => import('Components/Routes/ReadOnly'), component: () => import('Components/Routes/ReadOnly'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/ReadOnly', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/ReadOnly',
}, },
}, },
{ {
path: '/embeds', path: '/embeds',
component: () => import('Components/Routes/Embeds'), component: () => import('Components/Routes/Embeds'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/Embeds', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/Embeds',
}, },
}, },
{ {
path: '/placeholder', path: '/placeholder',
component: () => import('Components/Routes/Placeholder'), component: () => import('Components/Routes/Placeholder'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/Placeholder', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/Placeholder',
}, },
}, },
{ {
path: '/focus', path: '/focus',
component: () => import('Components/Routes/Focus'), component: () => import('Components/Routes/Focus'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/Focus', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/Focus',
}, },
}, },
{ {
path: '/collaboration', path: '/collaboration',
component: () => import('Components/Routes/Collaboration'), component: () => import('Components/Routes/Collaboration'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/Collaboration', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/Collaboration',
}, },
}, },
{ {
path: '/title', path: '/title',
component: () => import('Components/Routes/Title'), component: () => import('Components/Routes/Title'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/Title', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/Title',
}, },
}, },
{ {
path: '/trailing-paragraph', path: '/trailing-paragraph',
component: () => import('Components/Routes/TrailingParagraph'), component: () => import('Components/Routes/TrailingParagraph'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/TrailingParagraph', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/TrailingParagraph',
}, },
}, },
{ {
path: '/drag-handle', path: '/drag-handle',
component: () => import('Components/Routes/DragHandle'), component: () => import('Components/Routes/DragHandle'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/DragHandle', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/DragHandle',
}, },
}, },
{ {
path: '/export', path: '/export',
component: () => import('Components/Routes/Export'), component: () => import('Components/Routes/Export'),
meta: { meta: {
githubUrl: 'https://github.com/scrumpy/tiptap/tree/master/examples/Components/Routes/Export', githubUrl: 'https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/Export',
}, },
}, },
] ]

View File

@ -1,3 +1,13 @@
[[redirects]]
from = "https://tiptap.scrumpy.io"
to = "https://www.tiptap.dev"
status = 301
[[redirects]]
from = "https://tiptap.scrumpy.io/*"
to = "https://www.tiptap.dev/:splat"
status = 301
[[redirects]] [[redirects]]
from = "/docs/*" from = "/docs/*"
to = "https://tiptap-docs.netlify.com/:splat" to = "https://tiptap-docs.netlify.com/:splat"
@ -7,3 +17,4 @@
from = "/*" from = "/*"
to = "/index.html" to = "/index.html"
status = 200 status = 200

View File

@ -47,7 +47,7 @@
"eslint-plugin-import": "^2.18.2", "eslint-plugin-import": "^2.18.2",
"eslint-plugin-vue": "6.0.1", "eslint-plugin-vue": "6.0.1",
"file-loader": "^5.0.2", "file-loader": "^5.0.2",
"fuse.js": "^3.4.6", "fuse.js": "^6.0.0",
"glob": "^7.1.6", "glob": "^7.1.6",
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^3.2.0",
"http-proxy-middleware": "^0.20.0", "http-proxy-middleware": "^0.20.0",
@ -76,7 +76,7 @@
"sass-loader": "^8.0.0", "sass-loader": "^8.0.0",
"style-loader": "^1.0.1", "style-loader": "^1.0.1",
"terser": "^4.4.2", "terser": "^4.4.2",
"tippy.js": "^4.3.5", "tippy.js": "^6.1.0",
"vue": "^2.6.10", "vue": "^2.6.10",
"vue-loader": "^15.7.2", "vue-loader": "^15.7.2",
"vue-router": "^3.1.3", "vue-router": "^3.1.3",
@ -91,6 +91,6 @@
}, },
"dependencies": { "dependencies": {
"socket.io-client": "^2.3.0", "socket.io-client": "^2.3.0",
"vue-github-button": "^1.1.2" "vue-github-button": "^1.2.0"
} }
} }

View File

@ -1,8 +1,8 @@
{ {
"name": "tiptap-commands", "name": "tiptap-commands",
"version": "1.12.5", "version": "1.14.1",
"description": "Commands for tiptap", "description": "Commands for tiptap",
"homepage": "https://tiptap.scrumpy.io", "homepage": "https://tiptap.dev",
"license": "MIT", "license": "MIT",
"main": "dist/commands.common.js", "main": "dist/commands.common.js",
"module": "dist/commands.esm.js", "module": "dist/commands.esm.js",
@ -14,19 +14,19 @@
], ],
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/scrumpy/tiptap.git" "url": "git+https://github.com/ueberdosis/tiptap.git"
}, },
"bugs": { "bugs": {
"url": "https://github.com/scrumpy/tiptap/issues" "url": "https://github.com/ueberdosis/tiptap/issues"
}, },
"dependencies": { "dependencies": {
"prosemirror-commands": "1.1.2", "prosemirror-commands": "^1.1.4",
"prosemirror-inputrules": "1.1.2", "prosemirror-inputrules": "^1.1.2",
"prosemirror-model": "1.8.2", "prosemirror-model": "^1.10.0",
"prosemirror-schema-list": "1.1.2", "prosemirror-schema-list": "^1.1.2",
"prosemirror-state": "1.3.2", "prosemirror-state": "^1.3.3",
"prosemirror-tables": "1.0.0", "prosemirror-tables": "^1.1.0",
"prosemirror-utils": "0.9.6", "prosemirror-utils": "^0.9.6",
"tiptap-utils": "^1.8.3" "tiptap-utils": "^1.10.1"
} }
} }

View File

@ -3,7 +3,7 @@ import { Slice, Fragment } from 'prosemirror-model'
export default function (regexp, type, getAttrs) { export default function (regexp, type, getAttrs) {
const handler = fragment => { const handler = (fragment, parent) => {
const nodes = [] const nodes = []
fragment.forEach(child => { fragment.forEach(child => {
@ -16,7 +16,7 @@ export default function (regexp, type, getAttrs) {
// eslint-disable-next-line // eslint-disable-next-line
while (!isLink && (match = regexp.exec(text)) !== null) { while (!isLink && (match = regexp.exec(text)) !== null) {
if (match[1]) { if (parent && parent.type.allowsMarkType(type) && match[1]) {
const start = match.index const start = match.index
const end = start + match[0].length const end = start + match[0].length
const textStart = start + match[0].indexOf(match[1]) const textStart = start + match[0].indexOf(match[1])
@ -43,7 +43,7 @@ export default function (regexp, type, getAttrs) {
nodes.push(child.cut(pos)) nodes.push(child.cut(pos))
} }
} else { } else {
nodes.push(child.copy(handler(child.content))) nodes.push(child.copy(handler(child.content, child)))
} }
}) })

View File

@ -1,14 +1,14 @@
import { wrapIn, lift } from 'prosemirror-commands' import { wrapIn, lift } from 'prosemirror-commands'
import { nodeIsActive } from 'tiptap-utils' import { nodeIsActive } from 'tiptap-utils'
export default function (type) { export default function (type, attrs = {}) {
return (state, dispatch, view) => { return (state, dispatch, view) => {
const isActive = nodeIsActive(state, type) const isActive = nodeIsActive(state, type, attrs)
if (isActive) { if (isActive) {
return lift(state, dispatch) return lift(state, dispatch)
} }
return wrapIn(type)(state, dispatch, view) return wrapIn(type, attrs)(state, dispatch, view)
} }
} }

View File

@ -1,8 +1,8 @@
{ {
"name": "tiptap-extensions", "name": "tiptap-extensions",
"version": "1.28.6", "version": "1.31.1",
"description": "Extensions for tiptap", "description": "Extensions for tiptap",
"homepage": "https://tiptap.scrumpy.io", "homepage": "https://tiptap.dev",
"license": "MIT", "license": "MIT",
"main": "dist/extensions.common.js", "main": "dist/extensions.common.js",
"module": "dist/extensions.esm.js", "module": "dist/extensions.esm.js",
@ -15,23 +15,23 @@
], ],
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/scrumpy/tiptap.git" "url": "git+https://github.com/ueberdosis/tiptap.git"
}, },
"bugs": { "bugs": {
"url": "https://github.com/scrumpy/tiptap/issues" "url": "https://github.com/ueberdosis/tiptap/issues"
}, },
"dependencies": { "dependencies": {
"lowlight": "1.13.0", "lowlight": "^1.14.0",
"prosemirror-collab": "1.2.2", "prosemirror-collab": "^1.2.2",
"prosemirror-history": "1.1.3", "prosemirror-history": "^1.1.3",
"prosemirror-model": "1.8.2", "prosemirror-model": "^1.10.0",
"prosemirror-state": "1.3.2", "prosemirror-state": "^1.3.3",
"prosemirror-tables": "1.0.0", "prosemirror-tables": "^1.1.0",
"prosemirror-transform": "1.2.3", "prosemirror-transform": "^1.2.6",
"prosemirror-utils": "0.9.6", "prosemirror-utils": "^0.9.6",
"prosemirror-view": "1.13.7", "prosemirror-view": "^1.15.0",
"tiptap": "^1.26.6", "tiptap": "^1.29.1",
"tiptap-commands": "^1.12.5" "tiptap-commands": "^1.14.1"
}, },
"peerDependencies": { "peerDependencies": {
"vue": "^2.5.17", "vue": "^2.5.17",

View File

@ -17,12 +17,6 @@ export default class Placeholder extends Extension {
} }
} }
get update() {
return view => {
view.updateState(view.state)
}
}
get plugins() { get plugins() {
return [ return [
new Plugin({ new Plugin({

View File

@ -11,6 +11,7 @@ export default class Link extends Mark {
get defaultOptions() { get defaultOptions() {
return { return {
openOnClick: true, openOnClick: true,
target: null,
} }
} }
@ -20,6 +21,9 @@ export default class Link extends Mark {
href: { href: {
default: null, default: null,
}, },
target: {
default: null,
},
}, },
inclusive: false, inclusive: false,
parseDOM: [ parseDOM: [
@ -27,12 +31,14 @@ export default class Link extends Mark {
tag: 'a[href]', tag: 'a[href]',
getAttrs: dom => ({ getAttrs: dom => ({
href: dom.getAttribute('href'), href: dom.getAttribute('href'),
target: dom.getAttribute('target'),
}), }),
}, },
], ],
toDOM: node => ['a', { toDOM: node => ['a', {
...node.attrs, ...node.attrs,
rel: 'noopener noreferrer nofollow', rel: 'noopener noreferrer nofollow',
target: this.options.target,
}, 0], }, 0],
} }
} }
@ -71,7 +77,7 @@ export default class Link extends Mark {
if (attrs.href && event.target instanceof HTMLAnchorElement) { if (attrs.href && event.target instanceof HTMLAnchorElement) {
event.stopPropagation() event.stopPropagation()
window.open(attrs.href) window.open(attrs.href, attrs.target)
} }
}, },
}, },

View File

@ -29,6 +29,39 @@ export default class TodoItem extends Node {
<div class="todo-content" ref="content" :contenteditable="view.editable.toString()"></div> <div class="todo-content" ref="content" :contenteditable="view.editable.toString()"></div>
</li> </li>
`, `,
/*
The render function enables TodoItem to work in `runtimeonly` builds,
which is required for frameworks requiring strict CSP policies. For
example, doing this is required in Chrome Extensions. Having both
the template and the render function ensures there are no issues
converting the node to JSON and rendering the component.
*/
render(h) {
return h('li', {
attrs: {
'data-type': this.node.type.name,
'data-done': this.node.attrs.done.toString(),
'data-drag-handle': '',
},
}, [
h('span', {
class: 'todo-checkbox',
attrs: {
contenteditable: false,
},
on: {
click: this.onChange,
},
}),
h('div', {
class: 'todo-content',
attrs: {
contenteditable: this.view.editable.toString(),
},
ref: 'content',
}),
])
},
} }
} }

View File

@ -62,17 +62,20 @@ export default function HighlightPlugin({ name }) {
name: new PluginKey('highlight'), name: new PluginKey('highlight'),
state: { state: {
init: (_, { doc }) => getDecorations({ doc, name }), init: (_, { doc }) => getDecorations({ doc, name }),
apply: (transaction, decorationSet, oldState, state) => { apply: (transaction, decorationSet, oldState, newState) => {
// TODO: find way to cache decorations // TODO: find way to cache decorations
// see: https://discuss.prosemirror.net/t/how-to-update-multiple-inline-decorations-on-node-change/1493 // https://discuss.prosemirror.net/t/how-to-update-multiple-inline-decorations-on-node-change/1493
const oldNodeName = oldState.selection.$head.parent.type.name
const nodeName = state.selection.$head.parent.type.name const newNodeName = newState.selection.$head.parent.type.name
const previousNodeName = oldState.selection.$head.parent.type.name const oldNodes = findBlockNodes(oldState.doc)
.filter(item => item.node.type.name === name)
if (transaction.docChanged && [nodeName, previousNodeName].includes(name)) { const newNodes = findBlockNodes(newState.doc)
.filter(item => item.node.type.name === name)
// Apply decorations if selection includes named node, or transaction changes named node.
if (transaction.docChanged && ([oldNodeName, newNodeName].includes(name)
|| newNodes.length !== oldNodes.length)) {
return getDecorations({ doc: transaction.doc, name }) return getDecorations({ doc: transaction.doc, name })
} }
return decorationSet.map(transaction.mapping, transaction.doc) return decorationSet.map(transaction.mapping, transaction.doc)
}, },
}, },

View File

@ -1,8 +1,8 @@
{ {
"name": "tiptap-utils", "name": "tiptap-utils",
"version": "1.8.3", "version": "1.10.1",
"description": "Utility functions for tiptap", "description": "Utility functions for tiptap",
"homepage": "https://tiptap.scrumpy.io", "homepage": "https://tiptap.dev",
"license": "MIT", "license": "MIT",
"main": "dist/utils.common.js", "main": "dist/utils.common.js",
"module": "dist/utils.esm.js", "module": "dist/utils.esm.js",
@ -14,15 +14,15 @@
], ],
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/scrumpy/tiptap.git" "url": "git+https://github.com/ueberdosis/tiptap.git"
}, },
"bugs": { "bugs": {
"url": "https://github.com/scrumpy/tiptap/issues" "url": "https://github.com/ueberdosis/tiptap/issues"
}, },
"dependencies": { "dependencies": {
"prosemirror-model": "1.8.2", "prosemirror-model": "^1.10.0",
"prosemirror-state": "1.3.2", "prosemirror-state": "^1.3.3",
"prosemirror-tables": "1.0.0", "prosemirror-tables": "^1.1.0",
"prosemirror-utils": "0.9.6" "prosemirror-utils": "^0.9.6"
} }
} }

View File

@ -1,4 +1,5 @@
export { default as getMarkAttrs } from './utils/getMarkAttrs' export { default as getMarkAttrs } from './utils/getMarkAttrs'
export { default as getNodeAttrs } from './utils/getNodeAttrs'
export { default as getMarkRange } from './utils/getMarkRange' export { default as getMarkRange } from './utils/getMarkRange'
export { default as markIsActive } from './utils/markIsActive' export { default as markIsActive } from './utils/markIsActive'
export { default as nodeEqualsType } from './utils/nodeEqualsType' export { default as nodeEqualsType } from './utils/nodeEqualsType'

View File

@ -0,0 +1,18 @@
export default function getNodeAttrs(state, type) {
const { from, to } = state.selection
let nodes = []
state.doc.nodesBetween(from, to, node => {
nodes = [...nodes, node]
})
const node = nodes
.reverse()
.find(nodeItem => nodeItem.type.name === type.name)
if (node) {
return node.attrs
}
return {}
}

View File

@ -12,5 +12,5 @@ export default function nodeIsActive(state, type, attrs = {}) {
return !!node return !!node
} }
return node.node.hasMarkup(type, attrs) return node.node.hasMarkup(type, { ...node.node.attrs, ...attrs })
} }

View File

@ -1,8 +1,8 @@
{ {
"name": "tiptap", "name": "tiptap",
"version": "1.26.6", "version": "1.29.1",
"description": "A rich-text editor for Vue.js", "description": "A rich-text editor for Vue.js",
"homepage": "https://tiptap.scrumpy.io", "homepage": "https://tiptap.dev",
"license": "MIT", "license": "MIT",
"main": "dist/tiptap.common.js", "main": "dist/tiptap.common.js",
"module": "dist/tiptap.esm.js", "module": "dist/tiptap.esm.js",
@ -14,22 +14,22 @@
], ],
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/scrumpy/tiptap.git" "url": "git+https://github.com/ueberdosis/tiptap.git"
}, },
"bugs": { "bugs": {
"url": "https://github.com/scrumpy/tiptap/issues" "url": "https://github.com/ueberdosis/tiptap/issues"
}, },
"dependencies": { "dependencies": {
"prosemirror-commands": "1.1.2", "prosemirror-commands": "1.1.4",
"prosemirror-dropcursor": "1.3.2", "prosemirror-dropcursor": "1.3.2",
"prosemirror-gapcursor": "1.1.2", "prosemirror-gapcursor": "1.1.5",
"prosemirror-inputrules": "1.1.2", "prosemirror-inputrules": "1.1.2",
"prosemirror-keymap": "1.1.3", "prosemirror-keymap": "1.1.4",
"prosemirror-model": "1.8.2", "prosemirror-model": "1.10.0",
"prosemirror-state": "1.3.2", "prosemirror-state": "1.3.3",
"prosemirror-view": "1.13.7", "prosemirror-view": "1.15.0",
"tiptap-commands": "^1.12.5", "tiptap-commands": "^1.14.1",
"tiptap-utils": "^1.8.3" "tiptap-utils": "^1.10.1"
}, },
"peerDependencies": { "peerDependencies": {
"vue": "^2.5.17", "vue": "^2.5.17",

View File

@ -55,6 +55,7 @@ export default {
commands: this.editor.commands, commands: this.editor.commands,
isActive: this.editor.isActive, isActive: this.editor.isActive,
getMarkAttrs: this.editor.getMarkAttrs.bind(this.editor), getMarkAttrs: this.editor.getMarkAttrs.bind(this.editor),
getNodeAttrs: this.editor.getNodeAttrs.bind(this.editor),
menu: this.menu, menu: this.menu,
}) })
}, },

View File

@ -52,6 +52,7 @@ export default {
commands: this.editor.commands, commands: this.editor.commands,
isActive: this.editor.isActive, isActive: this.editor.isActive,
getMarkAttrs: this.editor.getMarkAttrs.bind(this.editor), getMarkAttrs: this.editor.getMarkAttrs.bind(this.editor),
getNodeAttrs: this.editor.getNodeAttrs.bind(this.editor),
}) })
}, },

View File

@ -60,6 +60,7 @@ export default {
commands: this.editor.commands, commands: this.editor.commands,
isActive: this.editor.isActive, isActive: this.editor.isActive,
getMarkAttrs: this.editor.getMarkAttrs.bind(this.editor), getMarkAttrs: this.editor.getMarkAttrs.bind(this.editor),
getNodeAttrs: this.editor.getNodeAttrs.bind(this.editor),
menu: this.menu, menu: this.menu,
}) })
}, },

View File

@ -11,7 +11,12 @@ import { gapCursor } from 'prosemirror-gapcursor'
import { keymap } from 'prosemirror-keymap' import { keymap } from 'prosemirror-keymap'
import { baseKeymap } from 'prosemirror-commands' import { baseKeymap } from 'prosemirror-commands'
import { inputRules, undoInputRule } from 'prosemirror-inputrules' import { inputRules, undoInputRule } from 'prosemirror-inputrules'
import { markIsActive, nodeIsActive, getMarkAttrs } from 'tiptap-utils' import {
markIsActive,
nodeIsActive,
getMarkAttrs,
getNodeAttrs,
} from 'tiptap-utils'
import { import {
injectCSS, injectCSS,
camelCase, camelCase,
@ -267,9 +272,9 @@ export default class Editor extends Emitter {
} }
if (typeof content === 'string') { if (typeof content === 'string') {
const element = document.createElement('div') const htmlString = `<div>${content}</div>`
element.innerHTML = content.trim() const parser = new window.DOMParser()
const element = parser.parseFromString(htmlString, 'text/html').body
return DOMParser.fromSchema(this.schema).parse(element, parseOptions) return DOMParser.fromSchema(this.schema).parse(element, parseOptions)
} }
@ -475,6 +480,12 @@ export default class Editor extends Emitter {
return this.activeMarkAttrs[type] return this.activeMarkAttrs[type]
} }
getNodeAttrs(type = null) {
return {
...getNodeAttrs(this.state, this.schema.nodes[type]),
}
}
get isActive() { get isActive() {
return Object return Object
.entries({ .entries({
@ -487,14 +498,11 @@ export default class Editor extends Emitter {
}), {}) }), {})
} }
registerPlugin(plugin = null) { registerPlugin(plugin = null, handlePlugins) {
if (!plugin) { const plugins = typeof handlePlugins === 'function'
return ? handlePlugins(plugin, this.state.plugins)
} : [plugin, ...this.state.plugins]
const newState = this.state.reconfigure({ plugins })
const newState = this.state.reconfigure({
plugins: this.state.plugins.concat([plugin]),
})
this.view.updateState(newState) this.view.updateState(newState)
} }

View File

@ -18,20 +18,22 @@ class Menu {
// the mousedown event is fired before blur so we can prevent it // the mousedown event is fired before blur so we can prevent it
this.mousedownHandler = this.handleClick.bind(this) this.mousedownHandler = this.handleClick.bind(this)
this.options.element.addEventListener('mousedown', this.mousedownHandler) this.options.element.addEventListener('mousedown', this.mousedownHandler, { capture: true })
this.options.editor.on('focus', ({ view }) => { this.focusHandler = ({ view }) => {
this.update(view) this.update(view)
}) }
this.options.editor.on('focus', this.focusHandler)
this.options.editor.on('blur', ({ event }) => { this.blurHandler = ({ event }) => {
if (this.preventHide) { if (this.preventHide) {
this.preventHide = false this.preventHide = false
return return
} }
this.hide(event) this.hide(event)
}) }
this.options.editor.on('blur', this.blurHandler)
// sometimes we have to update the position // sometimes we have to update the position
// because of a loaded images for example // because of a loaded images for example
@ -100,6 +102,7 @@ class Menu {
hide(event) { hide(event) {
if (event if (event
&& event.relatedTarget && event.relatedTarget
&& this.options.element.parentNode
&& this.options.element.parentNode.contains(event.relatedTarget) && this.options.element.parentNode.contains(event.relatedTarget)
) { ) {
return return
@ -115,6 +118,9 @@ class Menu {
if (this.resizeObserver) { if (this.resizeObserver) {
this.resizeObserver.unobserve(this.editorView.dom) this.resizeObserver.unobserve(this.editorView.dom)
} }
this.options.editor.off('focus', this.focusHandler)
this.options.editor.off('blur', this.blurHandler)
} }
} }

View File

@ -8,16 +8,17 @@ class Menu {
// the mousedown event is fired before blur so we can prevent it // the mousedown event is fired before blur so we can prevent it
this.mousedownHandler = this.handleClick.bind(this) this.mousedownHandler = this.handleClick.bind(this)
this.options.element.addEventListener('mousedown', this.mousedownHandler) this.options.element.addEventListener('mousedown', this.mousedownHandler, { capture: true })
this.options.editor.on('blur', () => { this.blurHandler = () => {
if (this.preventHide) { if (this.preventHide) {
this.preventHide = false this.preventHide = false
return return
} }
this.options.editor.emit('menubar:focusUpdate', false) this.options.editor.emit('menubar:focusUpdate', false)
}) }
this.options.editor.on('blur', this.blurHandler)
} }
handleClick() { handleClick() {
@ -26,6 +27,7 @@ class Menu {
destroy() { destroy() {
this.options.element.removeEventListener('mousedown', this.mousedownHandler) this.options.element.removeEventListener('mousedown', this.mousedownHandler)
this.options.editor.off('blur', this.blurHandler)
} }
} }

View File

@ -50,7 +50,6 @@ function coordsAtPos(view, pos, end = false) {
} }
} }
class Menu { class Menu {
constructor({ options, editorView }) { constructor({ options, editorView }) {
@ -71,20 +70,22 @@ class Menu {
// the mousedown event is fired before blur so we can prevent it // the mousedown event is fired before blur so we can prevent it
this.mousedownHandler = this.handleClick.bind(this) this.mousedownHandler = this.handleClick.bind(this)
this.options.element.addEventListener('mousedown', this.mousedownHandler) this.options.element.addEventListener('mousedown', this.mousedownHandler, { capture: true })
this.options.editor.on('focus', ({ view }) => { this.focusHandler = ({ view }) => {
this.update(view) this.update(view)
}) }
this.options.editor.on('focus', this.focusHandler)
this.options.editor.on('blur', ({ event }) => { this.blurHandler = ({ event }) => {
if (this.preventHide) { if (this.preventHide) {
this.preventHide = false this.preventHide = false
return return
} }
this.hide(event) this.hide(event)
}) }
this.options.editor.on('blur', this.blurHandler)
} }
handleClick() { handleClick() {
@ -155,6 +156,7 @@ class Menu {
hide(event) { hide(event) {
if (event if (event
&& event.relatedTarget && event.relatedTarget
&& this.options.element.parentNode
&& this.options.element.parentNode.contains(event.relatedTarget) && this.options.element.parentNode.contains(event.relatedTarget)
) { ) {
return return
@ -166,6 +168,8 @@ class Menu {
destroy() { destroy() {
this.options.element.removeEventListener('mousedown', this.mousedownHandler) this.options.element.removeEventListener('mousedown', this.mousedownHandler)
this.options.editor.off('focus', this.focusHandler)
this.options.editor.off('blur', this.blurHandler)
} }
} }

View File

@ -45,6 +45,10 @@ export default class ComponentView {
this.setSelection = this.extension.setSelection this.setSelection = this.extension.setSelection
} }
if (typeof this.extension.update === 'function') {
this.update = this.extension.update
}
this.vm = new Component({ this.vm = new Component({
parent: this.parent, parent: this.parent,
propsData: props, propsData: props,

View File

@ -23,10 +23,6 @@ export default class Extension {
return 'extension' return 'extension'
} }
get update() {
return () => {}
}
get defaultOptions() { get defaultOptions() {
return {} return {}
} }

View File

@ -31,7 +31,7 @@ export default class ExtensionManager {
Object.assign(obj, { [prop]: value }) Object.assign(obj, { [prop]: value })
if (changed) { if (changed) {
extension.update(view) view.updateState(view.state)
} }
return true return true

4421
yarn.lock

File diff suppressed because it is too large Load Diff