diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..3662b370 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib" +} \ No newline at end of file diff --git a/docs/src/demos/Examples/Focus/index.vue b/docs/src/demos/Examples/Focus/index.vue index c7c08a5f..58610fbd 100644 --- a/docs/src/demos/Examples/Focus/index.vue +++ b/docs/src/demos/Examples/Focus/index.vue @@ -9,12 +9,7 @@ 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 History from '@tiptap/extension-history' -import Bold from '@tiptap/extension-bold' -import Italic from '@tiptap/extension-italic' import Code from '@tiptap/extension-code' -import CodeBlock from '@tiptap/extension-codeblock' -import Heading from '@tiptap/extension-heading' import Focus from '@tiptap/extension-focus' export default { @@ -31,16 +26,11 @@ export default { mounted() { this.editor = new Editor({ extensions: [ - new Document(), - new History(), - new Paragraph(), - new Text(), - new Bold(), - new Italic(), - new Code(), - new CodeBlock(), - new Heading(), - new Focus({ + Document(), + Paragraph(), + Text(), + Code(), + Focus({ className: 'has-focus', nested: true, }), @@ -48,16 +38,12 @@ export default { autoFocus: true, content: `
- With the focus extension you can add custom classes to focused nodes. Default options:
+ The focus extension adds custom classes to focused nodes. By default, it’ll add a has-focus class, even to nested nodes:
{\n className: 'has-focus',\n nested: true,\n}
+ { className: 'has-focus', nested: true }
nested to true also nested elements like this list item will be captured.
- nested: true nested elements like this list item will be focused.This is a radically reduced version of tiptap for minimalisits. It has only support for a document, paragraphs and text, that’s it.
', extensions: [ - new Document(), - new Paragraph(), - new Text(), + Document(), + Paragraph(), + Text(), ], }) diff --git a/docs/src/demos/ExtensionConfiguration/index.vue b/docs/src/demos/ExtensionConfiguration/index.vue index 3a7b5cbd..e060ebfe 100644 --- a/docs/src/demos/ExtensionConfiguration/index.vue +++ b/docs/src/demos/ExtensionConfiguration/index.vue @@ -26,10 +26,10 @@ export default { this.editor = new Editor({ content: 'I’m running tiptap with Vue.js. This demo is interactive, try to edit the text.
', extensions: [ - new Document(), - new Paragraph(), - new Text(), - new Bold(), + Document(), + Paragraph(), + Text(), + Bold(), ], }) }, diff --git a/docs/src/demos/Extensions/Bold/index.vue b/docs/src/demos/Extensions/Bold/index.vue index 7416151b..fd96bf11 100644 --- a/docs/src/demos/Extensions/Bold/index.vue +++ b/docs/src/demos/Extensions/Bold/index.vue @@ -30,10 +30,10 @@ export default { mounted() { this.editor = new Editor({ extensions: [ - new Document(), - new Paragraph(), - new Text(), - new Bold(), + Document(), + Paragraph(), + Text(), + Bold(), ], content: `This isn’t bold.
diff --git a/docs/src/demos/Extensions/Code/index.vue b/docs/src/demos/Extensions/Code/index.vue index 4f51c0cd..4a89e425 100644 --- a/docs/src/demos/Extensions/Code/index.vue +++ b/docs/src/demos/Extensions/Code/index.vue @@ -30,10 +30,10 @@ export default { mounted() { this.editor = new Editor({ extensions: [ - new Document(), - new Paragraph(), - new Text(), - new Code(), + Document(), + Paragraph(), + Text(), + Code(), ], content: `This isn’t code.
diff --git a/docs/src/demos/Extensions/Document/index.spec.js b/docs/src/demos/Extensions/Document/index.spec.js new file mode 100644 index 00000000..91a6553e --- /dev/null +++ b/docs/src/demos/Extensions/Document/index.spec.js @@ -0,0 +1,5 @@ +context('/api/extensions/document', () => { + beforeEach(() => { + cy.visit('/api/extensions/document') + }) +}) \ No newline at end of file diff --git a/docs/src/demos/Extensions/Document/index.vue b/docs/src/demos/Extensions/Document/index.vue new file mode 100644 index 00000000..567e8b58 --- /dev/null +++ b/docs/src/demos/Extensions/Document/index.vue @@ -0,0 +1,44 @@ + +Edit this text and press undo to test this extension.
diff --git a/docs/src/demos/Extensions/Italic/index.vue b/docs/src/demos/Extensions/Italic/index.vue index 65e46a82..d86c3c9a 100644 --- a/docs/src/demos/Extensions/Italic/index.vue +++ b/docs/src/demos/Extensions/Italic/index.vue @@ -30,10 +30,10 @@ export default { mounted() { this.editor = new Editor({ extensions: [ - new Document(), - new Paragraph(), - new Text(), - new Italic(), + Document(), + Paragraph(), + Text(), + Italic(), ], content: `This isn’t italic.
diff --git a/docs/src/demos/Extensions/Paragraph/index.spec.js b/docs/src/demos/Extensions/Paragraph/index.spec.js new file mode 100644 index 00000000..682369eb --- /dev/null +++ b/docs/src/demos/Extensions/Paragraph/index.spec.js @@ -0,0 +1,5 @@ +context('/api/extensions/paragraph', () => { + beforeEach(() => { + cy.visit('/api/extensions/paragraph') + }) +}) \ No newline at end of file diff --git a/docs/src/demos/Extensions/Paragraph/index.vue b/docs/src/demos/Extensions/Paragraph/index.vue new file mode 100644 index 00000000..18796117 --- /dev/null +++ b/docs/src/demos/Extensions/Paragraph/index.vue @@ -0,0 +1,44 @@ + +This editor is based on Prosemirror, fully extendable and renderless. You can easily add custom nodes as Vue components.
', extensions: [ - new Document(), - new Paragraph(), - new Text(), - new CodeBlock(), - new History(), - new Bold(), - new Italic(), - new Code(), - new Heading(), + Document(), + Paragraph(), + Text(), + CodeBlock(), + History(), + Bold(), + Italic(), + Code(), + Heading(), ], }) }, diff --git a/docs/src/docPages/api/events.md b/docs/src/docPages/api/events.md index cbf94584..1d647f5a 100644 --- a/docs/src/docPages/api/events.md +++ b/docs/src/docPages/api/events.md @@ -1 +1,34 @@ -# Events \ No newline at end of file +# Events + +:::warning Out of date +This content is written for tiptap 1 and needs an update. +::: + +There are some events you can listen for. A full list of events can be found [here](/api/classes.md#editor-options). + +```js +const editor = new Editor({ + onInit: () => { + // editor is initialized + }, + onUpdate: ({ getHTML }) => { + // get new content on update + const newContent = getHTML() + }, +}) +``` + +It's also possible to register event listeners afterwards. + +```js +const editor = new Editor(…) + +editor.on('init', () => { + // editor is initialized +}) + +editor.on('update', ({ getHTML }) => { + // get new content on update + const newContent = getHTML() +}) +``` diff --git a/docs/src/docPages/api/extensions/blockquote.md b/docs/src/docPages/api/extensions/blockquote.md index 0e899d1f..180c5b68 100644 --- a/docs/src/docPages/api/extensions/blockquote.md +++ b/docs/src/docPages/api/extensions/blockquote.md @@ -2,7 +2,9 @@ The Blockquote extension enables you to use the `` HTML tag in the editor. ## Options -*None* +| Option | Type | Default | Description | +| ------ | ---- | ------- | ----------- | +| class | string | – | Add a custom class to the rendered HTML tag. | ## Commands | Command | Options | Description | @@ -39,7 +41,7 @@ export default { return { editor: new Editor({ extensions: [ - new Blockquote(), + Blockquote(), ], content: `diff --git a/docs/src/docPages/api/extensions/bold.md b/docs/src/docPages/api/extensions/bold.md index fb29a364..f45b469b 100644 --- a/docs/src/docPages/api/extensions/bold.md +++ b/docs/src/docPages/api/extensions/bold.md @@ -6,7 +6,9 @@ The extension will generate the corresponding `` HTML tags when reading ::: ## Options -*None* +| Option | Type | Default | Description | +| ------ | ---- | ------- | ----------- | +| class | string | – | Add a custom class to the rendered HTML tag. | ## Commands | Command | Options | Description | diff --git a/docs/src/docPages/api/extensions/bullet-list.md b/docs/src/docPages/api/extensions/bullet-list.md index d547079b..b85fe1c3 100644 --- a/docs/src/docPages/api/extensions/bullet-list.md +++ b/docs/src/docPages/api/extensions/bullet-list.md @@ -6,7 +6,9 @@ It’s intended to be used with the `ListItem` extension. ::: ## Options -*None* +| Option | Type | Default | Description | +| ------ | ---- | ------- | ----------- | +| class | string | – | Add a custom class to the rendered HTML tag. | ## Commands | Command | Options | Description | @@ -43,7 +45,7 @@ export default { return { editor: new Editor({ extensions: [ - new BulletList(), + BulletList(), ], content: `diff --git a/docs/src/docPages/api/extensions/code.md b/docs/src/docPages/api/extensions/code.md index f337ce74..5e1056c0 100644 --- a/docs/src/docPages/api/extensions/code.md +++ b/docs/src/docPages/api/extensions/code.md @@ -2,7 +2,9 @@ The Code extensions enables you to use the `
` HTML tag in the editor. If you paste in text with `` tags it will rendered accordingly. ## Options -*None* +| Option | Type | Default | Description | +| ------ | ---- | ------- | ----------- | +| class | string | – | Add a custom class to the rendered HTML tag. | ## Commands | Command | Options | Description | diff --git a/docs/src/docPages/api/extensions/document.md b/docs/src/docPages/api/extensions/document.md index eb9d8f65..017eec5b 100644 --- a/docs/src/docPages/api/extensions/document.md +++ b/docs/src/docPages/api/extensions/document.md @@ -1 +1,14 @@ -# Document \ No newline at end of file +# Document +**The `Document` extension is required**, no matter what you build with tiptap. It’s a so called “topNode”, a node that’s the home to all other nodes. Think of it like the `` tag for your document. + +The node is very tiny though. It defines a name of the node (`document`), is configured to be a top node (`topNode: true`) and that it can contain multiple other nodes (`block`). That’s all. But have a look yourself: + +:::warning Breaking Change from 1.x → 2.x +Tiptap 1 tried to hide that node from you, but it has always been there. A tiny, but important change though: **We renamed the default type from `doc` to `document`.** To keep it like that, use your own implementation of the `Document` node or migrate the stored JSON to use the new name. +::: + +## Source Code +[packages/extension-document/](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-document/) + +## Usage +\ No newline at end of file diff --git a/docs/src/docPages/api/extensions/heading.md b/docs/src/docPages/api/extensions/heading.md index 54485c51..3b58c236 100644 --- a/docs/src/docPages/api/extensions/heading.md +++ b/docs/src/docPages/api/extensions/heading.md @@ -3,7 +3,8 @@ Enables you to use headline HTML tags in the editor. ## Options | Option | Type | Default | Description | -| ------ | ---- | ---- | ----- | +| ------ | ---- | ------- | ----------- | +| class | string | – | Add a custom class to the rendered HTML tag. | | levels | Array | [1, 2, 3, 4, 5, 6] | Specifies which headlines are supported. | ## Commands @@ -51,7 +52,7 @@ export default { return { editor: new Editor({ extensions: [ - new Heading({ + Heading({ levels: [1, 2], }), ], diff --git a/docs/src/docPages/api/extensions/horizontal-rule.md b/docs/src/docPages/api/extensions/horizontal-rule.md index d70138b4..a1f6ee30 100644 --- a/docs/src/docPages/api/extensions/horizontal-rule.md +++ b/docs/src/docPages/api/extensions/horizontal-rule.md @@ -2,7 +2,9 @@ Enables you to use the `
` HTML tag in the editor. ## Options -*None* +| Option | Type | Default | Description | +| ------ | ---- | ------- | ----------- | +| class | string | – | Add a custom class to the rendered HTML tag. | ## Commands | Command | Options | Description | @@ -39,7 +41,7 @@ export default { return { editor: new Editor({ extensions: [ - new HorizontalRule(), + HorizontalRule(), ], content: `Some text.
diff --git a/docs/src/docPages/api/extensions/italic.md b/docs/src/docPages/api/extensions/italic.md index 7c6dbe93..c9387a39 100644 --- a/docs/src/docPages/api/extensions/italic.md +++ b/docs/src/docPages/api/extensions/italic.md @@ -6,7 +6,9 @@ The extension will generate the corresponding `` HTML tags when reading cont ::: ## Options -*None* +| Option | Type | Default | Description | +| ------ | ---- | ------- | ----------- | +| class | string | – | Add a custom class to the rendered HTML tag. | ## Commands | Command | Options | Description | diff --git a/docs/src/docPages/api/extensions/link.md b/docs/src/docPages/api/extensions/link.md index 13f0be23..8d1e9c4a 100644 --- a/docs/src/docPages/api/extensions/link.md +++ b/docs/src/docPages/api/extensions/link.md @@ -3,5 +3,6 @@ Enables you to use the `` HTML tag in the editor. ## Options | Option | Type | Default | Description | -| ------ | ---- | ---- | ----- | -| openOnClick | Boolean | true | Specifies if links will be opened on click. | \ No newline at end of file +| ------ | ---- | ------- | ----------- | +| class | string | – | Add a custom class to the rendered HTML tag. | +| openOnClick | Boolean | true | Specifies if links will be opened on click. | diff --git a/docs/src/docPages/api/extensions/list-item.md b/docs/src/docPages/api/extensions/list-item.md index 8d3dc689..7e25bca6 100644 --- a/docs/src/docPages/api/extensions/list-item.md +++ b/docs/src/docPages/api/extensions/list-item.md @@ -3,4 +3,9 @@ Enables you to use the `- ` HTML tag in the editor. ::: warning Restrictions This extensions is intended to be used with the `BulletList` or `OrderedList` extension. -::: \ No newline at end of file +::: + +## Options +| Option | Type | Default | Description | +| ------ | ---- | ------- | ----------- | +| class | string | – | Add a custom class to the rendered HTML tag. | \ No newline at end of file diff --git a/docs/src/docPages/api/extensions/ordered-list.md b/docs/src/docPages/api/extensions/ordered-list.md index 390c2c40..19a31083 100644 --- a/docs/src/docPages/api/extensions/ordered-list.md +++ b/docs/src/docPages/api/extensions/ordered-list.md @@ -6,7 +6,9 @@ This extensions is intended to be used with the `ListItem` extension. ::: ## Options -*None* +| Option | Type | Default | Description | +| ------ | ---- | ------- | ----------- | +| class | string | – | Add a custom class to the rendered HTML tag. | ## Commands | Command | Options | Description | @@ -43,7 +45,7 @@ export default { return { editor: new Editor({ extensions: [ - new OrderedList(), + OrderedList(), ], content: `
diff --git a/docs/src/docPages/api/extensions/paragraph.md b/docs/src/docPages/api/extensions/paragraph.md index 73036543..d35713a8 100644 --- a/docs/src/docPages/api/extensions/paragraph.md +++ b/docs/src/docPages/api/extensions/paragraph.md @@ -1,2 +1,19 @@ # Paragraph -Enables you to use paragraphs in the editor. \ No newline at end of file +Yes, the schema is very strict. Without this extension you won’t even be able to use paragraphs in the editor. + +## Options +| Option | Type | Default | Description | +| ------ | ---- | ------- | ----------- | +| class | string | – | Add a custom class to the rendered HTML tag. | + +## Commands +*None* + +## Keybindings +*None* + +## Source Code +[packages/extension-paragraph/](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-paragraph/) + +## Usage +
diff --git a/docs/src/docPages/api/extensions/strike.md b/docs/src/docPages/api/extensions/strike.md index 463de394..d6548c86 100644 --- a/docs/src/docPages/api/extensions/strike.md +++ b/docs/src/docPages/api/extensions/strike.md @@ -2,7 +2,9 @@ Enables you to use the ` ` HTML tag in the editor. ## Options -*None* +| Option | Type | Default | Description | +| ------ | ---- | ------- | ----------- | +| class | string | – | Add a custom class to the rendered HTML tag. | ## Commands | Command | Options | Description | @@ -40,7 +42,7 @@ export default { return { editor: new Editor({ extensions: [ - new Strike(), + Strike(), ], content: `diff --git a/docs/src/docPages/api/extensions/todo-item.md b/docs/src/docPages/api/extensions/todo-item.md index 5896231b..aa6756ef 100644 --- a/docs/src/docPages/api/extensions/todo-item.md +++ b/docs/src/docPages/api/extensions/todo-item.md @@ -7,7 +7,7 @@ This extensions is intended to be used with the `TodoList` extension. ## Options | Option | Type | Default | Description | -| ------ | ---- | ---- | ----- | +| ------ | ---- | ------- | ----------- | | nested | Boolean | false | Specifies if you can nest todo lists. | ## Commands diff --git a/docs/src/docPages/api/extensions/todo-list.md b/docs/src/docPages/api/extensions/todo-list.md index 89f0f89a..2fefea44 100644 --- a/docs/src/docPages/api/extensions/todo-list.md +++ b/docs/src/docPages/api/extensions/todo-list.md @@ -43,10 +43,10 @@ export default { return { editor: new Editor({ extensions: [ - new TodoItem({ + TodoItem({ nested: true, }), - new TodoList(), + TodoList(), ], content: `
That's strikethrough.diff --git a/docs/src/docPages/api/extensions/underline.md b/docs/src/docPages/api/extensions/underline.md index 8e639900..00d4272c 100644 --- a/docs/src/docPages/api/extensions/underline.md +++ b/docs/src/docPages/api/extensions/underline.md @@ -2,7 +2,9 @@ Enables you to use the `` HTML tag in the editor. ## Options -*None* +| Option | Type | Default | Description | +| ------ | ---- | ------- | ----------- | +| class | string | – | Add a custom class to the rendered HTML tag. | ## Commands | Command | Options | Description | @@ -40,7 +42,7 @@ export default { return { editor: new Editor({ extensions: [ - new Underline(), + Underline(), ], content: `
This is underlined.
diff --git a/docs/src/docPages/api/schema/get-schema.md b/docs/src/docPages/api/schema/get-schema.md index 1ab24544..9c547660 100644 --- a/docs/src/docPages/api/schema/get-schema.md +++ b/docs/src/docPages/api/schema/get-schema.md @@ -13,9 +13,9 @@ import Text from '@tiptap/extension-text' const editor = new Editor({ extensions: [ - new Document, - new Paragraph, - new Text, + Document(), + Paragraph(), + Text(), // add more extensions here ]) }) @@ -33,9 +33,9 @@ import Paragraph from '@tiptap/extension-paragraph' import Text from '@tiptap/extension-text' const schema = getSchema([ - new Document, - new Paragraph, - new Text, + Document(), + Paragraph(), + Text(), // add more extensions here ]) ``` diff --git a/docs/src/docPages/examples/focus.md b/docs/src/docPages/examples/focus.md index f9b145b9..04e59d5b 100644 --- a/docs/src/docPages/examples/focus.md +++ b/docs/src/docPages/examples/focus.md @@ -1,3 +1,3 @@ # Focus -\ No newline at end of file + \ No newline at end of file diff --git a/docs/src/docPages/general/roadmap.md b/docs/src/docPages/general/roadmap.md deleted file mode 100644 index 3b0fa3ba..00000000 --- a/docs/src/docPages/general/roadmap.md +++ /dev/null @@ -1,80 +0,0 @@ -# Roadmap - -## Tasks - -1. Refactoring the API & Extension Manager -2. Improve testing: Add editor instance to the DOM element -3. Building the first batch of basic extensions (bold, italic), writing tests -4. Building more complex examples from the extensions - -## New features - -* generate schema without initializing tiptap, to make SSR easier (e. g. `getSchema([new Doc(), new Paragraph()])`) - -## Requested features - -* Basic Styling - * https://github.com/ueberdosis/tiptap/issues/507 -* Support vor Vue.js 3 -* Easily add custom classes to everything - * https://github.com/ueberdosis/tiptap/discussions/817 -* Text snippets - * https://github.com/ueberdosis/tiptap/issues/737 -* Markdown Support - -## Requested extensions - -* Alignment - * https://github.com/ueberdosis/tiptap/pull/544 -* Font color -* Font family -* Font size -* Created embed from pasted YouTube URL -* Superscript/Subscript - * https://github.com/ueberdosis/tiptap/discussions/813 -* Math Support - * https://github.com/ueberdosis/tiptap/issues/179 - * https://github.com/ueberdosis/tiptap/issues/698 -* Resizeable Images - * https://gist.github.com/zachjharris/a5442efbdff11948d085b6b1406dfbe6 - -## Ideas - -* A `@tiptap/extensions` package would be helpful to make imports easier. -* Add more shorcuts: - * Ctrl+I → Italic ✅ - * Ctrl+B → Bold ✅ - * Ctrl+K → Link (Medium, Tumblr, Slack, Google Docs, Word) - * Ctrl+Shift+K → Code (Slack) - * Shift+Enter → Line break - * Ctrl+Shift+X → Strikethrough (Slack) - * Alt+Shift+5 → Strikethrough (Google Docs) - * Ctrl+Shift+6 → Strikethrough (Tumblr) - * Ctrl+Alt+0 → Paragraph (Google Docs) - * Ctrl+Alt+1 to 6 → Heading (Google Docs, Word, ~Medium, ~Slack) - * Ctrl+Shift+2 → Heading (Tumblr) - * Ctrl+Shift+7 → Ordered list (Google Docs, Slack, Tumblr) - * Ctrl+Shift+8 → Unordered list (Google Docs, Slack, Tumblr) - * Tab, Shift+Tab → Increase / decrease nesting in lists - * Ctrl+], Ctrl+[ → Same as above (when Tab needs to be used) - * Ctrl+Shift+9 → Blockquote (Tumblr) - * Ctrl+Alt+K → Code block (Slack) - * Ctrl+R → Horizontal ruler (Stack Overflow) -* Markdown shortcuts - * #+Space → Heading (the number of # determines the header level) - * *+Space, -+Space → Unordered list - * 1.+Space → Ordered list - * >+Space → Blockquote - * ```+Space → Code block - * ---- → Horizontal ruler - * ![] → Embedded resource (not part of Slack, but would it not be fancy?) - * :emoji: → Emoji (based on the name). A nice-to-have, most certainly. -* General shortcuts - * Ctrl+C, Ctrl+X, Ctrl+V: copy, cut, paste - * Ctrl+Z, Ctrl+Shift+Z, Ctrl+Y: undo, redo - * Ctrl+Backspace: delete previous word - * Ctrl+Delete: delete next word - * Ctrl+Home, Ctrl+End: go to the start / end of the whole document - * Ctrl+F, Ctrl+G: find, find next occurrence - * Ctrl+S: if there is no auto-saving, this should save the document - * Ctrl+/: show shortcuts (Medium, Slack) diff --git a/docs/src/docPages/general/upgrade-guide.md b/docs/src/docPages/general/upgrade-guide.md deleted file mode 100644 index 8c8e6d1f..00000000 --- a/docs/src/docPages/general/upgrade-guide.md +++ /dev/null @@ -1,3 +0,0 @@ -# Upgrade Guide - -## Upgrading from 1.x to 2.x \ No newline at end of file diff --git a/docs/src/docPages/overview/contributing.md b/docs/src/docPages/overview/contributing.md new file mode 100644 index 00000000..70951017 --- /dev/null +++ b/docs/src/docPages/overview/contributing.md @@ -0,0 +1,11 @@ +# Contributing + +## What kind of contributions are welcome + +## What kind of contributions won’t be merged + +## How to send your first Pull Request + +## Code style + +## Testing \ No newline at end of file diff --git a/docs/src/docPages/general/installation.md b/docs/src/docPages/overview/installation.md similarity index 92% rename from docs/src/docPages/general/installation.md rename to docs/src/docPages/overview/installation.md index 06d78b73..7d792f68 100644 --- a/docs/src/docPages/general/installation.md +++ b/docs/src/docPages/overview/installation.md @@ -2,15 +2,15 @@ tiptap has a very modular package structure and is independent of any framework. Depending on what you want to do with tiptap there are a few different ways to install tiptap in your project. Choose the way that fits your project best. -## Vanilla JavaScript +## Plain JavaScript Use tiptap with vanilla JavaScript for a very lightweight and raw experience. If you feel like it, you can even use it to connect the tiptap core with other frameworks not mentioned here. ```bash -# Use npm +# With npm npm install @tiptap/core @tiptap/starter-kit -# Or: Use Yarn +# Or: With Yarn yarn add @tiptap/core @tiptap/starter-kit ``` @@ -18,11 +18,11 @@ Great, that should be enough to start. Here is the most essential code you need ```js import { Editor } from '@tiptap/core' -import extensions from '@tiptap/starter-kit' +import defaultExtensions from '@tiptap/starter-kit' new Editor({ element: document.getElementsByClassName('element'), - extensions: extensions(), + extensions: defaultExtensions(), content: ' Your content.
', }) ``` @@ -32,10 +32,10 @@ new Editor({ To use tiptap with Vue.js (and tools that are based on Vue.js) install the Vue.js adapter in your project: ```bash -# Using npm +# With npm npm install @tiptap/vue @tiptap/vue-starter-kit -# Using Yarn +# Or: With Yarn yarn add @tiptap/vue @tiptap/vue-starter-kit ``` diff --git a/docs/src/docPages/overview/roadmap.md b/docs/src/docPages/overview/roadmap.md new file mode 100644 index 00000000..55bfc8ce --- /dev/null +++ b/docs/src/docPages/overview/roadmap.md @@ -0,0 +1,3 @@ +# Roadmap + +See https://github.com/ueberdosis/tiptap-next/projects/1 diff --git a/docs/src/docPages/overview/sponsoring.md b/docs/src/docPages/overview/sponsoring.md new file mode 100644 index 00000000..802fe27b --- /dev/null +++ b/docs/src/docPages/overview/sponsoring.md @@ -0,0 +1,3 @@ +# Sponsoring + +https://github.com/sponsors/ueberdosis diff --git a/docs/src/docPages/overview/upgrade-guide.md b/docs/src/docPages/overview/upgrade-guide.md new file mode 100644 index 00000000..0ec1b9f8 --- /dev/null +++ b/docs/src/docPages/overview/upgrade-guide.md @@ -0,0 +1,35 @@ +# Upgrade Guide + +## Reasons to upgrade to tiptap 2.x + +* TypeScript: auto complete, less bugs, generated API documentation +* Amazing documentation with 100+ pages +* Active maintenance, no more updates to 1.x +* Tons of new extensions planned +* Less bugs, tested code based + +## Upgrading from 1.x to 2.x +The new API will look pretty familiar too you, but there are a ton of changes though. To make the upgrade a little bit easier, here is everything you should do: + +### New document type +**We renamed the default `Document` type from `doc` to `document`.** To keep it like that, use your own implementation of the `Document` node or migrate the stored JSON to use the new name. + +```js +import Document from '@tiptap/extension-document' +const CustomDocument = Document.name('doc').create() + +new Editor({ + extensions: [ + CustomDocument(), + … + ] +}) +``` + +### New extension API + +In case you’ve built some custom extensions for your project, you’ll need to rewrite them to fit the new API. No worries, though, you can keep a lot of your work though. The schema, commands, keys, inputRules, pasteRules all work like they did before. It’s just different how you register them. + +```js +const CustomExtension = … +``` \ No newline at end of file diff --git a/docs/src/links.yaml b/docs/src/links.yaml index aa754d15..46114d00 100644 --- a/docs/src/links.yaml +++ b/docs/src/links.yaml @@ -1,14 +1,20 @@ -- title: General +- title: Overview items: - title: Introduction link: / - title: Installation - link: /general/installation + link: /overview/installation - title: Upgrade Guide - link: /general/upgrade-guide + link: /overview/upgrade-guide + draft: true + - title: Contributing + link: /overview/contributing + draft: true + - title: Sponsoring + link: /overview/sponsoring draft: true - title: Roadmap - link: /general/roadmap + link: /overview/roadmap draft: true - title: Guide @@ -40,62 +46,62 @@ link: /examples/basic - title: Simple link: /examples/simple - - title: Menu Bubble - link: /examples/menu-bubble - draft: true - - title: Floating Menu - link: /examples/floating-menu - draft: true - - title: Links - link: /examples/links - draft: true - - title: Images - link: /examples/images - draft: true - - title: Hiding Menu Bar - link: /examples/hiding-menu-bar - draft: true - - title: Todo List - link: /examples/todo-list - draft: true - - title: Tables - link: /examples/tables - draft: true - - title: Search and Replace - link: /examples/search-and-replace - draft: true - - title: Suggestions - link: /examples/suggestions - draft: true + # - title: Menu Bubble + # link: /examples/menu-bubble + # draft: true + # - title: Floating Menu + # link: /examples/floating-menu + # draft: true + # - title: Links + # link: /examples/links + # draft: true + # - title: Images + # link: /examples/images + # draft: true + # - title: Hiding Menu Bar + # link: /examples/hiding-menu-bar + # draft: true + # - title: Todo List + # link: /examples/todo-list + # draft: true + # - title: Tables + # link: /examples/tables + # draft: true + # - title: Search and Replace + # link: /examples/search-and-replace + # draft: true + # - title: Suggestions + # link: /examples/suggestions + # draft: true - title: Markdown Shortcuts link: /examples/markdown-shortcuts - - title: Code Highlighting - link: /examples/code-highlighting - draft: true + # - title: Code Highlighting + # link: /examples/code-highlighting + # draft: true - title: History link: /examples/history - title: Read-Only link: /examples/read-only - - title: Embeds - link: /examples/embeds - draft: true - - title: Placeholder - link: /examples/placeholder - draft: true + # - title: Embeds + # link: /examples/embeds + # draft: true + # - title: Placeholder + # link: /examples/placeholder + # draft: true - title: Focus link: /examples/focus - - title: Collaboration - link: /examples/collaboration - draft: true - - title: Title - link: /examples/title - draft: true - - title: Trailing Paragraph - link: /examples/trailing-paragraph - draft: true - - title: Drag Handle - link: /examples/drag-handle - draft: true + # - title: Collaboration + # link: /examples/collaboration + # draft: true + # - title: Title + # link: /examples/title + # draft: true + # - title: Trailing Paragraph + # link: /examples/trailing-paragraph + # draft: true + # - title: Drag Handle + # link: /examples/drag-handle + # draft: true - title: Export HTML or JSON link: /examples/export-html-or-json @@ -128,7 +134,6 @@ # draft: true - title: Document link: /api/extensions/document - draft: true - title: Hardbreak link: /api/extensions/hard-break draft: true @@ -156,7 +161,6 @@ draft: true - title: Paragraph link: /api/extensions/paragraph - draft: true # - title: Placeholder # link: /api/extensions/placeholder # draft: true diff --git a/packages/core/package.json b/packages/core/package.json index 61d1cb1f..cf9c34e4 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -12,9 +12,12 @@ "dist" ], "dependencies": { + "@types/clone-deep": "^4.0.1", "@types/prosemirror-dropcursor": "^1.0.0", "@types/prosemirror-gapcursor": "^1.0.1", + "clone-deep": "^4.0.1", "collect.js": "^4.28.2", + "deepmerge": "^4.2.2", "prosemirror-commands": "^1.1.3", "prosemirror-dropcursor": "^1.3.2", "prosemirror-gapcursor": "^1.1.5", @@ -24,8 +27,7 @@ "prosemirror-state": "^1.3.3", "prosemirror-tables": "^1.1.1", "prosemirror-utils": "^0.9.6", - "prosemirror-view": "^1.15.6", - "verbal-expressions": "^1.0.2" + "prosemirror-view": "^1.15.6" }, "scripts": { "build": "microbundle" diff --git a/packages/core/src/Editor.ts b/packages/core/src/Editor.ts index ce8d6758..2eefc7b2 100644 --- a/packages/core/src/Editor.ts +++ b/packages/core/src/Editor.ts @@ -12,10 +12,10 @@ import removeElement from './utils/removeElement' import getSchemaTypeByName from './utils/getSchemaTypeByName' import getHtmlFromFragment from './utils/getHtmlFromFragment' import ExtensionManager from './ExtensionManager' +import EventEmitter from './EventEmitter' import Extension from './Extension' import Node from './Node' import Mark from './Mark' -import EventEmitter from './EventEmitter' import ComponentRenderer from './ComponentRenderer' import defaultPlugins from './plugins' import * as commands from './commands' diff --git a/packages/core/src/Extension.ts b/packages/core/src/Extension.ts index 7f157c97..27b44d39 100644 --- a/packages/core/src/Extension.ts +++ b/packages/core/src/Extension.ts @@ -1,54 +1,113 @@ +import cloneDeep from 'clone-deep' import { Plugin } from 'prosemirror-state' -import { Editor, Command } from './Editor' +import { Editor, CommandSpec } from './Editor' -export default abstract class Extension { +type AnyObject = { + [key: string]: any +} - constructor(options = {}) { - this.options = { - ...this.defaultOptions(), - ...options, +type NoInfer= [T][T extends any ? 0 : never] + +type MergeStrategy = 'extend' | 'overwrite' + +type Configs = { + [key: string]: { + stategy: MergeStrategy + value: any + }[] +} + +export interface ExtensionCallback { + name: string + editor: Editor + options: Options +} + +export interface ExtensionExtends { + name: string + options: Options + commands: (params: Callback) => CommandSpec + inputRules: (params: Callback) => any[] + pasteRules: (params: Callback) => any[] + keys: (params: Callback) => { + [key: string]: Function + } + plugins: (params: Callback) => Plugin[] +} + +export default class Extension< + Options = {}, + Callback = ExtensionCallback , + Extends extends ExtensionExtends = ExtensionExtends +> { + type = 'extension' + config: AnyObject = {} + configs: Configs = {} + options: Partial = {} + + protected storeConfig(key: string, value: any, stategy: MergeStrategy) { + const item = { + stategy, + value, + } + + if (this.configs[key]) { + this.configs[key].push(item) + } else { + this.configs[key] = [item] } } + + public configure(options: Partial ) { + this.options = { ...this.options, ...options } + return this + } + + public name(value: Extends['name']) { + this.storeConfig('name', value, 'overwrite') + return this + } + + public defaults(value: Options) { + this.storeConfig('defaults', value, 'overwrite') + return this + } + + public commands(value: Extends['commands']) { + this.storeConfig('commands', value, 'overwrite') + return this + } + + public keys(value: Extends['keys']) { + this.storeConfig('keys', value, 'overwrite') + return this + } + + public inputRules(value: Extends['inputRules']) { + this.storeConfig('inputRules', value, 'overwrite') + return this + } + + public pasteRules(value: Extends['pasteRules']) { + this.storeConfig('pasteRules', value, 'overwrite') + return this + } + + public plugins(value: Extends['plugins']) { + this.storeConfig('plugins', value, 'overwrite') + return this + } + + public extend >(key: T, value: Extends[T]) { + this.storeConfig(key, value, 'extend') + return this + } - editor!: Editor - options: { [key: string]: any } = {} - - public abstract name: string - - public extensionType = 'extension' + public create() { + type ParentOptions = Options - public created() {} - - public bindEditor(editor: Editor): void { - this.editor = editor + return (options?: Partial >) => { + return cloneDeep(this, true).configure(options as Options) + } } - - defaultOptions(): { [key: string]: any } { - return {} - } - - update(): any { - return () => {} - } - - plugins(): Plugin[] { - return [] - } - - inputRules(): any { - return [] - } - - pasteRules(): any { - return [] - } - - keys(): { [key: string]: Function } { - return {} - } - - commands(): { [key: string]: Command } { - return {} - } - } diff --git a/packages/core/src/ExtensionManager.ts b/packages/core/src/ExtensionManager.ts index badf5b6c..8199d8ac 100644 --- a/packages/core/src/ExtensionManager.ts +++ b/packages/core/src/ExtensionManager.ts @@ -1,3 +1,4 @@ +import deepmerge from 'deepmerge' import collect from 'collect.js' import { Plugin } from 'prosemirror-state' import { keymap } from 'prosemirror-keymap' @@ -5,6 +6,7 @@ import { inputRules } from 'prosemirror-inputrules' import { EditorView, Decoration } from 'prosemirror-view' import { Node as ProsemirrorNode } from 'prosemirror-model' import { Editor } from './Editor' +import Extension from './Extension' import Node from './Node' import Mark from './Mark' import capitalize from './utils/capitalize' @@ -21,30 +23,116 @@ export default class ExtensionManager { constructor(extensions: Extensions, editor: Editor) { this.editor = editor this.extensions = extensions + this.extensions.forEach(extension => { - extension.bindEditor(editor) + this.resolveConfig(extension, 'name') + this.resolveConfig(extension, 'defaults') + this.resolveConfig(extension, 'topNode') + this.resolveConfig(extension, 'schema', ['name', 'options']) + editor.on('schemaCreated', () => { - this.editor.registerCommands(extension.commands()) - extension.created() + this.resolveConfig(extension, 'commands', ['name', 'options', 'editor', 'type']) + this.resolveConfig(extension, 'inputRules', ['name', 'options', 'editor', 'type']) + this.resolveConfig(extension, 'pasteRules', ['name', 'options', 'editor', 'type']) + this.resolveConfig(extension, 'keys', ['name', 'options', 'editor', 'type']) + this.resolveConfig(extension, 'plugins', ['name', 'options', 'editor', 'type']) + + if (extension.config.commands) { + this.editor.registerCommands(extension.config.commands) + } }) }) } - get topNode() { - return getTopNodeFromExtensions(this.extensions) + resolveConfig( + extension: Extension | Node | Mark, + name: string, + propValues: ('name' | 'options' | 'editor' | 'type')[] = [] + ) { + if (!extension.configs[name]) { + return + } + + extension.config[name] = extension.configs[name] + .reduce((accumulator, { stategy, value: rawValue }) => { + const props: any = {} + + if (propValues.includes('name')) { + props.name = extension.config.name + } + + if (propValues.includes('options')) { + props.options = deepmerge(extension.config.defaults, extension.options) + } + + if (propValues.includes('editor')) { + props.editor = this.editor + } + + if (propValues.includes('type')) { + props.type = extension.type === 'node' + ? this.editor.schema.nodes[extension.config.name] + : this.editor.schema.marks[extension.config.name] + } + + const value = typeof rawValue === 'function' + ? rawValue(props) + : rawValue + + if (accumulator === undefined) { + return value + } + + if (stategy === 'overwrite') { + return value + } + + if (stategy === 'extend') { + return deepmerge(accumulator, value) + } + + return accumulator + }, undefined) + } + + // get topNode() { + // return getTopNodeFromExtensions(this.extensions) + // } + + // get nodes(): any { + // return getNodesFromExtensions(this.extensions) + // } + + // get marks(): any { + // return getMarksFromExtensions(this.extensions) + // } + + get topNode(): any { + const topNode = collect(this.extensions).firstWhere('config.topNode', true) + + if (topNode) { + return topNode.config.name + } } get nodes(): any { - return getNodesFromExtensions(this.extensions) + return collect(this.extensions) + .where('type', 'node') + .mapWithKeys((extension: Node) => [extension.config.name, extension.config.schema]) + .all() } get marks(): any { - return getMarksFromExtensions(this.extensions) + return collect(this.extensions) + .where('type', 'mark') + .mapWithKeys((extension: Mark) => [extension.config.name, extension.config.schema]) + .all() } get plugins(): Plugin[] { const plugins = collect(this.extensions) - .flatMap(extension => extension.plugins()) + .flatMap(extension => extension.config.plugins) + .filter(plugin => plugin) .toArray() return [ @@ -57,54 +145,57 @@ export default class ExtensionManager { get inputRules(): any { return collect(this.extensions) - .flatMap(extension => extension.inputRules()) + .flatMap(extension => extension.config.inputRules) + .filter(plugin => plugin) .toArray() } get pasteRules(): any { return collect(this.extensions) - .flatMap(extension => extension.pasteRules()) + .flatMap(extension => extension.config.pasteRules) + .filter(plugin => plugin) .toArray() } get keymaps() { return collect(this.extensions) - .map(extension => extension.keys()) - .filter(keys => !!Object.keys(keys).length) - // @ts-ignore + .map(extension => extension.config.keys) + .filter(keys => keys) .map(keys => keymap(keys)) .toArray() } get nodeViews() { - const { renderer: Renderer } = this.editor + // const { renderer: Renderer } = this.editor - if (!Renderer || !Renderer.type) { - return {} - } + // if (!Renderer || !Renderer.type) { + // return {} + // } - const prop = `to${capitalize(Renderer.type)}` + // const prop = `to${capitalize(Renderer.type)}` - return collect(this.extensions) - .where('extensionType', 'node') - .filter((extension: any) => extension.schema()[prop]) - .map((extension: any) => { - return ( - node: ProsemirrorNode, - view: EditorView, - getPos: (() => number) | boolean, - decorations: Decoration[], - ) => { - return new Renderer(extension.schema()[prop], { - extension, - editor: this.editor, - node, - getPos, - decorations, - }) - } - }) - .all() + // return collect(this.extensions) + // .where('extensionType', 'node') + // .filter((extension: any) => extension.schema()[prop]) + // .map((extension: any) => { + // return ( + // node: ProsemirrorNode, + // view: EditorView, + // getPos: (() => number) | boolean, + // decorations: Decoration[], + // ) => { + // return new Renderer(extension.schema()[prop], { + // extension, + // editor: this.editor, + // node, + // getPos, + // decorations, + // }) + // } + // }) + // .all() + + return {} } } diff --git a/packages/core/src/Mark.ts b/packages/core/src/Mark.ts index 06cc4ca7..10a7fd82 100644 --- a/packages/core/src/Mark.ts +++ b/packages/core/src/Mark.ts @@ -1,18 +1,28 @@ -import Extension from './Extension' -import { MarkSpec } from 'prosemirror-model' - -export default abstract class Mark extends Extension { - - constructor(options = {}) { - super(options) - } - - public extensionType = 'mark' - - abstract schema(): MarkSpec - - get type() { - return this.editor.schema.marks[this.name] - } +import { MarkSpec, MarkType } from 'prosemirror-model' +import Extension, { ExtensionCallback, ExtensionExtends } from './Extension' +import { Editor } from './Editor' +export interface MarkCallback { + name: string + editor: Editor + options: Options + type: MarkType } + +export interface MarkExtends extends ExtensionExtends { + topMark: boolean + schema: (params: Omit ) => MarkSpec +} + +export default class Mark< + Options = {}, + Callback = MarkCallback , + Extends extends MarkExtends = MarkExtends +> extends Extension { + type = 'mark' + + public schema(value: Extends['schema']) { + this.storeConfig('schema', value, 'overwrite') + return this + } +} \ No newline at end of file diff --git a/packages/core/src/Node.ts b/packages/core/src/Node.ts index 79436aa9..bd5e0f4e 100644 --- a/packages/core/src/Node.ts +++ b/packages/core/src/Node.ts @@ -1,20 +1,33 @@ -import Extension from './Extension' -import { NodeSpec } from 'prosemirror-model' - -export default abstract class Node extends Extension { - - constructor(options = {}) { - super(options) - } - - public extensionType = 'node' - - public topNode = false - - abstract schema(): NodeSpec - - get type() { - return this.editor.schema.nodes[this.name] - } +import { NodeSpec, NodeType } from 'prosemirror-model' +import Extension, { ExtensionCallback, ExtensionExtends } from './Extension' +import { Editor } from './Editor' +export interface NodeCallback { + name: string + editor: Editor + options: Options + type: NodeType +} + +export interface NodeExtends extends ExtensionExtends { + topNode: boolean + schema: (params: Omit ) => NodeSpec +} + +export default class Node< + Options = {}, + Callback = NodeCallback , + Extends extends NodeExtends = NodeExtends +> extends Extension { + type = 'node' + + public topNode(value: Extends['topNode'] = true) { + this.storeConfig('topNode', value, 'overwrite') + return this + } + + public schema(value: Extends['schema']) { + this.storeConfig('schema', value, 'overwrite') + return this + } } diff --git a/packages/extension-bold/index.ts b/packages/extension-bold/index.ts index 2daffc53..e4e7b0a6 100644 --- a/packages/extension-bold/index.ts +++ b/packages/extension-bold/index.ts @@ -1,6 +1,4 @@ -import { Mark, CommandSpec, markInputRule, markPasteRule } from '@tiptap/core' -import { MarkSpec } from 'prosemirror-model' -import VerEx from 'verbal-expressions' +import { Mark, markInputRule, markPasteRule } from '@tiptap/core' declare module '@tiptap/core/src/Editor' { interface Editor { @@ -8,75 +6,44 @@ declare module '@tiptap/core/src/Editor' { } } -export default class Bold extends Mark { +export const starInputRegex = /(?:^|\s)((?:\*\*)((?:[^\*\*]+))(?:\*\*))$/gm +export const starPasteRegex = /(?:^|\s)((?:\*\*)((?:[^\*\*]+))(?:\*\*))/gm +export const underscoreInputRegex = /(?:^|\s)((?:__)((?:[^__]+))(?:__))$/gm +export const underscorePasteRegex = /(?:^|\s)((?:__)((?:[^__]+))(?:__))/gm - name = 'bold' - - schema(): MarkSpec { - return { - parseDOM: [ - { - tag: 'strong', - }, - { - tag: 'b', - getAttrs: node => (node as HTMLElement).style.fontWeight !== 'normal' && null, - }, - { - style: 'font-weight', - getAttrs: value => /^(bold(er)?|[5-9]\d{2,})$/.test(value as string) && null, - }, - ], - toDOM: () => ['strong', 0], - } - } - - commands(): CommandSpec { - return { - bold: next => () => { - this.editor.toggleMark(this.name) - next() +export default new Mark() + .name('bold') + .schema(() => ({ + parseDOM: [ + { + tag: 'strong', }, - } - } - - keys() { - return { - 'Mod-b': () => this.editor.bold() - } - } - - inputRules() { - return ['**', '__'].map(character => { - const regex = VerEx() - .add('(?:^|\\s)') - .beginCapture() - .find(character) - .beginCapture() - .somethingBut(character) - .endCapture() - .find(character) - .endCapture() - .endOfLine() - - return markInputRule(regex, this.type) - }) - } - - pasteRules() { - return ['**', '__'].map(character => { - const regex = VerEx() - .add('(?:^|\\s)') - .beginCapture() - .find(character) - .beginCapture() - .somethingBut(character) - .endCapture() - .find(character) - .endCapture() - - return markPasteRule(regex, this.type) - }) - } - -} \ No newline at end of file + { + tag: 'b', + getAttrs: node => (node as HTMLElement).style.fontWeight !== 'normal' && null, + }, + { + style: 'font-weight', + getAttrs: value => /^(bold(er)?|[5-9]\d{2,})$/.test(value as string) && null, + }, + ], + toDOM: () => ['strong', 0], + })) + .commands(({ editor, name, type }) => ({ + bold: next => () => { + editor.toggleMark(name) + next() + }, + })) + .keys(({ editor }) => ({ + 'Mod-b': () => editor.bold() + })) + .inputRules(({ type }) => [ + markInputRule(starInputRegex, type), + markInputRule(underscoreInputRegex, type), + ]) + .pasteRules(({ type }) => [ + markPasteRule(starPasteRegex, type), + markPasteRule(underscorePasteRegex, type), + ]) + .create() diff --git a/packages/extension-code/index.ts b/packages/extension-code/index.ts index 815b2aee..cba8c320 100644 --- a/packages/extension-code/index.ts +++ b/packages/extension-code/index.ts @@ -1,6 +1,4 @@ -import { Mark, markInputRule, markPasteRule, CommandSpec } from '@tiptap/core' -import { MarkSpec } from 'prosemirror-model' -import VerEx from 'verbal-expressions' +import { Mark, markInputRule, markPasteRule } from '@tiptap/core' declare module '@tiptap/core/src/Editor' { interface Editor { @@ -8,62 +6,31 @@ declare module '@tiptap/core/src/Editor' { } } -export default class Code extends Mark { +export const inputRegex = /(?:^|\s)((?:`)((?:[^`]+))(?:`))$/gm +export const pasteRegex = /(?:^|\s)((?:`)((?:[^`]+))(?:`))/gm - name = 'code' - - schema(): MarkSpec { - return { - excludes: '_', - parseDOM: [ - { tag: 'code' }, - ], - toDOM: () => ['code', 0], - } - } - - commands(): CommandSpec { - return { - code: next => () => { - this.editor.toggleMark(this.name) - next() - }, - } - } - - keys() { - return { - 'Mod-`': () => this.editor.code() - } - } - - inputRules() { - const regex = VerEx() - .add('(?:^|\\s)') - .beginCapture() - .find('`') - .beginCapture() - .somethingBut('`') - .endCapture() - .find('`') - .endCapture() - .endOfLine() - - return markInputRule(regex, this.type) - } - - pasteRules() { - const regex = VerEx() - .add('(?:^|\\s)') - .beginCapture() - .find('`') - .beginCapture() - .somethingBut('`') - .endCapture() - .find('`') - .endCapture() - - return markPasteRule(regex, this.type) - } - -} \ No newline at end of file +export default new Mark() + .name('code') + .schema(() => ({ + excludes: '_', + parseDOM: [ + { tag: 'code' }, + ], + toDOM: () => ['code', 0], + })) + .commands(({ editor, name }) => ({ + code: next => () => { + editor.toggleMark(name) + next() + }, + })) + .keys(({ editor }) => ({ + 'Mod-`': () => editor.code() + })) + .inputRules(({ type }) => [ + markInputRule(inputRegex, type) + ]) + .pasteRules(({ type }) => [ + markPasteRule(inputRegex, type) + ]) + .create() diff --git a/packages/extension-codeblock/index.ts b/packages/extension-codeblock/index.ts index 96ed30dc..dfd2a736 100644 --- a/packages/extension-codeblock/index.ts +++ b/packages/extension-codeblock/index.ts @@ -1,23 +1,17 @@ import { Node } from '@tiptap/core' -import { NodeSpec } from 'prosemirror-model' -export default class CodeBlock extends Node { - - name = 'code_block' - - schema(): NodeSpec { - return { - content: 'text*', - marks: '', - group: 'block', - code: true, - defining: true, - draggable: false, - parseDOM: [ - { tag: 'pre', preserveWhitespace: 'full' }, - ], - toDOM: () => ['pre', ['code', 0]], - } - } - -} \ No newline at end of file +export default new Node() + .name('code_block') + .schema(() => ({ + content: 'text*', + marks: '', + group: 'block', + code: true, + defining: true, + draggable: false, + parseDOM: [ + { tag: 'pre', preserveWhitespace: 'full' }, + ], + toDOM: () => ['pre', ['code', 0]], + })) + .create() diff --git a/packages/extension-document/index.ts b/packages/extension-document/index.ts index 86213562..7099a8d4 100644 --- a/packages/extension-document/index.ts +++ b/packages/extension-document/index.ts @@ -1,16 +1,9 @@ import { Node } from '@tiptap/core' -import { NodeSpec } from 'prosemirror-model' -export default class Document extends Node { - - name = 'document' - - topNode = true - - schema(): NodeSpec { - return { - content: 'block+', - } - } - -} \ No newline at end of file +export default new Node() + .name('document') + .topNode() + .schema(() => ({ + content: 'block+', + })) + .create() \ No newline at end of file diff --git a/packages/extension-focus/index.ts b/packages/extension-focus/index.ts index ae00586c..73c605d8 100644 --- a/packages/extension-focus/index.ts +++ b/packages/extension-focus/index.ts @@ -2,57 +2,45 @@ import { Extension } from '@tiptap/core' import { Plugin } from 'prosemirror-state' import { DecorationSet, Decoration } from 'prosemirror-view' -interface FocusOptions { +export interface FocusOptions { className: string, nested: boolean, } -export default class Focus extends Extension { +export default new Extension () + .name('focus') + .defaults({ + className: 'has-focus', + nested: false, + }) + .plugins(({ editor, options }) => [ + new Plugin({ + props: { + decorations: ({ doc, selection }) => { + const { isEditable, isFocused } = editor + const { anchor } = selection + const decorations: Decoration[] = [] - name = 'focus' + if (!isEditable || !isFocused) { + return + } - constructor(options: Partial = {}) { - super(options) - } + doc.descendants((node, pos) => { + const hasAnchor = anchor >= pos && anchor <= (pos + node.nodeSize) - defaultOptions(): FocusOptions { - return { - className: 'has-focus', - nested: false, - } - } - - plugins() { - return [ - new Plugin({ - props: { - decorations: ({ doc, selection }) => { - const { isEditable, isFocused } = this.editor - const { anchor } = selection - const decorations: Decoration[] = [] - - if (!isEditable || !isFocused) { - return + if (hasAnchor && !node.isText) { + const decoration = Decoration.node(pos, pos + node.nodeSize, { + class: options.className, + }) + decorations.push(decoration) } - doc.descendants((node, pos) => { - const hasAnchor = anchor >= pos && anchor <= (pos + node.nodeSize) + return options.nested + }) - if (hasAnchor && !node.isText) { - const decoration = Decoration.node(pos, pos + node.nodeSize, { - class: this.options.className, - }) - decorations.push(decoration) - } - - return this.options.nested - }) - - return DecorationSet.create(doc, decorations) - }, + return DecorationSet.create(doc, decorations) }, - }), - ] - } - -} + }, + }), + ]) + .create() diff --git a/packages/extension-heading/index.ts b/packages/extension-heading/index.ts index 72b631e9..509027c9 100644 --- a/packages/extension-heading/index.ts +++ b/packages/extension-heading/index.ts @@ -1,11 +1,9 @@ -import { Node, CommandSpec } from '@tiptap/core' -import { NodeSpec } from 'prosemirror-model' -import VerEx from 'verbal-expressions' +import { Node } from '@tiptap/core' import { textblockTypeInputRule } from 'prosemirror-inputrules' type Level = 1 | 2 | 3 | 4 | 5 | 6 -interface HeadingOptions { +export interface HeadingOptions { levels: Level[], } @@ -15,60 +13,37 @@ declare module '@tiptap/core/src/Editor' { } } -export default class Heading extends Node { - - name = 'heading' - - constructor(options: Partial = {}) { - super(options) - } - - defaultOptions(): HeadingOptions { - return { - levels: [1, 2, 3, 4, 5, 6], - } - } - - schema(): NodeSpec { - return { - attrs: { - level: { - default: 1, - }, +export default new Node () + .name('heading') + .defaults({ + levels: [1, 2, 3, 4, 5, 6], + }) + .schema(({ options }) => ({ + attrs: { + level: { + default: 1, }, - content: 'inline*', - group: 'block', - defining: true, - draggable: false, - parseDOM: this.options.levels - .map((level: Level) => ({ - tag: `h${level}`, - attrs: { level }, - })), - toDOM: node => [`h${node.attrs.level}`, 0], - } - } - - commands(): CommandSpec { - return { - heading: next => attrs => { - this.editor.toggleNode(this.name, 'paragraph', attrs) - next() - }, - } - } - - inputRules() { - return this.options.levels.map((level: Level) => { - const regex = VerEx() - .startOfLine() - .find('#') - .repeatPrevious(level) - .whitespace() - .endOfLine() - - return textblockTypeInputRule(regex, this.type, { level }) + }, + content: 'inline*', + group: 'block', + defining: true, + draggable: false, + parseDOM: options.levels + .map((level: Level) => ({ + tag: `h${level}`, + attrs: { level }, + })), + toDOM: node => [`h${node.attrs.level}`, 0], + })) + .commands(({ editor, name }) => ({ + [name]: next => attrs => { + editor.toggleNode(name, 'paragraph', attrs) + next() + }, + })) + .inputRules(({ options, type }) => { + return options.levels.map((level: Level) => { + return textblockTypeInputRule(new RegExp(`^(#{1,${level}})\\s$`), type, { level }) }) - } - -} \ No newline at end of file + }) + .create() diff --git a/packages/extension-history/index.ts b/packages/extension-history/index.ts index 2bc3ac75..4d17c61c 100644 --- a/packages/extension-history/index.ts +++ b/packages/extension-history/index.ts @@ -1,4 +1,4 @@ -import { Extension, CommandSpec } from '@tiptap/core' +import { Extension } from '@tiptap/core' import { history, undo, @@ -14,49 +14,31 @@ declare module '@tiptap/core/src/Editor' { } } -interface HistoryOptions { - historyPluginOptions?: Object, +export interface HistoryOptions { + historyPluginOptions: Object, } -export default class History extends Extension { - - name = 'history' - - constructor(options: Partial = {}) { - super(options) - } - - defaultOptions(): HistoryOptions { - return { - historyPluginOptions: {}, - } - } - - commands(): CommandSpec { - return { - undo: (next, { view }) => () => { - undo(view.state, view.dispatch) - next() - }, - redo: (next, { view }) => () => { - redo(view.state, view.dispatch) - next() - }, - } - } - - keys() { - return { - 'Mod-z': () => this.editor.undo(), - 'Mod-y': () => this.editor.redo(), - 'Shift-Mod-z': () => this.editor.redo(), - } - } - - plugins() { - return [ - history(this.options.historyPluginOptions) - ] - } - -} \ No newline at end of file +export default new Extension () + .name('history') + .defaults({ + historyPluginOptions: {}, + }) + .commands(() => ({ + undo: (next, { view }) => () => { + undo(view.state, view.dispatch) + next() + }, + redo: (next, { view }) => () => { + redo(view.state, view.dispatch) + next() + }, + })) + .keys(({ editor }) => ({ + 'Mod-z': () => editor.undo(), + 'Mod-y': () => editor.redo(), + 'Shift-Mod-z': () => editor.redo(), + })) + .plugins(({ options }) => [ + history(options.historyPluginOptions) + ]) + .create() diff --git a/packages/extension-italic/index.ts b/packages/extension-italic/index.ts index d0492e4b..237b8ac4 100644 --- a/packages/extension-italic/index.ts +++ b/packages/extension-italic/index.ts @@ -1,6 +1,4 @@ -import { Mark, markInputRule, markPasteRule, CommandSpec } from '@tiptap/core' -import { MarkSpec } from 'prosemirror-model' -import VerEx from 'verbal-expressions' +import { Mark, markInputRule, markPasteRule } from '@tiptap/core' declare module '@tiptap/core/src/Editor' { interface Editor { @@ -8,67 +6,36 @@ declare module '@tiptap/core/src/Editor' { } } -export default class Italic extends Mark { +export const starInputRegex = /(?:^|\s)((?:\*)((?:[^\*]+))(?:\*))$/gm +export const starPasteRegex = /(?:^|\s)((?:\*)((?:[^\*]+))(?:\*))/gm +export const underscoreInputRegex = /(?:^|\s)((?:_)((?:[^_]+))(?:_))$/gm +export const underscorePasteRegex = /(?:^|\s)((?:_)((?:[^_]+))(?:_))/gm - name = 'italic' - - schema(): MarkSpec { - return { - parseDOM: [ - { tag: 'i' }, - { tag: 'em' }, - { style: 'font-style=italic' }, - ], - toDOM: () => ['em', 0], - } - } - - commands(): CommandSpec { - return { - italic: next => () => { - this.editor.toggleMark(this.name) - next() - }, - } - } - - keys() { - return { - 'Mod-i': () => this.editor.italic() - } - } - - inputRules() { - return ['*', '_'].map(character => { - const regex = VerEx() - .add('(?:^|\\s)') - .beginCapture() - .find(character) - .beginCapture() - .somethingBut(character) - .endCapture() - .find(character) - .endCapture() - .endOfLine() - - return markInputRule(regex, this.type) - }) - } - - pasteRules() { - return ['*', '_'].map(character => { - const regex = VerEx() - .add('(?:^|\\s)') - .beginCapture() - .find(character) - .beginCapture() - .somethingBut(character) - .endCapture() - .find(character) - .endCapture() - - return markPasteRule(regex, this.type) - }) - } - -} \ No newline at end of file +export default new Mark() + .name('italic') + .schema(() => ({ + parseDOM: [ + { tag: 'i' }, + { tag: 'em' }, + { style: 'font-style=italic' }, + ], + toDOM: () => ['em', 0], + })) + .commands(({ editor, name }) => ({ + italic: next => () => { + editor.toggleMark(name) + next() + }, + })) + .keys(({ editor }) => ({ + 'Mod-i': () => editor.italic() + })) + .inputRules(({ type }) => [ + markInputRule(starInputRegex, type), + markInputRule(underscoreInputRegex, type), + ]) + .pasteRules(({ type }) => [ + markPasteRule(starPasteRegex, type), + markPasteRule(underscorePasteRegex, type), + ]) + .create() diff --git a/packages/extension-paragraph/index.ts b/packages/extension-paragraph/index.ts index 05a386aa..a09ee093 100644 --- a/packages/extension-paragraph/index.ts +++ b/packages/extension-paragraph/index.ts @@ -1,19 +1,13 @@ import { Node } from '@tiptap/core' -import { NodeSpec } from 'prosemirror-model' // import ParagraphComponent from './paragraph.vue' -export default class Paragraph extends Node { - - name = 'paragraph' - - schema(): NodeSpec { - return { - content: 'inline*', - group: 'block', - parseDOM: [{ tag: 'p' }], - toDOM: () => ['p', 0], - // toVue: ParagraphComponent, - } - } - -} \ No newline at end of file +export default new Node() + .name('paragraph') + .schema(() => ({ + content: 'inline*', + group: 'block', + parseDOM: [{ tag: 'p' }], + toDOM: () => ['p', 0], + // toVue: ParagraphComponent, + })) + .create() \ No newline at end of file diff --git a/packages/extension-text/index.ts b/packages/extension-text/index.ts index 3ca1675b..46fe7b40 100644 --- a/packages/extension-text/index.ts +++ b/packages/extension-text/index.ts @@ -1,14 +1,8 @@ import { Node } from '@tiptap/core' -import { NodeSpec } from 'prosemirror-model' -export default class Text extends Node { - - name = 'text' - - schema(): NodeSpec { - return { - group: 'inline', - } - } - -} \ No newline at end of file +export default new Node() + .name('text') + .schema(() => ({ + group: 'inline', + })) + .create() \ No newline at end of file diff --git a/packages/starter-kit/index.ts b/packages/starter-kit/index.ts index 93e6fb89..0e3915a9 100644 --- a/packages/starter-kit/index.ts +++ b/packages/starter-kit/index.ts @@ -10,14 +10,14 @@ import Heading from '@tiptap/extension-heading' export default function defaultExtensions() { return [ - new Document(), - new History(), - new Paragraph(), - new Text(), - new Bold(), - new Italic(), - new Code(), - new CodeBlock(), - new Heading(), + Document(), + History(), + Paragraph(), + Text(), + Bold(), + Italic(), + Code(), + CodeBlock(), + Heading(), ] } \ No newline at end of file diff --git a/packages/vue-starter-kit/index.ts b/packages/vue-starter-kit/index.ts index 0c749598..3cb508c2 100644 --- a/packages/vue-starter-kit/index.ts +++ b/packages/vue-starter-kit/index.ts @@ -12,14 +12,14 @@ import Heading from '@tiptap/extension-heading' export function defaultExtensions() { return [ - new Document(), - new History(), - new Paragraph(), - new Text(), - new Bold(), - new Italic(), - new Code(), - new CodeBlock(), - new Heading(), + Document(), + History(), + Paragraph(), + Text(), + Bold(), + Italic(), + Code(), + CodeBlock(), + Heading(), ] } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 0df94a70..b961c275 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2259,6 +2259,11 @@ dependencies: defer-to-connect "^1.0.1" +"@types/clone-deep@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/clone-deep/-/clone-deep-4.0.1.tgz#7c488443ab9f571cd343d774551b78e9264ea990" + integrity sha512-bdkCSkyVHsgl3Goe1y16T9k6JuQx7SiDREkq728QjKmTZkGJZuS8R3gGcnGzVuGBP0mssKrzM/GlMOQxtip9cg== + "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" @@ -13694,11 +13699,6 @@ vendors@^1.0.0: resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== -verbal-expressions@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/verbal-expressions/-/verbal-expressions-1.0.2.tgz#1f2d28fdcf7169be270777ff5fadcdb2b3b905c5" - integrity sha512-LV8eG4ckcg1iIhGjOF+j1jb0b58m1DgGywce+2U8kbRrB5wZnGe4XCyUyOujZR9D/+rJGXTmxnL30o3zAgmC4w== - verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"