Merge branch 'main' of https://github.com/ueberdosis/tiptap-next into main
This commit is contained in:
76
.github/workflows/main.yml
vendored
76
.github/workflows/main.yml
vendored
@@ -54,7 +54,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
status: ${{ job.status }}
|
status: ${{ job.status }}
|
||||||
steps: ${{ toJson(steps) }}
|
steps: ${{ toJson(steps) }}
|
||||||
channel: '#tiptap-next'
|
channel: '#tiptap-notifications'
|
||||||
if: failure()
|
if: failure()
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@@ -107,52 +107,52 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
status: ${{ job.status }}
|
status: ${{ job.status }}
|
||||||
steps: ${{ toJson(steps) }}
|
steps: ${{ toJson(steps) }}
|
||||||
channel: '#tiptap-next'
|
channel: '#tiptap-notifications'
|
||||||
if: failure()
|
if: failure()
|
||||||
|
|
||||||
build:
|
# build:
|
||||||
runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
|
|
||||||
needs: lint
|
# needs: lint
|
||||||
|
|
||||||
env:
|
# env:
|
||||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
# SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||||
|
|
||||||
strategy:
|
# strategy:
|
||||||
matrix:
|
# matrix:
|
||||||
node-version: [14]
|
# node-version: [14]
|
||||||
|
|
||||||
steps:
|
# steps:
|
||||||
|
|
||||||
- uses: actions/checkout@v2.3.3
|
# - uses: actions/checkout@v2.3.3
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
# - name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v2.1.2
|
# uses: actions/setup-node@v2.1.2
|
||||||
with:
|
# with:
|
||||||
node-version: ${{ matrix.node-version }}
|
# node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
- name: Load cached dependencies
|
# - name: Load cached dependencies
|
||||||
uses: actions/cache@v2
|
# uses: actions/cache@v2
|
||||||
id: cache
|
# id: cache
|
||||||
with:
|
# with:
|
||||||
path: |
|
# path: |
|
||||||
**/node_modules
|
# **/node_modules
|
||||||
/home/runner/.cache/Cypress
|
# /home/runner/.cache/Cypress
|
||||||
key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
|
# key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
|
||||||
- name: Install dependencies
|
# - name: Install dependencies
|
||||||
id: install-dependencies
|
# id: install-dependencies
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
# if: steps.cache.outputs.cache-hit != 'true'
|
||||||
run: yarn install
|
# run: yarn install
|
||||||
|
|
||||||
- name: Build packages dependencies
|
# - name: Build packages dependencies
|
||||||
id: build-packages
|
# id: build-packages
|
||||||
run: yarn build:packages
|
# run: yarn build:packages
|
||||||
|
|
||||||
- name: Send Slack notifications
|
# - name: Send Slack notifications
|
||||||
uses: act10ns/slack@v1
|
# uses: act10ns/slack@v1
|
||||||
with:
|
# with:
|
||||||
status: ${{ job.status }}
|
# status: ${{ job.status }}
|
||||||
steps: ${{ toJson(steps) }}
|
# steps: ${{ toJson(steps) }}
|
||||||
channel: '#tiptap-next'
|
# channel: '#tiptap-notifications'
|
||||||
if: failure()
|
# if: failure()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<pre>{{ html }}</pre>
|
<pre><code>{{ html }}</code></pre>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
5
docs/src/demos/Extensions/Gapcursor/index.spec.js
Normal file
5
docs/src/demos/Extensions/Gapcursor/index.spec.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
context('/examples/gapcursor', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/examples/gapcursor')
|
||||||
|
})
|
||||||
|
})
|
||||||
78
docs/src/demos/Extensions/Gapcursor/index.vue
Normal file
78
docs/src/demos/Extensions/Gapcursor/index.vue
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-starter-kit'
|
||||||
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Text from '@tiptap/extension-text'
|
||||||
|
import Gapcursor from '@tiptap/extension-gapcursor'
|
||||||
|
import Image from '@tiptap/extension-image'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
Document(),
|
||||||
|
Paragraph(),
|
||||||
|
Text(),
|
||||||
|
Image(),
|
||||||
|
Gapcursor(),
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<p>Try to set the cursor behind the image with your arrow keys! You should see big blinking cursor right from the image. This is the gapcursor.</p>
|
||||||
|
<img src="https://source.unsplash.com/8xznAGy4HcY/800x400" />
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/* Copied from the original prosemirror-gapcursor plugin by Marijn Haverbeke */
|
||||||
|
/* https://github.com/ProseMirror/prosemirror-gapcursor/blob/master/style/gapcursor.css */
|
||||||
|
|
||||||
|
.ProseMirror-gapcursor {
|
||||||
|
display: none;
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
border: 10px solid red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ProseMirror-gapcursor:after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: -2px;
|
||||||
|
width: 20px;
|
||||||
|
border-top: 1px solid black;
|
||||||
|
animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ProseMirror-cursor-blink {
|
||||||
|
to {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ProseMirror-focused .ProseMirror-gapcursor {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -27,35 +27,67 @@ When a user clicks on a button outside of the content, the editor isn’t in foc
|
|||||||
|
|
||||||
All chained commands are kind of queued up. They are combined to one single transaction. That means, the content is only updated once, also the `update` event is only triggered once.
|
All chained commands are kind of queued up. They are combined to one single transaction. That means, the content is only updated once, also the `update` event is only triggered once.
|
||||||
|
|
||||||
|
## Dry run for commands
|
||||||
|
Sometimes, you don’t want to actually run the commands, but only know if it would be possible to run commands, for example to show or hide buttons in a menu. Inside of a command you can *try* to execute other commands, without actually changing the content like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
commands.try([
|
||||||
|
() => commands.splitBlock(),
|
||||||
|
() => commands.whatever(),
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
Outside of components you can do this, too. The editor exposes a try method, which passes all commands and expects an array of commands you want to try:
|
||||||
|
|
||||||
|
```js
|
||||||
|
editor.try(({ commands }) => [
|
||||||
|
() => commands.splitBlock(),
|
||||||
|
() => commands.whatever(),
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
This stops at the first command which return `false`. Commands after that won’t be executed. Even if all commands would be possible to run, none of the changes are applied to the document.
|
||||||
|
|
||||||
## List of commands
|
## List of commands
|
||||||
Have a look at all of the core commands listed below. They should give you a good first impression of what’s possible.
|
Have a look at all of the core commands listed below. They should give you a good first impression of what’s possible.
|
||||||
|
|
||||||
### Content
|
### Content
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
| --------------- | ----------------------------------------------------------- |
|
| ---------------- | ----------------------------------------------------------- |
|
||||||
| .clearContent() | Clear the whole document. |
|
| .clearContent() | Clear the whole document. |
|
||||||
| .insertgetHTML() | Insert a string of HTML at the currently selected position. |
|
| .insertgetHTML() | Insert a string of HTML at the currently selected position. |
|
||||||
| .insertText() | Insert a string of text at the currently selected position. |
|
| .insertText() | Insert a string of text at the currently selected position. |
|
||||||
|
| .insertHTML() | |
|
||||||
| .setContent() | Replace the whole document with new content. |
|
| .setContent() | Replace the whole document with new content. |
|
||||||
|
|
||||||
### Nodes & Marks
|
### Nodes & Marks
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
| ------------------- | ------------------------------------------------------ |
|
| ---------------------- | ------------------------------------------ |
|
||||||
|
| .clearNodes() | |
|
||||||
|
| .removeMark() | |
|
||||||
| .removeMark() | Remove a mark in the current selection. |
|
| .removeMark() | Remove a mark in the current selection. |
|
||||||
|
| .removeMarks() | |
|
||||||
| .removeMarks() | Remove all marks in the current selection. |
|
| .removeMarks() | Remove all marks in the current selection. |
|
||||||
|
| .resetNodeAttributes() | |
|
||||||
| .selectParentNode() | Select the parent node. |
|
| .selectParentNode() | Select the parent node. |
|
||||||
| .toggleMark() | Toggle a mark on and off. |
|
|
||||||
| .toggleBlockType() | Toggle a node with another node. |
|
|
||||||
| .setBlockType() | Replace a given range with a node. |
|
| .setBlockType() | Replace a given range with a node. |
|
||||||
|
| .setNodeAttributes() | |
|
||||||
|
| .splitBlock() | Forks a new node from an existing node. |
|
||||||
|
| .toggleBlockType() | Toggle a node with another node. |
|
||||||
|
| .toggleMark() | |
|
||||||
|
| .toggleMark() | Toggle a mark on and off. |
|
||||||
|
| .toggleWrap() | |
|
||||||
|
| .updateMark() | |
|
||||||
| .updateMark() | Update a mark with new attributes. |
|
| .updateMark() | Update a mark with new attributes. |
|
||||||
|
|
||||||
### Lists
|
### Lists
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
| ------------------- | ------------------------------------------------------ |
|
| ---------------- | ------------------------------------------------------ |
|
||||||
| .liftListItem() | Lift the list item into a wrapping list. |
|
| .liftListItem() | Lift the list item into a wrapping list. |
|
||||||
| .sinkListItem() | Sink the list item down into an inner list. |
|
| .sinkListItem() | Sink the list item down into an inner list. |
|
||||||
| .splitListItem() | Splits a textblock of a list item into two list items. |
|
| .splitListItem() | Splits a textblock of a list item into two list items. |
|
||||||
| .toggleList() | Toggle between different list styles. |
|
| .toggleList() | Toggle between different list styles. |
|
||||||
|
| .wrapInList() | |
|
||||||
|
|
||||||
### Selection
|
### Selection
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
@@ -67,4 +99,4 @@ Have a look at all of the core commands listed below. They should give you a goo
|
|||||||
| .selectAll() | Select the whole document. |
|
| .selectAll() | Select the whole document. |
|
||||||
|
|
||||||
### Extensions
|
### Extensions
|
||||||
All extension can add additional commands (and most do), check out the specific [documentation for the provided extensions](/api/extensions), [nodes](/api/nodes), and [marks](/api/marks) to learn more about that. Of course, you can [add your custom extensions](/guide/build-custom-extensions) with custom commands aswell.
|
All extensions can add additional commands (and most do), check out the specific [documentation for the provided nodes](/api/nodes), [marks](/api/marks), and [extensions](/api/extensions) to learn more about those. Of course, you can [add your custom extensions](/guide/build-custom-extensions) with custom commands aswell.
|
||||||
|
|||||||
@@ -15,12 +15,6 @@ yarn add @tiptap/extension-focus
|
|||||||
| className | string | has-focus | The class that is applied to the focused element. |
|
| className | string | has-focus | The class that is applied to the focused element. |
|
||||||
| nested | boolean | true | When enabled nested elements get the focus class, too. |
|
| nested | boolean | true | When enabled nested elements get the focus class, too. |
|
||||||
|
|
||||||
## Commands
|
|
||||||
*None*
|
|
||||||
|
|
||||||
## Keyboard shortcuts
|
|
||||||
*None*
|
|
||||||
|
|
||||||
## Source code
|
## Source code
|
||||||
[packages/extension-focus/](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-focus/)
|
[packages/extension-focus/](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-focus/)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
# Gapcursor
|
# Gapcursor
|
||||||
|
This extension loads the [ProseMirror Gapcursor plugin](https://github.com/ProseMirror/prosemirror-gapcursor) by Marijn Haverbeke, which adds a gap for the cursor in places that don’t allow regular selection. For example, after a table at the end of a document.
|
||||||
|
|
||||||
|
Note that tiptap is renderless, but the dropcursor needs CSS for its appearance. The default CSS is added to the usage example below.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
```bash
|
```bash
|
||||||
@@ -9,17 +12,8 @@ npm install @tiptap/extension-gapcursor
|
|||||||
yarn add @tiptap/extension-gapcursor
|
yarn add @tiptap/extension-gapcursor
|
||||||
```
|
```
|
||||||
|
|
||||||
## Settings
|
|
||||||
*None*
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
*None*
|
|
||||||
|
|
||||||
## Keyboard shortcuts
|
|
||||||
*None*
|
|
||||||
|
|
||||||
## Source code
|
## Source code
|
||||||
[packages/extension-gapcursor/](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-gapcursor/)
|
[packages/extension-gapcursor/](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-gapcursor/)
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
<demo name="Extensions/Gapcursor" highlight="" />
|
<demo name="Extensions/Gapcursor" highlight="12,33" />
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ tiptap comes with sensible keyboard shortcut defaults. Depending on what you wan
|
|||||||
Most of the core extensions register their own keyboard shortcuts. Depending on what set of extension you use, not all of the below listed keyboard shortcuts work for your editor.
|
Most of the core extensions register their own keyboard shortcuts. Depending on what set of extension you use, not all of the below listed keyboard shortcuts work for your editor.
|
||||||
|
|
||||||
### Essentials
|
### Essentials
|
||||||
|
❌ = untested
|
||||||
|
|
||||||
| Action | Windows/Linux | macOS |
|
| Action | Windows/Linux | macOS |
|
||||||
| ------------------------ | ------------------------------- | --------------------------- |
|
| ------------------------ | ------------------------------- | --------------------------- |
|
||||||
| Copy | `Control` `C` | `Cmd` `C` |
|
| Copy | `Control` `C` | `Cmd` `C` |
|
||||||
@@ -17,35 +19,35 @@ Most of the core extensions register their own keyboard shortcuts. Depending on
|
|||||||
| Paste without formatting | `Control` `Shift` `V` | `Cmd` `Shift` `V` |
|
| Paste without formatting | `Control` `Shift` `V` | `Cmd` `Shift` `V` |
|
||||||
| Undo | `Control` `Z` | `Cmd` `Z` |
|
| Undo | `Control` `Z` | `Cmd` `Z` |
|
||||||
| Redo | `Control` `Shift` `Z` | `Cmd` `Shift` `Z` |
|
| Redo | `Control` `Shift` `Z` | `Cmd` `Shift` `Z` |
|
||||||
| Insert or edit link | `Control` `K` | `Cmd` `K` |
|
| ❌ Insert or edit link | `Control` `K` | `Cmd` `K` |
|
||||||
| Open link | `Alt` `Enter` | `Alt` `Enter` |
|
| ❌ Open link | `Alt` `Enter` | `Alt` `Enter` |
|
||||||
| Find | `Control` `F` | `Cmd` `F` |
|
| ❌ Find | `Control` `F` | `Cmd` `F` |
|
||||||
| Find and replace | `Control` `H` | `Cmd` `Shift` `H` |
|
| ❌ Find and replace | `Control` `H` | `Cmd` `Shift` `H` |
|
||||||
| Find again | `Control` `G` | `Cmd` `G` |
|
| ❌ Find again | `Control` `G` | `Cmd` `G` |
|
||||||
| Find previous | `Control` `Shift` `G` | `Cmd` `Shift` `G` |
|
| ❌ Find previous | `Control` `Shift` `G` | `Cmd` `Shift` `G` |
|
||||||
| Repeat last action | `Control` `Y` | `Cmd` `Y` |
|
| ❌ Repeat last action | `Control` `Y` | `Cmd` `Y` |
|
||||||
| Add a line break | `Shift` `Enter` | `Shift` `Enter` |
|
| Add a line break | `Shift` `Enter` | `Shift` `Enter` |
|
||||||
|
|
||||||
### Text Formatting
|
### Text Formatting
|
||||||
| Action | Windows/Linux | macOS |
|
| Action | Windows/Linux | macOS |
|
||||||
| --------------------- | ------------------------------------------------------- | --------------------------- |
|
| ----------------------- | -------------------------------------------- | --------------------------- |
|
||||||
| Bold | `Control` `B` | `Cmd` `B` |
|
| Bold | `Control` `B` | `Cmd` `B` |
|
||||||
| Italicize | `Control` `I` | `Cmd` `I` |
|
| Italicize | `Control` `I` | `Cmd` `I` |
|
||||||
| Underline | `Control` `U` | `Cmd` `U` |
|
| Underline | `Control` `U` | `Cmd` `U` |
|
||||||
| Strikethrough | `Alt` `Shift` `5` | `Cmd` `Shift` `X` |
|
| ❌ Strikethrough | `Alt` `Shift` `5` | `Cmd` `Shift` `X` |
|
||||||
| Superscript | `Control` `.` | `Cmd` `.` |
|
| ❌ Superscript | `Control` `.` | `Cmd` `.` |
|
||||||
| Subscript | `Control` `,` | `Cmd` `,` |
|
| ❌ Subscript | `Control` `,` | `Cmd` `,` |
|
||||||
| Copy text formatting | `Control` `Alt` `C` | `Cmd` `Alt` `C` |
|
| ❌ Copy text formatting | `Control` `Alt` `C` | `Cmd` `Alt` `C` |
|
||||||
| Paste text formatting | `Control` `Alt` `V` | `Cmd` `Alt` `V` |
|
| ❌ Paste text formatting | `Control` `Alt` `V` | `Cmd` `Alt` `V` |
|
||||||
| Clear text formatting | `Control` <code>\</code><br>`Control` `Space` | `Cmd` `\` |
|
| ❌ Clear text formatting | `Control` `\`<br>`Control` `Space` | `Cmd` `\` |
|
||||||
| Increase font size | `Control` `Shift` `>` | `Cmd` `Shift` `>` |
|
| ❌ Increase font size | `Control` `Shift` `>` | `Cmd` `Shift` `>` |
|
||||||
| Decrease font size | `Control` `Shift` `<` | `Cmd` `Shift` `<` |
|
| ❌ Decrease font size | `Control` `Shift` `<` | `Cmd` `Shift` `<` |
|
||||||
|
|
||||||
### Paragraph Formatting
|
### Paragraph Formatting
|
||||||
| Action | Windows/Linux | macOS |
|
| Action | Windows/Linux | macOS |
|
||||||
| ------------------------------ | ------------------------------- | ------------------------------- |
|
| -------------------------------- | ------------------------------- | ------------------------------- |
|
||||||
| Increase paragraph indentation | `Control` `]` | `Cmd` `]` |
|
| ❌ Increase paragraph indentation | `Control` `]` | `Cmd` `]` |
|
||||||
| Decrease paragraph indentation | `Control` `[` | `Cmd` `[` |
|
| ❌ Decrease paragraph indentation | `Control` `[` | `Cmd` `[` |
|
||||||
| Apply normal text style | `Control` `Alt` `0` | `Cmd` `Alt` `0` |
|
| Apply normal text style | `Control` `Alt` `0` | `Cmd` `Alt` `0` |
|
||||||
| Apply heading style 1 | `Control` `Alt` `1` | `Cmd` `Alt` `1` |
|
| Apply heading style 1 | `Control` `Alt` `1` | `Cmd` `Alt` `1` |
|
||||||
| Apply heading style 2 | `Control` `Alt` `2` | `Cmd` `Alt` `2` |
|
| Apply heading style 2 | `Control` `Alt` `2` | `Cmd` `Alt` `2` |
|
||||||
@@ -57,10 +59,10 @@ Most of the core extensions register their own keyboard shortcuts. Depending on
|
|||||||
| Center align | `Control` `Shift` `E` | `Cmd` `Shift` `E` |
|
| Center align | `Control` `Shift` `E` | `Cmd` `Shift` `E` |
|
||||||
| Right align | `Control` `Shift` `R` | `Cmd` `Shift` `R` |
|
| Right align | `Control` `Shift` `R` | `Cmd` `Shift` `R` |
|
||||||
| Justify | `Control` `Shift` `J` | `Cmd` `Shift` `J` |
|
| Justify | `Control` `Shift` `J` | `Cmd` `Shift` `J` |
|
||||||
| Numbered list | `Control` `Shift` `7` | `Cmd` `Shift` `7` |
|
| ❌ Numbered list | `Control` `Shift` `7` | `Cmd` `Shift` `7` |
|
||||||
| Bulleted list | `Control` `Shift` `8` | `Cmd` `Shift` `8` |
|
| ❌ Bulleted list | `Control` `Shift` `8` | `Cmd` `Shift` `8` |
|
||||||
| Move paragraph up | `Control` `Shift` `↑` | `Control` `Shift` `↑` |
|
| ❌ Move paragraph up | `Control` `Shift` `↑` | `Control` `Shift` `↑` |
|
||||||
| Move paragraph down | `Control` `Shift` `↓` | `Control` `Shift` `↓` |
|
| ❌ Move paragraph down | `Control` `Shift` `↓` | `Control` `Shift` `↓` |
|
||||||
|
|
||||||
### Text Selection
|
### Text Selection
|
||||||
| Action | Windows/Linux | macOS |
|
| Action | Windows/Linux | macOS |
|
||||||
@@ -70,10 +72,10 @@ Most of the core extensions register their own keyboard shortcuts. Depending on
|
|||||||
| Extend selection one character to right | `Shift` `→` | `Shift` `→` |
|
| Extend selection one character to right | `Shift` `→` | `Shift` `→` |
|
||||||
| Extend selection one line up | `Shift` `↑` | `Shift` `↑` |
|
| Extend selection one line up | `Shift` `↑` | `Shift` `↑` |
|
||||||
| Extend selection one line down | `Shift` `↓` | `Shift` `↓` |
|
| Extend selection one line down | `Shift` `↓` | `Shift` `↓` |
|
||||||
| Extend selection one paragraph up | `Alt` `Shift` `↑` | `Alt` `Shift` `↑` |
|
| ❌ Extend selection one paragraph up | `Alt` `Shift` `↑` | `Alt` `Shift` `↑` |
|
||||||
| Extend selection one paragraph down | `Alt` `Shift` `↓` | `Alt` `Shift` `↓` |
|
| ❌ Extend selection one paragraph down | `Alt` `Shift` `↓` | `Alt` `Shift` `↓` |
|
||||||
| Extend selection to the beginning of the document | `Control` `Shift` `↑` | `Cmd` `Shift` `↑` |
|
| Extend selection to the beginning of the document | `Control` `Shift` `↑` | `Cmd` `Shift` `↑` |
|
||||||
| Extend selection to the end of the document | `Control` `Shift` `↓` | `Cmd` `Shift` `↓` |
|
| ❌ Extend selection to the end of the document | `Control` `Shift` `↓` | `Cmd` `Shift` `↓` |
|
||||||
|
|
||||||
## Overwrite keyboard shortcuts
|
## Overwrite keyboard shortcuts
|
||||||
Keyboard shortcuts may be strings like `'Shift-Control-Enter'`. Keys are based on the strings that can appear in `event.key`, concatenated with a `-`. There is a little tool called [keycode.info](https://keycode.info/), which shows the `event.key` interactively.
|
Keyboard shortcuts may be strings like `'Shift-Control-Enter'`. Keys are based on the strings that can appear in `event.key`, concatenated with a `-`. There is a little tool called [keycode.info](https://keycode.info/), which shows the `event.key` interactively.
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ This schema is *very* strict. You can’t use any HTML element or attribute that
|
|||||||
Let me give you one example: If you paste something like `This is <strong>important</strong>` into tiptap, don’t have any extension that handles `strong` tags registered, you’ll only see `This is important` – without the strong tags.
|
Let me give you one example: If you paste something like `This is <strong>important</strong>` into tiptap, don’t have any extension that handles `strong` tags registered, you’ll only see `This is important` – without the strong tags.
|
||||||
|
|
||||||
## How a schema looks like
|
## How a schema looks like
|
||||||
|
When you’ll work with the provided extensions only, you don’t have to care that much about the schema. If you’re building your own extensions, it’s probably helpful to understand how the schema works. Let’s look at the most simple schema for a typical ProseMirror editor:
|
||||||
The most simple schema for a typical *ProseMirror* editor is looking something like that:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
// the underlying ProseMirror schema
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
document: {
|
document: {
|
||||||
@@ -32,58 +32,95 @@ The most simple schema for a typical *ProseMirror* editor is looking something l
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
:::warning Out of date
|
|
||||||
This content is written for tiptap 1 and needs an update.
|
|
||||||
:::
|
|
||||||
|
|
||||||
We register three nodes here. `document`, `paragraph` and `text`. `document` is the root node which allows one or more block nodes as children (`content: 'block+'`). Since `paragraph` is in the group of block nodes (`group: 'block'`) our document can only contain paragraphs. Our paragraphs allow zero or more inline nodes as children (`content: 'inline*'`) so there can only be `text` in it. `parseDOM` defines how a node can be parsed from pasted HTML. `toDOM` defines how it will be rendered in the DOM.
|
We register three nodes here. `document`, `paragraph` and `text`. `document` is the root node which allows one or more block nodes as children (`content: 'block+'`). Since `paragraph` is in the group of block nodes (`group: 'block'`) our document can only contain paragraphs. Our paragraphs allow zero or more inline nodes as children (`content: 'inline*'`) so there can only be `text` in it. `parseDOM` defines how a node can be parsed from pasted HTML. `toDOM` defines how it will be rendered in the DOM.
|
||||||
|
|
||||||
In tiptap we define every node in its own `Extension` class instead. This allows us to split logic per node. Under the hood the schema will be merged together.
|
In tiptap every node, mark and extension is living in its own file. This allows us to split the logic. Under the hood the whole schema will be merged together:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
class Document extends Node {
|
// the tiptap schema API
|
||||||
name = 'document'
|
import { createNode } from '@tiptap/core'
|
||||||
topNode = true
|
|
||||||
|
|
||||||
schema() {
|
const Document = createNode({
|
||||||
return {
|
name: 'document',
|
||||||
|
topNode: true,
|
||||||
content: 'block+',
|
content: 'block+',
|
||||||
}
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Paragraph extends Node {
|
const Paragraph = createNode({
|
||||||
name = 'paragraph'
|
name: 'paragraph',
|
||||||
|
|
||||||
schema() {
|
|
||||||
return {
|
|
||||||
content: 'inline*',
|
|
||||||
group: 'block',
|
group: 'block',
|
||||||
parseDOM: [{ tag: 'p' }],
|
content: 'inline*',
|
||||||
toDOM: () => ['p', 0],
|
parseHTML() {
|
||||||
}
|
return [
|
||||||
}
|
{ tag: 'p' },
|
||||||
}
|
]
|
||||||
|
},
|
||||||
|
renderHTML({ attributes }) {
|
||||||
|
return ['p', attributes, 0]
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
class Text extends Node {
|
const Text = createNode({
|
||||||
name = 'text'
|
name: 'text',
|
||||||
|
|
||||||
schema() {
|
|
||||||
return {
|
|
||||||
group: 'inline',
|
group: 'inline',
|
||||||
}
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Difference between a Node and a Mark
|
## Nodes and marks
|
||||||
|
|
||||||
*Nodes* are like blocks of content, for example paragraphs, headings, code blocks, blockquotes and many more.
|
### Differences
|
||||||
|
Nodes are like blocks of content, for example paragraphs, headings, code blocks, blockquotes and many more.
|
||||||
|
|
||||||
*Marks* can apply a different style to specific parts of text inside a *Node*. That’s the case for **bold**, *italic* or ~~striked~~ text. [Links](#) are *Marks*, too.
|
Marks can be applied to specific parts of a node. That’s the case for **bold**, *italic* or ~~striked~~ text. [Links](#) are marks, too.
|
||||||
|
|
||||||
|
### The node schema
|
||||||
|
|
||||||
|
#### Content
|
||||||
|
> The content expression for this node, as described in the schema guide. When not given, the node does not allow any content.
|
||||||
|
|
||||||
|
#### Marks
|
||||||
|
> The marks that are allowed inside of this node. May be a space-separated string referring to mark names or groups, "_" to explicitly allow all marks, or "" to disallow marks. When not given, nodes with inline content default to allowing all marks, other nodes default to not allowing marks.
|
||||||
|
|
||||||
|
#### Group
|
||||||
|
> The group or space-separated groups to which this node belongs, which can be referred to in the content expressions for the schema.
|
||||||
|
|
||||||
|
#### Inline
|
||||||
|
> Should be set to true for inline nodes. (Implied for text nodes.)
|
||||||
|
|
||||||
|
#### Atom
|
||||||
|
> Can be set to true to indicate that, though this isn't a leaf node, it doesn't have directly editable content and should be treated as a single unit in the view.
|
||||||
|
|
||||||
|
#### Selectable
|
||||||
|
> Controls whether nodes of this type can be selected as a node selection. Defaults to true for non-text nodes.
|
||||||
|
|
||||||
|
#### Draggable
|
||||||
|
> Determines whether nodes of this type can be dragged without being selected. Defaults to false.
|
||||||
|
|
||||||
|
#### Code
|
||||||
|
> Can be used to indicate that this node contains code, which causes some commands to behave differently.
|
||||||
|
|
||||||
|
#### Defining
|
||||||
|
> Determines whether this node is considered an important parent node during replace operations (such as paste). Non-defining (the default) nodes get dropped when their entire content is replaced, whereas defining nodes persist and wrap the inserted content. Likewise, in inserted content the defining parents of the content are preserved when possible. Typically, non-default-paragraph textblock types, and possibly list items, are marked as defining.
|
||||||
|
|
||||||
|
#### Isolating
|
||||||
|
> When enabled (default is false), the sides of nodes of this type count as boundaries that regular editing operations, like backspacing or lifting, won't cross. An example of a node that should probably have this enabled is a table cell.
|
||||||
|
|
||||||
|
### The mark schema
|
||||||
|
#### Inclusive
|
||||||
|
> Whether this mark should be active when the cursor is positioned at its end (or at its start when that is also the start of the parent node). Defaults to true.
|
||||||
|
|
||||||
|
#### Excludes
|
||||||
|
> Determines which other marks this mark can coexist with. Should be a space-separated strings naming other marks or groups of marks When a mark is added to a set, all marks that it excludes are removed in the process. If the set contains any mark that excludes the new mark but is not, itself, excluded by the new mark, the mark can not be added an the set. You can use the value "_" to indicate that the mark excludes all marks in the schema.
|
||||||
|
|
||||||
|
> Defaults to only being exclusive with marks of the same type. You can set it to an empty string (or any string not containing the mark's own name) to allow multiple marks of a given type to coexist (as long as they have different attributes).
|
||||||
|
|
||||||
|
#### Group
|
||||||
|
> The group or space-separated groups to which this mark belongs.
|
||||||
|
|
||||||
|
#### Spanning
|
||||||
|
> Determines whether marks of this type can span multiple adjacent nodes when serialized to DOM/HTML. Defaults to true.
|
||||||
|
|
||||||
## Get the underlying ProseMirror schema
|
## Get the underlying ProseMirror schema
|
||||||
|
|
||||||
There are a few use cases where you need to work with the underlying schema. You’ll need that if you’re using the tiptap collaborative text editing features or if you want to manually render your content as HTML.
|
There are a few use cases where you need to work with the underlying schema. You’ll need that if you’re using the tiptap collaborative text editing features or if you want to manually render your content as HTML.
|
||||||
|
|
||||||
### Option 1: With an Editor
|
### Option 1: With an Editor
|
||||||
@@ -123,71 +160,3 @@ const schema = getSchema([
|
|||||||
// add more extensions here
|
// add more extensions here
|
||||||
])
|
])
|
||||||
```
|
```
|
||||||
|
|
||||||
## Generate HTML from ProseMirror JSON
|
|
||||||
|
|
||||||
If you need to render the content on the server side, for example to render a blog post which was written with tiptap, you’ll probably need a way to do just that without an actual editor instance. That’s what `generateHTML()` is for. It’s a utility function that renders HTML without an actual editor instance.
|
|
||||||
|
|
||||||
:::info Browser-only rendering
|
|
||||||
Import a lightweight implementation from `@tiptap/core` if you’re using the function in a browser context only.
|
|
||||||
:::
|
|
||||||
|
|
||||||
<demo name="Api/Schema/GenerateHTML" highlight="6,29-33"/>
|
|
||||||
|
|
||||||
### Converting JSON<>HTML with PHP
|
|
||||||
|
|
||||||
We needed to do the same thing in PHP at some point, so we published libraries to convert ProseMirror JSON to HTML and vice-versa:
|
|
||||||
|
|
||||||
* [ueberdosis/prosemirror-php](https://github.com/ueberdosis/prosemirror-php) (PHP)
|
|
||||||
* [ueberdosis/prosemirror-to-html](https://github.com/ueberdosis/prosemirror-to-html) (PHP)
|
|
||||||
* [ueberdosis/html-to-prosemirror](https://github.com/ueberdosis/html-to-prosemirror) (PHP)
|
|
||||||
|
|
||||||
|
|
||||||
### Node Schema
|
|
||||||
|
|
||||||
|
|
||||||
#### Content
|
|
||||||
> The content expression for this node, as described in the schema guide. When not given, the node does not allow any content.
|
|
||||||
|
|
||||||
|
|
||||||
#### Marks
|
|
||||||
> The marks that are allowed inside of this node. May be a space-separated string referring to mark names or groups, "_" to explicitly allow all marks, or "" to disallow marks. When not given, nodes with inline content default to allowing all marks, other nodes default to not allowing marks.
|
|
||||||
|
|
||||||
#### Group
|
|
||||||
> The group or space-separated groups to which this node belongs, which can be referred to in the content expressions for the schema.
|
|
||||||
|
|
||||||
#### Inline
|
|
||||||
> Should be set to true for inline nodes. (Implied for text nodes.)
|
|
||||||
|
|
||||||
#### Atom
|
|
||||||
> Can be set to true to indicate that, though this isn't a leaf node, it doesn't have directly editable content and should be treated as a single unit in the view.
|
|
||||||
|
|
||||||
#### Selectable
|
|
||||||
> Controls whether nodes of this type can be selected as a node selection. Defaults to true for non-text nodes.
|
|
||||||
|
|
||||||
#### Draggable
|
|
||||||
> Determines whether nodes of this type can be dragged without being selected. Defaults to false.
|
|
||||||
|
|
||||||
#### Code
|
|
||||||
> Can be used to indicate that this node contains code, which causes some commands to behave differently.
|
|
||||||
|
|
||||||
#### Defining
|
|
||||||
> Determines whether this node is considered an important parent node during replace operations (such as paste). Non-defining (the default) nodes get dropped when their entire content is replaced, whereas defining nodes persist and wrap the inserted content. Likewise, in inserted content the defining parents of the content are preserved when possible. Typically, non-default-paragraph textblock types, and possibly list items, are marked as defining.
|
|
||||||
|
|
||||||
#### Isolating
|
|
||||||
> When enabled (default is false), the sides of nodes of this type count as boundaries that regular editing operations, like backspacing or lifting, won't cross. An example of a node that should probably have this enabled is a table cell.
|
|
||||||
|
|
||||||
### Mark Schema
|
|
||||||
#### Inclusive
|
|
||||||
> Whether this mark should be active when the cursor is positioned at its end (or at its start when that is also the start of the parent node). Defaults to true.
|
|
||||||
|
|
||||||
#### Excludes
|
|
||||||
> Determines which other marks this mark can coexist with. Should be a space-separated strings naming other marks or groups of marks When a mark is added to a set, all marks that it excludes are removed in the process. If the set contains any mark that excludes the new mark but is not, itself, excluded by the new mark, the mark can not be added an the set. You can use the value "_" to indicate that the mark excludes all marks in the schema.
|
|
||||||
|
|
||||||
> Defaults to only being exclusive with marks of the same type. You can set it to an empty string (or any string not containing the mark's own name) to allow multiple marks of a given type to coexist (as long as they have different attributes).
|
|
||||||
|
|
||||||
#### Group
|
|
||||||
> The group or space-separated groups to which this mark belongs.
|
|
||||||
|
|
||||||
#### Spanning
|
|
||||||
> Determines whether marks of this type can span multiple adjacent nodes when serialized to DOM/HTML. Defaults to true.
|
|
||||||
|
|||||||
@@ -289,6 +289,8 @@ const CustomUnderline = Underline.extend({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Node views
|
||||||
|
|
||||||
## Option 2: Start from scratch
|
## Option 2: Start from scratch
|
||||||
|
|
||||||
### Read the documentation
|
### Read the documentation
|
||||||
|
|||||||
@@ -88,3 +88,14 @@ You should really consider to work with HTML or JSON to store your content, they
|
|||||||
If you still think you need Markdown, [Nextcloud Text](https://github.com/nextcloud/text) uses tiptap 1 to work with Markdown. Their code is open source, so maybe you can learn from them.
|
If you still think you need Markdown, [Nextcloud Text](https://github.com/nextcloud/text) uses tiptap 1 to work with Markdown. Their code is open source, so maybe you can learn from them.
|
||||||
|
|
||||||
That said, tiptap **does** support Markdown shortcuts to format your content. Try typing `**two asterisks**` to make your text bold for example.
|
That said, tiptap **does** support Markdown shortcuts to format your content. Try typing `**two asterisks**` to make your text bold for example.
|
||||||
|
|
||||||
|
## Generate HTML from ProseMirror JSON
|
||||||
|
If you need to render the content on the server side, for example to render a blog post which was written with tiptap, you’ll probably need a way to do just that without an actual editor instance.
|
||||||
|
|
||||||
|
That’s what `generateHTML()` is for. It’s a utility function that renders HTML without an actual editor instance.
|
||||||
|
|
||||||
|
:::info Browser-only rendering
|
||||||
|
Import a lightweight implementation from `@tiptap/core` if you’re using the function in a browser context only.
|
||||||
|
:::
|
||||||
|
|
||||||
|
<demo name="Api/Schema/GenerateHTML" highlight="6,29-33"/>
|
||||||
|
|||||||
8
docs/src/docPages/open.md
Normal file
8
docs/src/docPages/open.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Monthly reports
|
||||||
|
|
||||||
|
## October 2020
|
||||||
|
|
||||||
|
## September 2020
|
||||||
|
|
||||||
|
## August 2020
|
||||||
|
|
||||||
@@ -173,7 +173,6 @@
|
|||||||
draft: true
|
draft: true
|
||||||
- title: Gapcursor
|
- title: Gapcursor
|
||||||
link: /api/extensions/gapcursor
|
link: /api/extensions/gapcursor
|
||||||
draft: true
|
|
||||||
- title: History
|
- title: History
|
||||||
link: /api/extensions/history
|
link: /api/extensions/history
|
||||||
- title: TextAlign
|
- title: TextAlign
|
||||||
@@ -188,14 +187,13 @@
|
|||||||
link: /api/events
|
link: /api/events
|
||||||
- title: Schema
|
- title: Schema
|
||||||
link: /api/schema
|
link: /api/schema
|
||||||
|
draft: true
|
||||||
- title: Keyboard Shortcuts
|
- title: Keyboard Shortcuts
|
||||||
link: /api/keyboard-shortcuts
|
link: /api/keyboard-shortcuts
|
||||||
|
|
||||||
- title: Sponsoring
|
- title: Sponsoring
|
||||||
items:
|
items:
|
||||||
# - title: Benefits for sponsors
|
- title: Monthly reports
|
||||||
# link: /benefits-for-sponsors
|
link: /open
|
||||||
# - title: Sponsoring reports
|
|
||||||
# link: /open
|
|
||||||
- title: Become a sponsor
|
- title: Become a sponsor
|
||||||
link: /sponsor
|
link: /sponsor
|
||||||
|
|||||||
@@ -265,5 +265,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> blockquote {
|
||||||
|
border-left: 2px solid rgba($colorGrey, 0.5);
|
||||||
|
padding-left: $spacing;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ const TextAlign = createExtension({
|
|||||||
Enter: () => this.editor.splitBlock({
|
Enter: () => this.editor.splitBlock({
|
||||||
withAttributes: true,
|
withAttributes: true,
|
||||||
}),
|
}),
|
||||||
|
'Ctrl-Shift-l': () => this.editor.textAlign('left'),
|
||||||
|
'Ctrl-Shift-e': () => this.editor.textAlign('center'),
|
||||||
|
'Ctrl-Shift-r': () => this.editor.textAlign('right'),
|
||||||
|
'Ctrl-Shift-j': () => this.editor.textAlign('justify'),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user