chore: reformat all files

This commit is contained in:
Nick the Sick 2024-11-11 15:25:02 +01:00
parent 6be1f7b15d
commit eee0e834f0
No known key found for this signature in database
GPG Key ID: F575992F156E5BCC
721 changed files with 7992 additions and 9640 deletions

View File

@ -1,5 +1,5 @@
--- ---
"@tiptap/extension-placeholder": major '@tiptap/extension-placeholder': major
--- ---
Officially remove the `considerAnyAsEmpty` which has not been used since version 2.5 Officially remove the `considerAnyAsEmpty` which has not been used since version 2.5

View File

@ -1,5 +1,5 @@
--- ---
"@tiptap/extension-list-keymap": patch '@tiptap/extension-list-keymap': patch
--- ---
Fix backspace behavior when selection is not collapsed Fix backspace behavior when selection is not collapsed

View File

@ -1,5 +1,5 @@
--- ---
"@tiptap/core": patch '@tiptap/core': patch
--- ---
preserve existing node attributes when running setNode preserve existing node attributes when running setNode

View File

@ -1,59 +1,59 @@
--- ---
"@tiptap/extension-collaboration-cursor": major '@tiptap/extension-collaboration-cursor': major
"@tiptap/extension-code-block-lowlight": major '@tiptap/extension-code-block-lowlight': major
"@tiptap/extension-character-count": major '@tiptap/extension-character-count': major
"@tiptap/extension-horizontal-rule": major '@tiptap/extension-horizontal-rule': major
"@tiptap/extension-collaboration": major '@tiptap/extension-collaboration': major
"@tiptap/extension-floating-menu": major '@tiptap/extension-floating-menu': major
"@tiptap/extension-ordered-list": major '@tiptap/extension-ordered-list': major
"@tiptap/extension-table-header": major '@tiptap/extension-table-header': major
"@tiptap/extension-bubble-menu": major '@tiptap/extension-bubble-menu': major
"@tiptap/extension-bullet-list": major '@tiptap/extension-bullet-list': major
"@tiptap/extension-font-family": major '@tiptap/extension-font-family': major
"@tiptap/extension-list-keymap": major '@tiptap/extension-list-keymap': major
"@tiptap/extension-placeholder": major '@tiptap/extension-placeholder': major
"@tiptap/extension-superscript": major '@tiptap/extension-superscript': major
"@tiptap/extension-blockquote": major '@tiptap/extension-blockquote': major
"@tiptap/extension-code-block": major '@tiptap/extension-code-block': major
"@tiptap/extension-dropcursor": major '@tiptap/extension-dropcursor': major
"@tiptap/extension-hard-break": major '@tiptap/extension-hard-break': major
"@tiptap/extension-table-cell": major '@tiptap/extension-table-cell': major
"@tiptap/extension-text-align": major '@tiptap/extension-text-align': major
"@tiptap/extension-text-style": major '@tiptap/extension-text-style': major
"@tiptap/extension-typography": major '@tiptap/extension-typography': major
"@tiptap/extension-gapcursor": major '@tiptap/extension-gapcursor': major
"@tiptap/extension-highlight": major '@tiptap/extension-highlight': major
"@tiptap/extension-list-item": major '@tiptap/extension-list-item': major
"@tiptap/extension-paragraph": major '@tiptap/extension-paragraph': major
"@tiptap/extension-subscript": major '@tiptap/extension-subscript': major
"@tiptap/extension-table-row": major '@tiptap/extension-table-row': major
"@tiptap/extension-task-item": major '@tiptap/extension-task-item': major
"@tiptap/extension-task-list": major '@tiptap/extension-task-list': major
"@tiptap/extension-underline": major '@tiptap/extension-underline': major
"@tiptap/extension-document": major '@tiptap/extension-document': major
"@tiptap/extension-heading": major '@tiptap/extension-heading': major
"@tiptap/extension-history": major '@tiptap/extension-history': major
"@tiptap/extension-mention": major '@tiptap/extension-mention': major
"@tiptap/extension-youtube": major '@tiptap/extension-youtube': major
"@tiptap/extension-italic": major '@tiptap/extension-italic': major
"@tiptap/extension-strike": major '@tiptap/extension-strike': major
"@tiptap/extension-color": major '@tiptap/extension-color': major
"@tiptap/extension-focus": major '@tiptap/extension-focus': major
"@tiptap/extension-image": major '@tiptap/extension-image': major
"@tiptap/extension-table": major '@tiptap/extension-table': major
"@tiptap/extension-bold": major '@tiptap/extension-bold': major
"@tiptap/extension-code": major '@tiptap/extension-code': major
"@tiptap/extension-link": major '@tiptap/extension-link': major
"@tiptap/extension-text": major '@tiptap/extension-text': major
"@tiptap/starter-kit": major '@tiptap/starter-kit': major
"@tiptap/suggestion": major '@tiptap/suggestion': major
"@tiptap/react": major '@tiptap/react': major
"@tiptap/vue-2": major '@tiptap/vue-2': major
"@tiptap/vue-3": major '@tiptap/vue-3': major
"@tiptap/core": major '@tiptap/core': major
"@tiptap/html": major '@tiptap/html': major
"@tiptap/pm": major '@tiptap/pm': major
"tiptap-demos": major 'tiptap-demos': major
--- ---
We are now building packages with tsup which does not support UMD builds, please repackage if you require UMD builds We are now building packages with tsup which does not support UMD builds, please repackage if you require UMD builds

View File

@ -1,11 +1,11 @@
--- ---
"@tiptap/extension-floating-menu": major '@tiptap/extension-floating-menu': major
"@tiptap/extension-bubble-menu": major '@tiptap/extension-bubble-menu': major
"@tiptap/extension-mention": major '@tiptap/extension-mention': major
"@tiptap/suggestion": major '@tiptap/suggestion': major
"@tiptap/react": major '@tiptap/react': major
"@tiptap/vue-2": major '@tiptap/vue-2': major
"@tiptap/vue-3": major '@tiptap/vue-3': major
--- ---
Removed tippy.js and replaced it with [Floating UI](https://floating-ui.com/) - a newer, more lightweight and customizable floating element library. Removed tippy.js and replaced it with [Floating UI](https://floating-ui.com/) - a newer, more lightweight and customizable floating element library.

View File

@ -1,5 +1,5 @@
--- ---
"@tiptap/vue-3": patch '@tiptap/vue-3': patch
--- ---
Fix editor destruction before transition end if editor is nested Fix editor destruction before transition end if editor is nested

View File

@ -1,5 +1,5 @@
--- ---
"@tiptap/extension-bubble-menu": patch '@tiptap/extension-bubble-menu': patch
--- ---
Add `element: HTMLElement` to `shouldShow` options within the BubbleMenu options. Add `element: HTMLElement` to `shouldShow` options within the BubbleMenu options.

View File

@ -1,5 +1,5 @@
--- ---
"@tiptap/core": minor '@tiptap/core': minor
--- ---
Previously, only a json representation of the node could be inserted into the editor. This change allows for the insertion of Prosemirror `Node`s and `Fragment`s directly into the editor through the `insertContentAt`, `setContent` and `insertContent` commands. Previously, only a json representation of the node could be inserted into the editor. This change allows for the insertion of Prosemirror `Node`s and `Fragment`s directly into the editor through the `insertContentAt`, `setContent` and `insertContent` commands.

View File

@ -1,5 +1,5 @@
--- ---
"@tiptap/core": patch '@tiptap/core': patch
--- ---
Addresses a bug with `insertContentAt`'s `simulatedPasteRules` option where it could only accept text and not Prosemirror `Node` and `Content` Addresses a bug with `insertContentAt`'s `simulatedPasteRules` option where it could only accept text and not Prosemirror `Node` and `Content`

View File

@ -1,9 +1,9 @@
--- ---
"@tiptap/core": major '@tiptap/core': major
--- ---
Fix `getPos` type in `NodeViewRendererProps` to potentially be `undefined` Fix `getPos` type in `NodeViewRendererProps` to potentially be `undefined`
Breaking change: Types may flag uses of getPos where an `undefined` possibility isn't handled. Breaking change: Types may flag uses of getPos where an `undefined` possibility isn't handled.
Why this change was made: To ensure the type reflects the real functionality of this function. Why this change was made: To ensure the type reflects the real functionality of this function.
How to update: Ensure that the return value of `getPos` exists before making use of the value. How to update: Ensure that the return value of `getPos` exists before making use of the value.

View File

@ -1,5 +1,5 @@
--- ---
"@tiptap/core": patch '@tiptap/core': patch
--- ---
Updates the types of `addOptions` and `addStorage` to have the parent be possibly undefined which is the most accurate typing Updates the types of `addOptions` and `addStorage` to have the parent be possibly undefined which is the most accurate typing

View File

@ -1,5 +1,5 @@
--- ---
"@tiptap/core": major '@tiptap/core': major
--- ---
`insertContent` and `insertContentAt` commands should not split text nodes like paragraphs into multiple nodes when the inserted content is at the beginning of the text to avoid empty nodes being created `insertContent` and `insertContentAt` commands should not split text nodes like paragraphs into multiple nodes when the inserted content is at the beginning of the text to avoid empty nodes being created

View File

@ -1,5 +1,5 @@
--- ---
"@tiptap/core": patch '@tiptap/core': patch
--- ---
feat: add `once` to EventEmitters feat: add `once` to EventEmitters

View File

@ -1,5 +1,5 @@
--- ---
"@tiptap/vue-2": patch '@tiptap/vue-2': patch
--- ---
Pin vue-ts-types to a working version for vue-2 Pin vue-ts-types to a working version for vue-2

View File

@ -1,5 +1,5 @@
--- ---
"@tiptap/react": minor '@tiptap/react': minor
--- ---
Throw an error in development mode if immediatelyRender is not set in SSR mode Throw an error in development mode if immediatelyRender is not set in SSR mode

View File

@ -1,5 +1,5 @@
--- ---
"@tiptap/react": patch '@tiptap/react': patch
--- ---
React 19 is now allowed as a peer dep, we did not have to make any changes for React 19 React 19 is now allowed as a peer dep, we did not have to make any changes for React 19

View File

@ -1,5 +1,5 @@
--- ---
"@tiptap/extension-mention": patch '@tiptap/extension-mention': patch
--- ---
add zero-width space to resolve cursor selection issue add zero-width space to resolve cursor selection issue

View File

@ -1,6 +1,6 @@
--- ---
"@tiptap/core": patch '@tiptap/core': patch
"@tiptap/extension-hard-break": patch '@tiptap/extension-hard-break': patch
--- ---
Add Node `linebreakReplacement` support and enable on hard-break nodes Add Node `linebreakReplacement` support and enable on hard-break nodes

View File

@ -1,5 +1,5 @@
--- ---
"@tiptap/core": patch '@tiptap/core': patch
--- ---
Improve handling of selections with `updateAttributes`. Should no longer modify parent nodes of the same type. Improve handling of selections with `updateAttributes`. Should no longer modify parent nodes of the same type.

View File

@ -1,5 +1,5 @@
--- ---
"@tiptap/extension-table": patch '@tiptap/extension-table': patch
--- ---
enforce cellMinWidth even on column not resized by the user, fixes #5435 enforce cellMinWidth even on column not resized by the user, fixes #5435

View File

@ -1,5 +1,5 @@
--- ---
"@tiptap/starter-kit": major '@tiptap/starter-kit': major
--- ---
We have now added the Link, ListKeymap, and Underline extensions to the starter kit for a smoother onboarding experience We have now added the Link, ListKeymap, and Underline extensions to the starter kit for a smoother onboarding experience

View File

@ -1,6 +1,6 @@
--- ---
"@tiptap/extension-link": patch '@tiptap/extension-link': patch
"tiptap-demos": patch 'tiptap-demos': patch
--- ---
The link extension's `validate` option now applies to both auto-linking and XSS mitigation. While, the new `shouldAutoLink` option is used to disable auto linking on an otherwise valid url. The link extension's `validate` option now applies to both auto-linking and XSS mitigation. While, the new `shouldAutoLink` option is used to disable auto linking on an otherwise valid url.

View File

@ -1,4 +1,4 @@
title: "Community Extension: " title: 'Community Extension: '
body: body:
- type: markdown - type: markdown
attributes: attributes:
@ -9,7 +9,7 @@ body:
attributes: attributes:
label: Description label: Description
description: Please describe how your extension works and what it does. description: Please describe how your extension works and what it does.
placeholder: "My extension does …" placeholder: 'My extension does …'
validations: validations:
required: true required: true
- type: textarea - type: textarea
@ -17,7 +17,7 @@ body:
attributes: attributes:
label: Installation label: Installation
description: Please describe how users can install your extension. description: Please describe how users can install your extension.
placeholder: "npm install …" placeholder: 'npm install …'
validations: validations:
required: true required: true
- type: textarea - type: textarea
@ -25,7 +25,7 @@ body:
attributes: attributes:
label: Usage label: Usage
description: Please describe how users can use your extension in their editor. description: Please describe how users can use your extension in their editor.
placeholder: "To use my extension you have to …" placeholder: 'To use my extension you have to …'
validations: validations:
required: true required: true
- type: dropdown - type: dropdown
@ -34,11 +34,11 @@ body:
label: Type label: Type
description: Please select the type of this extension. description: Please select the type of this extension.
options: options:
- "Node" - 'Node'
- "Mark" - 'Mark'
- "Prosemirror plugin" - 'Prosemirror plugin'
- "Package or Kit" - 'Package or Kit'
- "Other" - 'Other'
validations: validations:
required: true required: true
- type: textarea - type: textarea
@ -46,6 +46,6 @@ body:
attributes: attributes:
label: Other label: Other
description: Feel free to add any other information about your extension. description: Feel free to add any other information about your extension.
placeholder: "I hope you like …" placeholder: 'I hope you like …'
validations: validations:
required: false required: false

View File

@ -1,6 +1,6 @@
title: "Feature Request: " title: 'Feature Request: '
labels: labels:
- "Type: Feature Request" - 'Type: Feature Request'
body: body:
- type: markdown - type: markdown
attributes: attributes:
@ -11,7 +11,7 @@ body:
attributes: attributes:
label: Description label: Description
description: Please describe the feature you would like to see in Tiptap. description: Please describe the feature you would like to see in Tiptap.
placeholder: "I wish there was an extension for …" placeholder: 'I wish there was an extension for …'
validations: validations:
required: true required: true
- type: textarea - type: textarea
@ -19,7 +19,7 @@ body:
attributes: attributes:
label: Use Case label: Use Case
description: Please describe the use case for this feature. description: Please describe the use case for this feature.
placeholder: "I want to use this feature for …" placeholder: 'I want to use this feature for …'
validations: validations:
required: true required: true
- type: dropdown - type: dropdown
@ -28,9 +28,9 @@ body:
label: Type label: Type
description: Please select the type of this feature. description: Please select the type of this feature.
options: options:
- "New extension" - 'New extension'
- "New feature" - 'New feature'
- "New Tiptap API" - 'New Tiptap API'
- "Other" - 'Other'
validations: validations:
required: true required: true

View File

@ -1,4 +1,4 @@
title: "Community Extension: " title: 'Community Extension: '
body: body:
- type: markdown - type: markdown
attributes: attributes:
@ -9,7 +9,7 @@ body:
attributes: attributes:
label: Description label: Description
description: Please describe what your project is about description: Please describe what your project is about
placeholder: "My project is about …" placeholder: 'My project is about …'
validations: validations:
required: true required: true
- type: input - type: input
@ -17,7 +17,7 @@ body:
attributes: attributes:
label: URL label: URL
description: If possible share the URL of your project. description: If possible share the URL of your project.
placeholder: "https://example.com" placeholder: 'https://example.com'
validations: validations:
required: false required: false
- type: textarea - type: textarea
@ -25,7 +25,7 @@ body:
attributes: attributes:
label: About label: About
description: Feel free to talk about how you used Tiptap in your project, what you liked about it, what you didn't like about it, and what you would like to see in the future. description: Feel free to talk about how you used Tiptap in your project, what you liked about it, what you didn't like about it, and what you would like to see in the future.
placeholder: "If used Tiptap to …" placeholder: 'If used Tiptap to …'
validations: validations:
required: true required: true
- type: dropdown - type: dropdown
@ -34,12 +34,12 @@ body:
label: Type label: Type
description: Please select the type of your project. description: Please select the type of your project.
options: options:
- "Chat Application" - 'Chat Application'
- "Commenting Application" - 'Commenting Application'
- "Content Management System" - 'Content Management System'
- "Document Editor" - 'Document Editor'
- "Document Editor with Collaboration" - 'Document Editor with Collaboration'
- "Other" - 'Other'
validations: validations:
required: true required: true
- type: textarea - type: textarea
@ -47,6 +47,6 @@ body:
attributes: attributes:
label: Other label: Other
description: Feel free to add any other information about your project. description: Feel free to add any other information about your project.
placeholder: "I hope you like …" placeholder: 'I hope you like …'
validations: validations:
required: false required: false

View File

@ -1,14 +1,14 @@
name: Bug Report name: Bug Report
title: "[Bug]: " title: '[Bug]: '
description: Found a bug in the editor core or one of the extensions? Report it here to help us improve. description: Found a bug in the editor core or one of the extensions? Report it here to help us improve.
labels: labels:
- "Type: Bug" - 'Type: Bug'
- "Category: Open Source" - 'Category: Open Source'
- "Status: New" - 'Status: New'
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: "### Please provide details to help us diagnose the bug." value: '### Please provide details to help us diagnose the bug.'
- type: input - type: input
id: packages id: packages
attributes: attributes:
@ -30,7 +30,7 @@ body:
attributes: attributes:
label: Bug Description label: Bug Description
description: Provide a clear and concise description of what the bug is. description: Provide a clear and concise description of what the bug is.
placeholder: "The issue occurs when..." placeholder: 'The issue occurs when...'
validations: validations:
required: true required: true
- type: dropdown - type: dropdown
@ -59,7 +59,7 @@ body:
id: sandbox id: sandbox
attributes: attributes:
label: Code Example URL label: Code Example URL
description: "Link a CodeSandbox, Stackblitz, GitHub repository, or similar to help us reproduce the issue faster." description: 'Link a CodeSandbox, Stackblitz, GitHub repository, or similar to help us reproduce the issue faster.'
placeholder: https://codesandbox.io/s/example placeholder: https://codesandbox.io/s/example
validations: validations:
required: false required: false
@ -74,14 +74,14 @@ body:
id: context id: context
attributes: attributes:
label: Additional Context (Optional) label: Additional Context (Optional)
description: "Add any other context about the problem here, such as screenshots or videos." description: 'Add any other context about the problem here, such as screenshots or videos.'
- type: checkboxes - type: checkboxes
attributes: attributes:
label: Dependency Updates label: Dependency Updates
description: "Have you updated your dependencies? This can often resolve issues." description: 'Have you updated your dependencies? This can often resolve issues.'
options: options:
- label: Yes, I've updated all my dependencies. - label: Yes, I've updated all my dependencies.
required: true required: true
- type: markdown - type: markdown
attributes: attributes:
value: "Thank you for helping us improve our open-source projects by reporting this issue!" value: 'Thank you for helping us improve our open-source projects by reporting this issue!'

View File

@ -1,14 +1,14 @@
name: Bug Report (Tiptap Pro) name: Bug Report (Tiptap Pro)
title: "[PRO]: " title: '[PRO]: '
description: If you've encountered a bug with Tiptap Pro features, please report it here. description: If you've encountered a bug with Tiptap Pro features, please report it here.
labels: labels:
- "Type: Bug" - 'Type: Bug'
- "Category: Pro" - 'Category: Pro'
- "Status: New" - 'Status: New'
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: "### Please ensure this issue is for Tiptap Pro features only. Provide as much detail as possible to help us identify the issue quickly." value: '### Please ensure this issue is for Tiptap Pro features only. Provide as much detail as possible to help us identify the issue quickly.'
- type: input - type: input
id: packages id: packages
attributes: attributes:
@ -30,7 +30,7 @@ body:
attributes: attributes:
label: Description of the Bug label: Description of the Bug
description: Provide a clear and concise description of what the bug is. description: Provide a clear and concise description of what the bug is.
placeholder: "The issue occurs when..." placeholder: 'The issue occurs when...'
validations: validations:
required: true required: true
- type: dropdown - type: dropdown
@ -48,10 +48,10 @@ body:
required: true required: true
- type: markdown - type: markdown
attributes: attributes:
value: "### Helpful Code Examples" value: '### Helpful Code Examples'
- type: markdown - type: markdown
attributes: attributes:
value: "Providing a CodeSandbox link is crucial for diagnosing issues faster. Below are templates you might use:" value: 'Providing a CodeSandbox link is crucial for diagnosing issues faster. Below are templates you might use:'
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
@ -63,7 +63,7 @@ body:
id: sandbox id: sandbox
attributes: attributes:
label: Code Example (Preferred) label: Code Example (Preferred)
description: "Provide a link to a CodeSandbox or other code repository to help us reproduce the issue." description: 'Provide a link to a CodeSandbox or other code repository to help us reproduce the issue.'
placeholder: https://codesandbox.io/s/example placeholder: https://codesandbox.io/s/example
validations: validations:
required: false required: false
@ -78,14 +78,14 @@ body:
id: context id: context
attributes: attributes:
label: Additional Context (Optional) label: Additional Context (Optional)
description: "Add any other context about the problem here, like screenshots or videos." description: 'Add any other context about the problem here, like screenshots or videos.'
- type: checkboxes - type: checkboxes
attributes: attributes:
label: Dependency Updates label: Dependency Updates
description: "Have you updated your dependencies? It can often resolve issues." description: 'Have you updated your dependencies? It can often resolve issues.'
options: options:
- label: Yes, I've updated all my dependencies. - label: Yes, I've updated all my dependencies.
required: true required: true
- type: markdown - type: markdown
attributes: attributes:
value: "Thank you for contributing to Tiptap Pro by reporting this issue!" value: 'Thank you for contributing to Tiptap Pro by reporting this issue!'

View File

@ -1,10 +1,10 @@
name: Documentation feedback name: Documentation feedback
description: Share what we need to explain better. description: Share what we need to explain better.
title: "[Documentation]: " title: '[Documentation]: '
labels: labels:
- "Type: Documentation" - 'Type: Documentation'
- "Category: Open Source" - 'Category: Open Source'
- "Status: New" - 'Status: New'
body: body:
- type: input - type: input
id: url id: url
@ -17,28 +17,28 @@ body:
id: part-of-the-documentation id: part-of-the-documentation
attributes: attributes:
label: What part of the documentation needs improvement? label: What part of the documentation needs improvement?
placeholder: "Ive read the following page of the documentation …" placeholder: 'Ive read the following page of the documentation …'
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: good-parts id: good-parts
attributes: attributes:
label: What is helpful about that part? label: What is helpful about that part?
placeholder: "I think this part is really good: …" placeholder: 'I think this part is really good: …'
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: bad-parts id: bad-parts
attributes: attributes:
label: What is hard to understand, missing or misleading? label: What is hard to understand, missing or misleading?
placeholder: "But you really need to improve …" placeholder: 'But you really need to improve …'
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: context id: context
attributes: attributes:
label: Anything to add? (optional) label: Anything to add? (optional)
description: "Add any other context or screenshots here." description: 'Add any other context or screenshots here.'
- type: markdown - type: markdown
attributes: attributes:
value: | value: |

View File

@ -3,7 +3,6 @@
version: 2 version: 2
updates: updates:
- package-ecosystem: 'github-actions' - package-ecosystem: 'github-actions'
directory: '/' directory: '/'
open-pull-requests-limit: 10 open-pull-requests-limit: 10
@ -12,4 +11,3 @@ updates:
day: 'monday' day: 'monday'
reviewers: reviewers:
- 'bdbch' - 'bdbch'

View File

@ -1,19 +1,25 @@
## Changes Overview ## Changes Overview
<!-- Briefly describe your changes. --> <!-- Briefly describe your changes. -->
## Implementation Approach ## Implementation Approach
<!-- Describe your approach to implementing these changes. Keep it concise. --> <!-- Describe your approach to implementing these changes. Keep it concise. -->
## Testing Done ## Testing Done
<!-- Explain how you tested these changes. Link to test scenarios or specs if relevant. --> <!-- Explain how you tested these changes. Link to test scenarios or specs if relevant. -->
## Verification Steps ## Verification Steps
<!-- Describe steps reviewers can take to verify the functionality of your changes. --> <!-- Describe steps reviewers can take to verify the functionality of your changes. -->
## Additional Notes ## Additional Notes
<!-- Add any other notes or screenshots about the PR here. --> <!-- Add any other notes or screenshots about the PR here. -->
## Checklist ## Checklist
- [ ] I have created a [changeset](https://github.com/changesets/changesets) for this PR if necessary. - [ ] I have created a [changeset](https://github.com/changesets/changesets) for this PR if necessary.
- [ ] My changes do not break the library. - [ ] My changes do not break the library.
- [ ] I have added tests where applicable. - [ ] I have added tests where applicable.
@ -21,4 +27,5 @@
- [ ] I have fixed any lint issues. - [ ] I have fixed any lint issues.
## Related Issues ## Related Issues
<!-- Link any related issues here --> <!-- Link any related issues here -->

View File

@ -73,16 +73,16 @@ jobs:
matrix: matrix:
node-version: [20] node-version: [20]
test-spec: test-spec:
- { name: "Integration", spec: "./tests/cypress/integration/**/*.spec.{js,ts}" } - { name: 'Integration', spec: './tests/cypress/integration/**/*.spec.{js,ts}' }
#- { name: "Demos/Commands", spec: "./demos/src/Commands/**/*.spec.{js,ts}" } #- { name: "Demos/Commands", spec: "./demos/src/Commands/**/*.spec.{js,ts}" }
- { name: "Demos/Examples", spec: "./demos/src/Examples/**/*.spec.{js,ts}" } - { name: 'Demos/Examples', spec: './demos/src/Examples/**/*.spec.{js,ts}' }
- { name: "Demos/Experiments", spec: "./demos/src/Experiments/**/*.spec.{js,ts}" } - { name: 'Demos/Experiments', spec: './demos/src/Experiments/**/*.spec.{js,ts}' }
- { name: "Demos/Extensions", spec: "./demos/src/Extensions/**/*.spec.{js,ts}" } - { name: 'Demos/Extensions', spec: './demos/src/Extensions/**/*.spec.{js,ts}' }
- { name: "Demos/GuideContent", spec: "./demos/src/GuideContent/**/*.spec.{js,ts}" } - { name: 'Demos/GuideContent', spec: './demos/src/GuideContent/**/*.spec.{js,ts}' }
- { name: "Demos/GuideGettingStarted", spec: "./demos/src/GuideGettingStarted/**/*.spec.{js,ts}" } - { name: 'Demos/GuideGettingStarted', spec: './demos/src/GuideGettingStarted/**/*.spec.{js,ts}' }
#- { name: "Demos/GuideNodeViews", "./demos/src/GuideNodeViews/**/*.spec.{js,ts}" } #- { name: "Demos/GuideNodeViews", "./demos/src/GuideNodeViews/**/*.spec.{js,ts}" }
- { name: "Demos/Marks", spec: "./demos/src/Marks/**/*.spec.{js,ts}" } - { name: 'Demos/Marks', spec: './demos/src/Marks/**/*.spec.{js,ts}' }
- { name: "Demos/Nodes", spec: "./demos/src/Nodes/**/*.spec.{js,ts}" } - { name: 'Demos/Nodes', spec: './demos/src/Nodes/**/*.spec.{js,ts}' }
#- { name: "Demos/Overview", spec: "./demos/src/Overview/**/*.spec.{js,ts}" } #- { name: "Demos/Overview", spec: "./demos/src/Overview/**/*.spec.{js,ts}" }
steps: steps:

View File

@ -34,7 +34,7 @@ jobs:
- name: Setup Node ${{ matrix.node-version }} - name: Setup Node ${{ matrix.node-version }}
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
registry-url: 'https://registry.npmjs.org/' registry-url: 'https://registry.npmjs.org/'
- name: Load cached dependencies - name: Load cached dependencies

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
# Tiptap Editor # Tiptap Editor
The Tiptap Editor is a headless, framework-agnostic rich text editor that's customizable and extendable through extensions. Its headless nature means it comes without a set user interface, offering full design freedom (for a jumpstart, see linked [UI templates](#examples-codesandbox-and-ui-templates) below). Tiptap is based on the highly reliable [ProseMirror](https://github.com/ProseMirror/prosemirror) library. The Tiptap Editor is a headless, framework-agnostic rich text editor that's customizable and extendable through extensions. Its headless nature means it comes without a set user interface, offering full design freedom (for a jumpstart, see linked [UI templates](#examples-codesandbox-and-ui-templates) below). Tiptap is based on the highly reliable [ProseMirror](https://github.com/ProseMirror/prosemirror) library.
Tiptap Editor is complemented by the collaboration open-source backend [Hocuspocus](https://github.com/ueberdosis/hocuspocus). Both the Editor and Hocuspocus form the foundation of the [Tiptap Suite](https://tiptap.dev/). Tiptap Editor is complemented by the collaboration open-source backend [Hocuspocus](https://github.com/ueberdosis/hocuspocus). Both the Editor and Hocuspocus form the foundation of the [Tiptap Suite](https://tiptap.dev/).
@ -13,12 +14,12 @@ Tiptap Editor is complemented by the collaboration open-source backend [Hocuspoc
### How does the Tiptap Editor work? ### How does the Tiptap Editor work?
- **Headless Framework:** Tiptap does not rely on a user interface. So there is no need for class overrides or code hacks. If you do need an example UI feel free to browse our [UI templates](#examples-codesandbox-and-ui-templates) linked below. - **Headless Framework:** Tiptap does not rely on a user interface. So there is no need for class overrides or code hacks. If you do need an example UI feel free to browse our [UI templates](#examples-codesandbox-and-ui-templates) linked below.
- **Framework-agnostic:** The Tiptap Editor is designed to work across different frontend frameworks. This means whether you're using Vue, React, or plain JavaScript, Tiptap integrates without compatibility issues. - **Framework-agnostic:** The Tiptap Editor is designed to work across different frontend frameworks. This means whether you're using Vue, React, or plain JavaScript, Tiptap integrates without compatibility issues.
- **Extension based:** Extensions in Tiptap allow for a tailored editing experience, from simple text styling to advanced features like drag-and-drop block editing. You have the option to choose from over 100 extensions available in the [documentation](https://tiptap.dev/docs/editor/extensions) and [community](https://github.com/ueberdosis/awesome-tiptap/#community-extensions) to enhance your editor's functionality. - **Extension based:** Extensions in Tiptap allow for a tailored editing experience, from simple text styling to advanced features like drag-and-drop block editing. You have the option to choose from over 100 extensions available in the [documentation](https://tiptap.dev/docs/editor/extensions) and [community](https://github.com/ueberdosis/awesome-tiptap/#community-extensions) to enhance your editor's functionality.
- **Customize your UX:** The editor was built to give you control to define your own [extensions](https://tiptap.dev/docs/editor/guide/custom-extensions) and [nodes](https://tiptap.dev/docs/editor/api/nodes). - **Customize your UX:** The editor was built to give you control to define your own [extensions](https://tiptap.dev/docs/editor/guide/custom-extensions) and [nodes](https://tiptap.dev/docs/editor/api/nodes).
### Editor Pro Extensions ### Editor Pro Extensions
The **Pro Extensions** are a set of advanced functionalities that enhance the capabilities of the Tiptap Editor. They are additional features that can be integrated into the base editor to provide more sophisticated editing options. The **Pro Extensions** are a set of advanced functionalities that enhance the capabilities of the Tiptap Editor. They are additional features that can be integrated into the base editor to provide more sophisticated editing options.
Key functionalities include collaborative editing, which allows multiple users to edit documents simultaneously, drag-and-drop file management for easier handling of documents and media, and unique node ID assignment. Review the docs right [here](https://tiptap.dev/docs/editor/extensions). Key functionalities include collaborative editing, which allows multiple users to edit documents simultaneously, drag-and-drop file management for easier handling of documents and media, and unique node ID assignment. Review the docs right [here](https://tiptap.dev/docs/editor/extensions).
@ -26,28 +27,35 @@ Key functionalities include collaborative editing, which allows multiple users t
Pro Extensions are free with a [Tiptap account](https://cloud.tiptap.dev/pro-extensions). Once signed up, review the guide in your account. Pro Extensions are free with a [Tiptap account](https://cloud.tiptap.dev/pro-extensions). Once signed up, review the guide in your account.
### Make your editor collaborative ### Make your editor collaborative
Interested in collaborative editing? Check out our open-source package [Hocuspocus](https://github.com/ueberdosis/hocuspocus) - a collaboration backend built around the CRDT power of [Yjs](https://github.com/yjs/yjs). Hocuspocus serves as the backbone for the [Tiptap Suite](https://tiptap.dev/). Interested in collaborative editing? Check out our open-source package [Hocuspocus](https://github.com/ueberdosis/hocuspocus) - a collaboration backend built around the CRDT power of [Yjs](https://github.com/yjs/yjs). Hocuspocus serves as the backbone for the [Tiptap Suite](https://tiptap.dev/).
## Documentation ## Documentation
For more detailed information, make sure to check out our [documentation](https://tiptap.dev/docs/editor/installation). If you encounter any problems or have suggestions for our system, please open an issue. For more detailed information, make sure to check out our [documentation](https://tiptap.dev/docs/editor/installation). If you encounter any problems or have suggestions for our system, please open an issue.
### Examples, CodeSandbox and UI Templates ### Examples, CodeSandbox and UI Templates
Have a look at the [examples to see Tiptap in action](https://tiptap.dev/examples) or review and fork our codesandboxes. Have a look at the [examples to see Tiptap in action](https://tiptap.dev/examples) or review and fork our codesandboxes.
- [Basic example of the Tiptap editor.](https://codesandbox.io/p/devbox/editor-9x9dkd?embed=1&file=%2Fsrc%2FApp.js) - [Basic example of the Tiptap editor.](https://codesandbox.io/p/devbox/editor-9x9dkd?embed=1&file=%2Fsrc%2FApp.js)
- [Collaboration ready Tiptap CodeSandbox](https://codesandbox.io/p/devbox/collaboration-4stk94) - [Collaboration ready Tiptap CodeSandbox](https://codesandbox.io/p/devbox/collaboration-4stk94)
- React notion-like block editor template: [Demo](https://templates.tiptap.dev/) - React notion-like block editor template: [Demo](https://templates.tiptap.dev/)
## About Tiptap ## About Tiptap
Tiptap is a collection of developer components based on open-source technology, forming the basis of our advanced, paid features. It includes the open-source editor component, collaboration features, Content AI, and Tiptap Cloud. We are developing open-source products that also shape our paid features. We're committed to improving both, ensuring quality and reliability in every update. Tiptap is a collection of developer components based on open-source technology, forming the basis of our advanced, paid features. It includes the open-source editor component, collaboration features, Content AI, and Tiptap Cloud. We are developing open-source products that also shape our paid features. We're committed to improving both, ensuring quality and reliability in every update.
For more details, visit the Tiptap [documentation](https://tiptap.dev/docs/editor/introduction) or [website](https://tiptap.dev/). For more details, visit the Tiptap [documentation](https://tiptap.dev/docs/editor/introduction) or [website](https://tiptap.dev/).
### Community ### Community
For help, discussion about best practices, or any other conversation that would benefit from being searchable: For help, discussion about best practices, or any other conversation that would benefit from being searchable:
[Discuss Tiptap on GitHub](https://github.com/ueberdosis/tiptap/discussions) [Discuss Tiptap on GitHub](https://github.com/ueberdosis/tiptap/discussions)
### Sponsors 💖 ### Sponsors 💖
<table> <table>
<tr> <tr>
<td align="center"> <td align="center">
@ -102,9 +110,11 @@ For help, discussion about best practices, or any other conversation that would
[iFixit](https://www.ifixit.com/), [ApostropheCMS](https://apostrophecms.com/), [Novadiscovery](http://www.novadiscovery.com/), [Omics Data Automation](https://www.omicsautomation.com), [Flow Mobile](https://www.flowmobile.app/), [DocIQ](https://www.dociq.io/) and [hundreds of awesome individuals](https://github.com/sponsors/ueberdosis). [iFixit](https://www.ifixit.com/), [ApostropheCMS](https://apostrophecms.com/), [Novadiscovery](http://www.novadiscovery.com/), [Omics Data Automation](https://www.omicsautomation.com), [Flow Mobile](https://www.flowmobile.app/), [DocIQ](https://www.dociq.io/) and [hundreds of awesome individuals](https://github.com/sponsors/ueberdosis).
### Contributing ### Contributing
Feel like adding some magic of your own to Tiptap Editor Core? We welcome contributions! Please see our [CONTRIBUTING](CONTRIBUTING.md) guidelines for how to get started. Feel like adding some magic of your own to Tiptap Editor Core? We welcome contributions! Please see our [CONTRIBUTING](CONTRIBUTING.md) guidelines for how to get started.
### Contributors ### Contributors
[Sam Willis](https://github.com/samwillis), [Sam Willis](https://github.com/samwillis),
[Brian Hung](https://github.com/BrianHung), [Brian Hung](https://github.com/BrianHung),
[Dirk Holtwick](https://github.com/holtwick), [Dirk Holtwick](https://github.com/holtwick),
@ -118,4 +128,5 @@ Feel like adding some magic of your own to Tiptap Editor Core? We welcome contri
[Gregor](https://github.com/gambolputty) and [many more](../../contributors). [Gregor](https://github.com/gambolputty) and [many more](../../contributors).
## License ## License
The MIT License (MIT). Please see [License File](LICENSE.md) for more information. The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

View File

@ -1,10 +1,4 @@
module.exports = { module.exports = {
presets: [ presets: ['@babel/preset-env', '@babel/preset-react'],
'@babel/preset-env', plugins: ['@babel/plugin-proposal-nullish-coalescing-operator', '@babel/plugin-proposal-optional-chaining'],
'@babel/preset-react',
],
plugins: [
'@babel/plugin-proposal-nullish-coalescing-operator',
'@babel/plugin-proposal-optional-chaining',
],
} }

View File

@ -1,10 +1,9 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"/> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="refresh" content="0; url=/preview/" /> <meta http-equiv="refresh" content="0; url=/preview/" />
</head> </head>
<body> <body></body>
</body>
</html> </html>

View File

@ -6,23 +6,18 @@
:key="index" :key="index"
@click="setTab(language.name)" @click="setTab(language.name)"
class="px-3 py-2 text-sm text-white leading-[125%] font-semibold rounded-[0.625rem] transition-all" class="px-3 py-2 text-sm text-white leading-[125%] font-semibold rounded-[0.625rem] transition-all"
:class="[currentTab === language.name :class="[
? 'opacity-100 bg-[#1C1917]' currentTab === language.name
: 'opacity-50 bg-transparent hover:opacity-100 hover:bg-[#1C1917]' ? 'opacity-100 bg-[#1C1917]'
: 'opacity-50 bg-transparent hover:opacity-100 hover:bg-[#1C1917]',
]" ]"
> >
{{ language.name }} {{ language.name }}
</button> </button>
</div> </div>
<div class="overflow-hidden"> <div class="overflow-hidden">
<div <div class="bg-white" :class="[hidePreview ? 'hidden' : '']">
class="bg-white" <demo-frame :src="currentIframeUrl" :key="currentIframeUrl" />
:class="[hidePreview ? 'hidden' : '']"
>
<demo-frame
:src="currentIframeUrl"
:key="currentIframeUrl"
/>
</div> </div>
<div class="text-white bg-black" v-if="!hideSource && currentFile"> <div class="text-white bg-black" v-if="!hideSource && currentFile">
@ -30,9 +25,10 @@
<div class="flex flex-auto px-4 border-b-2 border-gray-800"> <div class="flex flex-auto px-4 border-b-2 border-gray-800">
<button <button
class="inline-flex relative mr-4 py-2 pb-[calc(0.3rem + 2px)] mb-[-2px] border-b-2 border-transparent font-mono text-sm whitespace-nowrap" class="inline-flex relative mr-4 py-2 pb-[calc(0.3rem + 2px)] mb-[-2px] border-b-2 border-transparent font-mono text-sm whitespace-nowrap"
:class="[!showDebug && currentFile.content === file.content :class="[
? 'text-white border-white font-bold' !showDebug && currentFile.content === file.content
: 'text-gray-400' ? 'text-white border-white font-bold'
: 'text-gray-400',
]" ]"
v-for="(file, index) in source" v-for="(file, index) in source"
:key="index" :key="index"
@ -44,10 +40,7 @@
<button <button
v-if="debugJSON" v-if="debugJSON"
class="inline-flex relative py-2 pb-[calc(0.3rem + 2px)] mb-[-2px] border-b-2 border-transparent font-mono text-sm ml-auto" class="inline-flex relative py-2 pb-[calc(0.3rem + 2px)] mb-[-2px] border-b-2 border-transparent font-mono text-sm ml-auto"
:class="[showDebug :class="[showDebug ? 'text-white border-white font-bold' : 'text-gray-400']"
? 'text-white border-white font-bold'
: 'text-gray-400'
]"
@click="showDebug = !showDebug" @click="showDebug = !showDebug"
> >
Inspect Inspect
@ -67,9 +60,7 @@
<a class="flex-shrink min-w-0 overflow-hidden overflow-ellipsis whitespace-nowrap" :href="currentIframeUrl"> <a class="flex-shrink min-w-0 overflow-hidden overflow-ellipsis whitespace-nowrap" :href="currentIframeUrl">
{{ name }}/{{ currentTab }} {{ name }}/{{ currentTab }}
</a> </a>
<a class="pl-4 whitespace-nowrap" :href="githubUrl" target="_blank"> <a class="pl-4 whitespace-nowrap" :href="githubUrl" target="_blank"> Edit on GitHub </a>
Edit on GitHub
</a>
</div> </div>
</div> </div>
</div> </div>
@ -128,9 +119,7 @@ export default {
}, },
query() { query() {
return Object.fromEntries(Object return Object.fromEntries(Object.entries(this.$route.query).map(([key, value]) => [key, this.fromString(value)]))
.entries(this.$route.query)
.map(([key, value]) => [key, this.fromString(value)]))
}, },
inline() { inline() {
@ -220,9 +209,10 @@ export default {
mounted() { mounted() {
// TODO: load language from url params // TODO: load language from url params
const intitialTab = localStorage.tab && this.tabs.some(tab => tab.name === localStorage.tab) const intitialTab =
? localStorage.tab localStorage.tab && this.tabs.some(tab => tab.name === localStorage.tab)
: this.sortedTabs[0]?.name ? localStorage.tab
: this.sortedTabs[0]?.name
this.setTab(intitialTab, false) this.setTab(intitialTab, false)

View File

@ -1,21 +1,16 @@
<template> <template>
<div class="flex flex-col relative min-h-[5rem]"> <div class="flex flex-col relative min-h-[5rem]">
<div class="absolute top-0 left-0 w-full h-full flex justify-center items-center pointer-events-none" v-if="isLoading"> <div
<svg class="absolute top-0 left-0 w-full h-full flex justify-center items-center pointer-events-none"
class="animate-spin -ml-1 mr-3 h-5 w-5" v-if="isLoading"
xmlns="http://www.w3.org/2000/svg" >
fill="none" <svg class="animate-spin -ml-1 mr-3 h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
viewBox="0 0 24 24" <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
> <path
<circle class="opacity-75"
class="opacity-25" fill="currentColor"
cx="12" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/> />
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg> </svg>
</div> </div>
<iframe <iframe
@ -53,6 +48,4 @@ export default {
} }
</script> </script>
<style> <style></style>
</style>

View File

@ -1,8 +1,8 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"/> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Preview</title> <title>Preview</title>
</head> </head>
<body> <body>

View File

@ -9,17 +9,16 @@ import { createRouter, createWebHistory } from 'vue-router'
import Demo from './Demo.vue' import Demo from './Demo.vue'
import App from './index.vue' import App from './index.vue'
const routes = demos const routes = demos.map(({ name, tabs }) => {
.map(({ name, tabs }) => { return {
return { path: `/${name}`,
path: `/${name}`, component: Demo,
component: Demo, props: {
props: { name,
name, tabs,
tabs, },
}, }
} })
})
const router = createRouter({ const router = createRouter({
history: createWebHistory('preview'), history: createWebHistory('preview'),
@ -30,25 +29,28 @@ createApp(App)
.directive('resize', { .directive('resize', {
beforeMount: (el, { value = {} }) => { beforeMount: (el, { value = {} }) => {
el.addEventListener('load', () => { el.addEventListener('load', () => {
iframeResize({ iframeResize(
...value, {
// messageCallback(messageData) { ...value,
// if (messageData.message.type !== 'resize') { // messageCallback(messageData) {
// return // if (messageData.message.type !== 'resize') {
// } // return
// }
// const style = window.getComputedStyle(el.parentElement) // const style = window.getComputedStyle(el.parentElement)
// const maxHeight = parseInt(style.getPropertyValue('max-height'), 10) // const maxHeight = parseInt(style.getPropertyValue('max-height'), 10)
// if (messageData.message.height > maxHeight) { // if (messageData.message.height > maxHeight) {
// el.setAttribute('scrolling', 'auto') // el.setAttribute('scrolling', 'auto')
// } else { // } else {
// el.setAttribute('scrolling', 'no') // el.setAttribute('scrolling', 'no')
// } // }
// el?.iFrameResizer?.resize?.() // el?.iFrameResizer?.resize?.()
// }, // },
}, el) },
el,
)
}) })
}, },
unmounted(el) { unmounted(el) {

View File

@ -5,7 +5,6 @@ import { ref } from 'vue'
const showDemoList = process.env.NODE_ENV === 'development' const showDemoList = process.env.NODE_ENV === 'development'
const searchValue = ref('') const searchValue = ref('')
</script> </script>
<template> <template>
@ -16,17 +15,16 @@ const searchValue = ref('')
placeholder="Search for a demo..." placeholder="Search for a demo..."
autofocus autofocus
v-model="searchValue" v-model="searchValue"
> />
<ul v-if="showDemoList || listing"> <ul v-if="showDemoList || listing">
<li <li
class="p-5 border-b-2 border-black" class="p-5 border-b-2 border-black"
v-for="route in $router.options.routes.filter(route => searchValue === ''? true : route.props.name.toLowerCase().includes(searchValue.toLowerCase()))" v-for="route in $router.options.routes.filter(route =>
searchValue === '' ? true : route.props.name.toLowerCase().includes(searchValue.toLowerCase()),
)"
:key="route.path" :key="route.path"
> >
<router-link <router-link class="block mb-2 font-medium" :to="route.path">
class="block mb-2 font-medium"
:to="route.path"
>
{{ route.props.name }} {{ route.props.name }}
</router-link> </router-link>
@ -77,9 +75,7 @@ export default {
computed: { computed: {
query() { query() {
return Object.fromEntries(Object return Object.fromEntries(Object.entries(this.$route.query).map(([key, value]) => [key, this.fromString(value)]))
.entries(this.$route.query)
.map(([key, value]) => [key, this.fromString(value)]))
}, },
listing() { listing() {

View File

@ -9,17 +9,7 @@ async function init() {
highlighter = await shiki.createHighlighter({ highlighter = await shiki.createHighlighter({
themes: ['material-theme-darker'], themes: ['material-theme-darker'],
langs: [ langs: ['html', 'js', 'jsx', 'ts', 'tsx', 'css', 'vue-html', 'vue', 'scss'],
'html',
'js',
'jsx',
'ts',
'tsx',
'css',
'vue-html',
'vue',
'scss',
],
}) })
return highlighter return highlighter

View File

@ -4,92 +4,98 @@
@font-face { @font-face {
font-family: 'Inter'; font-family: 'Inter';
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
font-display: swap; font-display: swap;
src: url("https://rsms.me/inter/font-files/Inter-Regular.woff2?v=3.19") format("woff2"), src:
url("https://rsms.me/inter/font-files/Inter-Regular.woff?v=3.19") format("woff"); url('https://rsms.me/inter/font-files/Inter-Regular.woff2?v=3.19') format('woff2'),
url('https://rsms.me/inter/font-files/Inter-Regular.woff?v=3.19') format('woff');
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: 'Inter';
font-style: italic; font-style: italic;
font-weight: 400; font-weight: 400;
font-display: swap; font-display: swap;
src: url("https://rsms.me/inter/font-files/Inter-Italic.woff2?v=3.19") format("woff2"), src:
url("https://rsms.me/inter/font-files/Inter-Italic.woff?v=3.19") format("woff"); url('https://rsms.me/inter/font-files/Inter-Italic.woff2?v=3.19') format('woff2'),
url('https://rsms.me/inter/font-files/Inter-Italic.woff?v=3.19') format('woff');
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: 'Inter';
font-style: normal; font-style: normal;
font-weight: 500; font-weight: 500;
font-display: swap; font-display: swap;
src: url("https://rsms.me/inter/font-files/Inter-Medium.woff2?v=3.19") format("woff2"), src:
url("https://rsms.me/inter/font-files/Inter-Medium.woff?v=3.19") format("woff"); url('https://rsms.me/inter/font-files/Inter-Medium.woff2?v=3.19') format('woff2'),
url('https://rsms.me/inter/font-files/Inter-Medium.woff?v=3.19') format('woff');
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: 'Inter';
font-style: italic; font-style: italic;
font-weight: 500; font-weight: 500;
font-display: swap; font-display: swap;
src: url("https://rsms.me/inter/font-files/Inter-MediumItalic.woff2?v=3.19") format("woff2"), src:
url("https://rsms.me/inter/font-files/Inter-MediumItalic.woff?v=3.19") format("woff"); url('https://rsms.me/inter/font-files/Inter-MediumItalic.woff2?v=3.19') format('woff2'),
url('https://rsms.me/inter/font-files/Inter-MediumItalic.woff?v=3.19') format('woff');
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: 'Inter';
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
font-display: swap; font-display: swap;
src: url("https://rsms.me/inter/font-files/Inter-SemiBold.woff2?v=3.19") format("woff2"), src:
url("https://rsms.me/inter/font-files/Inter-SemiBold.woff?v=3.19") format("woff"); url('https://rsms.me/inter/font-files/Inter-SemiBold.woff2?v=3.19') format('woff2'),
url('https://rsms.me/inter/font-files/Inter-SemiBold.woff?v=3.19') format('woff');
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: 'Inter';
font-style: italic; font-style: italic;
font-weight: 600; font-weight: 600;
font-display: swap; font-display: swap;
src: url("https://rsms.me/inter/font-files/Inter-SemiBoldItalic.woff2?v=3.19") format("woff2"), src:
url("https://rsms.me/inter/font-files/Inter-SemiBoldItalic.woff?v=3.19") format("woff"); url('https://rsms.me/inter/font-files/Inter-SemiBoldItalic.woff2?v=3.19') format('woff2'),
url('https://rsms.me/inter/font-files/Inter-SemiBoldItalic.woff?v=3.19') format('woff');
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: 'Inter';
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
font-display: swap; font-display: swap;
src: url("https://rsms.me/inter/font-files/Inter-Bold.woff2?v=3.19") format("woff2"), src:
url("https://rsms.me/inter/font-files/Inter-Bold.woff?v=3.19") format("woff"); url('https://rsms.me/inter/font-files/Inter-Bold.woff2?v=3.19') format('woff2'),
url('https://rsms.me/inter/font-files/Inter-Bold.woff?v=3.19') format('woff');
} }
@font-face { @font-face {
font-family: 'Inter'; font-family: 'Inter';
font-style: italic; font-style: italic;
font-weight: 700; font-weight: 700;
font-display: swap; font-display: swap;
src: url("https://rsms.me/inter/font-files/Inter-BoldItalic.woff2?v=3.19") format("woff2"), src:
url("https://rsms.me/inter/font-files/Inter-BoldItalic.woff?v=3.19") format("woff"); url('https://rsms.me/inter/font-files/Inter-BoldItalic.woff2?v=3.19') format('woff2'),
url('https://rsms.me/inter/font-files/Inter-BoldItalic.woff?v=3.19') format('woff');
} }
@font-face { @font-face {
font-family: 'JetBrains Mono'; font-family: 'JetBrains Mono';
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
font-display: swap; font-display: swap;
src: src:
url("https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff2/JetBrainsMono-Regular.woff2") format("woff2"), url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff2/JetBrainsMono-Regular.woff2') format('woff2'),
url("https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff2/JetBrainsMono-Regular.woff") format("woff"), url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff2/JetBrainsMono-Regular.woff') format('woff');
;
} }
@font-face { @font-face {
font-family: 'JetBrains Mono'; font-family: 'JetBrains Mono';
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
font-display: swap; font-display: swap;
src: src:
url("https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff2/JetBrainsMono-Bold.woff2") format("woff2"), url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff2/JetBrainsMono-Bold.woff2') format('woff2'),
url("https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff2/JetBrainsMono-Bold.woff") format("woff"), url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff2/JetBrainsMono-Bold.woff') format('woff');
;
} }
::-webkit-scrollbar { ::-webkit-scrollbar {

View File

@ -18,21 +18,18 @@ export default function init(name: string, source: any) {
const root = document.getElementById('app') const root = document.getElementById('app')
if (root) { if (root) {
createRoot(root) createRoot(root).render(React.createElement(module.default))
.render(React.createElement(module.default))
} }
debug() debug()
}) })
.catch(() => { .catch(() => {
import(`../src/${demoCategory}/${demoName}/${frameworkName}/index.jsx`) import(`../src/${demoCategory}/${demoName}/${frameworkName}/index.jsx`).then(module => {
.then(module => { const root = document.getElementById('app')
const root = document.getElementById('app')
if (root) { if (root) {
createRoot(root) createRoot(root).render(React.createElement(module.default))
.render(React.createElement(module.default)) }
} debug()
debug() })
})
}) })
} }

View File

@ -1,22 +1,22 @@
/* Base HTML and global element styles*/ /* Base HTML and global element styles*/
:root { :root {
--white: #FFF; --white: #fff;
--black: #2E2B29; --black: #2e2b29;
--black-contrast: #110F0E; --black-contrast: #110f0e;
--gray-1: rgba(61, 37, 20, 0.05); --gray-1: rgba(61, 37, 20, 0.05);
--gray-2: rgba(61, 37, 20, 0.08); --gray-2: rgba(61, 37, 20, 0.08);
--gray-3: rgba(61, 37, 20, 0.12); --gray-3: rgba(61, 37, 20, 0.12);
--gray-4: rgba(53, 38, 28, 0.30); --gray-4: rgba(53, 38, 28, 0.3);
--gray-5: rgba(28, 25, 23, 0.60); --gray-5: rgba(28, 25, 23, 0.6);
--green: #22C55E; --green: #22c55e;
--purple: #6A00F5; --purple: #6a00f5;
--purple-contrast: #5800CC; --purple-contrast: #5800cc;
--purple-light: rgba(88, 5, 255, 0.05); --purple-light: rgba(88, 5, 255, 0.05);
--yellow-contrast: #FACC15; --yellow-contrast: #facc15;
--yellow: rgba(250, 204, 21, 0.4); --yellow: rgba(250, 204, 21, 0.4);
--yellow-light: #FFFAE5; --yellow-light: #fffae5;
--red: #FF5C33; --red: #ff5c33;
--red-light: #FFEBE5; --red-light: #ffebe5;
--shadow: 0px 12px 33px 0px rgba(0, 0, 0, 0.06), 0px 3.618px 9.949px 0px rgba(0, 0, 0, 0.04); --shadow: 0px 12px 33px 0px rgba(0, 0, 0, 0.06), 0px 3.618px 9.949px 0px rgba(0, 0, 0, 0.04);
} }
@ -27,7 +27,22 @@
} }
html { html {
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-family:
Inter,
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
'Helvetica Neue',
Arial,
'Noto Sans',
sans-serif,
'Apple Color Emoji',
'Segoe UI Emoji',
'Segoe UI Symbol',
'Noto Color Emoji';
line-height: 1.5; line-height: 1.5;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
@ -104,7 +119,7 @@ textarea {
line-height: 1.15; line-height: 1.15;
margin: none; margin: none;
padding: 0.375rem 0.625rem; padding: 0.375rem 0.625rem;
transition: all 0.2s cubic-bezier(0.65,0.05,0.36,1); transition: all 0.2s cubic-bezier(0.65, 0.05, 0.36, 1);
&:hover { &:hover {
background-color: var(--gray-3); background-color: var(--gray-3);
@ -150,7 +165,7 @@ select:not([disabled]) {
cursor: pointer; cursor: pointer;
} }
input[type="text"], input[type='text'],
textarea { textarea {
background-color: unset; background-color: unset;
border: 1px solid var(--gray-3); border: 1px solid var(--gray-3);
@ -166,7 +181,8 @@ textarea {
border-color: var(--gray-4); border-color: var(--gray-4);
} }
&:focus-visible, &:focus { &:focus-visible,
&:focus {
border-color: var(--purple); border-color: var(--purple);
outline: none; outline: none;
} }
@ -229,7 +245,7 @@ form {
background-color: var(--purple-light); background-color: var(--purple-light);
&::after { &::after {
content: ""; content: '';
background-image: url("data:image/svg+xml;utf8,<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='28px' height='30px' viewBox='0 0 24 30' style='enable-background:new 0 0 50 50;' xml:space='preserve'><rect x='0' y='10' width='6' height='10' fill='%236A00F5' rx='3' ry='3'><animateTransform attributeType='xml' attributeName='transform' type='translate' values='0 0; 0 5; 0 -5; 0 0' begin='0' dur='0.6s' repeatCount='indefinite'/></rect><rect x='10' y='10' width='6' height='10' fill='%236A00F5' rx='3' ry='3'><animateTransform attributeType='xml' attributeName='transform' type='translate' values='0 0; 0 5; 0 -5; 0 0' begin='0.2s' dur='0.6s' repeatCount='indefinite'/></rect><rect x='20' y='10' width='6' height='10' fill='%236A00F5' rx='3' ry='3'><animateTransform attributeType='xml' attributeName='transform' type='translate' values='0 0; 0 5; 0 -5; 0 0' begin='0.4s' dur='0.6s' repeatCount='indefinite'/></rect></svg>"); background-image: url("data:image/svg+xml;utf8,<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='28px' height='30px' viewBox='0 0 24 30' style='enable-background:new 0 0 50 50;' xml:space='preserve'><rect x='0' y='10' width='6' height='10' fill='%236A00F5' rx='3' ry='3'><animateTransform attributeType='xml' attributeName='transform' type='translate' values='0 0; 0 5; 0 -5; 0 0' begin='0' dur='0.6s' repeatCount='indefinite'/></rect><rect x='10' y='10' width='6' height='10' fill='%236A00F5' rx='3' ry='3'><animateTransform attributeType='xml' attributeName='transform' type='translate' values='0 0; 0 5; 0 -5; 0 0' begin='0.2s' dur='0.6s' repeatCount='indefinite'/></rect><rect x='20' y='10' width='6' height='10' fill='%236A00F5' rx='3' ry='3'><animateTransform attributeType='xml' attributeName='transform' type='translate' values='0 0; 0 5; 0 -5; 0 0' begin='0.4s' dur='0.6s' repeatCount='indefinite'/></rect></svg>");
background-size: cover; background-size: cover;
background-repeat: no-repeat; background-repeat: no-repeat;
@ -272,13 +288,13 @@ hr {
} }
kbd { kbd {
background-color: var(--gray-2); background-color: var(--gray-2);
border: 1px solid var(--gray-2); border: 1px solid var(--gray-2);
border-radius: 0.25rem; border-radius: 0.25rem;
font-size: 0.6rem; font-size: 0.6rem;
line-height: 1.15; line-height: 1.15;
padding: 0.1rem 0.25rem; padding: 0.1rem 0.25rem;
text-transform: uppercase; text-transform: uppercase;
} }
/* Layout and structure */ /* Layout and structure */
@ -345,7 +361,7 @@ kbd {
line-height: 1.15; line-height: 1.15;
min-height: 1.5rem; min-height: 1.5rem;
padding: 0 0.375rem; padding: 0 0.375rem;
transition: all 0.2s cubic-bezier(0.65,0.05,0.36,1); transition: all 0.2s cubic-bezier(0.65, 0.05, 0.36, 1);
&:has(input:checked) { &:has(input:checked) {
background-color: var(--white); background-color: var(--white);

View File

@ -10,12 +10,11 @@ export default function init(name: string, source: any) {
const [demoCategory, demoName, frameworkName] = splitName(name) const [demoCategory, demoName, frameworkName] = splitName(name)
import(`../src/${demoCategory}/${demoName}/${frameworkName}/index.svelte`) import(`../src/${demoCategory}/${demoName}/${frameworkName}/index.svelte`).then(Module => {
.then(Module => { const Component = Module.default
const Component = Module.default
new Component({ target: document.querySelector('#app') }) // eslint-disable-line new Component({ target: document.querySelector('#app') }) // eslint-disable-line
debug() debug()
}) })
} }

View File

@ -12,14 +12,13 @@ export default function init(name: string, source: any) {
const [demoCategory, demoName, frameworkName] = splitName(name) const [demoCategory, demoName, frameworkName] = splitName(name)
import(`../src/${demoCategory}/${demoName}/${frameworkName}/index.vue`) import(`../src/${demoCategory}/${demoName}/${frameworkName}/index.vue`).then(module => {
.then(module => { const app = createApp(module.default)
const app = createApp(module.default)
if (typeof module.configureApp === 'function') { if (typeof module.configureApp === 'function') {
module.configureApp(app) module.configureApp(app)
} }
app.mount('#app') app.mount('#app')
debug() debug()
}) })
} }

View File

@ -13,7 +13,13 @@ const MenuBar = ({ editor }) => {
}, [editor]) }, [editor])
const onCutToEnd = useCallback(() => { const onCutToEnd = useCallback(() => {
editor.chain().cut({ from: editor.state.selection.$from.pos, to: editor.state.selection.$to.pos }, editor.state.doc.nodeSize - 2).run() editor
.chain()
.cut(
{ from: editor.state.selection.$from.pos, to: editor.state.selection.$to.pos },
editor.state.doc.nodeSize - 2,
)
.run()
}, [editor]) }, [editor])
if (!editor) { if (!editor) {

View File

@ -5,11 +5,11 @@
} }
/* List styles */ /* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem; margin: 1.25rem 1rem 1.25rem 0.4rem;
li p { li p {
margin-top: 0.25em; margin-top: 0.25em;
margin-bottom: 0.25em; margin-bottom: 0.25em;
@ -17,39 +17,39 @@
} }
/* Heading styles */ /* Heading styles */
h1, h1,
h2, h2,
h3, h3,
h4, h4,
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem; margin-top: 2.5rem;
text-wrap: pretty; text-wrap: pretty;
} }
h1, h1,
h2 { h2 {
margin-top: 3.5rem; margin-top: 3.5rem;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
h1 { h1 {
font-size: 1.4rem; font-size: 1.4rem;
} }
h2 { h2 {
font-size: 1.2rem; font-size: 1.2rem;
} }
h3 { h3 {
font-size: 1.1rem; font-size: 1.1rem;
} }
h4, h4,
h5, h5,
h6 { h6 {
font-size: 1rem; font-size: 1rem;
} }
/* Code and preformatted text styles */ /* Code and preformatted text styles */

View File

@ -42,13 +42,18 @@ const MenuBar = () => {
<button data-test-id="html-content" onClick={() => editor.chain().insertContent(htmlContent).focus().run()}> <button data-test-id="html-content" onClick={() => editor.chain().insertContent(htmlContent).focus().run()}>
Insert HTML content Insert HTML content
</button> </button>
<button data-test-id="html-content-spans" onClick={() => editor.chain().insertContent('<p><b>Hello</b> <i>World</i></p>').focus().run()}> <button
data-test-id="html-content-spans"
onClick={() => editor.chain().insertContent('<p><b>Hello</b> <i>World</i></p>').focus().run()}
>
Insert HTML with span tags content Insert HTML with span tags content
</button> </button>
<button data-test-id="text-content" onClick={() => editor.chain().insertContent(textContent).focus().run()}> <button data-test-id="text-content" onClick={() => editor.chain().insertContent(textContent).focus().run()}>
Insert text content Insert text content
</button> </button>
<button data-test-id="image-content" onClick={insertImage}>Insert image</button> <button data-test-id="image-content" onClick={insertImage}>
Insert image
</button>
</div> </div>
</div> </div>
) )
@ -72,7 +77,5 @@ const extensions = [
const content = '' const content = ''
export default () => { export default () => {
return ( return <EditorProvider slotBefore={<MenuBar />} extensions={extensions} content={content}></EditorProvider>
<EditorProvider slotBefore={<MenuBar />} extensions={extensions} content={content}></EditorProvider>
)
} }

View File

@ -11,7 +11,10 @@ context('/src/Commands/InsertContent/React/', () => {
cy.get('button[data-test-id="html-content"]').click() cy.get('button[data-test-id="html-content"]').click()
// check if the content html is correct // check if the content html is correct
cy.get('.tiptap').should('contain.html', '<h1><a target="_blank" rel="noopener noreferrer nofollow" href="https://tiptap.dev/">Tiptap</a></h1><p><strong>Hello World</strong></p><p>This is a paragraph<br>with a break.</p><p>And this is some additional string content.</p>') cy.get('.tiptap').should(
'contain.html',
'<h1><a target="_blank" rel="noopener noreferrer nofollow" href="https://tiptap.dev/">Tiptap</a></h1><p><strong>Hello World</strong></p><p>This is a paragraph<br>with a break.</p><p>And this is some additional string content.</p>',
)
}) })
it('should keep spaces inbetween tags in html content', () => { it('should keep spaces inbetween tags in html content', () => {
@ -32,7 +35,10 @@ context('/src/Commands/InsertContent/React/', () => {
cy.get('button[data-test-id="text-content"]').click() cy.get('button[data-test-id="text-content"]').click()
// check if the content html is correct // check if the content html is correct
cy.get('.tiptap').should('contain.html', 'Hello World\nThis is content with a new line. Is this working?\n\nLets see if multiple new lines are inserted correctly') cy.get('.tiptap').should(
'contain.html',
'Hello World\nThis is content with a new line. Is this working?\n\nLets see if multiple new lines are inserted correctly',
)
}) })
it('should keep newlines in pre tag', () => { it('should keep newlines in pre tag', () => {
@ -86,7 +92,9 @@ context('/src/Commands/InsertContent/React/', () => {
it('should remove newlines and tabs when parseOptions.preserveWhitespace=false', () => { it('should remove newlines and tabs when parseOptions.preserveWhitespace=false', () => {
cy.get('.tiptap').then(([{ editor }]) => { cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.insertContent('\n<h1>Tiptap</h1><p><strong>Hello\n World</strong>\n</p>\n', { parseOptions: { preserveWhitespace: false } }) editor.commands.insertContent('\n<h1>Tiptap</h1><p><strong>Hello\n World</strong>\n</p>\n', {
parseOptions: { preserveWhitespace: false },
})
cy.get('.tiptap').should('contain.html', '<h1>Tiptap</h1><p><strong>Hello World</strong></p>') cy.get('.tiptap').should('contain.html', '<h1>Tiptap</h1><p><strong>Hello World</strong></p>')
}) })
}) })
@ -96,7 +104,10 @@ context('/src/Commands/InsertContent/React/', () => {
editor.commands.insertContent('<p>HelloWorld</p>') editor.commands.insertContent('<p>HelloWorld</p>')
editor.commands.setTextSelection(6) editor.commands.setTextSelection(6)
editor.commands.insertContent('<img src="https://example.image/1" alt="This is an example" />') editor.commands.insertContent('<img src="https://example.image/1" alt="This is an example" />')
cy.get('.tiptap').should('contain.html', '<p>Hello</p><img src="https://example.image/1" alt="This is an example" contenteditable="false" draggable="true"><p>World</p>') cy.get('.tiptap').should(
'contain.html',
'<p>Hello</p><img src="https://example.image/1" alt="This is an example" contenteditable="false" draggable="true"><p>World</p>',
)
}) })
}) })
@ -105,7 +116,10 @@ context('/src/Commands/InsertContent/React/', () => {
editor.commands.insertContent('<p>HelloWorld</p>') editor.commands.insertContent('<p>HelloWorld</p>')
editor.commands.setTextSelection(1) editor.commands.setTextSelection(1)
editor.commands.insertContent('<img src="https://example.image/1" alt="This is an example" />') editor.commands.insertContent('<img src="https://example.image/1" alt="This is an example" />')
cy.get('.tiptap').should('contain.html', '<img src="https://example.image/1" alt="This is an example" contenteditable="false" draggable="true"><p>HelloWorld</p>') cy.get('.tiptap').should(
'contain.html',
'<img src="https://example.image/1" alt="This is an example" contenteditable="false" draggable="true"><p>HelloWorld</p>',
)
}) })
}) })
it('should respect editor.options.parseOptions if defined to be `false`', () => { it('should respect editor.options.parseOptions if defined to be `false`', () => {
@ -131,5 +145,4 @@ context('/src/Commands/InsertContent/React/', () => {
cy.get('.tiptap').should('contain.html', '<h1>Tiptap</h1><p><strong>Hello World</strong></p>') cy.get('.tiptap').should('contain.html', '<h1>Tiptap</h1><p><strong>Hello World</strong></p>')
}) })
}) })
}) })

View File

@ -5,11 +5,11 @@
} }
/* List styles */ /* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem; margin: 1.25rem 1rem 1.25rem 0.4rem;
li p { li p {
margin-top: 0.25em; margin-top: 0.25em;
margin-bottom: 0.25em; margin-bottom: 0.25em;
@ -17,39 +17,39 @@
} }
/* Heading styles */ /* Heading styles */
h1, h1,
h2, h2,
h3, h3,
h4, h4,
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem; margin-top: 2.5rem;
text-wrap: pretty; text-wrap: pretty;
} }
h1, h1,
h2 { h2 {
margin-top: 3.5rem; margin-top: 3.5rem;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
h1 { h1 {
font-size: 1.4rem; font-size: 1.4rem;
} }
h2 { h2 {
font-size: 1.2rem; font-size: 1.2rem;
} }
h3 { h3 {
font-size: 1.1rem; font-size: 1.1rem;
} }
h4, h4,
h5, h5,
h6 { h6 {
font-size: 1rem; font-size: 1rem;
} }
/* Code and preformatted text styles */ /* Code and preformatted text styles */

View File

@ -17,19 +17,11 @@ const MenuBar = () => {
<div className="control-group"> <div className="control-group">
<div> <div>
<label> <label>
<input <input type="checkbox" checked={useInputRules} onChange={() => setUseInputRules(prev => !prev)} />
type="checkbox"
checked={useInputRules}
onChange={() => setUseInputRules(prev => !prev)}
/>
Apply input rules Apply input rules
</label> </label>
<label> <label>
<input <input type="checkbox" checked={usePasteRules} onChange={() => setUsePasteRules(prev => !prev)} />
type="checkbox"
checked={usePasteRules}
onChange={() => setUsePasteRules(prev => !prev)}
/>
Apply paste rules Apply paste rules
</label> </label>
</div> </div>
@ -163,14 +155,10 @@ const MenuBar = () => {
) )
} }
const extensions = [ const extensions = [StarterKit]
StarterKit,
]
const content = '' const content = ''
export default () => { export default () => {
return ( return <EditorProvider slotBefore={<MenuBar />} extensions={extensions} content={content}></EditorProvider>
<EditorProvider slotBefore={<MenuBar />} extensions={extensions} content={content}></EditorProvider>
)
} }

View File

@ -5,11 +5,11 @@
} }
/* List styles */ /* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem; margin: 1.25rem 1rem 1.25rem 0.4rem;
li p { li p {
margin-top: 0.25em; margin-top: 0.25em;
margin-bottom: 0.25em; margin-bottom: 0.25em;
@ -17,39 +17,39 @@
} }
/* Heading styles */ /* Heading styles */
h1, h1,
h2, h2,
h3, h3,
h4, h4,
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem; margin-top: 2.5rem;
text-wrap: pretty; text-wrap: pretty;
} }
h1, h1,
h2 { h2 {
margin-top: 3.5rem; margin-top: 3.5rem;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
h1 { h1 {
font-size: 1.4rem; font-size: 1.4rem;
} }
h2 { h2 {
font-size: 1.2rem; font-size: 1.2rem;
} }
h3 { h3 {
font-size: 1.1rem; font-size: 1.1rem;
} }
h4, h4,
h5, h5,
h6 { h6 {
font-size: 1rem; font-size: 1rem;
} }
/* Code and preformatted text styles */ /* Code and preformatted text styles */

View File

@ -25,7 +25,5 @@ const extensions = [
const content = '' const content = ''
export default () => { export default () => {
return ( return <EditorProvider extensions={extensions} content={content}></EditorProvider>
<EditorProvider extensions={extensions} content={content}></EditorProvider>
)
} }

View File

@ -30,7 +30,10 @@ context('/src/Commands/SetContent/React/', () => {
it('should insert a Prosemirror Fragment as content', () => { it('should insert a Prosemirror Fragment as content', () => {
cy.get('.tiptap').then(([{ editor }]) => { cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent(editor.schema.node('doc', null, editor.schema.node('paragraph', null, editor.schema.text('Hello World.'))).content) editor.commands.setContent(
editor.schema.node('doc', null, editor.schema.node('paragraph', null, editor.schema.text('Hello World.')))
.content,
)
cy.get('.tiptap').should('contain.html', '<p>Hello World.</p>') cy.get('.tiptap').should('contain.html', '<p>Hello World.</p>')
}) })
}) })
@ -57,8 +60,13 @@ context('/src/Commands/SetContent/React/', () => {
it('should insert more complex html content', () => { it('should insert more complex html content', () => {
cy.get('.tiptap').then(([{ editor }]) => { cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('<h1>Welcome to Tiptap</h1><p>This is a paragraph.</p><ul><li><p>List Item A</p></li><li><p>List Item B</p><ul><li><p>Subchild</p></li></ul></li></ul>') editor.commands.setContent(
cy.get('.tiptap').should('contain.html', '<h1>Welcome to Tiptap</h1><p>This is a paragraph.</p><ul><li><p>List Item A</p></li><li><p>List Item B</p><ul><li><p>Subchild</p></li></ul></li></ul>') '<h1>Welcome to Tiptap</h1><p>This is a paragraph.</p><ul><li><p>List Item A</p></li><li><p>List Item B</p><ul><li><p>Subchild</p></li></ul></li></ul>',
)
cy.get('.tiptap').should(
'contain.html',
'<h1>Welcome to Tiptap</h1><p>This is a paragraph.</p><ul><li><p>List Item A</p></li><li><p>List Item B</p><ul><li><p>Subchild</p></li></ul></li></ul>',
)
}) })
}) })
@ -94,7 +102,10 @@ context('/src/Commands/SetContent/React/', () => {
it('should insert mentions', () => { it('should insert mentions', () => {
cy.get('.tiptap').then(([{ editor }]) => { cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('<p><span data-type="mention" data-id="1" data-label="John Doe">@John Doe</span></p>') editor.commands.setContent('<p><span data-type="mention" data-id="1" data-label="John Doe">@John Doe</span></p>')
cy.get('.tiptap').should('contain.html', '<span data-type="mention" data-id="1" data-label="John Doe" contenteditable="false">@John Doe</span>') cy.get('.tiptap').should(
'contain.html',
'<span data-type="mention" data-id="1" data-label="John Doe" contenteditable="false">@John Doe</span>',
)
}) })
}) })
@ -109,7 +120,9 @@ context('/src/Commands/SetContent/React/', () => {
// This exists in insertContentAt as well // This exists in insertContentAt as well
it('should keep newlines and tabs between html fragments when preserveWhitespace = full', () => { it('should keep newlines and tabs between html fragments when preserveWhitespace = full', () => {
cy.get('.tiptap').then(([{ editor }]) => { cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('<h1>Tiptap</h1>\n\t<p><strong>Hello World</strong></p>', false, { preserveWhitespace: 'full' }) editor.commands.setContent('<h1>Tiptap</h1>\n\t<p><strong>Hello World</strong></p>', false, {
preserveWhitespace: 'full',
})
cy.get('.tiptap').should('contain.html', '<h1>Tiptap</h1><p>\n\t</p><p><strong>Hello World</strong></p>') cy.get('.tiptap').should('contain.html', '<h1>Tiptap</h1><p>\n\t</p><p><strong>Hello World</strong></p>')
}) })
}) })
@ -174,7 +187,9 @@ context('/src/Commands/SetContent/React/', () => {
it('should remove newlines and tabs when parseOptions.preserveWhitespace=false', () => { it('should remove newlines and tabs when parseOptions.preserveWhitespace=false', () => {
cy.get('.tiptap').then(([{ editor }]) => { cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('\n<h1>Tiptap</h1><p><strong>Hello\n World</strong>\n</p>\n', false, { preserveWhitespace: false }) editor.commands.setContent('\n<h1>Tiptap</h1><p><strong>Hello\n World</strong>\n</p>\n', false, {
preserveWhitespace: false,
})
cy.get('.tiptap').should('contain.html', '<h1>Tiptap</h1><p><strong>Hello World</strong></p>') cy.get('.tiptap').should('contain.html', '<h1>Tiptap</h1><p><strong>Hello World</strong></p>')
}) })
}) })

View File

@ -24,8 +24,8 @@
} }
pre { pre {
background: #0D0D0D; background: #0d0d0d;
color: #FFF; color: #fff;
font-family: 'JetBrainsMono', monospace; font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
@ -45,12 +45,12 @@
blockquote { blockquote {
padding-left: 1rem; padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1); border-left: 2px solid rgba(#0d0d0d, 0.1);
} }
hr { hr {
border: none; border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1); border-top: 2px solid rgba(#0d0d0d, 0.1);
margin: 2rem 0; margin: 2rem 0;
} }
} }

View File

@ -78,9 +78,7 @@ const getInitialUser = () => {
} }
} }
const Editor = ({ const Editor = ({ ydoc, provider, room }) => {
ydoc, provider, room,
}) => {
const [status, setStatus] = useState('connecting') const [status, setStatus] = useState('connecting')
const [currentUser, setCurrentUser] = useState(getInitialUser) const [currentUser, setCurrentUser] = useState(getInitialUser)
@ -187,15 +185,12 @@ const Editor = ({
<EditorContent editor={editor} className="main-group" /> <EditorContent editor={editor} className="main-group" />
<div <div className="collab-status-group" data-state={status === 'connected' ? 'online' : 'offline'}>
className="collab-status-group"
data-state={status === 'connected' ? 'online' : 'offline'}
>
<label> <label>
{status === 'connected' {status === 'connected'
? `${editor.storage.collaborationCursor.users.length} user${ ? `${editor.storage.collaborationCursor.users.length} user${
editor.storage.collaborationCursor.users.length === 1 ? '' : 's' editor.storage.collaborationCursor.users.length === 1 ? '' : 's'
} online in ${room}` } online in ${room}`
: 'offline'} : 'offline'}
</label> </label>
<button style={{ '--color': currentUser.color }} onClick={setName}> <button style={{ '--color': currentUser.color }} onClick={setName}>

View File

@ -5,11 +5,11 @@
} }
/* List styles */ /* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem; margin: 1.25rem 1rem 1.25rem 0.4rem;
li p { li p {
margin-top: 0.25em; margin-top: 0.25em;
margin-bottom: 0.25em; margin-bottom: 0.25em;
@ -17,39 +17,39 @@
} }
/* Heading styles */ /* Heading styles */
h1, h1,
h2, h2,
h3, h3,
h4, h4,
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem; margin-top: 2.5rem;
text-wrap: pretty; text-wrap: pretty;
} }
h1, h1,
h2 { h2 {
margin-top: 3.5rem; margin-top: 3.5rem;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
h1 { h1 {
font-size: 1.4rem; font-size: 1.4rem;
} }
h2 { h2 {
font-size: 1.2rem; font-size: 1.2rem;
} }
h3 { h3 {
font-size: 1.1rem; font-size: 1.1rem;
} }
h4, h4,
h5, h5,
h6 { h6 {
font-size: 1rem; font-size: 1rem;
} }
/* Code and preformatted text styles */ /* Code and preformatted text styles */
@ -91,14 +91,14 @@
/* Highlight specific styles */ /* Highlight specific styles */
mark { mark {
background-color: #FAF594; background-color: #faf594;
border-radius: 0.4rem; border-radius: 0.4rem;
box-decoration-break: clone; box-decoration-break: clone;
padding: 0.1rem 0.3rem; padding: 0.1rem 0.3rem;
} }
/* Task list specific styles */ /* Task list specific styles */
ul[data-type="taskList"] { ul[data-type='taskList'] {
list-style: none; list-style: none;
margin-left: 0; margin-left: 0;
padding: 0; padding: 0;
@ -118,11 +118,11 @@
} }
} }
input[type="checkbox"] { input[type='checkbox'] {
cursor: pointer; cursor: pointer;
} }
ul[data-type="taskList"] { ul[data-type='taskList'] {
margin: 0; margin: 0;
} }
} }
@ -170,7 +170,7 @@
} }
/* Column-half */ /* Column-half */
body { body {
overflow: hidden; overflow: hidden;
} }
@ -205,11 +205,11 @@ body {
flex-direction: row; flex-direction: row;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 400; font-weight: 400;
gap: 1rem; gap: 1rem;
justify-content: space-between; justify-content: space-between;
padding: 0.375rem 0.5rem 0.375rem 1rem; padding: 0.375rem 0.5rem 0.375rem 1rem;
position: sticky; position: sticky;
width: 100%; width: 100%;
z-index: 100; z-index: 100;
button { button {
@ -230,13 +230,13 @@ body {
&::before { &::before {
background-color: var(--color); background-color: var(--color);
border-radius: 0.375rem; border-radius: 0.375rem;
content: ""; content: '';
height: 100%; height: 100%;
left: 0; left: 0;
opacity: 0.5; opacity: 0.5;
position: absolute; position: absolute;
top: 0; top: 0;
transition: all 0.2s cubic-bezier(0.65,0.05,0.36,1); transition: all 0.2s cubic-bezier(0.65, 0.05, 0.36, 1);
width: 100%; width: 100%;
z-index: -1; z-index: -1;
} }
@ -256,13 +256,13 @@ body {
&::before { &::before {
border-radius: 50%; border-radius: 50%;
content: " "; content: ' ';
height: 0.35rem; height: 0.35rem;
width: 0.35rem; width: 0.35rem;
} }
} }
&[data-state="online"] { &[data-state='online'] {
label { label {
&::before { &::before {
background-color: var(--green); background-color: var(--green);
@ -270,7 +270,7 @@ body {
} }
} }
&[data-state="offline"] { &[data-state='offline'] {
label { label {
&::before { &::before {
background-color: var(--red); background-color: var(--red);

View File

@ -6,10 +6,7 @@ import Collaboration from '@tiptap/extension-collaboration'
import CollaborationCursor from '@tiptap/extension-collaboration-cursor' import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
import { EditorContent, useEditor } from '@tiptap/react' import { EditorContent, useEditor } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit' import StarterKit from '@tiptap/starter-kit'
import React, { import React, { useCallback, useEffect, useState } from 'react'
useCallback, useEffect,
useState,
} from 'react'
import * as Y from 'yjs' import * as Y from 'yjs'
const room = 'room-1' const room = 'room-1'
@ -55,10 +52,12 @@ const websocketProvider = new TiptapCollabProvider({
}) })
const getInitialUser = () => { const getInitialUser = () => {
return JSON.parse(localStorage.getItem('currentUser')) || { return (
name: getRandomName(), JSON.parse(localStorage.getItem('currentUser')) || {
color: getRandomColor(), name: getRandomName(),
} color: getRandomColor(),
}
)
} }
export default () => { export default () => {

View File

@ -27,9 +27,9 @@
background: #0d0d0d; background: #0d0d0d;
border-radius: 0.5rem; border-radius: 0.5rem;
color: #fff; color: #fff;
font-family: "JetBrainsMono", monospace; font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
code { code {
background: none; background: none;
color: inherit; color: inherit;
@ -62,7 +62,7 @@
margin: 2rem 0; margin: 2rem 0;
} }
ul[data-type="taskList"] { ul[data-type='taskList'] {
list-style: none; list-style: none;
padding: 0; padding: 0;
@ -127,7 +127,7 @@
&::before { &::before {
background: rgba(#0d0d0d, 0.5); background: rgba(#0d0d0d, 0.5);
border-radius: 50%; border-radius: 50%;
content: " "; content: ' ';
display: inline-block; display: inline-block;
flex: 0 0 auto; flex: 0 0 auto;
height: 0.5rem; height: 0.5rem;

View File

@ -44,15 +44,13 @@ export default () => {
// empty // empty
if (url === '') { if (url === '') {
editor.chain().focus().extendMarkRange('link').unsetLink() editor.chain().focus().extendMarkRange('link').unsetLink().run()
.run()
return return
} }
// update link // update link
editor.chain().focus().extendMarkRange('link').setLink({ href: url }) editor.chain().focus().extendMarkRange('link').setLink({ href: url }).run()
.run()
}, [editor]) }, [editor])
if (!editor) { if (!editor) {
@ -63,11 +61,7 @@ export default () => {
<> <>
<div className="control-group"> <div className="control-group">
<div className="button-group"> <div className="button-group">
<button <button onClick={setLink} className={editorState.isLink ? 'is-active' : ''} data-testid="setLink">
onClick={setLink}
className={editorState.isLink ? 'is-active' : ''}
data-testid="setLink"
>
Set link Set link
</button> </button>
<button <button

View File

@ -42,61 +42,39 @@ context('/src/Examples/AutolinkValidation/React/', () => {
cy.get('.tiptap').type('https://tiptap.dev {home}') cy.get('.tiptap').type('https://tiptap.dev {home}')
cy.get('.tiptap').should('have.text', 'https://tiptap.dev ') cy.get('.tiptap').should('have.text', 'https://tiptap.dev ')
cy.get('[data-testid=unsetLink]').click() cy.get('[data-testid=unsetLink]').click()
cy.get('.tiptap') cy.get('.tiptap').find('a').should('have.length', 0)
.find('a')
.should('have.length', 0)
cy.get('.tiptap').type('{end}http://www.example.com/ ') cy.get('.tiptap').type('{end}http://www.example.com/ ')
cy.get('.tiptap') cy.get('.tiptap').find('a').should('have.length', 1).should('have.attr', 'href', 'http://www.example.com/')
.find('a')
.should('have.length', 1)
.should('have.attr', 'href', 'http://www.example.com/')
}) })
it('should not relink unset links after hitting next paragraph', () => { it('should not relink unset links after hitting next paragraph', () => {
cy.get('.tiptap').type('https://tiptap.dev {home}') cy.get('.tiptap').type('https://tiptap.dev {home}')
cy.get('.tiptap').should('have.text', 'https://tiptap.dev ') cy.get('.tiptap').should('have.text', 'https://tiptap.dev ')
cy.get('[data-testid=unsetLink]').click() cy.get('[data-testid=unsetLink]').click()
cy.get('.tiptap') cy.get('.tiptap').find('a').should('have.length', 0)
.find('a')
.should('have.length', 0)
cy.get('.tiptap').type('{end}typing other text should prevent the link from relinking when hitting enter{enter}') cy.get('.tiptap').type('{end}typing other text should prevent the link from relinking when hitting enter{enter}')
cy.get('.tiptap') cy.get('.tiptap').find('a').should('have.length', 0)
.find('a')
.should('have.length', 0)
}) })
it('should not relink unset links after modifying', () => { it('should not relink unset links after modifying', () => {
cy.get('.tiptap').type('https://tiptap.dev {home}') cy.get('.tiptap').type('https://tiptap.dev {home}')
cy.get('.tiptap').should('have.text', 'https://tiptap.dev ') cy.get('.tiptap').should('have.text', 'https://tiptap.dev ')
cy.get('[data-testid=unsetLink]').click() cy.get('[data-testid=unsetLink]').click()
cy.get('.tiptap') cy.get('.tiptap').find('a').should('have.length', 0)
.find('a') cy.get('.tiptap').type('{home}').type('{rightArrow}'.repeat('https://'.length)).type('blah')
.should('have.length', 0)
cy.get('.tiptap')
.type('{home}')
.type('{rightArrow}'.repeat('https://'.length))
.type('blah')
cy.get('.tiptap').should('have.text', 'https://blahtiptap.dev ') cy.get('.tiptap').should('have.text', 'https://blahtiptap.dev ')
cy.get('.tiptap') cy.get('.tiptap').find('a').should('have.length', 0)
.find('a')
.should('have.length', 0)
}) })
it('should autolink after hitting enter (new paragraph)', () => { it('should autolink after hitting enter (new paragraph)', () => {
cy.get('.tiptap').type('https://tiptap.dev{enter}') cy.get('.tiptap').type('https://tiptap.dev{enter}')
cy.get('.tiptap').should('have.text', 'https://tiptap.dev') cy.get('.tiptap').should('have.text', 'https://tiptap.dev')
cy.get('.tiptap') cy.get('.tiptap').find('a').should('have.length', 1).should('have.attr', 'href', 'https://tiptap.dev')
.find('a')
.should('have.length', 1)
.should('have.attr', 'href', 'https://tiptap.dev')
}) })
it('should autolink after hitting shift-enter (hardbreak)', () => { it('should autolink after hitting shift-enter (hardbreak)', () => {
cy.get('.tiptap').type('https://tiptap.dev{shift+enter}') cy.get('.tiptap').type('https://tiptap.dev{shift+enter}')
cy.get('.tiptap').should('have.text', 'https://tiptap.dev') cy.get('.tiptap').should('have.text', 'https://tiptap.dev')
cy.get('.tiptap') cy.get('.tiptap').find('a').should('have.length', 1).should('have.attr', 'href', 'https://tiptap.dev')
.find('a')
.should('have.length', 1)
.should('have.attr', 'href', 'https://tiptap.dev')
}) })
}) })

View File

@ -5,11 +5,11 @@
} }
/* List styles */ /* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem; margin: 1.25rem 1rem 1.25rem 0.4rem;
li p { li p {
margin-top: 0.25em; margin-top: 0.25em;
margin-bottom: 0.25em; margin-bottom: 0.25em;
@ -17,39 +17,39 @@
} }
/* Heading styles */ /* Heading styles */
h1, h1,
h2, h2,
h3, h3,
h4, h4,
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem; margin-top: 2.5rem;
text-wrap: pretty; text-wrap: pretty;
} }
h1, h1,
h2 { h2 {
margin-top: 3.5rem; margin-top: 3.5rem;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
h1 { h1 {
font-size: 1.4rem; font-size: 1.4rem;
} }
h2 { h2 {
font-size: 1.2rem; font-size: 1.2rem;
} }
h3 { h3 {
font-size: 1.1rem; font-size: 1.1rem;
} }
h4, h4,
h5, h5,
h6 { h6 {
font-size: 1rem; font-size: 1rem;
} }
/* Code and preformatted text styles */ /* Code and preformatted text styles */

View File

@ -70,8 +70,8 @@ export default {
} }
pre { pre {
background: #0D0D0D; background: #0d0d0d;
color: #FFF; color: #fff;
font-family: 'JetBrainsMono', monospace; font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
@ -95,7 +95,7 @@ export default {
blockquote { blockquote {
padding-left: 1rem; padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1); border-left: 2px solid rgba(#0d0d0d, 0.1);
} }
} }
</style> </style>

View File

@ -38,12 +38,8 @@ const MenuBar = ({ editor }) => {
> >
Code Code
</button> </button>
<button onClick={() => editor.chain().focus().unsetAllMarks().run()}> <button onClick={() => editor.chain().focus().unsetAllMarks().run()}>Clear marks</button>
Clear marks <button onClick={() => editor.chain().focus().clearNodes().run()}>Clear nodes</button>
</button>
<button onClick={() => editor.chain().focus().clearNodes().run()}>
Clear nodes
</button>
<button <button
onClick={() => editor.chain().focus().setParagraph().run()} onClick={() => editor.chain().focus().setParagraph().run()}
className={editor.isActive('paragraph') ? 'is-active' : ''} className={editor.isActive('paragraph') ? 'is-active' : ''}
@ -110,18 +106,10 @@ const MenuBar = ({ editor }) => {
> >
Blockquote Blockquote
</button> </button>
<button onClick={() => editor.chain().focus().setHorizontalRule().run()}> <button onClick={() => editor.chain().focus().setHorizontalRule().run()}>Horizontal rule</button>
Horizontal rule <button onClick={() => editor.chain().focus().setHardBreak().run()}>Hard break</button>
</button> <button onClick={() => editor.chain().focus().undo().run()}>Undo</button>
<button onClick={() => editor.chain().focus().setHardBreak().run()}> <button onClick={() => editor.chain().focus().redo().run()}>Redo</button>
Hard break
</button>
<button onClick={() => editor.chain().focus().undo().run()}>
Undo
</button>
<button onClick={() => editor.chain().focus().redo().run()}>
Redo
</button>
</div> </div>
</div> </div>
) )
@ -129,9 +117,7 @@ const MenuBar = ({ editor }) => {
export default () => { export default () => {
const editor = useEditor({ const editor = useEditor({
extensions: [ extensions: [StarterKit],
StarterKit,
],
content, content,
editorProps: { editorProps: {
attributes: { attributes: {

View File

@ -5,11 +5,11 @@
} }
/* List styles */ /* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem; margin: 1.25rem 1rem 1.25rem 0.4rem;
li p { li p {
margin-top: 0.25em; margin-top: 0.25em;
margin-bottom: 0.25em; margin-bottom: 0.25em;
@ -17,39 +17,39 @@
} }
/* Heading styles */ /* Heading styles */
h1, h1,
h2, h2,
h3, h3,
h4, h4,
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem; margin-top: 2.5rem;
text-wrap: pretty; text-wrap: pretty;
} }
h1, h1,
h2 { h2 {
margin-top: 3.5rem; margin-top: 3.5rem;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
h1 { h1 {
font-size: 1.4rem; font-size: 1.4rem;
} }
h2 { h2 {
font-size: 1.2rem; font-size: 1.2rem;
} }
h3 { h3 {
font-size: 1.1rem; font-size: 1.1rem;
} }
h4, h4,
h5, h5,
h6 { h6 {
font-size: 1rem; font-size: 1rem;
} }
/* Code and preformatted text styles */ /* Code and preformatted text styles */

View File

@ -5,66 +5,93 @@
<button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }"> <button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }">
Bold Bold
</button> </button>
<button @click="editor.chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }"> <button
@click="editor.chain().focus().toggleItalic().run()"
:class="{ 'is-active': editor.isActive('italic') }"
>
Italic Italic
</button> </button>
<button @click="editor.chain().focus().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }"> <button
@click="editor.chain().focus().toggleStrike().run()"
:class="{ 'is-active': editor.isActive('strike') }"
>
Strike Strike
</button> </button>
<button @click="editor.chain().focus().toggleCode().run()" :class="{ 'is-active': editor.isActive('code') }"> <button @click="editor.chain().focus().toggleCode().run()" :class="{ 'is-active': editor.isActive('code') }">
Code Code
</button> </button>
<button @click="editor.chain().focus().unsetAllMarks().run()"> <button @click="editor.chain().focus().unsetAllMarks().run()">Clear marks</button>
Clear marks <button @click="editor.chain().focus().clearNodes().run()">Clear nodes</button>
</button> <button
<button @click="editor.chain().focus().clearNodes().run()"> @click="editor.chain().focus().setParagraph().run()"
Clear nodes :class="{ 'is-active': editor.isActive('paragraph') }"
</button> >
<button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }">
Paragraph Paragraph
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"> <button
@click="editor.chain().focus().toggleHeading({ level: 1 }).run()"
:class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
>
H1 H1
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"> <button
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
:class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
>
H2 H2
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"> <button
@click="editor.chain().focus().toggleHeading({ level: 3 }).run()"
:class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"
>
H3 H3
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 4 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 4 }) }"> <button
@click="editor.chain().focus().toggleHeading({ level: 4 }).run()"
:class="{ 'is-active': editor.isActive('heading', { level: 4 }) }"
>
H4 H4
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 5 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 5 }) }"> <button
@click="editor.chain().focus().toggleHeading({ level: 5 }).run()"
:class="{ 'is-active': editor.isActive('heading', { level: 5 }) }"
>
H5 H5
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 6 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 6 }) }"> <button
@click="editor.chain().focus().toggleHeading({ level: 6 }).run()"
:class="{ 'is-active': editor.isActive('heading', { level: 6 }) }"
>
H6 H6
</button> </button>
<button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }"> <button
@click="editor.chain().focus().toggleBulletList().run()"
:class="{ 'is-active': editor.isActive('bulletList') }"
>
Bullet list Bullet list
</button> </button>
<button @click="editor.chain().focus().toggleOrderedList().run()" :class="{ 'is-active': editor.isActive('orderedList') }"> <button
@click="editor.chain().focus().toggleOrderedList().run()"
:class="{ 'is-active': editor.isActive('orderedList') }"
>
Ordered list Ordered list
</button> </button>
<button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }"> <button
@click="editor.chain().focus().toggleCodeBlock().run()"
:class="{ 'is-active': editor.isActive('codeBlock') }"
>
Code block Code block
</button> </button>
<button @click="editor.chain().focus().toggleBlockquote().run()" :class="{ 'is-active': editor.isActive('blockquote') }"> <button
@click="editor.chain().focus().toggleBlockquote().run()"
:class="{ 'is-active': editor.isActive('blockquote') }"
>
Blockquote Blockquote
</button> </button>
<button @click="editor.chain().focus().setHorizontalRule().run()"> <button @click="editor.chain().focus().setHorizontalRule().run()">Horizontal rule</button>
Horizontal rule <button @click="editor.chain().focus().setHardBreak().run()">Hard break</button>
</button> <button @click="editor.chain().focus().undo().run()">Undo</button>
<button @click="editor.chain().focus().setHardBreak().run()"> <button @click="editor.chain().focus().redo().run()">Redo</button>
Hard break
</button>
<button @click="editor.chain().focus().undo().run()">
Undo
</button>
<button @click="editor.chain().focus().redo().run()">
Redo
</button>
</div> </div>
</div> </div>
<editor-content :editor="editor" /> <editor-content :editor="editor" />
@ -90,9 +117,7 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [StarterKit],
StarterKit,
],
content, content,
editorProps: { editorProps: {
attributes: { attributes: {

View File

@ -37,12 +37,8 @@ const MenuBar = ({ editor }) => {
> >
Code Code
</button> </button>
<button onClick={() => editor.chain().focus().unsetAllMarks().run()}> <button onClick={() => editor.chain().focus().unsetAllMarks().run()}>Clear marks</button>
Clear marks <button onClick={() => editor.chain().focus().clearNodes().run()}>Clear nodes</button>
</button>
<button onClick={() => editor.chain().focus().clearNodes().run()}>
Clear nodes
</button>
<button <button
onClick={() => editor.chain().focus().setParagraph().run()} onClick={() => editor.chain().focus().setParagraph().run()}
className={editor.isActive('paragraph') ? 'is-active' : ''} className={editor.isActive('paragraph') ? 'is-active' : ''}
@ -109,27 +105,17 @@ const MenuBar = ({ editor }) => {
> >
Blockquote Blockquote
</button> </button>
<button onClick={() => editor.chain().focus().setHorizontalRule().run()}> <button onClick={() => editor.chain().focus().setHorizontalRule().run()}>Horizontal rule</button>
Horizontal rule <button onClick={() => editor.chain().focus().setHardBreak().run()}>Hard break</button>
</button> <button onClick={() => editor.chain().focus().undo().run()}>Undo</button>
<button onClick={() => editor.chain().focus().setHardBreak().run()}> <button onClick={() => editor.chain().focus().redo().run()}>Redo</button>
Hard break
</button>
<button onClick={() => editor.chain().focus().undo().run()}>
Undo
</button>
<button onClick={() => editor.chain().focus().redo().run()}>
Redo
</button>
</div> </div>
) )
} }
export default () => { export default () => {
const editor = useEditor({ const editor = useEditor({
extensions: [ extensions: [StarterKit],
StarterKit,
],
content: ` content: `
<h2> <h2>
Hi there, Hi there,

View File

@ -5,11 +5,11 @@
} }
/* List styles */ /* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem; margin: 1.25rem 1rem 1.25rem 0.4rem;
li p { li p {
margin-top: 0.25em; margin-top: 0.25em;
margin-bottom: 0.25em; margin-bottom: 0.25em;
@ -17,39 +17,39 @@
} }
/* Heading styles */ /* Heading styles */
h1, h1,
h2, h2,
h3, h3,
h4, h4,
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem; margin-top: 2.5rem;
text-wrap: pretty; text-wrap: pretty;
} }
h1, h1,
h2 { h2 {
margin-top: 3.5rem; margin-top: 3.5rem;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
h1 { h1 {
font-size: 1.4rem; font-size: 1.4rem;
} }
h2 { h2 {
font-size: 1.2rem; font-size: 1.2rem;
} }
h3 { h3 {
font-size: 1.1rem; font-size: 1.1rem;
} }
h4, h4,
h5, h5,
h6 { h6 {
font-size: 1rem; font-size: 1rem;
} }
/* Code and preformatted text styles */ /* Code and preformatted text styles */

View File

@ -12,57 +12,75 @@
<button @click="editor.chain().focus().toggleCode().run()" :class="{ 'is-active': editor.isActive('code') }"> <button @click="editor.chain().focus().toggleCode().run()" :class="{ 'is-active': editor.isActive('code') }">
Code Code
</button> </button>
<button @click="editor.chain().focus().unsetAllMarks().run()"> <button @click="editor.chain().focus().unsetAllMarks().run()">Clear marks</button>
Clear marks <button @click="editor.chain().focus().clearNodes().run()">Clear nodes</button>
</button>
<button @click="editor.chain().focus().clearNodes().run()">
Clear nodes
</button>
<button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }"> <button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }">
Paragraph Paragraph
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"> <button
@click="editor.chain().focus().toggleHeading({ level: 1 }).run()"
:class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
>
H1 H1
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"> <button
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
:class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
>
H2 H2
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"> <button
@click="editor.chain().focus().toggleHeading({ level: 3 }).run()"
:class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"
>
H3 H3
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 4 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 4 }) }"> <button
@click="editor.chain().focus().toggleHeading({ level: 4 }).run()"
:class="{ 'is-active': editor.isActive('heading', { level: 4 }) }"
>
H4 H4
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 5 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 5 }) }"> <button
@click="editor.chain().focus().toggleHeading({ level: 5 }).run()"
:class="{ 'is-active': editor.isActive('heading', { level: 5 }) }"
>
H5 H5
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 6 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 6 }) }"> <button
@click="editor.chain().focus().toggleHeading({ level: 6 }).run()"
:class="{ 'is-active': editor.isActive('heading', { level: 6 }) }"
>
H6 H6
</button> </button>
<button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }"> <button
@click="editor.chain().focus().toggleBulletList().run()"
:class="{ 'is-active': editor.isActive('bulletList') }"
>
Bullet list Bullet list
</button> </button>
<button @click="editor.chain().focus().toggleOrderedList().run()" :class="{ 'is-active': editor.isActive('orderedList') }"> <button
@click="editor.chain().focus().toggleOrderedList().run()"
:class="{ 'is-active': editor.isActive('orderedList') }"
>
Ordered list Ordered list
</button> </button>
<button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }"> <button
@click="editor.chain().focus().toggleCodeBlock().run()"
:class="{ 'is-active': editor.isActive('codeBlock') }"
>
Code block Code block
</button> </button>
<button @click="editor.chain().focus().toggleBlockquote().run()" :class="{ 'is-active': editor.isActive('blockquote') }"> <button
@click="editor.chain().focus().toggleBlockquote().run()"
:class="{ 'is-active': editor.isActive('blockquote') }"
>
Blockquote Blockquote
</button> </button>
<button @click="editor.chain().focus().setHorizontalRule().run()"> <button @click="editor.chain().focus().setHorizontalRule().run()">Horizontal rule</button>
Horizontal rule <button @click="editor.chain().focus().setHardBreak().run()">Hard break</button>
</button> <button @click="editor.chain().focus().undo().run()">Undo</button>
<button @click="editor.chain().focus().setHardBreak().run()"> <button @click="editor.chain().focus().redo().run()">Redo</button>
Hard break
</button>
<button @click="editor.chain().focus().undo().run()">
Undo
</button>
<button @click="editor.chain().focus().redo().run()">
Redo
</button>
</div> </div>
<editor-content :editor="editor" /> <editor-content :editor="editor" />
</template> </template>
@ -87,9 +105,7 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [StarterKit],
StarterKit,
],
content: ` content: `
<h1 class="test"> <h1 class="test">
This is a red headline This is a red headline

View File

@ -3,15 +3,21 @@ import './CodeBlockComponent.scss'
import { NodeViewContent, NodeViewWrapper } from '@tiptap/react' import { NodeViewContent, NodeViewWrapper } from '@tiptap/react'
import React from 'react' import React from 'react'
export default ({ node: { attrs: { language: defaultLanguage } }, updateAttributes, extension }) => ( export default ({
node: {
attrs: { language: defaultLanguage },
},
updateAttributes,
extension,
}) => (
<NodeViewWrapper className="code-block"> <NodeViewWrapper className="code-block">
<select contentEditable={false} defaultValue={defaultLanguage} onChange={event => updateAttributes({ language: event.target.value })}> <select
<option value="null"> contentEditable={false}
auto defaultValue={defaultLanguage}
</option> onChange={event => updateAttributes({ language: event.target.value })}
<option disabled> >
<option value="null">auto</option>
</option> <option disabled></option>
{extension.options.lowlight.listLanguages().map((lang, index) => ( {extension.options.lowlight.listLanguages().map((lang, index) => (
<option key={index} value={lang}> <option key={index} value={lang}>
{lang} {lang}

View File

@ -1,7 +1,7 @@
.tiptap { .tiptap {
.code-block { .code-block {
position: relative; position: relative;
select { select {
position: absolute; position: absolute;
background-color: var(--white); background-color: var(--white);

View File

@ -33,7 +33,10 @@ const MenuBar = ({ editor }) => {
return ( return (
<div className="control-group"> <div className="control-group">
<div className="button-group"> <div className="button-group">
<button onClick={() => editor.chain().focus().toggleCodeBlock().run()} className={editor.isActive('codeBlock') ? 'is-active' : ''}> <button
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
className={editor.isActive('codeBlock') ? 'is-active' : ''}
>
Toggle code block Toggle code block
</button> </button>
</div> </div>
@ -47,13 +50,11 @@ export default () => {
Document, Document,
Paragraph, Paragraph,
Text, Text,
CodeBlockLowlight CodeBlockLowlight.extend({
.extend({ addNodeView() {
addNodeView() { return ReactNodeViewRenderer(CodeBlockComponent)
return ReactNodeViewRenderer(CodeBlockComponent) },
}, }).configure({ lowlight }),
})
.configure({ lowlight }),
], ],
content: ` content: `
<p> <p>

View File

@ -1,12 +1,8 @@
<template> <template>
<node-view-wrapper class="code-block"> <node-view-wrapper class="code-block">
<select contenteditable="false" v-model="selectedLanguage"> <select contenteditable="false" v-model="selectedLanguage">
<option :value="null"> <option :value="null">auto</option>
auto <option disabled></option>
</option>
<option disabled>
</option>
<option v-for="(language, index) in languages" :value="language" :key="index"> <option v-for="(language, index) in languages" :value="language" :key="index">
{{ language }} {{ language }}
</option> </option>

View File

@ -2,7 +2,10 @@
<div v-if="editor" class="container"> <div v-if="editor" class="container">
<div class="control-group"> <div class="control-group">
<div class="button-"> <div class="button-">
<button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }"> <button
@click="editor.chain().focus().toggleCodeBlock().run()"
:class="{ 'is-active': editor.isActive('codeBlock') }"
>
Toggle code block Toggle code block
</button> </button>
</div> </div>
@ -52,13 +55,11 @@ export default {
Document, Document,
Paragraph, Paragraph,
Text, Text,
CodeBlockLowlight CodeBlockLowlight.extend({
.extend({ addNodeView() {
addNodeView() { return VueNodeViewRenderer(CodeBlockComponent)
return VueNodeViewRenderer(CodeBlockComponent) },
}, }).configure({ lowlight }),
})
.configure({ lowlight }),
], ],
content: ` content: `
<p> <p>

View File

@ -106,8 +106,7 @@ export default ({ editor }) => {
{ {
icon: 'format-clear', icon: 'format-clear',
title: 'Clear format', title: 'Clear format',
action: () => editor.chain().focus().clearNodes().unsetAllMarks() action: () => editor.chain().focus().clearNodes().unsetAllMarks().run(),
.run(),
}, },
{ {
type: 'divider', type: 'divider',

View File

@ -3,14 +3,8 @@ import './MenuItem.scss'
import React from 'react' import React from 'react'
import remixiconUrl from 'remixicon/fonts/remixicon.symbol.svg' import remixiconUrl from 'remixicon/fonts/remixicon.symbol.svg'
export default ({ export default ({ icon, title, action, isActive = null }) => (
icon, title, action, isActive = null, <button className={`menu-item${isActive && isActive() ? ' is-active' : ''}`} onClick={action} title={title}>
}) => (
<button
className={`menu-item${isActive && isActive() ? ' is-active' : ''}`}
onClick={action}
title={title}
>
<svg className="remix"> <svg className="remix">
<use xlinkHref={`${remixiconUrl}#ri-${icon}`} /> <use xlinkHref={`${remixiconUrl}#ri-${icon}`} />
</svg> </svg>

View File

@ -9,10 +9,7 @@ import TaskItem from '@tiptap/extension-task-item'
import TaskList from '@tiptap/extension-task-list' import TaskList from '@tiptap/extension-task-list'
import { EditorContent, useEditor } from '@tiptap/react' import { EditorContent, useEditor } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit' import StarterKit from '@tiptap/starter-kit'
import React, { import React, { useCallback, useEffect, useState } from 'react'
useCallback, useEffect,
useState,
} from 'react'
import * as Y from 'yjs' import * as Y from 'yjs'
import { variables } from '../../../variables.js' import { variables } from '../../../variables.js'
@ -67,10 +64,12 @@ const websocketProvider = new TiptapCollabProvider({
}) })
const getInitialUser = () => { const getInitialUser = () => {
return JSON.parse(localStorage.getItem('currentUser')) || { return (
name: getRandomName(), JSON.parse(localStorage.getItem('currentUser')) || {
color: getRandomColor(), name: getRandomName(),
} color: getRandomColor(),
}
)
} }
export default () => { export default () => {

View File

@ -27,9 +27,9 @@
background: #0d0d0d; background: #0d0d0d;
border-radius: 0.5rem; border-radius: 0.5rem;
color: #fff; color: #fff;
font-family: "JetBrainsMono", monospace; font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
code { code {
background: none; background: none;
color: inherit; color: inherit;
@ -62,7 +62,7 @@
margin: 2rem 0; margin: 2rem 0;
} }
ul[data-type="taskList"] { ul[data-type='taskList'] {
list-style: none; list-style: none;
padding: 0; padding: 0;
@ -135,7 +135,7 @@
&::before { &::before {
background: rgba(#0d0d0d, 0.5); background: rgba(#0d0d0d, 0.5);
border-radius: 50%; border-radius: 50%;
content: " "; content: ' ';
display: inline-block; display: inline-block;
flex: 0 0 auto; flex: 0 0 auto;
height: 0.5rem; height: 0.5rem;

View File

@ -125,11 +125,7 @@ export default {
{ {
icon: 'format-clear', icon: 'format-clear',
title: 'Clear format', title: 'Clear format',
action: () => this.editor.chain() action: () => this.editor.chain().focus().clearNodes().unsetAllMarks().run(),
.focus()
.clearNodes()
.unsetAllMarks()
.run(),
}, },
{ {
type: 'divider', type: 'divider',

View File

@ -1,10 +1,5 @@
<template> <template>
<button <button class="menu-item" :class="{ 'is-active': isActive ? isActive() : null }" @click="action" :title="title">
class="menu-item"
:class="{ 'is-active': isActive ? isActive(): null }"
@click="action"
:title="title"
>
<svg class="remix"> <svg class="remix">
<use :xlink:href="`${remixiconUrl}#ri-${icon}`" /> <use :xlink:href="`${remixiconUrl}#ri-${icon}`" />
</svg> </svg>

View File

@ -5,11 +5,12 @@
<div class="editor__footer"> <div class="editor__footer">
<div :class="`editor__status editor__status--${status}`"> <div :class="`editor__status editor__status--${status}`">
<template v-if="status === 'connected'"> <template v-if="status === 'connected'">
{{ editor.storage.collaborationCursor.users.length }} user{{ editor.storage.collaborationCursor.users.length === 1 ? '' : 's' }} online in {{ room }} {{ editor.storage.collaborationCursor.users.length }} user{{
</template> editor.storage.collaborationCursor.users.length === 1 ? '' : 's'
<template v-else> }}
offline online in {{ room }}
</template> </template>
<template v-else> offline </template>
</div> </div>
<div class="editor__name"> <div class="editor__name">
<button @click="setName"> <button @click="setName">
@ -103,9 +104,7 @@ export default {
methods: { methods: {
setName() { setName() {
const name = (window.prompt('Name') || '') const name = (window.prompt('Name') || '').trim().substring(0, 32)
.trim()
.substring(0, 32)
if (name) { if (name) {
return this.updateCurrentUser({ return this.updateCurrentUser({
@ -122,20 +121,36 @@ export default {
}, },
getRandomColor() { getRandomColor() {
return getRandomElement([ return getRandomElement(['#958DF1', '#F98181', '#FBBC88', '#FAF594', '#70CFF8', '#94FADB', '#B9F18D'])
'#958DF1',
'#F98181',
'#FBBC88',
'#FAF594',
'#70CFF8',
'#94FADB',
'#B9F18D',
])
}, },
getRandomName() { getRandomName() {
return getRandomElement([ return getRandomElement([
'Lea Thompson', 'Cyndi Lauper', 'Tom Cruise', 'Madonna', 'Jerry Hall', 'Joan Collins', 'Winona Ryder', 'Christina Applegate', 'Alyssa Milano', 'Molly Ringwald', 'Ally Sheedy', 'Debbie Harry', 'Olivia Newton-John', 'Elton John', 'Michael J. Fox', 'Axl Rose', 'Emilio Estevez', 'Ralph Macchio', 'Rob Lowe', 'Jennifer Grey', 'Mickey Rourke', 'John Cusack', 'Matthew Broderick', 'Justine Bateman', 'Lisa Bonet', 'Lea Thompson',
'Cyndi Lauper',
'Tom Cruise',
'Madonna',
'Jerry Hall',
'Joan Collins',
'Winona Ryder',
'Christina Applegate',
'Alyssa Milano',
'Molly Ringwald',
'Ally Sheedy',
'Debbie Harry',
'Olivia Newton-John',
'Elton John',
'Michael J. Fox',
'Axl Rose',
'Emilio Estevez',
'Ralph Macchio',
'Rob Lowe',
'Jennifer Grey',
'Mickey Rourke',
'John Cusack',
'Matthew Broderick',
'Justine Bateman',
'Lisa Bonet',
]) ])
}, },
}, },
@ -149,10 +164,10 @@ export default {
<style lang="scss"> <style lang="scss">
.editor { .editor {
background-color: #FFF; background-color: #fff;
border: 3px solid #0D0D0D; border: 3px solid #0d0d0d;
border-radius: 0.75rem; border-radius: 0.75rem;
color: #0D0D0D; color: #0d0d0d;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-height: 26rem; max-height: 26rem;
@ -179,8 +194,8 @@ export default {
&__footer { &__footer {
align-items: center; align-items: center;
border-top: 3px solid #0D0D0D; border-top: 3px solid #0d0d0d;
color: #0D0D0D; color: #0d0d0d;
display: flex; display: flex;
flex: 0 0 auto; flex: 0 0 auto;
flex-wrap: wrap; flex-wrap: wrap;
@ -198,7 +213,7 @@ export default {
display: flex; display: flex;
&::before { &::before {
background: rgba(#0D0D0D, 0.5); background: rgba(#0d0d0d, 0.5);
border-radius: 50%; border-radius: 50%;
content: ' '; content: ' ';
display: inline-block; display: inline-block;
@ -213,7 +228,7 @@ export default {
} }
&--connected::before { &--connected::before {
background: #B9F18D; background: #b9f18d;
} }
} }
@ -222,15 +237,15 @@ export default {
background: none; background: none;
border: none; border: none;
border-radius: 0.4rem; border-radius: 0.4rem;
color: #0D0D0D; color: #0d0d0d;
font: inherit; font: inherit;
font-size: 12px; font-size: 12px;
font-weight: 600; font-weight: 600;
padding: 0.25rem 0.5rem; padding: 0.25rem 0.5rem;
&:hover { &:hover {
background-color: #0D0D0D; background-color: #0d0d0d;
color: #FFF; color: #fff;
} }
} }
} }
@ -238,8 +253,8 @@ export default {
/* Give a remote user a caret */ /* Give a remote user a caret */
.collaboration-cursor__caret { .collaboration-cursor__caret {
border-left: 1px solid #0D0D0D; border-left: 1px solid #0d0d0d;
border-right: 1px solid #0D0D0D; border-right: 1px solid #0d0d0d;
margin-left: -1px; margin-left: -1px;
margin-right: -1px; margin-right: -1px;
pointer-events: none; pointer-events: none;
@ -250,7 +265,7 @@ export default {
/* Render the username above the caret */ /* Render the username above the caret */
.collaboration-cursor__label { .collaboration-cursor__label {
border-radius: 3px 3px 3px 0; border-radius: 3px 3px 3px 0;
color: #0D0D0D; color: #0d0d0d;
font-size: 12px; font-size: 12px;
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
@ -289,9 +304,9 @@ export default {
} }
pre { pre {
background: #0D0D0D; background: #0d0d0d;
border-radius: 0.5rem; border-radius: 0.5rem;
color: #FFF; color: #fff;
font-family: 'JetBrainsMono', monospace; font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
@ -304,7 +319,7 @@ export default {
} }
mark { mark {
background-color: #FAF594; background-color: #faf594;
} }
img { img {
@ -317,17 +332,17 @@ export default {
} }
blockquote { blockquote {
border-left: 2px solid rgba(#0D0D0D, 0.1); border-left: 2px solid rgba(#0d0d0d, 0.1);
padding-left: 1rem; padding-left: 1rem;
} }
hr { hr {
border: none; border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1); border-top: 2px solid rgba(#0d0d0d, 0.1);
margin: 2rem 0; margin: 2rem 0;
} }
ul[data-type="taskList"] { ul[data-type='taskList'] {
list-style: none; list-style: none;
padding: 0; padding: 0;

View File

@ -1,11 +1,6 @@
import './MentionList.scss' import './MentionList.scss'
import React, { import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react'
forwardRef,
useEffect,
useImperativeHandle,
useState,
} from 'react'
export const MentionList = forwardRef((props, ref) => { export const MentionList = forwardRef((props, ref) => {
const [selectedIndex, setSelectedIndex] = useState(0) const [selectedIndex, setSelectedIndex] = useState(0)
@ -19,7 +14,7 @@ export const MentionList = forwardRef((props, ref) => {
} }
const upHandler = () => { const upHandler = () => {
setSelectedIndex(((selectedIndex + props.items.length) - 1) % props.items.length) setSelectedIndex((selectedIndex + props.items.length - 1) % props.items.length)
} }
const downHandler = () => { const downHandler = () => {
@ -55,8 +50,8 @@ export const MentionList = forwardRef((props, ref) => {
return ( return (
<div className="dropdown-menu"> <div className="dropdown-menu">
{props.items.length {props.items.length ? (
? props.items.map((item, index) => ( props.items.map((item, index) => (
<button <button
className={index === selectedIndex ? 'is-selected' : ''} className={index === selectedIndex ? 'is-selected' : ''}
key={index} key={index}
@ -65,8 +60,9 @@ export const MentionList = forwardRef((props, ref) => {
{item} {item}
</button> </button>
)) ))
: <div className="item">No result</div> ) : (
} <div className="item">No result</div>
)}
</div> </div>
) )
}) })

View File

@ -18,8 +18,8 @@
gap: 0.25rem; gap: 0.25rem;
text-align: left; text-align: left;
width: 100%; width: 100%;
&:hover, &:hover,
&:hover.is-selected { &:hover.is-selected {
background-color: var(--gray-3); background-color: var(--gray-3);
} }

View File

@ -44,26 +44,17 @@ export default () => {
}, },
}) })
const percentage = editor const percentage = editor ? Math.round((100 / limit) * characterCount) : 0
? Math.round((100 / limit) * characterCount)
: 0
return ( return (
<> <>
<EditorContent editor={editor} /> <EditorContent editor={editor} />
{editor {editor && (
&& <div className={`character-count ${editor.storage.characterCount.characters() === limit ? 'character-count--warning' : ''}`}> <div
<svg className={`character-count ${editor.storage.characterCount.characters() === limit ? 'character-count--warning' : ''}`}
height="20" >
width="20" <svg height="20" width="20" viewBox="0 0 20 20">
viewBox="0 0 20 20" <circle r="10" cx="10" cy="10" fill="#e9ecef" />
>
<circle
r="10"
cx="10"
cy="10"
fill="#e9ecef"
/>
<circle <circle
r="5" r="5"
cx="10" cx="10"
@ -74,17 +65,11 @@ export default () => {
strokeDasharray={`calc(${percentage} * 31.4 / 100) 31.4`} strokeDasharray={`calc(${percentage} * 31.4 / 100) 31.4`}
transform="rotate(-90) translate(-20)" transform="rotate(-90) translate(-20)"
/> />
<circle <circle r="6" cx="10" cy="10" fill="white" />
r="6"
cx="10"
cy="10"
fill="white"
/>
</svg> </svg>
{editor.storage.characterCount.characters()} / {limit} characters {editor.storage.characterCount.characters()} / {limit} characters
</div> </div>
} )}
</> </>
) )
} }

View File

@ -23,15 +23,16 @@ context('/src/Examples/Community/React/', () => {
cy.get('.dropdown-menu').should('be.visible') cy.get('.dropdown-menu').should('be.visible')
// select the first user // select the first user
cy.get('.dropdown-menu button').first().then($el => { cy.get('.dropdown-menu button')
const name = $el.text() .first()
.then($el => {
const name = $el.text()
$el.click() $el.click()
// check if the user is mentioned
cy.get('.tiptap').should('have.text', `@${name} `)
cy.get('.character-count').should('contain', '2 / 280 characters')
})
// check if the user is mentioned
cy.get('.tiptap').should('have.text', `@${name} `)
cy.get('.character-count').should('contain', '2 / 280 characters')
})
}) })
}) })

View File

@ -3,7 +3,7 @@
:first-child { :first-child {
margin-top: 0; margin-top: 0;
} }
.mention { .mention {
background-color: var(--purple-light); background-color: var(--purple-light);
border-radius: 0.4rem; border-radius: 0.4rem;
@ -19,7 +19,7 @@
color: var(--gray-5); color: var(--gray-5);
display: flex; display: flex;
font-size: 0.75rem; font-size: 0.75rem;
gap: .5rem; gap: 0.5rem;
margin: 1.5rem; margin: 1.5rem;
svg { svg {

View File

@ -1,8 +1,4 @@
import { import { computePosition, flip, shift } from '@floating-ui/dom'
computePosition,
flip,
shift,
} from '@floating-ui/dom'
import { posToDOMRect, ReactRenderer } from '@tiptap/react' import { posToDOMRect, ReactRenderer } from '@tiptap/react'
import { MentionList } from './MentionList.jsx' import { MentionList } from './MentionList.jsx'
@ -27,8 +23,34 @@ const updatePosition = (editor, element) => {
export default { export default {
items: ({ query }) => { items: ({ query }) => {
return [ return [
'Lea Thompson', 'Cyndi Lauper', 'Tom Cruise', 'Madonna', 'Jerry Hall', 'Joan Collins', 'Winona Ryder', 'Christina Applegate', 'Alyssa Milano', 'Molly Ringwald', 'Ally Sheedy', 'Debbie Harry', 'Olivia Newton-John', 'Elton John', 'Michael J. Fox', 'Axl Rose', 'Emilio Estevez', 'Ralph Macchio', 'Rob Lowe', 'Jennifer Grey', 'Mickey Rourke', 'John Cusack', 'Matthew Broderick', 'Justine Bateman', 'Lisa Bonet', 'Lea Thompson',
].filter(item => item.toLowerCase().startsWith(query.toLowerCase())).slice(0, 5) 'Cyndi Lauper',
'Tom Cruise',
'Madonna',
'Jerry Hall',
'Joan Collins',
'Winona Ryder',
'Christina Applegate',
'Alyssa Milano',
'Molly Ringwald',
'Ally Sheedy',
'Debbie Harry',
'Olivia Newton-John',
'Elton John',
'Michael J. Fox',
'Axl Rose',
'Emilio Estevez',
'Ralph Macchio',
'Rob Lowe',
'Jennifer Grey',
'Mickey Rourke',
'John Cusack',
'Matthew Broderick',
'Justine Bateman',
'Lisa Bonet',
]
.filter(item => item.toLowerCase().startsWith(query.toLowerCase()))
.slice(0, 5)
}, },
render: () => { render: () => {
@ -36,7 +58,6 @@ export default {
return { return {
onStart: props => { onStart: props => {
if (!props.clientRect) { if (!props.clientRect) {
return return
} }

View File

@ -10,9 +10,7 @@
{{ item }} {{ item }}
</button> </button>
</template> </template>
<div class="item" v-else> <div class="item" v-else>No result</div>
No result
</div>
</div> </div>
</template> </template>
@ -63,7 +61,7 @@ export default {
}, },
upHandler() { upHandler() {
this.selectedIndex = ((this.selectedIndex + this.items.length) - 1) % this.items.length this.selectedIndex = (this.selectedIndex + this.items.length - 1) % this.items.length
}, },
downHandler() { downHandler() {

View File

@ -23,15 +23,16 @@ context('/src/Examples/Community/Vue/', () => {
cy.get('.dropdown-menu').should('be.visible') cy.get('.dropdown-menu').should('be.visible')
// select the first user // select the first user
cy.get('.dropdown-menu button').first().then($el => { cy.get('.dropdown-menu button')
const name = $el.text() .first()
.then($el => {
const name = $el.text()
$el.click() $el.click()
// check if the user is mentioned
cy.get('.tiptap').should('have.text', `@${name} `)
cy.get('.character-count').should('contain', '2 / 280 characters')
})
// check if the user is mentioned
cy.get('.tiptap').should('have.text', `@${name} `)
cy.get('.character-count').should('contain', '2 / 280 characters')
})
}) })
}) })

View File

@ -1,18 +1,15 @@
<template> <template>
<editor-content :editor="editor" /> <editor-content :editor="editor" />
<div v-if="editor" :class="{'character-count': true, 'character-count--warning': editor.storage.characterCount.characters() === limit}"> <div
<svg v-if="editor"
height="20" :class="{
width="20" 'character-count': true,
viewBox="0 0 20 20" 'character-count--warning': editor.storage.characterCount.characters() === limit,
> }"
<circle >
r="10" <svg height="20" width="20" viewBox="0 0 20 20">
cx="10" <circle r="10" cx="10" cy="10" fill="#e9ecef" />
cy="10"
fill="#e9ecef"
/>
<circle <circle
r="5" r="5"
cx="10" cx="10"
@ -23,12 +20,7 @@
:stroke-dasharray="`calc(${percentage} * 31.4 / 100) 31.4`" :stroke-dasharray="`calc(${percentage} * 31.4 / 100) 31.4`"
transform="rotate(-90) translate(-20)" transform="rotate(-90) translate(-20)"
/> />
<circle <circle r="6" cx="10" cy="10" fill="white" />
r="6"
cx="10"
cy="10"
fill="white"
/>
</svg> </svg>
{{ editor.storage.characterCount.characters() }} / {{ limit }} characters {{ editor.storage.characterCount.characters() }} / {{ limit }} characters
@ -115,7 +107,7 @@ export default {
color: var(--gray-5); color: var(--gray-5);
display: flex; display: flex;
font-size: 0.75rem; font-size: 0.75rem;
gap: .5rem; gap: 0.5rem;
margin: 1.5rem; margin: 1.5rem;
svg { svg {

View File

@ -1,8 +1,4 @@
import { import { computePosition, flip, shift } from '@floating-ui/dom'
computePosition,
flip,
shift,
} from '@floating-ui/dom'
import { posToDOMRect, VueRenderer } from '@tiptap/vue-3' import { posToDOMRect, VueRenderer } from '@tiptap/vue-3'
import MentionList from './MentionList.vue' import MentionList from './MentionList.vue'
@ -27,8 +23,34 @@ const updatePosition = (editor, element) => {
export default { export default {
items: ({ query }) => { items: ({ query }) => {
return [ return [
'Lea Thompson', 'Cyndi Lauper', 'Tom Cruise', 'Madonna', 'Jerry Hall', 'Joan Collins', 'Winona Ryder', 'Christina Applegate', 'Alyssa Milano', 'Molly Ringwald', 'Ally Sheedy', 'Debbie Harry', 'Olivia Newton-John', 'Elton John', 'Michael J. Fox', 'Axl Rose', 'Emilio Estevez', 'Ralph Macchio', 'Rob Lowe', 'Jennifer Grey', 'Mickey Rourke', 'John Cusack', 'Matthew Broderick', 'Justine Bateman', 'Lisa Bonet', 'Lea Thompson',
].filter(item => item.toLowerCase().startsWith(query.toLowerCase())).slice(0, 5) 'Cyndi Lauper',
'Tom Cruise',
'Madonna',
'Jerry Hall',
'Joan Collins',
'Winona Ryder',
'Christina Applegate',
'Alyssa Milano',
'Molly Ringwald',
'Ally Sheedy',
'Debbie Harry',
'Olivia Newton-John',
'Elton John',
'Michael J. Fox',
'Axl Rose',
'Emilio Estevez',
'Ralph Macchio',
'Rob Lowe',
'Jennifer Grey',
'Mickey Rourke',
'John Cusack',
'Matthew Broderick',
'Justine Bateman',
'Lisa Bonet',
]
.filter(item => item.toLowerCase().startsWith(query.toLowerCase()))
.slice(0, 5)
}, },
render: () => { render: () => {

View File

@ -37,7 +37,5 @@ export default () => {
`, `,
}) })
return ( return <EditorContent editor={editor} />
<EditorContent editor={editor} />
)
} }

View File

@ -12,12 +12,16 @@ context('/src/Examples/CustomDocument/React/', () => {
it('should have a headline and a paragraph', () => { it('should have a headline and a paragraph', () => {
cy.get('.tiptap h1').should('exist').should('have.text', 'Itll always have a heading …') cy.get('.tiptap h1').should('exist').should('have.text', 'Itll always have a heading …')
cy.get('.tiptap p').should('exist').should('have.text', '… if you pass a custom document. Thats the beauty of having full control over the schema.') cy.get('.tiptap p')
.should('exist')
.should('have.text', '… if you pass a custom document. Thats the beauty of having full control over the schema.')
}) })
it('should have a tooltip for a paragraph on a new line', () => { it('should have a tooltip for a paragraph on a new line', () => {
cy.get('.tiptap').type('{enter}') cy.get('.tiptap').type('{enter}')
cy.get('.tiptap p[data-placeholder]').should('exist').should('have.attr', 'data-placeholder', 'Can you add some further context?') cy.get('.tiptap p[data-placeholder]')
.should('exist')
.should('have.attr', 'data-placeholder', 'Can you add some further context?')
}) })
it('should have a headline after clearing the document', () => { it('should have a headline after clearing the document', () => {
@ -31,16 +35,12 @@ context('/src/Examples/CustomDocument/React/', () => {
it('should have a headline after clearing the document & enter paragraph automatically after adding a headline', () => { it('should have a headline after clearing the document & enter paragraph automatically after adding a headline', () => {
cy.get('.tiptap').type('{selectall}{backspace}Hello world{enter}') cy.get('.tiptap').type('{selectall}{backspace}Hello world{enter}')
cy.get('.tiptap h1') cy.get('.tiptap h1').should('exist').should('have.text', 'Hello world')
.should('exist')
.should('have.text', 'Hello world')
cy.get('.tiptap p[data-placeholder]') cy.get('.tiptap p[data-placeholder]')
.should('exist') .should('exist')
.should('have.attr', 'data-placeholder', 'Can you add some further context?') .should('have.attr', 'data-placeholder', 'Can you add some further context?')
cy.get('.tiptap').type('This is a paragraph for this test document') cy.get('.tiptap').type('This is a paragraph for this test document')
cy.get('.tiptap p') cy.get('.tiptap p').should('exist').should('have.text', 'This is a paragraph for this test document')
.should('exist')
.should('have.text', 'This is a paragraph for this test document')
}) })
}) })

View File

@ -5,11 +5,11 @@
} }
/* List styles */ /* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem; margin: 1.25rem 1rem 1.25rem 0.4rem;
li p { li p {
margin-top: 0.25em; margin-top: 0.25em;
margin-bottom: 0.25em; margin-bottom: 0.25em;
@ -17,39 +17,39 @@
} }
/* Heading styles */ /* Heading styles */
h1, h1,
h2, h2,
h3, h3,
h4, h4,
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem; margin-top: 2.5rem;
text-wrap: pretty; text-wrap: pretty;
} }
h1, h1,
h2 { h2 {
margin-top: 3.5rem; margin-top: 3.5rem;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
h1 { h1 {
font-size: 1.4rem; font-size: 1.4rem;
} }
h2 { h2 {
font-size: 1.2rem; font-size: 1.2rem;
} }
h3 { h3 {
font-size: 1.1rem; font-size: 1.1rem;
} }
h4, h4,
h5, h5,
h6 { h6 {
font-size: 1rem; font-size: 1rem;
} }
/* Code and preformatted text styles */ /* Code and preformatted text styles */
@ -88,7 +88,7 @@
border-top: 1px solid var(--gray-2); border-top: 1px solid var(--gray-2);
margin: 2rem 0; margin: 2rem 0;
} }
/* Placeholder (at the top) */ /* Placeholder (at the top) */
/* p.is-editor-empty:first-child::before { /* p.is-editor-empty:first-child::before {
color: var(--gray-4); color: var(--gray-4);

View File

@ -12,12 +12,16 @@ context('/src/Examples/CustomDocument/Vue/', () => {
it('should have a headline and a paragraph', () => { it('should have a headline and a paragraph', () => {
cy.get('.tiptap h1').should('exist').should('have.text', 'Itll always have a heading …') cy.get('.tiptap h1').should('exist').should('have.text', 'Itll always have a heading …')
cy.get('.tiptap p').should('exist').should('have.text', '… if you pass a custom document. Thats the beauty of having full control over the schema.') cy.get('.tiptap p')
.should('exist')
.should('have.text', '… if you pass a custom document. Thats the beauty of having full control over the schema.')
}) })
it('should have a tooltip for a paragraph on a new line', () => { it('should have a tooltip for a paragraph on a new line', () => {
cy.get('.tiptap').type('{enter}') cy.get('.tiptap').type('{enter}')
cy.get('.tiptap p[data-placeholder]').should('exist').should('have.attr', 'data-placeholder', 'Can you add some further context?') cy.get('.tiptap p[data-placeholder]')
.should('exist')
.should('have.attr', 'data-placeholder', 'Can you add some further context?')
}) })
it('should have a headline after clearing the document', () => { it('should have a headline after clearing the document', () => {
@ -33,16 +37,12 @@ context('/src/Examples/CustomDocument/Vue/', () => {
it('should have a headline after clearing the document & enter paragraph automatically after adding a headline', () => { it('should have a headline after clearing the document & enter paragraph automatically after adding a headline', () => {
cy.get('.tiptap').type('{selectall}{backspace}Hello world{enter}') cy.get('.tiptap').type('{selectall}{backspace}Hello world{enter}')
cy.wait(100) cy.wait(100)
cy.get('.tiptap h1') cy.get('.tiptap h1').should('exist').should('have.text', 'Hello world')
.should('exist')
.should('have.text', 'Hello world')
cy.get('.tiptap p[data-placeholder]') cy.get('.tiptap p[data-placeholder]')
.should('exist') .should('exist')
.should('have.attr', 'data-placeholder', 'Can you add some further context?') .should('have.attr', 'data-placeholder', 'Can you add some further context?')
cy.get('.tiptap').type('This is a paragraph for this test document') cy.get('.tiptap').type('This is a paragraph for this test document')
cy.get('.tiptap p') cy.get('.tiptap p').should('exist').should('have.text', 'This is a paragraph for this test document')
.should('exist')
.should('have.text', 'This is a paragraph for this test document')
}) })
}) })

View File

@ -1,16 +1,19 @@
import { Paragraph as BaseParagraph } from '@tiptap/extension-paragraph' import { Paragraph as BaseParagraph } from '@tiptap/extension-paragraph'
import { import { NodeViewContent, NodeViewWrapper, ReactNodeViewRenderer } from '@tiptap/react'
NodeViewContent,
NodeViewWrapper,
ReactNodeViewRenderer,
} from '@tiptap/react'
const ParagraphComponent = ({ node }) => { const ParagraphComponent = ({ node }) => {
return ( return (
<NodeViewWrapper style={{ position: 'relative' }}> <NodeViewWrapper style={{ position: 'relative' }}>
<span contentEditable={false} className="label" style={{ <span
position: 'absolute', right: '100%', fontSize: '10px', color: '#999', contentEditable={false}
}}> className="label"
style={{
position: 'absolute',
right: '100%',
fontSize: '10px',
color: '#999',
}}
>
{node.textContent.length} {node.textContent.length}
</span> </span>
<NodeViewContent as="p" /> <NodeViewContent as="p" />

View File

@ -21,7 +21,5 @@ export default () => {
`, `,
}) })
return ( return <EditorContent editor={editor} />
<EditorContent editor={editor} />
)
} }

View File

@ -11,7 +11,9 @@ context('/src/Examples/CustomParagraph/React/', () => {
}) })
it('should have a paragraph and text length', () => { it('should have a paragraph and text length', () => {
cy.get('.tiptap p').should('exist').should('have.text', 'Each line shows the number of characters in the paragraph.') cy.get('.tiptap p')
.should('exist')
.should('have.text', 'Each line shows the number of characters in the paragraph.')
cy.get('.tiptap .label').should('exist').should('have.text', '58') cy.get('.tiptap .label').should('exist').should('have.text', '58')
}) })

Some files were not shown because too many files have changed in this diff Show More