This commit is contained in:
Philipp Kühn
2020-11-13 16:44:26 +01:00
37 changed files with 469 additions and 51 deletions

View File

@@ -14,7 +14,7 @@
<g-link <g-link
class="page-navigation__link" class="page-navigation__link"
exact exact
:to="nextPage.link" :to="nextPage.redirect || nextPage.link"
v-if="nextPage" v-if="nextPage"
> >
{{ nextPage.title }} {{ nextPage.title }}
@@ -41,10 +41,11 @@ export default {
flattenedItems() { flattenedItems() {
const flattenedItems = [] const flattenedItems = []
this.items.forEach(({ title, link, items }) => { this.items.forEach(({ title, link, redirect, items }) => {
flattenedItems.push({ flattenedItems.push({
title, title,
link, link,
redirect,
}) })
if (items) { if (items) {
@@ -68,7 +69,13 @@ export default {
}, },
previousPage() { previousPage() {
return this.flattenedItems[this.currentIndex - 1] let previousIndex = this.currentIndex - 1
while (this.flattenedItems[previousIndex].redirect) {
previousIndex -= 1
}
return this.flattenedItems[previousIndex]
}, },
}, },
} }

View File

@@ -124,3 +124,50 @@ export default {
}, },
} }
</script> </script>
<style lang="scss">
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
ul,
ol {
padding: 0 1rem;
}
code {
background-color: rgba(#616161, 0.1);
color: #616161;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
code {
color: inherit;
background: none;
font-size: 0.8rem;
}
}
img {
max-width: 100%;
height: auto;
}
hr {
margin: 1rem 0;
}
blockquote {
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
}
</style>

View File

@@ -281,4 +281,49 @@ export default {
border-radius: 3px; border-radius: 3px;
white-space: nowrap; white-space: nowrap;
} }
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
ul,
ol {
padding: 0 1rem;
}
code {
background-color: rgba(#616161, 0.1);
color: #616161;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
code {
color: inherit;
background: none;
font-size: 0.8rem;
}
}
img {
max-width: 100%;
height: auto;
}
hr {
margin: 1rem 0;
}
blockquote {
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
}
</style> </style>

View File

@@ -105,6 +105,7 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
/* Style the export */
.export { .export {
padding: 1rem 0 0; padding: 1rem 0 0;
@@ -126,4 +127,49 @@ export default {
color: #495057; color: #495057;
} }
} }
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
ul,
ol {
padding: 0 1rem;
}
code {
background-color: rgba(#616161, 0.1);
color: #616161;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
code {
color: inherit;
background: none;
font-size: 0.8rem;
}
}
img {
max-width: 100%;
height: auto;
}
hr {
margin: 1rem 0;
}
blockquote {
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
}
</style> </style>

View File

@@ -64,3 +64,17 @@ export default {
}, },
} }
</script> </script>
<style lang="scss">
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
a {
text-decoration: underline;
color: blue;
}
}
</style>

View File

@@ -6,6 +6,8 @@
<script> <script>
import { Editor, EditorContent, defaultExtensions } from '@tiptap/vue-starter-kit' import { Editor, EditorContent, defaultExtensions } from '@tiptap/vue-starter-kit'
import Strike from '@tiptap/extension-strike'
import Highlight from '@tiptap/extension-highlight'
export default { export default {
components: { components: {
@@ -28,10 +30,16 @@ export default {
To test that, start a new line and type <code>#</code> followed by a space to get a heading. Try <code>#</code>, <code>##</code>, <code>###</code>, <code>####</code>, <code>#####</code>, <code>######</code> for different levels. To test that, start a new line and type <code>#</code> followed by a space to get a heading. Try <code>#</code>, <code>##</code>, <code>###</code>, <code>####</code>, <code>#####</code>, <code>######</code> for different levels.
</p> </p>
<p> <p>
Those conventions are called input rules in tiptap. Some of them are enabled by default. Try <code>></code> for blockquotes, <code>*</code>, <code>-</code> or <code>+</code> for bullet lists, or <code>\`foobar\`</code> to highlight code. You can add your own input rules to existing extensions, and to your custom nodes and marks. Those conventions are called input rules in tiptap. Some of them are enabled by default. Try <code>></code> for blockquotes, <code>*</code>, <code>-</code> or <code>+</code> for bullet lists, or <code>\`foobar\`</code> to highlight code, <code>~~tildes~~</code> to strike text, or <code>==equal signs==</code> to highlight text.
</p>
<p>
You can overwrite existing input rules or add your own to new nodes and marks.
</p> </p>
`, `,
extensions: defaultExtensions(), extensions: [
...defaultExtensions(),
Highlight(),
],
}) })
}, },
@@ -40,3 +48,50 @@ export default {
}, },
} }
</script> </script>
<style lang="scss">
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
ul,
ol {
padding: 0 1rem;
}
code {
background-color: rgba(#616161, 0.1);
color: #616161;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
code {
color: inherit;
background: none;
font-size: 0.8rem;
}
}
img {
max-width: 100%;
height: auto;
}
hr {
margin: 1rem 0;
}
blockquote {
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
}
</style>

View File

@@ -42,3 +42,12 @@ export default {
}, },
} }
</script> </script>
<style lang="scss">
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
}
</style>

View File

@@ -53,6 +53,13 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
}
.checkbox { .checkbox {
margin-bottom: 1rem; margin-bottom: 1rem;

View File

@@ -72,4 +72,6 @@ context('/api/marks/link', () => {
.should('have.attr', 'href', url) .should('have.attr', 'href', url)
}) })
}) })
// TODO: Test invalid URLs
}) })

View File

@@ -95,18 +95,18 @@ Have a look at all of the core commands listed below. They should give you a goo
| .updateNodeAttributes() | Update attributes of a node. | | .updateNodeAttributes() | Update attributes of a node. |
### 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 one list item into two list items. |
| .toggleList() | Toggle between different list types. | | .toggleList() | Toggle between different list types. |
| .wrapInList() | Wrap a node in a list. | | .wrapInList() | Wrap a node in a list. |
### Selection ### Selection
| Command | Description | | Command | Description |
| ------------------ | --------------------------------------- | | ------------------ | --------------------------------------- |
| .blur() | Blurs the editor. | | .blur() | Removes focus from the editor. |
| .deleteSelection() | Delete the selection, if there is one. | | .deleteSelection() | Delete the selection, if there is one. |
| .focus() | Focus the editor at the given position. | | .focus() | Focus the editor at the given position. |
| .scrollIntoView() | Scroll the selection into view. | | .scrollIntoView() | Scroll the selection into view. |

View File

@@ -179,7 +179,7 @@ createNode({
#### Defining #### Defining
Nodes get dropped when their entire content is replaced (for example, when pasting new content) by default. If a node should be kept for such replace operations, configure them as `defining`. Nodes get dropped when their entire content is replaced (for example, when pasting new content) by default. If a node should be kept for such replace operations, configure them as `defining`.
Typically, that applies to [`Blockquote`](/api/extensions/blockquote), [`CodeBlock`](/api/extensions/code-block), [`Heading`](/api/extensions/heading), and [`ListItem`](/api/extensions/list-item). Typically, that applies to [`Blockquote`](/api/node/blockquote), [`CodeBlock`](/api/node/code-block), [`Heading`](/api/node/heading), and [`ListItem`](/api/node/list-item).
```js ```js
createNode({ createNode({

View File

@@ -282,6 +282,10 @@ const CustomParagraph = Paragraph.extend({
}) })
``` ```
:::warning Use the commands parameter inside of addCommands
All commands are also available through ~~this.editor.commands~~, but inside of `addCommands` you must use the `commands` parameter thats passed to it.
:::
### Keyboard shortcuts ### Keyboard shortcuts
Most core extensions come with sensible keyboard shortcut defaults. Depending on what you want to build, youll likely want to change them though. With the `addKeyboardShortcuts()` method you can overwrite the predefined shortcut map: Most core extensions come with sensible keyboard shortcut defaults. Depending on what you want to build, youll likely want to change them though. With the `addKeyboardShortcuts()` method you can overwrite the predefined shortcut map:

View File

@@ -3,10 +3,49 @@
## toc ## toc
## Introduction ## Introduction
The whole tiptap is code base is written in TypeScript. If you havent heard of it or never used it, no worries. You dont have to.
TypeScript extends JavaScript by adding types (hence the name). It adds new syntax, which doesnt exist in plain JavaScript. Its actually removed before running in the browser, but this step the compilation is important to find bugs early. It checks if you passe the right types of data to functions. For a big and complex project, thats very valuable. It means well get notified of lot of bugs, before shipping code to you.
Anyway, if you dont use TypeScript in your project, thats fine. Youll still be able to use tiptap and even get a really nice autocomplete for the tiptap API (if your editor supports it, but most do).
If youre using TypeScript in your project and want to extend tiptap, there are two things that are good to know.
## Options type ## Options type
To extend or create default options for an extension, youll need to define a custom type, here is an example:
```js
import { createExtension } from '@tiptap/core'
## Create a command export interface CustomExtensionOptions {
awesomeness: number,
}
const CustomExtension = createExtension({
defaultOptions: <CustomExtensionOptions>{
awesomeness: 100,
},
})
```
## Command type
The core package also exports a `Command` type, which needs to be added to all commands that you specify in your code. Here is an example:
```js
import { Command, createExtension } from '@tiptap/core'
const CustomExtension = createExtension({
addCommands() {
return {
/**
* Comments will be added to the autocomplete.
*/
yourCommand: (): Command => ({ commands }) => {
// …
},
}
},
})
```
Thats basically it. Were doing all the rest automatically.

View File

@@ -90,45 +90,45 @@ code {
color: white; color: white;
} }
.ProseMirror { // TODO: Move to examples
// .ProseMirror {
// > * + * {
// margin-top: 0.75em;
// }
> * + * { // ul,
margin-top: 0.75em; // ol {
} // padding: 0 1rem;
// }
ul, // pre {
ol { // background: $colorBlack;
padding: 0 1rem; // color: $colorWhite;
} // font-family: 'JetBrainsMono', monospace;
// padding: 0.75rem 1rem;
// border-radius: 0.5rem;
pre { // code {
background: $colorBlack; // color: inherit;
color: $colorWhite; // background: none;
font-family: 'JetBrainsMono', monospace; // font-size: 0.8rem;
padding: 0.75rem 1rem; // }
border-radius: 0.5rem; // }
code { // img {
color: inherit; // max-width: 100%;
background: none; // height: auto;
font-size: 0.8rem; // }
}
}
img { // hr {
max-width: 100%; // margin: 1rem 0;
height: auto; // }
}
hr { // blockquote {
margin: 1rem 0; // padding-left: 1rem;
} // border-left: 2px solid rgba($colorBlack, 0.1);
// }
blockquote { // }
padding-left: 1rem;
border-left: 2px solid rgba($colorBlack, 0.1);
}
}
.DocSearch { .DocSearch {
filter: saturate(0); filter: saturate(0);

View File

@@ -62,7 +62,6 @@
draft: true draft: true
- title: Working with TypeScript - title: Working with TypeScript
link: /guide/working-with-typescript link: /guide/working-with-typescript
draft: true
- title: API - title: API
items: items:

View File

@@ -31,33 +31,117 @@ import wrapInList from '../commands/wrapInList'
export const Commands = createExtension({ export const Commands = createExtension({
addCommands() { addCommands() {
return { return {
/**
* Removes focus from the editor.
*/
blur, blur,
/**
* Clear the whole document.
*/
clearContent, clearContent,
/**
* Normalize nodes to a simple paragraph.
*/
clearNodes, clearNodes,
/**
* Delete the selection, if there is one.
*/
deleteSelection, deleteSelection,
/**
* Extends the text selection to the current mark.
*/
extendMarkRange, extendMarkRange,
/**
* Focus the editor at the given position.
*/
focus, focus,
/**
* Insert a string of HTML at the current position.
*/
insertHTML, insertHTML,
/**
* Insert a string of text at the current position.
*/
insertText, insertText,
/**
* Lift the list item into a wrapping list.
*/
liftListItem, liftListItem,
/**
* Remove all marks in the current selection.
*/
removeMark, removeMark,
/**
* Remove all marks in the current selection.
*/
removeMarks, removeMarks,
/**
* Resets all node attributes to the default value.
*/
resetNodeAttributes, resetNodeAttributes,
/**
* Scroll the selection into view.
*/
scrollIntoView, scrollIntoView,
/**
* Select the whole document.
*/
selectAll, selectAll,
/**
* Select the parent node.
*/
selectParentNode, selectParentNode,
/**
* Replace a given range with a node.
*/
setBlockType, setBlockType,
/**
* Replace the whole document with new content.
*/
setContent, setContent,
/**
* Sink the list item down into an inner list.
*/
sinkListItem, sinkListItem,
/**
* Forks a new node from an existing node.
*/
splitBlock, splitBlock,
/**
* Splits one list item into two list items.
*/
splitListItem, splitListItem,
/**
* Toggle a node with another node.
*/
toggleBlockType, toggleBlockType,
/**
* Toggle between different list types.
*/
toggleList, toggleList,
/**
* Toggle a mark on and off.
*/
toggleMark, toggleMark,
/**
* Wraps nodes in another node, or removes an existing wrap.
*/
toggleWrap, toggleWrap,
/**
* Runs one command after the other and stops at the first which returns true.
*/
try: tryCommand, try: tryCommand,
/**
* Update a mark with new attributes.
*/
updateMarkAttributes, updateMarkAttributes,
/**
* Update attributes of a node.
*/
updateNodeAttributes, updateNodeAttributes,
/**
* Wrap a node in a list.
*/
wrapInList, wrapInList,
} }
}, },

View File

@@ -34,6 +34,9 @@ const Blockquote = createNode({
addCommands() { addCommands() {
return { return {
/**
* Toggle a blockquote node
*/
blockquote: (): Command => ({ commands }) => { blockquote: (): Command => ({ commands }) => {
return commands.toggleWrap('blockquote') return commands.toggleWrap('blockquote')
}, },

View File

@@ -43,7 +43,7 @@ const Bold = createMark({
addCommands() { addCommands() {
return { return {
/** /**
* bold command * Toggle a bold mark
*/ */
bold: (): Command => ({ commands }) => { bold: (): Command => ({ commands }) => {
return commands.toggleMark('bold') return commands.toggleMark('bold')

View File

@@ -32,6 +32,9 @@ const BulletList = createNode({
addCommands() { addCommands() {
return { return {
/**
* Toggle a bullet list
*/
bulletList: (): Command => ({ commands }) => { bulletList: (): Command => ({ commands }) => {
return commands.toggleList('bulletList', 'listItem') return commands.toggleList('bulletList', 'listItem')
}, },

View File

@@ -74,6 +74,9 @@ const CodeBlock = createNode({
addCommands() { addCommands() {
return { return {
/**
* Toggle a code block
*/
codeBlock: (attrs?: CodeBlockOptions): Command => ({ commands }) => { codeBlock: (attrs?: CodeBlockOptions): Command => ({ commands }) => {
return commands.toggleBlockType('codeBlock', 'paragraph', attrs) return commands.toggleBlockType('codeBlock', 'paragraph', attrs)
}, },

View File

@@ -35,6 +35,9 @@ const Code = createMark({
addCommands() { addCommands() {
return { return {
/**
* Toggle inline code
*/
code: (): Command => ({ commands }) => { code: (): Command => ({ commands }) => {
return commands.toggleMark('code') return commands.toggleMark('code')
}, },

View File

@@ -30,6 +30,9 @@ const CollaborationCursor = createExtension({
addCommands() { addCommands() {
return { return {
/**
* Update details of the current user
*/
user: (attributes: { user: (attributes: {
name: string, name: string,
color: string, color: string,

View File

@@ -37,6 +37,9 @@ const FontFamily = createExtension({
addCommands() { addCommands() {
return { return {
/**
* Update the font family
*/
fontFamily: (fontFamily: string | null = null): Command => ({ chain }) => { fontFamily: (fontFamily: string | null = null): Command => ({ chain }) => {
return chain() return chain()
.updateMarkAttributes('textStyle', { fontFamily }) .updateMarkAttributes('textStyle', { fontFamily })

View File

@@ -22,6 +22,9 @@ const HardBreak = createNode({
addCommands() { addCommands() {
return { return {
/**
* Add a hard break
*/
hardBreak: (): Command => ({ commands, state, dispatch }) => { hardBreak: (): Command => ({ commands, state, dispatch }) => {
return commands.try([ return commands.try([
() => exitCode(state, dispatch), () => exitCode(state, dispatch),

View File

@@ -53,7 +53,7 @@ const Heading = createNode({
addCommands() { addCommands() {
return { return {
/** /**
* heading command * Toggle a heading node
*/ */
heading: (options: { level: Level }): Command => ({ commands }) => { heading: (options: { level: Level }): Command => ({ commands }) => {
if (!this.options.levels.includes(options.level)) { if (!this.options.levels.includes(options.level)) {

View File

@@ -58,6 +58,9 @@ const Highlight = createMark({
addCommands() { addCommands() {
return { return {
/**
* Toggle a highlight mark
*/
highlight: (attributes?: { color: string }): Command => ({ commands }) => { highlight: (attributes?: { color: string }): Command => ({ commands }) => {
return commands.toggleMark('highlight', attributes) return commands.toggleMark('highlight', attributes)
}, },

View File

@@ -14,9 +14,15 @@ const History = createExtension({
addCommands() { addCommands() {
return { return {
/**
* Undo recent changes
*/
undo: (): Command => ({ state, dispatch }) => { undo: (): Command => ({ state, dispatch }) => {
return undo(state, dispatch) return undo(state, dispatch)
}, },
/**
* Reapply reverted changes
*/
redo: (): Command => ({ state, dispatch }) => { redo: (): Command => ({ state, dispatch }) => {
return redo(state, dispatch) return redo(state, dispatch)
}, },

View File

@@ -27,6 +27,9 @@ const HorizontalRule = createNode({
addCommands() { addCommands() {
return { return {
/**
* Add a horizontal rule
*/
horizontalRule: (): Command => ({ tr }) => { horizontalRule: (): Command => ({ tr }) => {
tr.replaceSelectionWith(this.type.create()) tr.replaceSelectionWith(this.type.create())

View File

@@ -55,6 +55,9 @@ const Image = createNode({
addCommands() { addCommands() {
return { return {
/**
* Add an image
*/
image: (options: { src: string, alt?: string, title?: string }): Command => ({ tr }) => { image: (options: { src: string, alt?: string, title?: string }): Command => ({ tr }) => {
const { selection } = tr const { selection } = tr
const node = this.type.create(options) const node = this.type.create(options)

View File

@@ -44,6 +44,9 @@ const Italic = createMark({
addCommands() { addCommands() {
return { return {
/**
* Toggle an italic mark
*/
italic: (): Command => ({ commands }) => { italic: (): Command => ({ commands }) => {
return commands.toggleMark('italic') return commands.toggleMark('italic')
}, },

View File

@@ -51,6 +51,9 @@ const Link = createMark({
addCommands() { addCommands() {
return { return {
/**
* Toggle or update a link mark
*/
link: (options: { href?: string, target?: string } = {}): Command => ({ commands }) => { link: (options: { href?: string, target?: string } = {}): Command => ({ commands }) => {
if (!options.href) { if (!options.href) {
return commands.removeMark('link') return commands.removeMark('link')

View File

@@ -51,6 +51,9 @@ const OrderedList = createNode({
addCommands() { addCommands() {
return { return {
/**
* Toggle an ordered list
*/
orderedList: (): Command => ({ commands }) => { orderedList: (): Command => ({ commands }) => {
return commands.toggleList('orderedList', 'listItem') return commands.toggleList('orderedList', 'listItem')
}, },

View File

@@ -29,6 +29,9 @@ const Paragraph = createNode({
addCommands() { addCommands() {
return { return {
/**
* Toggle a paragraph
*/
paragraph: (): Command => ({ commands }) => { paragraph: (): Command => ({ commands }) => {
return commands.toggleBlockType('paragraph', 'paragraph') return commands.toggleBlockType('paragraph', 'paragraph')
}, },

View File

@@ -44,6 +44,9 @@ const Strike = createMark({
addCommands() { addCommands() {
return { return {
/**
* Toggle a strike mark
*/
strike: (): Command => ({ commands }) => { strike: (): Command => ({ commands }) => {
return commands.toggleMark('strike') return commands.toggleMark('strike')
}, },

View File

@@ -32,6 +32,9 @@ const TaskList = createNode({
addCommands() { addCommands() {
return { return {
/**
* Toggle a task list
*/
taskList: (): Command => ({ commands }) => { taskList: (): Command => ({ commands }) => {
return commands.toggleList('taskList', 'taskItem') return commands.toggleList('taskList', 'taskItem')
}, },

View File

@@ -34,6 +34,9 @@ const TextAlign = createExtension({
addCommands() { addCommands() {
return { return {
/**
* Update the text align attribute
*/
textAlign: (alignment: string): Command => ({ commands }) => { textAlign: (alignment: string): Command => ({ commands }) => {
if (!this.options.alignments.includes(alignment)) { if (!this.options.alignments.includes(alignment)) {
return false return false

View File

@@ -26,6 +26,9 @@ const TextStyle = createMark({
addCommands() { addCommands() {
return { return {
/**
* Remove spans without inline style attributes.
*/
removeEmptyTextStyle: (): Command => ({ state, commands }) => { removeEmptyTextStyle: (): Command => ({ state, commands }) => {
const attributes = getMarkAttrs(state, this.type) const attributes = getMarkAttrs(state, this.type)
const hasStyles = Object.entries(attributes).every(([, value]) => !!value) const hasStyles = Object.entries(attributes).every(([, value]) => !!value)