From 7adf1853d741d8c3ae8c492628f71caad39153c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Fri, 2 Apr 2021 00:07:40 +0200 Subject: [PATCH 01/18] add basic lowlight extension --- docs/src/demos/Nodes/CodeBlock/index.vue | 23 +++ .../demos/Nodes/CodeBlockLowlight/index.vue | 146 ++++++++++++++++++ .../docPages/api/nodes/code-block-lowlight.md | 41 +++++ docs/src/links.yaml | 2 + packages/core/src/Editor.ts | 3 + packages/core/src/Extension.ts | 8 + packages/core/src/ExtensionManager.ts | 7 + packages/core/src/Mark.ts | 9 ++ packages/core/src/Node.ts | 9 ++ packages/core/src/types.ts | 1 + .../extension-code-block-lowlight/README.md | 14 ++ .../package.json | 33 ++++ .../src/code-block-lowlight.ts | 35 +++++ .../src/index.ts | 5 + .../src/lowlight-plugin.ts | 110 +++++++++++++ yarn.lock | 30 ++++ 16 files changed, 476 insertions(+) create mode 100644 docs/src/demos/Nodes/CodeBlockLowlight/index.vue create mode 100644 docs/src/docPages/api/nodes/code-block-lowlight.md create mode 100644 packages/extension-code-block-lowlight/README.md create mode 100644 packages/extension-code-block-lowlight/package.json create mode 100644 packages/extension-code-block-lowlight/src/code-block-lowlight.ts create mode 100644 packages/extension-code-block-lowlight/src/index.ts create mode 100644 packages/extension-code-block-lowlight/src/lowlight-plugin.ts diff --git a/docs/src/demos/Nodes/CodeBlock/index.vue b/docs/src/demos/Nodes/CodeBlock/index.vue index 54b3c676..cea9f542 100644 --- a/docs/src/demos/Nodes/CodeBlock/index.vue +++ b/docs/src/demos/Nodes/CodeBlock/index.vue @@ -61,3 +61,26 @@ export default { }, } + + diff --git a/docs/src/demos/Nodes/CodeBlockLowlight/index.vue b/docs/src/demos/Nodes/CodeBlockLowlight/index.vue new file mode 100644 index 00000000..356e25b2 --- /dev/null +++ b/docs/src/demos/Nodes/CodeBlockLowlight/index.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/docs/src/docPages/api/nodes/code-block-lowlight.md b/docs/src/docPages/api/nodes/code-block-lowlight.md new file mode 100644 index 00000000..47a9a8b9 --- /dev/null +++ b/docs/src/docPages/api/nodes/code-block-lowlight.md @@ -0,0 +1,41 @@ +# CodeBlockLowlight +[![Version](https://img.shields.io/npm/v/@tiptap/extension-code-block-lowlight.svg?label=version)](https://www.npmjs.com/package/@tiptap/extension-code-block-lowlight) +[![Downloads](https://img.shields.io/npm/dm/@tiptap/extension-code-block-lowlight.svg)](https://npmcharts.com/compare/@tiptap/extension-code-block-lowlight?minimal=true) + +With the CodeBlock extension you can add fenced code blocks to your documents. It’ll wrap the code in `
` and `` HTML tags.
+
+Type ```  (three backticks and a space) or ∼∼∼  (three tildes and a space) and a code block is instantly added for you. You can even specify the language, try writing ```css . That should add a `language-css` class to the ``-tag.
+
+::: warning Restrictions
+The CodeBlock extension doesn’t come with styling and has no syntax highlighting built-in. It’s on our roadmap though.
+:::
+
+## Installation
+```bash
+# with npm
+npm install @tiptap/extension-code-block-lowlight
+
+# with Yarn
+yarn add @tiptap/extension-code-block-lowlight
+```
+
+## Settings
+| Option              | Type     | Default       | Description                                                           |
+| ------------------- | -------- | ------------- | --------------------------------------------------------------------- |
+| HTMLAttributes      | `Object` | `{}`          | Custom HTML attributes that should be added to the rendered HTML tag. |
+| languageClassPrefix | `String` | `'language-'` | Adds a prefix to language classes that are applied to code tags.      |
+
+## Commands
+| Command   | Parameters | Description                   |
+| --------- | ---------- | ----------------------------- |
+| codeBlock | —          | Wrap content in a code block. |
+
+## Keyboard shortcuts
+* Windows/Linux: `Control` `Alt` `C`
+* macOS: `Cmd` `Alt` `C`
+
+## Source code
+[packages/extension-code-block-lowlight/](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-code-block-lowlight/)
+
+## Usage
+
diff --git a/docs/src/links.yaml b/docs/src/links.yaml
index 7950055b..d61a485e 100644
--- a/docs/src/links.yaml
+++ b/docs/src/links.yaml
@@ -131,6 +131,8 @@
           link: /api/nodes/bullet-list
         - title: CodeBlock
           link: /api/nodes/code-block
+        - title: CodeBlockLowlight
+          link: /api/nodes/code-block-lowlight
         - title: Document
           link: /api/nodes/document
         - title: Emoji
diff --git a/packages/core/src/Editor.ts b/packages/core/src/Editor.ts
index f5452202..843c5f18 100644
--- a/packages/core/src/Editor.ts
+++ b/packages/core/src/Editor.ts
@@ -58,6 +58,7 @@ export class Editor extends EventEmitter {
     parseOptions: {},
     enableInputRules: true,
     enablePasteRules: true,
+    onBeforeCreate: () => null,
     onCreate: () => null,
     onUpdate: () => null,
     onSelectionUpdate: () => null,
@@ -75,6 +76,8 @@ export class Editor extends EventEmitter {
     this.createExtensionManager()
     this.createCommandManager()
     this.createSchema()
+    this.on('beforeCreate', this.options.onCreate)
+    this.emit('beforeCreate', { editor: this })
     this.createView()
     this.injectCSS()
     this.on('create', this.options.onCreate)
diff --git a/packages/core/src/Extension.ts b/packages/core/src/Extension.ts
index db0af867..6660338b 100644
--- a/packages/core/src/Extension.ts
+++ b/packages/core/src/Extension.ts
@@ -94,6 +94,14 @@ declare module '@tiptap/core' {
       [key: string]: any,
     }) | null,
 
+    /**
+     * The editor is not ready yet.
+     */
+     onBeforeCreate?: ((this: {
+      options: Options,
+      editor: Editor,
+    }) => void) | null,
+
     /**
      * The editor is ready.
      */
diff --git a/packages/core/src/ExtensionManager.ts b/packages/core/src/ExtensionManager.ts
index 7284d75c..8261260b 100644
--- a/packages/core/src/ExtensionManager.ts
+++ b/packages/core/src/ExtensionManager.ts
@@ -43,6 +43,13 @@ export default class ExtensionManager {
         }
       }
 
+      // console.log(extension.config.onBeforeCreate)
+
+      if (typeof extension.config.onBeforeCreate === 'function') {
+        console.log('JOOO')
+        this.editor.on('beforeCreate', extension.config.onBeforeCreate.bind(context))
+      }
+
       if (typeof extension.config.onCreate === 'function') {
         this.editor.on('create', extension.config.onCreate.bind(context))
       }
diff --git a/packages/core/src/Mark.ts b/packages/core/src/Mark.ts
index 7986b5df..b3ff2de2 100644
--- a/packages/core/src/Mark.ts
+++ b/packages/core/src/Mark.ts
@@ -104,6 +104,15 @@ declare module '@tiptap/core' {
       [key: string]: any,
     }) | null,
 
+    /**
+     * The editor is not ready yet.
+     */
+     onBeforeCreate?: ((this: {
+      options: Options,
+      editor: Editor,
+      type: MarkType,
+    }) => void) | null,
+
     /**
      * The editor is ready.
      */
diff --git a/packages/core/src/Node.ts b/packages/core/src/Node.ts
index 315b38cc..8fafdb37 100644
--- a/packages/core/src/Node.ts
+++ b/packages/core/src/Node.ts
@@ -109,6 +109,15 @@ declare module '@tiptap/core' {
       [key: string]: any,
     }) | null,
 
+    /**
+     * The editor is not ready yet.
+     */
+     onBeforeCreate?: ((this: {
+      options: Options,
+      editor: Editor,
+      type: NodeType,
+    }) => void) | null,
+
     /**
      * The editor is ready.
      */
diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts
index 453d52c6..75d20111 100644
--- a/packages/core/src/types.ts
+++ b/packages/core/src/types.ts
@@ -29,6 +29,7 @@ export interface EditorOptions {
   parseOptions: ParseOptions,
   enableInputRules: boolean,
   enablePasteRules: boolean,
+  onBeforeCreate: (props: { editor: Editor }) => void,
   onCreate: (props: { editor: Editor }) => void,
   onUpdate: (props: { editor: Editor }) => void,
   onViewUpdate: (props: { editor: Editor }) => void,
diff --git a/packages/extension-code-block-lowlight/README.md b/packages/extension-code-block-lowlight/README.md
new file mode 100644
index 00000000..d950097e
--- /dev/null
+++ b/packages/extension-code-block-lowlight/README.md
@@ -0,0 +1,14 @@
+# @tiptap/extension-code-block-lowlight
+[![Version](https://img.shields.io/npm/v/@tiptap/extension-code-block-lowlight.svg?label=version)](https://www.npmjs.com/package/@tiptap/extension-code-block-lowlight)
+[![Downloads](https://img.shields.io/npm/dm/@tiptap/extension-code-block-lowlight.svg)](https://npmcharts.com/compare/tiptap?minimal=true)
+[![License](https://img.shields.io/npm/l/@tiptap/extension-code-block-lowlight.svg)](https://www.npmjs.com/package/@tiptap/extension-code-block-lowlight)
+[![Sponsor](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub)](https://github.com/sponsors/ueberdosis)
+
+## Introduction
+tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as *New York Times*, *The Guardian* or *Atlassian*.
+
+## Offical Documentation
+Documentation can be found on the [tiptap website](https://tiptap.dev).
+
+## License
+tiptap is open-sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap-next/blob/main/LICENSE.md).
diff --git a/packages/extension-code-block-lowlight/package.json b/packages/extension-code-block-lowlight/package.json
new file mode 100644
index 00000000..f0ab3100
--- /dev/null
+++ b/packages/extension-code-block-lowlight/package.json
@@ -0,0 +1,33 @@
+{
+  "name": "@tiptap/extension-code-block-lowlight",
+  "description": "code block extension for tiptap",
+  "version": "2.0.0-beta.1",
+  "homepage": "https://tiptap.dev",
+  "keywords": [
+    "tiptap",
+    "tiptap extension"
+  ],
+  "license": "MIT",
+  "funding": {
+    "type": "github",
+    "url": "https://github.com/sponsors/ueberdosis"
+  },
+  "main": "dist/tiptap-extension-code-block-lowlight.cjs.js",
+  "umd": "dist/tiptap-extension-code-block-lowlight.umd.js",
+  "module": "dist/tiptap-extension-code-block-lowlight.esm.js",
+  "unpkg": "dist/tiptap-extension-code-block-lowlight.bundle.umd.min.js",
+  "types": "dist/packages/extension-code-block-lowlight/src/index.d.ts",
+  "files": [
+    "src",
+    "dist"
+  ],
+  "peerDependencies": {
+    "@tiptap/core": "^2.0.0-beta.1"
+  },
+  "dependencies": {
+    "@tiptap/extension-code-block-lowlight": "^2.0.0-beta.1",
+    "@types/lowlight": "^0.0.1",
+    "lowlight": "^1.20.0",
+    "prosemirror-inputrules": "^1.1.3"
+  }
+}
diff --git a/packages/extension-code-block-lowlight/src/code-block-lowlight.ts b/packages/extension-code-block-lowlight/src/code-block-lowlight.ts
new file mode 100644
index 00000000..9b8670cc
--- /dev/null
+++ b/packages/extension-code-block-lowlight/src/code-block-lowlight.ts
@@ -0,0 +1,35 @@
+import CodeBlock from '@tiptap/extension-code-block'
+import low from 'lowlight/lib/core'
+import { LowlightPlugin } from './lowlight-plugin'
+
+export interface CodeBlockLowlightOptions {
+  languageClassPrefix: string,
+  HTMLAttributes: {
+    [key: string]: any
+  },
+  languages: {
+    [key: string]: Function
+  },
+}
+
+export const CodeBlockLowlight = CodeBlock.extend({
+  name: 'codeBlockLowlight',
+
+  defaultOptions: {
+    languageClassPrefix: 'language-',
+    HTMLAttributes: {},
+    languages: {},
+  },
+
+  onBeforeCreate() {
+    Object.entries(this.options.languages).forEach(([name, mapping]) => {
+      low.registerLanguage(name, mapping)
+    })
+  },
+
+  addProseMirrorPlugins() {
+    return [
+      LowlightPlugin({ name: 'codeBlockLowlight' }),
+    ]
+  },
+})
diff --git a/packages/extension-code-block-lowlight/src/index.ts b/packages/extension-code-block-lowlight/src/index.ts
new file mode 100644
index 00000000..257f48ac
--- /dev/null
+++ b/packages/extension-code-block-lowlight/src/index.ts
@@ -0,0 +1,5 @@
+import { CodeBlockLowlight } from './code-block-lowlight'
+
+export * from './code-block-lowlight'
+
+export default CodeBlockLowlight
diff --git a/packages/extension-code-block-lowlight/src/lowlight-plugin.ts b/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
new file mode 100644
index 00000000..c8c47454
--- /dev/null
+++ b/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
@@ -0,0 +1,110 @@
+import { Plugin, PluginKey } from 'prosemirror-state'
+import { Decoration, DecorationSet } from 'prosemirror-view'
+import { Node as ProsemirrorNode } from 'prosemirror-model'
+import low from 'lowlight/lib/core'
+
+type NodeWithPos = {
+  node: ProsemirrorNode,
+  pos: number,
+}
+
+const findBlockNodes = (doc: ProsemirrorNode) => {
+  const nodes: NodeWithPos[] = []
+
+  doc.descendants((node, pos) => {
+    if (node.isBlock) {
+      nodes.push({
+        node,
+        pos,
+      })
+    }
+  })
+
+  return nodes
+}
+
+function getDecorations({ doc, name }: { doc: ProsemirrorNode, name: string}) {
+  const decorations: Decoration[] = []
+  const blocks = findBlockNodes(doc).filter(block => block.node.type.name === name)
+
+  function parseNodes(nodes: any[], className: string[] = []): any {
+    return nodes.map(node => {
+
+      const classes = [
+        ...className,
+        ...node.properties ? node.properties.className : [],
+      ]
+
+      if (node.children) {
+        return parseNodes(node.children, classes)
+      }
+
+      return {
+        text: node.value,
+        classes,
+      }
+    })
+  }
+
+  blocks.forEach(block => {
+    let startPos = block.pos + 1
+    const nodes = low.highlightAuto(block.node.textContent).value
+
+    parseNodes(nodes)
+      .flat()
+      .map((node: any) => {
+        const from = startPos
+        const to = from + node.text.length
+
+        startPos = to
+
+        return {
+          ...node,
+          from,
+          to,
+        }
+      })
+      .forEach((node: any) => {
+        const decoration = Decoration.inline(node.from, node.to, {
+          class: node.classes.join(' '),
+        })
+        decorations.push(decoration)
+      })
+  })
+
+  return DecorationSet.create(doc, decorations)
+}
+
+export function LowlightPlugin({ name }: { name: string }) {
+  return new Plugin({
+    key: new PluginKey('highlight'),
+
+    state: {
+      init: (_, { doc }) => getDecorations({ doc, name }),
+      apply: (transaction, decorationSet, oldState, newState) => {
+        // TODO: find way to cache decorations
+        // https://discuss.prosemirror.net/t/how-to-update-multiple-inline-decorations-on-node-change/1493
+        const oldNodeName = oldState.selection.$head.parent.type.name
+        const newNodeName = newState.selection.$head.parent.type.name
+        const oldNodes = findBlockNodes(oldState.doc)
+          .filter(node => node.node.type.name === name)
+        const newNodes = findBlockNodes(newState.doc)
+          .filter(node => node.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 decorationSet.map(transaction.mapping, transaction.doc)
+      },
+    },
+
+    props: {
+      decorations(state) {
+        return this.getState(state)
+      },
+    },
+  })
+}
diff --git a/yarn.lock b/yarn.lock
index 1f2291fe..99c747f2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2184,6 +2184,11 @@
   resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
   integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
 
+"@types/lowlight@^0.0.1":
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/@types/lowlight/-/lowlight-0.0.1.tgz#221bc67a6c517bae71e6f200fa1cad0feaeeb965"
+  integrity sha512-yPpbpV1KfpFOZ0ZZbsgwWumraiAKoX7/Ng75Ah//w+ZBt4j0xwrQ2aHSlk2kPzQVK4LiPbNFE1LjC00IL4nl/A==
+
 "@types/mdast@^3.0.3":
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.3.tgz#2d7d671b1cd1ea3deb306ea75036c2a0407d2deb"
@@ -6545,6 +6550,13 @@ fastq@^1.6.0:
   dependencies:
     reusify "^1.0.4"
 
+fault@^1.0.0:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.4.tgz#eafcfc0a6d214fc94601e170df29954a4f842f13"
+  integrity sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==
+  dependencies:
+    format "^0.2.0"
+
 faye-websocket@^0.11.3:
   version "0.11.3"
   resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e"
@@ -6780,6 +6792,11 @@ form-data@~2.3.2:
     combined-stream "^1.0.6"
     mime-types "^2.1.12"
 
+format@^0.2.0:
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
+  integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=
+
 forwarded@~0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
@@ -7674,6 +7691,11 @@ hex-color-regex@^1.1.0:
   resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
   integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
 
+highlight.js@~10.7.0:
+  version "10.7.1"
+  resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.1.tgz#a8ec4152db24ea630c90927d6cae2a45f8ecb955"
+  integrity sha512-S6G97tHGqJ/U8DsXcEdnACbirtbx58Bx9CzIVeYli8OuswCfYI/LsXH2EiGcoGio1KAC3x4mmUwulOllJ2ZyRA==
+
 hirestime@^3.2.1:
   version "3.2.2"
   resolved "https://registry.yarnpkg.com/hirestime/-/hirestime-3.2.2.tgz#1b5ff4c796b6b70586fa6efa4850952c6e1be484"
@@ -9418,6 +9440,14 @@ lowercase-keys@^2.0.0:
   resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
   integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
 
+lowlight@^1.20.0:
+  version "1.20.0"
+  resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.20.0.tgz#ddb197d33462ad0d93bf19d17b6c301aa3941888"
+  integrity sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==
+  dependencies:
+    fault "^1.0.0"
+    highlight.js "~10.7.0"
+
 lpad-align@^1.0.1:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/lpad-align/-/lpad-align-1.1.2.tgz#21f600ac1c3095c3c6e497ee67271ee08481fe9e"

From c8f384b9fc758d24edc274be84af30e9093b5d30 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= 
Date: Fri, 2 Apr 2021 22:20:20 +0200
Subject: [PATCH 02/18] remove log

---
 packages/core/src/ExtensionManager.ts | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/packages/core/src/ExtensionManager.ts b/packages/core/src/ExtensionManager.ts
index 8261260b..352f0cbd 100644
--- a/packages/core/src/ExtensionManager.ts
+++ b/packages/core/src/ExtensionManager.ts
@@ -43,10 +43,7 @@ export default class ExtensionManager {
         }
       }
 
-      // console.log(extension.config.onBeforeCreate)
-
       if (typeof extension.config.onBeforeCreate === 'function') {
-        console.log('JOOO')
         this.editor.on('beforeCreate', extension.config.onBeforeCreate.bind(context))
       }
 

From faa0ef8bfa3a3b78990b07027c9db0b525e52785 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= 
Date: Fri, 2 Apr 2021 22:21:17 +0200
Subject: [PATCH 03/18] check for language in highlighting

---
 .../extension-code-block-lowlight/src/lowlight-plugin.ts  | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/packages/extension-code-block-lowlight/src/lowlight-plugin.ts b/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
index c8c47454..ef4dfeb0 100644
--- a/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
+++ b/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
@@ -48,7 +48,13 @@ function getDecorations({ doc, name }: { doc: ProsemirrorNode, name: string}) {
 
   blocks.forEach(block => {
     let startPos = block.pos + 1
-    const nodes = low.highlightAuto(block.node.textContent).value
+    const { language } = block.node.attrs
+    // TODO: add missing type for `listLanguages`
+    // @ts-ignore
+    const languages = low.listLanguages() as string[]
+    const nodes = language && languages.includes(language)
+      ? low.highlight(language, block.node.textContent).value
+      : low.highlightAuto(block.node.textContent).value
 
     parseNodes(nodes)
       .flat()

From 1f2cbc559460659a8eac832711ac40a0269dcdd7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= 
Date: Fri, 2 Apr 2021 22:24:00 +0200
Subject: [PATCH 04/18] fix code style

---
 docs/src/demos/Examples/Book/index.vue                 | 1 +
 docs/src/demos/Examples/CollaborativeEditing/index.vue | 1 +
 docs/src/demos/Examples/Default/React/styles.scss      | 1 +
 docs/src/demos/Examples/Default/Vue/index.vue          | 1 +
 docs/src/demos/Examples/Formatting/index.vue           | 1 +
 docs/src/demos/Examples/MarkdownShortcuts/index.vue    | 1 +
 docs/src/demos/Examples/Tables/index.vue               | 1 +
 docs/src/demos/Experiments/GlobalDragHandle/index.vue  | 1 +
 docs/src/demos/Experiments/MultipleEditors/index.vue   | 1 +
 docs/src/demos/Guide/Content/ExportHTML/index.vue      | 1 +
 docs/src/demos/Guide/Content/ExportJSON/index.vue      | 1 +
 docs/src/demos/Nodes/CodeBlock/index.vue               | 1 +
 docs/src/demos/Nodes/CodeBlockLowlight/index.vue       | 1 +
 13 files changed, 13 insertions(+)

diff --git a/docs/src/demos/Examples/Book/index.vue b/docs/src/demos/Examples/Book/index.vue
index 5558e0c2..8d0848dc 100644
--- a/docs/src/demos/Examples/Book/index.vue
+++ b/docs/src/demos/Examples/Book/index.vue
@@ -138,6 +138,7 @@ export default {
 
     code {
       color: inherit;
+      padding: 0;
       background: none;
       font-size: 0.8rem;
     }
diff --git a/docs/src/demos/Examples/CollaborativeEditing/index.vue b/docs/src/demos/Examples/CollaborativeEditing/index.vue
index 1ec75188..e0198cfa 100644
--- a/docs/src/demos/Examples/CollaborativeEditing/index.vue
+++ b/docs/src/demos/Examples/CollaborativeEditing/index.vue
@@ -289,6 +289,7 @@ export default {
 
     code {
       color: inherit;
+      padding: 0;
       background: none;
       font-size: 0.8rem;
     }
diff --git a/docs/src/demos/Examples/Default/React/styles.scss b/docs/src/demos/Examples/Default/React/styles.scss
index dc996245..9ba5fc02 100644
--- a/docs/src/demos/Examples/Default/React/styles.scss
+++ b/docs/src/demos/Examples/Default/React/styles.scss
@@ -32,6 +32,7 @@
 
     code {
       color: inherit;
+      padding: 0;
       background: none;
       font-size: 0.8rem;
     }
diff --git a/docs/src/demos/Examples/Default/Vue/index.vue b/docs/src/demos/Examples/Default/Vue/index.vue
index 5800130b..89b7c5b5 100644
--- a/docs/src/demos/Examples/Default/Vue/index.vue
+++ b/docs/src/demos/Examples/Default/Vue/index.vue
@@ -163,6 +163,7 @@ export default {
 
     code {
       color: inherit;
+      padding: 0;
       background: none;
       font-size: 0.8rem;
     }
diff --git a/docs/src/demos/Examples/Formatting/index.vue b/docs/src/demos/Examples/Formatting/index.vue
index 81dd2fd5..978a1c62 100644
--- a/docs/src/demos/Examples/Formatting/index.vue
+++ b/docs/src/demos/Examples/Formatting/index.vue
@@ -138,6 +138,7 @@ export default {
 
     code {
       color: inherit;
+      padding: 0;
       background: none;
       font-size: 0.8rem;
     }
diff --git a/docs/src/demos/Examples/MarkdownShortcuts/index.vue b/docs/src/demos/Examples/MarkdownShortcuts/index.vue
index 3716dc7c..33d82e8f 100644
--- a/docs/src/demos/Examples/MarkdownShortcuts/index.vue
+++ b/docs/src/demos/Examples/MarkdownShortcuts/index.vue
@@ -89,6 +89,7 @@ export default {
 
     code {
       color: inherit;
+      padding: 0;
       background: none;
       font-size: 0.8rem;
     }
diff --git a/docs/src/demos/Examples/Tables/index.vue b/docs/src/demos/Examples/Tables/index.vue
index 90923f63..67531de8 100644
--- a/docs/src/demos/Examples/Tables/index.vue
+++ b/docs/src/demos/Examples/Tables/index.vue
@@ -176,6 +176,7 @@ export default {
 
     code {
       color: inherit;
+      padding: 0;
       background: none;
       font-size: 0.8rem;
     }
diff --git a/docs/src/demos/Experiments/GlobalDragHandle/index.vue b/docs/src/demos/Experiments/GlobalDragHandle/index.vue
index 90fec8b2..7dd493fe 100644
--- a/docs/src/demos/Experiments/GlobalDragHandle/index.vue
+++ b/docs/src/demos/Experiments/GlobalDragHandle/index.vue
@@ -104,6 +104,7 @@ export default {
 
       code {
         color: inherit;
+        padding: 0;
         background: none;
         font-size: 0.8rem;
       }
diff --git a/docs/src/demos/Experiments/MultipleEditors/index.vue b/docs/src/demos/Experiments/MultipleEditors/index.vue
index 1f7350b2..8cd1d82e 100644
--- a/docs/src/demos/Experiments/MultipleEditors/index.vue
+++ b/docs/src/demos/Experiments/MultipleEditors/index.vue
@@ -203,6 +203,7 @@ export default {
 
     code {
       color: inherit;
+      padding: 0;
       background: none;
       font-size: 0.8rem;
     }
diff --git a/docs/src/demos/Guide/Content/ExportHTML/index.vue b/docs/src/demos/Guide/Content/ExportHTML/index.vue
index c91eb78d..e4e3e8bb 100644
--- a/docs/src/demos/Guide/Content/ExportHTML/index.vue
+++ b/docs/src/demos/Guide/Content/ExportHTML/index.vue
@@ -145,6 +145,7 @@ export default {
 
     code {
       color: inherit;
+      padding: 0;
       background: none;
       font-size: 0.8rem;
     }
diff --git a/docs/src/demos/Guide/Content/ExportJSON/index.vue b/docs/src/demos/Guide/Content/ExportJSON/index.vue
index 9c9cf014..1af2d3c6 100644
--- a/docs/src/demos/Guide/Content/ExportJSON/index.vue
+++ b/docs/src/demos/Guide/Content/ExportJSON/index.vue
@@ -152,6 +152,7 @@ export default {
 
     code {
       color: inherit;
+      padding: 0;
       background: none;
       font-size: 0.8rem;
     }
diff --git a/docs/src/demos/Nodes/CodeBlock/index.vue b/docs/src/demos/Nodes/CodeBlock/index.vue
index cea9f542..9246e4f8 100644
--- a/docs/src/demos/Nodes/CodeBlock/index.vue
+++ b/docs/src/demos/Nodes/CodeBlock/index.vue
@@ -78,6 +78,7 @@ export default {
 
     code {
       color: inherit;
+      padding: 0;
       background: none;
       font-size: 0.8rem;
     }
diff --git a/docs/src/demos/Nodes/CodeBlockLowlight/index.vue b/docs/src/demos/Nodes/CodeBlockLowlight/index.vue
index 356e25b2..a211987a 100644
--- a/docs/src/demos/Nodes/CodeBlockLowlight/index.vue
+++ b/docs/src/demos/Nodes/CodeBlockLowlight/index.vue
@@ -86,6 +86,7 @@ export default {
 
     code {
       color: inherit;
+      padding: 0;
       background: none;
       font-size: 0.8rem;
     }

From 74bd678e8bfe25cc5dcc04086591855a03f0c788 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= 
Date: Fri, 2 Apr 2021 22:30:12 +0200
Subject: [PATCH 05/18] remove custom name for lowlight code block

---
 .../extension-code-block-lowlight/src/code-block-lowlight.ts  | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/packages/extension-code-block-lowlight/src/code-block-lowlight.ts b/packages/extension-code-block-lowlight/src/code-block-lowlight.ts
index 9b8670cc..ca402c15 100644
--- a/packages/extension-code-block-lowlight/src/code-block-lowlight.ts
+++ b/packages/extension-code-block-lowlight/src/code-block-lowlight.ts
@@ -13,8 +13,6 @@ export interface CodeBlockLowlightOptions {
 }
 
 export const CodeBlockLowlight = CodeBlock.extend({
-  name: 'codeBlockLowlight',
-
   defaultOptions: {
     languageClassPrefix: 'language-',
     HTMLAttributes: {},
@@ -29,7 +27,7 @@ export const CodeBlockLowlight = CodeBlock.extend({
 
   addProseMirrorPlugins() {
     return [
-      LowlightPlugin({ name: 'codeBlockLowlight' }),
+      LowlightPlugin({ name: 'codeBlock' }),
     ]
   },
 })

From c94264894b4b2b6ad4f2ce70b30816fc24f2b7c0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= 
Date: Fri, 2 Apr 2021 23:02:05 +0200
Subject: [PATCH 06/18] fix a big in highlighter

---
 .../src/lowlight-plugin.ts                    | 40 +++++++++----------
 1 file changed, 20 insertions(+), 20 deletions(-)

diff --git a/packages/extension-code-block-lowlight/src/lowlight-plugin.ts b/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
index ef4dfeb0..5df3b7ff 100644
--- a/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
+++ b/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
@@ -23,29 +23,29 @@ const findBlockNodes = (doc: ProsemirrorNode) => {
   return nodes
 }
 
+function parseNodes(nodes: any[], className: string[] = []): any {
+  return nodes.map(node => {
+
+    const classes = [
+      ...className,
+      ...node.properties ? node.properties.className : [],
+    ]
+
+    if (node.children) {
+      return parseNodes(node.children, classes)
+    }
+
+    return {
+      text: node.value,
+      classes,
+    }
+  })
+}
+
 function getDecorations({ doc, name }: { doc: ProsemirrorNode, name: string}) {
   const decorations: Decoration[] = []
   const blocks = findBlockNodes(doc).filter(block => block.node.type.name === name)
 
-  function parseNodes(nodes: any[], className: string[] = []): any {
-    return nodes.map(node => {
-
-      const classes = [
-        ...className,
-        ...node.properties ? node.properties.className : [],
-      ]
-
-      if (node.children) {
-        return parseNodes(node.children, classes)
-      }
-
-      return {
-        text: node.value,
-        classes,
-      }
-    })
-  }
-
   blocks.forEach(block => {
     let startPos = block.pos + 1
     const { language } = block.node.attrs
@@ -57,7 +57,7 @@ function getDecorations({ doc, name }: { doc: ProsemirrorNode, name: string}) {
       : low.highlightAuto(block.node.textContent).value
 
     parseNodes(nodes)
-      .flat()
+      .flat(Infinity)
       .map((node: any) => {
         const from = startPos
         const to = from + node.text.length

From dd505d2773637664b4f5fe07e19658265b55215d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= 
Date: Fri, 2 Apr 2021 23:40:52 +0200
Subject: [PATCH 07/18] refactoring

---
 .../src/lowlight-plugin.ts                    | 70 +++++++++----------
 1 file changed, 33 insertions(+), 37 deletions(-)

diff --git a/packages/extension-code-block-lowlight/src/lowlight-plugin.ts b/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
index 5df3b7ff..30744f1d 100644
--- a/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
+++ b/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
@@ -23,60 +23,56 @@ const findBlockNodes = (doc: ProsemirrorNode) => {
   return nodes
 }
 
-function parseNodes(nodes: any[], className: string[] = []): any {
-  return nodes.map(node => {
+function parseNodes(nodes: any[], className: string[] = []): { text: string, classes: string[] }[] {
+  return nodes
+    .map(node => {
+      const classes = [
+        ...className,
+        ...node.properties
+          ? node.properties.className
+          : [],
+      ]
 
-    const classes = [
-      ...className,
-      ...node.properties ? node.properties.className : [],
-    ]
+      if (node.children) {
+        return parseNodes(node.children, classes)
+      }
 
-    if (node.children) {
-      return parseNodes(node.children, classes)
-    }
-
-    return {
-      text: node.value,
-      classes,
-    }
-  })
+      return {
+        text: node.value,
+        classes,
+      }
+    })
+    .flat()
 }
 
 function getDecorations({ doc, name }: { doc: ProsemirrorNode, name: string}) {
   const decorations: Decoration[] = []
-  const blocks = findBlockNodes(doc).filter(block => block.node.type.name === name)
 
-  blocks.forEach(block => {
-    let startPos = block.pos + 1
-    const { language } = block.node.attrs
-    // TODO: add missing type for `listLanguages`
-    // @ts-ignore
-    const languages = low.listLanguages() as string[]
-    const nodes = language && languages.includes(language)
-      ? low.highlight(language, block.node.textContent).value
-      : low.highlightAuto(block.node.textContent).value
+  findBlockNodes(doc)
+    .filter(block => block.node.type.name === name)
+    .forEach(block => {
+      let startPos = block.pos + 1
+      const { language } = block.node.attrs
+      // TODO: add missing type for `listLanguages`
+      // @ts-ignore
+      const languages = low.listLanguages() as string[]
+      const nodes = language && languages.includes(language)
+        ? low.highlight(language, block.node.textContent).value
+        : low.highlightAuto(block.node.textContent).value
 
-    parseNodes(nodes)
-      .flat(Infinity)
-      .map((node: any) => {
+      parseNodes(nodes).forEach(node => {
         const from = startPos
         const to = from + node.text.length
 
         startPos = to
 
-        return {
-          ...node,
-          from,
-          to,
-        }
-      })
-      .forEach((node: any) => {
-        const decoration = Decoration.inline(node.from, node.to, {
+        const decoration = Decoration.inline(from, to, {
           class: node.classes.join(' '),
         })
+
         decorations.push(decoration)
       })
-  })
+    })
 
   return DecorationSet.create(doc, decorations)
 }

From 6757813fd015f11380249f91e1c2e67ca004e840 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= 
Date: Fri, 2 Apr 2021 23:53:04 +0200
Subject: [PATCH 08/18] refactoring

---
 packages/core/src/helpers/findChildren.ts     | 22 ++++++++++++++
 packages/core/src/index.ts                    |  4 ++-
 .../src/lowlight-plugin.ts                    | 30 +++----------------
 3 files changed, 29 insertions(+), 27 deletions(-)
 create mode 100644 packages/core/src/helpers/findChildren.ts

diff --git a/packages/core/src/helpers/findChildren.ts b/packages/core/src/helpers/findChildren.ts
new file mode 100644
index 00000000..c580ec49
--- /dev/null
+++ b/packages/core/src/helpers/findChildren.ts
@@ -0,0 +1,22 @@
+import { Node as ProseMirrorNode } from 'prosemirror-model'
+import { Predicate } from '../types'
+
+type NodeWithPos = {
+  node: ProseMirrorNode,
+  pos: number,
+}
+
+export default function findChildren(node: ProseMirrorNode, predicate: Predicate): NodeWithPos[] {
+  const nodesWithPos: NodeWithPos[] = []
+
+  node.descendants((child, pos) => {
+    if (predicate(child)) {
+      nodesWithPos.push({
+        node: child,
+        pos,
+      })
+    }
+  })
+
+  return nodesWithPos
+}
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 78e57616..9e650ac9 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -12,6 +12,9 @@ export { default as markPasteRule } from './pasteRules/markPasteRule'
 export { default as callOrReturn } from './utilities/callOrReturn'
 export { default as mergeAttributes } from './utilities/mergeAttributes'
 
+export { default as findChildren } from './helpers/findChildren'
+export { default as findParentNode } from './helpers/findParentNode'
+export { default as findParentNodeClosestToPos } from './helpers/findParentNodeClosestToPos'
 export { default as generateHTML } from './helpers/generateHTML'
 export { default as getSchema } from './helpers/getSchema'
 export { default as getHTMLFromFragment } from './helpers/getHTMLFromFragment'
@@ -22,7 +25,6 @@ export { default as isNodeActive } from './helpers/isNodeActive'
 export { default as isNodeEmpty } from './helpers/isNodeEmpty'
 export { default as isNodeSelection } from './helpers/isNodeSelection'
 export { default as isTextSelection } from './helpers/isTextSelection'
-export { default as findParentNodeClosestToPos } from './helpers/findParentNodeClosestToPos'
 
 export interface Commands {}
 
diff --git a/packages/extension-code-block-lowlight/src/lowlight-plugin.ts b/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
index 30744f1d..84eec5d3 100644
--- a/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
+++ b/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
@@ -2,26 +2,7 @@ import { Plugin, PluginKey } from 'prosemirror-state'
 import { Decoration, DecorationSet } from 'prosemirror-view'
 import { Node as ProsemirrorNode } from 'prosemirror-model'
 import low from 'lowlight/lib/core'
-
-type NodeWithPos = {
-  node: ProsemirrorNode,
-  pos: number,
-}
-
-const findBlockNodes = (doc: ProsemirrorNode) => {
-  const nodes: NodeWithPos[] = []
-
-  doc.descendants((node, pos) => {
-    if (node.isBlock) {
-      nodes.push({
-        node,
-        pos,
-      })
-    }
-  })
-
-  return nodes
-}
+import { findChildren } from '@tiptap/core'
 
 function parseNodes(nodes: any[], className: string[] = []): { text: string, classes: string[] }[] {
   return nodes
@@ -48,8 +29,7 @@ function parseNodes(nodes: any[], className: string[] = []): { text: string, cla
 function getDecorations({ doc, name }: { doc: ProsemirrorNode, name: string}) {
   const decorations: Decoration[] = []
 
-  findBlockNodes(doc)
-    .filter(block => block.node.type.name === name)
+  findChildren(doc, node => node.type.name === name)
     .forEach(block => {
       let startPos = block.pos + 1
       const { language } = block.node.attrs
@@ -88,10 +68,8 @@ export function LowlightPlugin({ name }: { name: string }) {
         // https://discuss.prosemirror.net/t/how-to-update-multiple-inline-decorations-on-node-change/1493
         const oldNodeName = oldState.selection.$head.parent.type.name
         const newNodeName = newState.selection.$head.parent.type.name
-        const oldNodes = findBlockNodes(oldState.doc)
-          .filter(node => node.node.type.name === name)
-        const newNodes = findBlockNodes(newState.doc)
-          .filter(node => node.node.type.name === name)
+        const oldNodes = findChildren(oldState.doc, node => node.type.name === name)
+        const newNodes = findChildren(newState.doc, node => node.type.name === name)
 
         // Apply decorations if selection includes named node, or transaction changes named node.
         if (transaction.docChanged && ([oldNodeName, newNodeName].includes(name)

From 25582c64ccc56b76f8d2d36af7372f6162660093 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= 
Date: Sat, 3 Apr 2021 12:34:27 +0200
Subject: [PATCH 09/18] add y.js support for codeblock extension

---
 .../src/lowlight-plugin.ts                    | 28 +++++++++++++++++--
 1 file changed, 25 insertions(+), 3 deletions(-)

diff --git a/packages/extension-code-block-lowlight/src/lowlight-plugin.ts b/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
index 84eec5d3..93507909 100644
--- a/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
+++ b/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
@@ -71,9 +71,31 @@ export function LowlightPlugin({ name }: { name: string }) {
         const oldNodes = findChildren(oldState.doc, node => node.type.name === name)
         const newNodes = findChildren(newState.doc, node => 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)) {
+        if (
+          transaction.docChanged
+          // Apply decorations if:
+          && (
+            // selection includes named node,
+            [oldNodeName, newNodeName].includes(name)
+            // OR transaction adds/removes named node,
+            || newNodes.length !== oldNodes.length
+            // OR transaction has changes that completely encapsulte a node
+            // (for example, a transaction that affects the entire document).
+            // Such transactions can happen during collab syncing via y-prosemirror, for example.
+            || transaction.steps.some(step => {
+              // @ts-ignore
+              return step.from !== undefined
+                // @ts-ignore
+                && step.to !== undefined
+                && oldNodes.some(node => {
+                  // @ts-ignore
+                  return node.pos >= step.from
+                    // @ts-ignore
+                    && node.pos + node.node.nodeSize <= step.to
+                })
+            })
+          )
+        ) {
           return getDecorations({ doc: transaction.doc, name })
         }
 

From 143e0204a3dfea8494e7301ed49a1cbdc615e39d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= 
Date: Sun, 4 Apr 2021 23:41:44 +0200
Subject: [PATCH 10/18] refactoring

---
 .../src/code-block-lowlight.ts                       | 11 +++--------
 .../src/lowlight-plugin.ts                           | 12 ++++--------
 2 files changed, 7 insertions(+), 16 deletions(-)

diff --git a/packages/extension-code-block-lowlight/src/code-block-lowlight.ts b/packages/extension-code-block-lowlight/src/code-block-lowlight.ts
index ca402c15..e4b6ae39 100644
--- a/packages/extension-code-block-lowlight/src/code-block-lowlight.ts
+++ b/packages/extension-code-block-lowlight/src/code-block-lowlight.ts
@@ -1,12 +1,8 @@
-import CodeBlock from '@tiptap/extension-code-block'
+import CodeBlock, { CodeBlockOptions } from '@tiptap/extension-code-block'
 import low from 'lowlight/lib/core'
 import { LowlightPlugin } from './lowlight-plugin'
 
-export interface CodeBlockLowlightOptions {
-  languageClassPrefix: string,
-  HTMLAttributes: {
-    [key: string]: any
-  },
+export interface CodeBlockLowlightOptions extends CodeBlockOptions {
   languages: {
     [key: string]: Function
   },
@@ -14,8 +10,7 @@ export interface CodeBlockLowlightOptions {
 
 export const CodeBlockLowlight = CodeBlock.extend({
   defaultOptions: {
-    languageClassPrefix: 'language-',
-    HTMLAttributes: {},
+    ...CodeBlock.config.defaultOptions,
     languages: {},
   },
 
diff --git a/packages/extension-code-block-lowlight/src/lowlight-plugin.ts b/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
index 93507909..b3022fdd 100644
--- a/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
+++ b/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
@@ -31,7 +31,7 @@ function getDecorations({ doc, name }: { doc: ProsemirrorNode, name: string}) {
 
   findChildren(doc, node => node.type.name === name)
     .forEach(block => {
-      let startPos = block.pos + 1
+      let from = block.pos + 1
       const { language } = block.node.attrs
       // TODO: add missing type for `listLanguages`
       // @ts-ignore
@@ -41,16 +41,14 @@ function getDecorations({ doc, name }: { doc: ProsemirrorNode, name: string}) {
         : low.highlightAuto(block.node.textContent).value
 
       parseNodes(nodes).forEach(node => {
-        const from = startPos
         const to = from + node.text.length
-
-        startPos = to
-
         const decoration = Decoration.inline(from, to, {
           class: node.classes.join(' '),
         })
 
         decorations.push(decoration)
+
+        from = to
       })
     })
 
@@ -59,13 +57,11 @@ function getDecorations({ doc, name }: { doc: ProsemirrorNode, name: string}) {
 
 export function LowlightPlugin({ name }: { name: string }) {
   return new Plugin({
-    key: new PluginKey('highlight'),
+    key: new PluginKey('lowlight'),
 
     state: {
       init: (_, { doc }) => getDecorations({ doc, name }),
       apply: (transaction, decorationSet, oldState, newState) => {
-        // TODO: find way to cache decorations
-        // https://discuss.prosemirror.net/t/how-to-update-multiple-inline-decorations-on-node-change/1493
         const oldNodeName = oldState.selection.$head.parent.type.name
         const newNodeName = newState.selection.$head.parent.type.name
         const oldNodes = findChildren(oldState.doc, node => node.type.name === name)

From 65ef42a0b129275de04c9186782cc35aa85bd06c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= 
Date: Sun, 4 Apr 2021 23:44:57 +0200
Subject: [PATCH 11/18] refactoring

---
 .../src/code-block-lowlight.ts                       | 12 +++++++-----
 .../src/lowlight-plugin.ts                           |  8 ++++----
 2 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/packages/extension-code-block-lowlight/src/code-block-lowlight.ts b/packages/extension-code-block-lowlight/src/code-block-lowlight.ts
index e4b6ae39..5c6490bc 100644
--- a/packages/extension-code-block-lowlight/src/code-block-lowlight.ts
+++ b/packages/extension-code-block-lowlight/src/code-block-lowlight.ts
@@ -1,10 +1,10 @@
 import CodeBlock, { CodeBlockOptions } from '@tiptap/extension-code-block'
-import low from 'lowlight/lib/core'
+import lowlight from 'lowlight/lib/core'
 import { LowlightPlugin } from './lowlight-plugin'
 
 export interface CodeBlockLowlightOptions extends CodeBlockOptions {
   languages: {
-    [key: string]: Function
+    [key: string]: Function,
   },
 }
 
@@ -15,9 +15,11 @@ export const CodeBlockLowlight = CodeBlock.extend({
   },
 
   onBeforeCreate() {
-    Object.entries(this.options.languages).forEach(([name, mapping]) => {
-      low.registerLanguage(name, mapping)
-    })
+    Object
+      .entries(this.options.languages)
+      .forEach(([name, mapping]) => {
+        lowlight.registerLanguage(name, mapping)
+      })
   },
 
   addProseMirrorPlugins() {
diff --git a/packages/extension-code-block-lowlight/src/lowlight-plugin.ts b/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
index b3022fdd..4edb51e0 100644
--- a/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
+++ b/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
@@ -1,8 +1,8 @@
 import { Plugin, PluginKey } from 'prosemirror-state'
 import { Decoration, DecorationSet } from 'prosemirror-view'
 import { Node as ProsemirrorNode } from 'prosemirror-model'
-import low from 'lowlight/lib/core'
 import { findChildren } from '@tiptap/core'
+import lowlight from 'lowlight/lib/core'
 
 function parseNodes(nodes: any[], className: string[] = []): { text: string, classes: string[] }[] {
   return nodes
@@ -35,10 +35,10 @@ function getDecorations({ doc, name }: { doc: ProsemirrorNode, name: string}) {
       const { language } = block.node.attrs
       // TODO: add missing type for `listLanguages`
       // @ts-ignore
-      const languages = low.listLanguages() as string[]
+      const languages = lowlight.listLanguages() as string[]
       const nodes = language && languages.includes(language)
-        ? low.highlight(language, block.node.textContent).value
-        : low.highlightAuto(block.node.textContent).value
+        ? lowlight.highlight(language, block.node.textContent).value
+        : lowlight.highlightAuto(block.node.textContent).value
 
       parseNodes(nodes).forEach(node => {
         const to = from + node.text.length

From 12069519d669321b4f89ce1503b53cf62085b836 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= 
Date: Mon, 5 Apr 2021 00:07:58 +0200
Subject: [PATCH 12/18] add code block langauge switcher example

---
 .../Examples/CodeBlockLanguage/CodeBlock.vue  |  55 +++++++
 .../Examples/CodeBlockLanguage/index.vue      | 155 ++++++++++++++++++
 .../docPages/examples/code-block-language.md  |   3 +
 docs/src/links.yaml                           |   3 +
 4 files changed, 216 insertions(+)
 create mode 100644 docs/src/demos/Examples/CodeBlockLanguage/CodeBlock.vue
 create mode 100644 docs/src/demos/Examples/CodeBlockLanguage/index.vue
 create mode 100644 docs/src/docPages/examples/code-block-language.md

diff --git a/docs/src/demos/Examples/CodeBlockLanguage/CodeBlock.vue b/docs/src/demos/Examples/CodeBlockLanguage/CodeBlock.vue
new file mode 100644
index 00000000..1d0916f5
--- /dev/null
+++ b/docs/src/demos/Examples/CodeBlockLanguage/CodeBlock.vue
@@ -0,0 +1,55 @@
+
+
+
+
+
diff --git a/docs/src/demos/Examples/CodeBlockLanguage/index.vue b/docs/src/demos/Examples/CodeBlockLanguage/index.vue
new file mode 100644
index 00000000..a5a5bd03
--- /dev/null
+++ b/docs/src/demos/Examples/CodeBlockLanguage/index.vue
@@ -0,0 +1,155 @@
+
+
+
+
+
diff --git a/docs/src/docPages/examples/code-block-language.md b/docs/src/docPages/examples/code-block-language.md
new file mode 100644
index 00000000..bdac1b35
--- /dev/null
+++ b/docs/src/docPages/examples/code-block-language.md
@@ -0,0 +1,3 @@
+# Code block language
+
+
diff --git a/docs/src/links.yaml b/docs/src/links.yaml
index 1993ebf7..0dd877e0 100644
--- a/docs/src/links.yaml
+++ b/docs/src/links.yaml
@@ -76,6 +76,9 @@
       link: /examples/savvy
     - title: Interactivity
       link: /examples/interactivity
+    - title: Code block language
+      link: /examples/code-block-language
+      type: new
 
 - title: Guide
   items:

From 6720666fc3619d13e992093e617ff0b556e46fb5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= 
Date: Mon, 5 Apr 2021 00:23:12 +0200
Subject: [PATCH 13/18] fix language switcher example

---
 docs/src/demos/Examples/CodeBlockLanguage/CodeBlock.vue | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/docs/src/demos/Examples/CodeBlockLanguage/CodeBlock.vue b/docs/src/demos/Examples/CodeBlockLanguage/CodeBlock.vue
index 1d0916f5..70425392 100644
--- a/docs/src/demos/Examples/CodeBlockLanguage/CodeBlock.vue
+++ b/docs/src/demos/Examples/CodeBlockLanguage/CodeBlock.vue
@@ -1,6 +1,6 @@
 
 

From bd392ee8c20945c4bb58f88042a6cde7587d2234 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= 
Date: Thu, 8 Apr 2021 22:13:50 +0200
Subject: [PATCH 14/18] remove languages option from codeblocklowlight

---
 .../{CodeBlock.vue => CodeBlockComponent.vue} | 40 ++++++++++---------
 .../Examples/CodeBlockLanguage/index.vue      | 24 ++++-------
 .../demos/Nodes/CodeBlockLowlight/index.vue   | 11 ++---
 .../src/code-block-lowlight.ts                | 24 +----------
 4 files changed, 35 insertions(+), 64 deletions(-)
 rename docs/src/demos/Examples/CodeBlockLanguage/{CodeBlock.vue => CodeBlockComponent.vue} (71%)

diff --git a/docs/src/demos/Examples/CodeBlockLanguage/CodeBlock.vue b/docs/src/demos/Examples/CodeBlockLanguage/CodeBlockComponent.vue
similarity index 71%
rename from docs/src/demos/Examples/CodeBlockLanguage/CodeBlock.vue
rename to docs/src/demos/Examples/CodeBlockLanguage/CodeBlockComponent.vue
index 70425392..f5095930 100644
--- a/docs/src/demos/Examples/CodeBlockLanguage/CodeBlock.vue
+++ b/docs/src/demos/Examples/CodeBlockLanguage/CodeBlockComponent.vue
@@ -1,12 +1,15 @@