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

View File

@ -1,5 +1,5 @@
---
"@tiptap/extension-list-keymap": patch
'@tiptap/extension-list-keymap': patch
---
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

View File

@ -1,59 +1,59 @@
---
"@tiptap/extension-collaboration-cursor": major
"@tiptap/extension-code-block-lowlight": major
"@tiptap/extension-character-count": major
"@tiptap/extension-horizontal-rule": major
"@tiptap/extension-collaboration": major
"@tiptap/extension-floating-menu": major
"@tiptap/extension-ordered-list": major
"@tiptap/extension-table-header": major
"@tiptap/extension-bubble-menu": major
"@tiptap/extension-bullet-list": major
"@tiptap/extension-font-family": major
"@tiptap/extension-list-keymap": major
"@tiptap/extension-placeholder": major
"@tiptap/extension-superscript": major
"@tiptap/extension-blockquote": major
"@tiptap/extension-code-block": major
"@tiptap/extension-dropcursor": major
"@tiptap/extension-hard-break": major
"@tiptap/extension-table-cell": major
"@tiptap/extension-text-align": major
"@tiptap/extension-text-style": major
"@tiptap/extension-typography": major
"@tiptap/extension-gapcursor": major
"@tiptap/extension-highlight": major
"@tiptap/extension-list-item": major
"@tiptap/extension-paragraph": major
"@tiptap/extension-subscript": major
"@tiptap/extension-table-row": major
"@tiptap/extension-task-item": major
"@tiptap/extension-task-list": major
"@tiptap/extension-underline": major
"@tiptap/extension-document": major
"@tiptap/extension-heading": major
"@tiptap/extension-history": major
"@tiptap/extension-mention": major
"@tiptap/extension-youtube": major
"@tiptap/extension-italic": major
"@tiptap/extension-strike": major
"@tiptap/extension-color": major
"@tiptap/extension-focus": major
"@tiptap/extension-image": major
"@tiptap/extension-table": major
"@tiptap/extension-bold": major
"@tiptap/extension-code": major
"@tiptap/extension-link": major
"@tiptap/extension-text": major
"@tiptap/starter-kit": major
"@tiptap/suggestion": major
"@tiptap/react": major
"@tiptap/vue-2": major
"@tiptap/vue-3": major
"@tiptap/core": major
"@tiptap/html": major
"@tiptap/pm": major
"tiptap-demos": major
'@tiptap/extension-collaboration-cursor': major
'@tiptap/extension-code-block-lowlight': major
'@tiptap/extension-character-count': major
'@tiptap/extension-horizontal-rule': major
'@tiptap/extension-collaboration': major
'@tiptap/extension-floating-menu': major
'@tiptap/extension-ordered-list': major
'@tiptap/extension-table-header': major
'@tiptap/extension-bubble-menu': major
'@tiptap/extension-bullet-list': major
'@tiptap/extension-font-family': major
'@tiptap/extension-list-keymap': major
'@tiptap/extension-placeholder': major
'@tiptap/extension-superscript': major
'@tiptap/extension-blockquote': major
'@tiptap/extension-code-block': major
'@tiptap/extension-dropcursor': major
'@tiptap/extension-hard-break': major
'@tiptap/extension-table-cell': major
'@tiptap/extension-text-align': major
'@tiptap/extension-text-style': major
'@tiptap/extension-typography': major
'@tiptap/extension-gapcursor': major
'@tiptap/extension-highlight': major
'@tiptap/extension-list-item': major
'@tiptap/extension-paragraph': major
'@tiptap/extension-subscript': major
'@tiptap/extension-table-row': major
'@tiptap/extension-task-item': major
'@tiptap/extension-task-list': major
'@tiptap/extension-underline': major
'@tiptap/extension-document': major
'@tiptap/extension-heading': major
'@tiptap/extension-history': major
'@tiptap/extension-mention': major
'@tiptap/extension-youtube': major
'@tiptap/extension-italic': major
'@tiptap/extension-strike': major
'@tiptap/extension-color': major
'@tiptap/extension-focus': major
'@tiptap/extension-image': major
'@tiptap/extension-table': major
'@tiptap/extension-bold': major
'@tiptap/extension-code': major
'@tiptap/extension-link': major
'@tiptap/extension-text': major
'@tiptap/starter-kit': major
'@tiptap/suggestion': major
'@tiptap/react': major
'@tiptap/vue-2': major
'@tiptap/vue-3': major
'@tiptap/core': major
'@tiptap/html': major
'@tiptap/pm': major
'tiptap-demos': major
---
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-bubble-menu": major
"@tiptap/extension-mention": major
"@tiptap/suggestion": major
"@tiptap/react": major
"@tiptap/vue-2": major
"@tiptap/vue-3": major
'@tiptap/extension-floating-menu': major
'@tiptap/extension-bubble-menu': major
'@tiptap/extension-mention': major
'@tiptap/suggestion': major
'@tiptap/react': major
'@tiptap/vue-2': 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.

View File

@ -1,5 +1,5 @@
---
"@tiptap/vue-3": patch
'@tiptap/vue-3': patch
---
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.

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.

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`

View File

@ -1,5 +1,5 @@
---
"@tiptap/core": major
'@tiptap/core': major
---
Fix `getPos` type in `NodeViewRendererProps` to potentially be `undefined`

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

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

View File

@ -1,5 +1,5 @@
---
"@tiptap/core": patch
'@tiptap/core': patch
---
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

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

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

View File

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

View File

@ -1,6 +1,6 @@
---
"@tiptap/core": patch
"@tiptap/extension-hard-break": patch
'@tiptap/core': patch
'@tiptap/extension-hard-break': patch
---
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.

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

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

View File

@ -1,6 +1,6 @@
---
"@tiptap/extension-link": patch
"tiptap-demos": patch
'@tiptap/extension-link': 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.

View File

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

View File

@ -1,6 +1,6 @@
title: "Feature Request: "
title: 'Feature Request: '
labels:
- "Type: Feature Request"
- 'Type: Feature Request'
body:
- type: markdown
attributes:
@ -11,7 +11,7 @@ body:
attributes:
label: Description
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:
required: true
- type: textarea
@ -19,7 +19,7 @@ body:
attributes:
label: Use Case
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:
required: true
- type: dropdown
@ -28,9 +28,9 @@ body:
label: Type
description: Please select the type of this feature.
options:
- "New extension"
- "New feature"
- "New Tiptap API"
- "Other"
- 'New extension'
- 'New feature'
- 'New Tiptap API'
- 'Other'
validations:
required: true

View File

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

View File

@ -1,14 +1,14 @@
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.
labels:
- "Type: Bug"
- "Category: Open Source"
- "Status: New"
- 'Type: Bug'
- 'Category: Open Source'
- 'Status: New'
body:
- type: markdown
attributes:
value: "### Please provide details to help us diagnose the bug."
value: '### Please provide details to help us diagnose the bug.'
- type: input
id: packages
attributes:
@ -30,7 +30,7 @@ body:
attributes:
label: Bug Description
description: Provide a clear and concise description of what the bug is.
placeholder: "The issue occurs when..."
placeholder: 'The issue occurs when...'
validations:
required: true
- type: dropdown
@ -59,7 +59,7 @@ body:
id: sandbox
attributes:
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
validations:
required: false
@ -74,14 +74,14 @@ body:
id: context
attributes:
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
attributes:
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:
- label: Yes, I've updated all my dependencies.
required: true
- type: markdown
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)
title: "[PRO]: "
title: '[PRO]: '
description: If you've encountered a bug with Tiptap Pro features, please report it here.
labels:
- "Type: Bug"
- "Category: Pro"
- "Status: New"
- 'Type: Bug'
- 'Category: Pro'
- 'Status: New'
body:
- type: markdown
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
id: packages
attributes:
@ -30,7 +30,7 @@ body:
attributes:
label: Description of the Bug
description: Provide a clear and concise description of what the bug is.
placeholder: "The issue occurs when..."
placeholder: 'The issue occurs when...'
validations:
required: true
- type: dropdown
@ -48,10 +48,10 @@ body:
required: true
- type: markdown
attributes:
value: "### Helpful Code Examples"
value: '### Helpful Code Examples'
- type: markdown
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
attributes:
value: |
@ -63,7 +63,7 @@ body:
id: sandbox
attributes:
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
validations:
required: false
@ -78,14 +78,14 @@ body:
id: context
attributes:
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
attributes:
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:
- label: Yes, I've updated all my dependencies.
required: true
- type: markdown
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
description: Share what we need to explain better.
title: "[Documentation]: "
title: '[Documentation]: '
labels:
- "Type: Documentation"
- "Category: Open Source"
- "Status: New"
- 'Type: Documentation'
- 'Category: Open Source'
- 'Status: New'
body:
- type: input
id: url
@ -17,28 +17,28 @@ body:
id: part-of-the-documentation
attributes:
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:
required: true
- type: textarea
id: good-parts
attributes:
label: What is helpful about that part?
placeholder: "I think this part is really good: …"
placeholder: 'I think this part is really good: …'
validations:
required: true
- type: textarea
id: bad-parts
attributes:
label: What is hard to understand, missing or misleading?
placeholder: "But you really need to improve …"
placeholder: 'But you really need to improve …'
validations:
required: true
- type: textarea
id: context
attributes:
label: Anything to add? (optional)
description: "Add any other context or screenshots here."
description: 'Add any other context or screenshots here.'
- type: markdown
attributes:
value: |

View File

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

View File

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

View File

@ -73,16 +73,16 @@ jobs:
matrix:
node-version: [20]
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/Examples", spec: "./demos/src/Examples/**/*.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/GuideContent", spec: "./demos/src/GuideContent/**/*.spec.{js,ts}" }
- { name: "Demos/GuideGettingStarted", spec: "./demos/src/GuideGettingStarted/**/*.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/Extensions', spec: './demos/src/Extensions/**/*.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/GuideNodeViews", "./demos/src/GuideNodeViews/**/*.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/Marks', spec: './demos/src/Marks/**/*.spec.{js,ts}' }
- { name: 'Demos/Nodes', spec: './demos/src/Nodes/**/*.spec.{js,ts}' }
#- { name: "Demos/Overview", spec: "./demos/src/Overview/**/*.spec.{js,ts}" }
steps:

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
# 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.
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?
- **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.
- **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
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).
@ -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.
### 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/).
## 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.
### 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.
- [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)
- React notion-like block editor template: [Demo](https://templates.tiptap.dev/)
## 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.
For more details, visit the Tiptap [documentation](https://tiptap.dev/docs/editor/introduction) or [website](https://tiptap.dev/).
### Community
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)
### Sponsors 💖
<table>
<tr>
<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).
### 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.
### Contributors
[Sam Willis](https://github.com/samwillis),
[Brian Hung](https://github.com/BrianHung),
[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).
## License
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

View File

@ -1,10 +1,4 @@
module.exports = {
presets: [
'@babel/preset-env',
'@babel/preset-react',
],
plugins: [
'@babel/plugin-proposal-nullish-coalescing-operator',
'@babel/plugin-proposal-optional-chaining',
],
presets: ['@babel/preset-env', '@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">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="refresh" content="0; url=/preview/" />
</head>
<body>
</body>
<body></body>
</html>

View File

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

View File

@ -1,21 +1,16 @@
<template>
<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">
<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"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
<div
class="absolute top-0 left-0 w-full h-full flex justify-center items-center pointer-events-none"
v-if="isLoading"
>
<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">
<circle class="opacity-25" cx="12" 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"
/>
<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>
</div>
<iframe
@ -53,6 +48,4 @@ export default {
}
</script>
<style>
</style>
<style></style>

View File

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

View File

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

View File

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

View File

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

View File

@ -4,92 +4,98 @@
@font-face {
font-family: 'Inter';
font-style: normal;
font-style: normal;
font-weight: 400;
font-display: swap;
src: 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");
src:
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-family: 'Inter';
font-style: italic;
font-style: italic;
font-weight: 400;
font-display: swap;
src: 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");
src:
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-family: 'Inter';
font-style: normal;
font-style: normal;
font-weight: 500;
font-display: swap;
src: 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");
src:
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-family: 'Inter';
font-style: italic;
font-style: italic;
font-weight: 500;
font-display: swap;
src: 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");
src:
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-family: 'Inter';
font-style: normal;
font-style: normal;
font-weight: 600;
font-display: swap;
src: 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");
src:
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-family: 'Inter';
font-style: italic;
font-style: italic;
font-weight: 600;
font-display: swap;
src: 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");
src:
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-family: 'Inter';
font-style: normal;
font-style: normal;
font-weight: 700;
font-display: swap;
src: 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");
src:
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-family: 'Inter';
font-style: italic;
font-style: italic;
font-weight: 700;
font-display: swap;
src: 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");
src:
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-family: 'JetBrains Mono';
font-style: normal;
font-style: normal;
font-weight: 400;
font-display: swap;
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.woff") format("woff"),
;
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');
}
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-style: normal;
font-weight: 700;
font-display: swap;
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.woff") format("woff"),
;
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');
}
::-webkit-scrollbar {

View File

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

View File

@ -1,22 +1,22 @@
/* Base HTML and global element styles*/
:root {
--white: #FFF;
--black: #2E2B29;
--black-contrast: #110F0E;
--white: #fff;
--black: #2e2b29;
--black-contrast: #110f0e;
--gray-1: rgba(61, 37, 20, 0.05);
--gray-2: rgba(61, 37, 20, 0.08);
--gray-3: rgba(61, 37, 20, 0.12);
--gray-4: rgba(53, 38, 28, 0.30);
--gray-5: rgba(28, 25, 23, 0.60);
--green: #22C55E;
--purple: #6A00F5;
--purple-contrast: #5800CC;
--gray-4: rgba(53, 38, 28, 0.3);
--gray-5: rgba(28, 25, 23, 0.6);
--green: #22c55e;
--purple: #6a00f5;
--purple-contrast: #5800cc;
--purple-light: rgba(88, 5, 255, 0.05);
--yellow-contrast: #FACC15;
--yellow-contrast: #facc15;
--yellow: rgba(250, 204, 21, 0.4);
--yellow-light: #FFFAE5;
--red: #FF5C33;
--red-light: #FFEBE5;
--yellow-light: #fffae5;
--red: #ff5c33;
--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);
}
@ -27,7 +27,22 @@
}
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;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
@ -104,7 +119,7 @@ textarea {
line-height: 1.15;
margin: none;
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 {
background-color: var(--gray-3);
@ -150,7 +165,7 @@ select:not([disabled]) {
cursor: pointer;
}
input[type="text"],
input[type='text'],
textarea {
background-color: unset;
border: 1px solid var(--gray-3);
@ -166,7 +181,8 @@ textarea {
border-color: var(--gray-4);
}
&:focus-visible, &:focus {
&:focus-visible,
&:focus {
border-color: var(--purple);
outline: none;
}
@ -229,7 +245,7 @@ form {
background-color: var(--purple-light);
&::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-size: cover;
background-repeat: no-repeat;
@ -272,13 +288,13 @@ hr {
}
kbd {
background-color: var(--gray-2);
border: 1px solid var(--gray-2);
border-radius: 0.25rem;
font-size: 0.6rem;
line-height: 1.15;
padding: 0.1rem 0.25rem;
text-transform: uppercase;
background-color: var(--gray-2);
border: 1px solid var(--gray-2);
border-radius: 0.25rem;
font-size: 0.6rem;
line-height: 1.15;
padding: 0.1rem 0.25rem;
text-transform: uppercase;
}
/* Layout and structure */
@ -345,7 +361,7 @@ kbd {
line-height: 1.15;
min-height: 1.5rem;
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) {
background-color: var(--white);

View File

@ -10,12 +10,11 @@ export default function init(name: string, source: any) {
const [demoCategory, demoName, frameworkName] = splitName(name)
import(`../src/${demoCategory}/${demoName}/${frameworkName}/index.svelte`)
.then(Module => {
const Component = Module.default
import(`../src/${demoCategory}/${demoName}/${frameworkName}/index.svelte`).then(Module => {
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)
import(`../src/${demoCategory}/${demoName}/${frameworkName}/index.vue`)
.then(module => {
const app = createApp(module.default)
import(`../src/${demoCategory}/${demoName}/${frameworkName}/index.vue`).then(module => {
const app = createApp(module.default)
if (typeof module.configureApp === 'function') {
module.configureApp(app)
}
app.mount('#app')
debug()
})
if (typeof module.configureApp === 'function') {
module.configureApp(app)
}
app.mount('#app')
debug()
})
}

View File

@ -13,7 +13,13 @@ const MenuBar = ({ editor }) => {
}, [editor])
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])
if (!editor) {

View File

@ -42,13 +42,18 @@ const MenuBar = () => {
<button data-test-id="html-content" onClick={() => editor.chain().insertContent(htmlContent).focus().run()}>
Insert HTML content
</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
</button>
<button data-test-id="text-content" onClick={() => editor.chain().insertContent(textContent).focus().run()}>
Insert text content
</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>
)
@ -72,7 +77,5 @@ const extensions = [
const content = ''
export default () => {
return (
<EditorProvider slotBefore={<MenuBar />} extensions={extensions} content={content}></EditorProvider>
)
return <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()
// 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', () => {
@ -32,7 +35,10 @@ context('/src/Commands/InsertContent/React/', () => {
cy.get('button[data-test-id="text-content"]').click()
// 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', () => {
@ -86,7 +92,9 @@ context('/src/Commands/InsertContent/React/', () => {
it('should remove newlines and tabs when parseOptions.preserveWhitespace=false', () => {
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>')
})
})
@ -96,7 +104,10 @@ context('/src/Commands/InsertContent/React/', () => {
editor.commands.insertContent('<p>HelloWorld</p>')
editor.commands.setTextSelection(6)
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.setTextSelection(1)
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`', () => {
@ -131,5 +145,4 @@ context('/src/Commands/InsertContent/React/', () => {
cy.get('.tiptap').should('contain.html', '<h1>Tiptap</h1><p><strong>Hello World</strong></p>')
})
})
})

View File

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

View File

@ -25,7 +25,5 @@ const extensions = [
const content = ''
export default () => {
return (
<EditorProvider extensions={extensions} content={content}></EditorProvider>
)
return <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', () => {
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>')
})
})
@ -57,8 +60,13 @@ context('/src/Commands/SetContent/React/', () => {
it('should insert more complex html content', () => {
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>')
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>')
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>',
)
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', () => {
cy.get('.tiptap').then(([{ editor }]) => {
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
it('should keep newlines and tabs between html fragments when preserveWhitespace = full', () => {
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>')
})
})
@ -174,7 +187,9 @@ context('/src/Commands/SetContent/React/', () => {
it('should remove newlines and tabs when parseOptions.preserveWhitespace=false', () => {
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>')
})
})

View File

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

View File

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

View File

@ -91,14 +91,14 @@
/* Highlight specific styles */
mark {
background-color: #FAF594;
background-color: #faf594;
border-radius: 0.4rem;
box-decoration-break: clone;
padding: 0.1rem 0.3rem;
}
/* Task list specific styles */
ul[data-type="taskList"] {
ul[data-type='taskList'] {
list-style: none;
margin-left: 0;
padding: 0;
@ -118,11 +118,11 @@
}
}
input[type="checkbox"] {
input[type='checkbox'] {
cursor: pointer;
}
ul[data-type="taskList"] {
ul[data-type='taskList'] {
margin: 0;
}
}
@ -205,11 +205,11 @@ body {
flex-direction: row;
font-size: 0.75rem;
font-weight: 400;
gap: 1rem;
justify-content: space-between;
gap: 1rem;
justify-content: space-between;
padding: 0.375rem 0.5rem 0.375rem 1rem;
position: sticky;
width: 100%;
width: 100%;
z-index: 100;
button {
@ -230,13 +230,13 @@ body {
&::before {
background-color: var(--color);
border-radius: 0.375rem;
content: "";
content: '';
height: 100%;
left: 0;
opacity: 0.5;
position: absolute;
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%;
z-index: -1;
}
@ -256,13 +256,13 @@ body {
&::before {
border-radius: 50%;
content: " ";
content: ' ';
height: 0.35rem;
width: 0.35rem;
}
}
&[data-state="online"] {
&[data-state='online'] {
label {
&::before {
background-color: var(--green);
@ -270,7 +270,7 @@ body {
}
}
&[data-state="offline"] {
&[data-state='offline'] {
label {
&::before {
background-color: var(--red);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,66 +5,93 @@
<button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }">
Bold
</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
</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
</button>
<button @click="editor.chain().focus().toggleCode().run()" :class="{ 'is-active': editor.isActive('code') }">
Code
</button>
<button @click="editor.chain().focus().unsetAllMarks().run()">
Clear marks
</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().unsetAllMarks().run()">Clear marks</button>
<button @click="editor.chain().focus().clearNodes().run()">Clear nodes</button>
<button
@click="editor.chain().focus().setParagraph().run()"
:class="{ 'is-active': editor.isActive('paragraph') }"
>
Paragraph
</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
</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
</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
</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
</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
</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
</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
</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
</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
</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
</button>
<button @click="editor.chain().focus().setHorizontalRule().run()">
Horizontal rule
</button>
<button @click="editor.chain().focus().setHardBreak().run()">
Hard break
</button>
<button @click="editor.chain().focus().undo().run()">
Undo
</button>
<button @click="editor.chain().focus().redo().run()">
Redo
</button>
<button @click="editor.chain().focus().setHorizontalRule().run()">Horizontal rule</button>
<button @click="editor.chain().focus().setHardBreak().run()">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" />
@ -90,9 +117,7 @@ export default {
mounted() {
this.editor = new Editor({
extensions: [
StarterKit,
],
extensions: [StarterKit],
content,
editorProps: {
attributes: {

View File

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

View File

@ -12,57 +12,75 @@
<button @click="editor.chain().focus().toggleCode().run()" :class="{ 'is-active': editor.isActive('code') }">
Code
</button>
<button @click="editor.chain().focus().unsetAllMarks().run()">
Clear marks
</button>
<button @click="editor.chain().focus().clearNodes().run()">
Clear nodes
</button>
<button @click="editor.chain().focus().unsetAllMarks().run()">Clear marks</button>
<button @click="editor.chain().focus().clearNodes().run()">Clear nodes</button>
<button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }">
Paragraph
</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
</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
</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
</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
</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
</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
</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
</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
</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
</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
</button>
<button @click="editor.chain().focus().setHorizontalRule().run()">
Horizontal rule
</button>
<button @click="editor.chain().focus().setHardBreak().run()">
Hard break
</button>
<button @click="editor.chain().focus().undo().run()">
Undo
</button>
<button @click="editor.chain().focus().redo().run()">
Redo
</button>
<button @click="editor.chain().focus().setHorizontalRule().run()">Horizontal rule</button>
<button @click="editor.chain().focus().setHardBreak().run()">Hard break</button>
<button @click="editor.chain().focus().undo().run()">Undo</button>
<button @click="editor.chain().focus().redo().run()">Redo</button>
</div>
<editor-content :editor="editor" />
</template>
@ -87,9 +105,7 @@ export default {
mounted() {
this.editor = new Editor({
extensions: [
StarterKit,
],
extensions: [StarterKit],
content: `
<h1 class="test">
This is a red headline

View File

@ -3,15 +3,21 @@ import './CodeBlockComponent.scss'
import { NodeViewContent, NodeViewWrapper } from '@tiptap/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">
<select contentEditable={false} defaultValue={defaultLanguage} onChange={event => updateAttributes({ language: event.target.value })}>
<option value="null">
auto
</option>
<option disabled>
</option>
<select
contentEditable={false}
defaultValue={defaultLanguage}
onChange={event => updateAttributes({ language: event.target.value })}
>
<option value="null">auto</option>
<option disabled></option>
{extension.options.lowlight.listLanguages().map((lang, index) => (
<option key={index} value={lang}>
{lang}

View File

@ -33,7 +33,10 @@ const MenuBar = ({ editor }) => {
return (
<div className="control-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
</button>
</div>
@ -47,13 +50,11 @@ export default () => {
Document,
Paragraph,
Text,
CodeBlockLowlight
.extend({
addNodeView() {
return ReactNodeViewRenderer(CodeBlockComponent)
},
})
.configure({ lowlight }),
CodeBlockLowlight.extend({
addNodeView() {
return ReactNodeViewRenderer(CodeBlockComponent)
},
}).configure({ lowlight }),
],
content: `
<p>

View File

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

View File

@ -2,7 +2,10 @@
<div v-if="editor" class="container">
<div class="control-group">
<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
</button>
</div>
@ -52,13 +55,11 @@ export default {
Document,
Paragraph,
Text,
CodeBlockLowlight
.extend({
addNodeView() {
return VueNodeViewRenderer(CodeBlockComponent)
},
})
.configure({ lowlight }),
CodeBlockLowlight.extend({
addNodeView() {
return VueNodeViewRenderer(CodeBlockComponent)
},
}).configure({ lowlight }),
],
content: `
<p>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,11 +5,12 @@
<div class="editor__footer">
<div :class="`editor__status editor__status--${status}`">
<template v-if="status === 'connected'">
{{ editor.storage.collaborationCursor.users.length }} user{{ editor.storage.collaborationCursor.users.length === 1 ? '' : 's' }} online in {{ room }}
</template>
<template v-else>
offline
{{ editor.storage.collaborationCursor.users.length }} user{{
editor.storage.collaborationCursor.users.length === 1 ? '' : 's'
}}
online in {{ room }}
</template>
<template v-else> offline </template>
</div>
<div class="editor__name">
<button @click="setName">
@ -103,9 +104,7 @@ export default {
methods: {
setName() {
const name = (window.prompt('Name') || '')
.trim()
.substring(0, 32)
const name = (window.prompt('Name') || '').trim().substring(0, 32)
if (name) {
return this.updateCurrentUser({
@ -122,20 +121,36 @@ export default {
},
getRandomColor() {
return getRandomElement([
'#958DF1',
'#F98181',
'#FBBC88',
'#FAF594',
'#70CFF8',
'#94FADB',
'#B9F18D',
])
return getRandomElement(['#958DF1', '#F98181', '#FBBC88', '#FAF594', '#70CFF8', '#94FADB', '#B9F18D'])
},
getRandomName() {
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">
.editor {
background-color: #FFF;
border: 3px solid #0D0D0D;
background-color: #fff;
border: 3px solid #0d0d0d;
border-radius: 0.75rem;
color: #0D0D0D;
color: #0d0d0d;
display: flex;
flex-direction: column;
max-height: 26rem;
@ -179,8 +194,8 @@ export default {
&__footer {
align-items: center;
border-top: 3px solid #0D0D0D;
color: #0D0D0D;
border-top: 3px solid #0d0d0d;
color: #0d0d0d;
display: flex;
flex: 0 0 auto;
flex-wrap: wrap;
@ -198,7 +213,7 @@ export default {
display: flex;
&::before {
background: rgba(#0D0D0D, 0.5);
background: rgba(#0d0d0d, 0.5);
border-radius: 50%;
content: ' ';
display: inline-block;
@ -213,7 +228,7 @@ export default {
}
&--connected::before {
background: #B9F18D;
background: #b9f18d;
}
}
@ -222,15 +237,15 @@ export default {
background: none;
border: none;
border-radius: 0.4rem;
color: #0D0D0D;
color: #0d0d0d;
font: inherit;
font-size: 12px;
font-weight: 600;
padding: 0.25rem 0.5rem;
&:hover {
background-color: #0D0D0D;
color: #FFF;
background-color: #0d0d0d;
color: #fff;
}
}
}
@ -238,8 +253,8 @@ export default {
/* Give a remote user a caret */
.collaboration-cursor__caret {
border-left: 1px solid #0D0D0D;
border-right: 1px solid #0D0D0D;
border-left: 1px solid #0d0d0d;
border-right: 1px solid #0d0d0d;
margin-left: -1px;
margin-right: -1px;
pointer-events: none;
@ -250,7 +265,7 @@ export default {
/* Render the username above the caret */
.collaboration-cursor__label {
border-radius: 3px 3px 3px 0;
color: #0D0D0D;
color: #0d0d0d;
font-size: 12px;
font-style: normal;
font-weight: 600;
@ -289,9 +304,9 @@ export default {
}
pre {
background: #0D0D0D;
background: #0d0d0d;
border-radius: 0.5rem;
color: #FFF;
color: #fff;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
@ -304,7 +319,7 @@ export default {
}
mark {
background-color: #FAF594;
background-color: #faf594;
}
img {
@ -317,17 +332,17 @@ export default {
}
blockquote {
border-left: 2px solid rgba(#0D0D0D, 0.1);
border-left: 2px solid rgba(#0d0d0d, 0.1);
padding-left: 1rem;
}
hr {
border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1);
border-top: 2px solid rgba(#0d0d0d, 0.1);
margin: 2rem 0;
}
ul[data-type="taskList"] {
ul[data-type='taskList'] {
list-style: none;
padding: 0;

View File

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

View File

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

View File

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

View File

@ -19,7 +19,7 @@
color: var(--gray-5);
display: flex;
font-size: 0.75rem;
gap: .5rem;
gap: 0.5rem;
margin: 1.5rem;
svg {

View File

@ -1,8 +1,4 @@
import {
computePosition,
flip,
shift,
} from '@floating-ui/dom'
import { computePosition, flip, shift } from '@floating-ui/dom'
import { posToDOMRect, ReactRenderer } from '@tiptap/react'
import { MentionList } from './MentionList.jsx'
@ -27,8 +23,34 @@ const updatePosition = (editor, element) => {
export default {
items: ({ query }) => {
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',
].filter(item => item.toLowerCase().startsWith(query.toLowerCase())).slice(0, 5)
'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',
]
.filter(item => item.toLowerCase().startsWith(query.toLowerCase()))
.slice(0, 5)
},
render: () => {
@ -36,7 +58,6 @@ export default {
return {
onStart: props => {
if (!props.clientRect) {
return
}

View File

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

View File

@ -23,15 +23,16 @@ context('/src/Examples/Community/Vue/', () => {
cy.get('.dropdown-menu').should('be.visible')
// select the first user
cy.get('.dropdown-menu button').first().then($el => {
const name = $el.text()
cy.get('.dropdown-menu button')
.first()
.then($el => {
const name = $el.text()
$el.click()
// check if the user is mentioned
cy.get('.tiptap').should('have.text', `@${name} `)
cy.get('.character-count').should('contain', '2 / 280 characters')
})
$el.click()
// 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>
<editor-content :editor="editor" />
<div v-if="editor" :class="{'character-count': true, 'character-count--warning': editor.storage.characterCount.characters() === limit}">
<svg
height="20"
width="20"
viewBox="0 0 20 20"
>
<circle
r="10"
cx="10"
cy="10"
fill="#e9ecef"
/>
<div
v-if="editor"
:class="{
'character-count': true,
'character-count--warning': editor.storage.characterCount.characters() === limit,
}"
>
<svg height="20" width="20" viewBox="0 0 20 20">
<circle r="10" cx="10" cy="10" fill="#e9ecef" />
<circle
r="5"
cx="10"
@ -23,12 +20,7 @@
:stroke-dasharray="`calc(${percentage} * 31.4 / 100) 31.4`"
transform="rotate(-90) translate(-20)"
/>
<circle
r="6"
cx="10"
cy="10"
fill="white"
/>
<circle r="6" cx="10" cy="10" fill="white" />
</svg>
{{ editor.storage.characterCount.characters() }} / {{ limit }} characters
@ -115,7 +107,7 @@ export default {
color: var(--gray-5);
display: flex;
font-size: 0.75rem;
gap: .5rem;
gap: 0.5rem;
margin: 1.5rem;
svg {

View File

@ -1,8 +1,4 @@
import {
computePosition,
flip,
shift,
} from '@floating-ui/dom'
import { computePosition, flip, shift } from '@floating-ui/dom'
import { posToDOMRect, VueRenderer } from '@tiptap/vue-3'
import MentionList from './MentionList.vue'
@ -27,8 +23,34 @@ const updatePosition = (editor, element) => {
export default {
items: ({ query }) => {
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',
].filter(item => item.toLowerCase().startsWith(query.toLowerCase())).slice(0, 5)
'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',
]
.filter(item => item.toLowerCase().startsWith(query.toLowerCase()))
.slice(0, 5)
},
render: () => {

View File

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

View File

@ -12,12 +12,16 @@ context('/src/Examples/CustomDocument/React/', () => {
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 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', () => {
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', () => {
@ -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', () => {
cy.get('.tiptap').type('{selectall}{backspace}Hello world{enter}')
cy.get('.tiptap h1')
.should('exist')
.should('have.text', 'Hello world')
cy.get('.tiptap h1').should('exist').should('have.text', 'Hello world')
cy.get('.tiptap p[data-placeholder]')
.should('exist')
.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 p')
.should('exist')
.should('have.text', 'This is a paragraph for this test document')
cy.get('.tiptap p').should('exist').should('have.text', 'This is a paragraph for this test document')
})
})

View File

@ -12,12 +12,16 @@ context('/src/Examples/CustomDocument/Vue/', () => {
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 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', () => {
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', () => {
@ -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', () => {
cy.get('.tiptap').type('{selectall}{backspace}Hello world{enter}')
cy.wait(100)
cy.get('.tiptap h1')
.should('exist')
.should('have.text', 'Hello world')
cy.get('.tiptap h1').should('exist').should('have.text', 'Hello world')
cy.get('.tiptap p[data-placeholder]')
.should('exist')
.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 p')
.should('exist')
.should('have.text', 'This is a paragraph for this test document')
cy.get('.tiptap p').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 {
NodeViewContent,
NodeViewWrapper,
ReactNodeViewRenderer,
} from '@tiptap/react'
import { NodeViewContent, NodeViewWrapper, ReactNodeViewRenderer } from '@tiptap/react'
const ParagraphComponent = ({ node }) => {
return (
<NodeViewWrapper style={{ position: 'relative' }}>
<span contentEditable={false} className="label" style={{
position: 'absolute', right: '100%', fontSize: '10px', color: '#999',
}}>
<span
contentEditable={false}
className="label"
style={{
position: 'absolute',
right: '100%',
fontSize: '10px',
color: '#999',
}}
>
{node.textContent.length}
</span>
<NodeViewContent as="p" />

View File

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

View File

@ -11,7 +11,9 @@ context('/src/Examples/CustomParagraph/React/', () => {
})
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')
})

View File

@ -11,7 +11,9 @@ context('/src/Examples/CustomParagraph/React/', () => {
})
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')
})

View File

@ -15,19 +15,14 @@ const MenuBar = () => {
selector: ctx => {
return {
isBold: ctx.editor.isActive('bold'),
canBold: ctx.editor.can().chain().focus().toggleBold()
.run(),
canBold: ctx.editor.can().chain().focus().toggleBold().run(),
isItalic: ctx.editor.isActive('italic'),
canItalic: ctx.editor.can().chain().focus().toggleItalic()
.run(),
canItalic: ctx.editor.can().chain().focus().toggleItalic().run(),
isStrike: ctx.editor.isActive('strike'),
canStrike: ctx.editor.can().chain().focus().toggleStrike()
.run(),
canStrike: ctx.editor.can().chain().focus().toggleStrike().run(),
isCode: ctx.editor.isActive('code'),
canCode: ctx.editor.can().chain().focus().toggleCode()
.run(),
canClearMarks: ctx.editor.can().chain().focus().unsetAllMarks()
.run(),
canCode: ctx.editor.can().chain().focus().toggleCode().run(),
canClearMarks: ctx.editor.can().chain().focus().unsetAllMarks().run(),
isParagraph: ctx.editor.isActive('paragraph'),
isHeading1: ctx.editor.isActive('heading', { level: 1 }),
isHeading2: ctx.editor.isActive('heading', { level: 2 }),
@ -39,10 +34,8 @@ const MenuBar = () => {
isOrderedList: ctx.editor.isActive('orderedList'),
isCodeBlock: ctx.editor.isActive('codeBlock'),
isBlockquote: ctx.editor.isActive('blockquote'),
canUndo: ctx.editor.can().chain().focus().undo()
.run(),
canRedo: ctx.editor.can().chain().focus().redo()
.run(),
canUndo: ctx.editor.can().chain().focus().undo().run(),
canRedo: ctx.editor.can().chain().focus().redo().run(),
isPurple: editor.isActive('textStyle', { color: '#958DF1' }),
}
},
@ -83,12 +76,8 @@ const MenuBar = () => {
>
Code
</button>
<button onClick={() => editor.chain().focus().unsetAllMarks().run()}>
Clear marks
</button>
<button onClick={() => editor.chain().focus().clearNodes().run()}>
Clear nodes
</button>
<button onClick={() => editor.chain().focus().unsetAllMarks().run()}>Clear marks</button>
<button onClick={() => editor.chain().focus().clearNodes().run()}>Clear nodes</button>
<button
onClick={() => editor.chain().focus().setParagraph().run()}
className={editorState.isParagraph ? 'is-active' : ''}
@ -155,22 +144,12 @@ const MenuBar = () => {
>
Blockquote
</button>
<button onClick={() => editor.chain().focus().setHorizontalRule().run()}>
Horizontal rule
</button>
<button onClick={() => editor.chain().focus().setHardBreak().run()}>
Hard break
</button>
<button
onClick={() => editor.chain().focus().undo().run()}
disabled={!editorState.canUndo}
>
<button onClick={() => editor.chain().focus().setHorizontalRule().run()}>Horizontal rule</button>
<button onClick={() => editor.chain().focus().setHardBreak().run()}>Hard break</button>
<button onClick={() => editor.chain().focus().undo().run()} disabled={!editorState.canUndo}>
Undo
</button>
<button
onClick={() => editor.chain().focus().redo().run()}
disabled={!editorState.canRedo}
>
<button onClick={() => editor.chain().focus().redo().run()} disabled={!editorState.canRedo}>
Redo
</button>
<button
@ -231,7 +210,5 @@ const content = `
`
export default () => {
return (
<EditorProvider slotBefore={<MenuBar />} extensions={extensions} content={content}></EditorProvider>
)
return <EditorProvider slotBefore={<MenuBar />} extensions={extensions} content={content}></EditorProvider>
}

View File

@ -2,70 +2,114 @@
<div v-if="editor" class="container">
<div class="control-group">
<div class="button-group">
<button @click="editor.chain().focus().toggleBold().run()" :disabled="!editor.can().chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }">
<button
@click="editor.chain().focus().toggleBold().run()"
:disabled="!editor.can().chain().focus().toggleBold().run()"
:class="{ 'is-active': editor.isActive('bold') }"
>
Bold
</button>
<button @click="editor.chain().focus().toggleItalic().run()" :disabled="!editor.can().chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }">
<button
@click="editor.chain().focus().toggleItalic().run()"
:disabled="!editor.can().chain().focus().toggleItalic().run()"
:class="{ 'is-active': editor.isActive('italic') }"
>
Italic
</button>
<button @click="editor.chain().focus().toggleStrike().run()" :disabled="!editor.can().chain().focus().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }">
<button
@click="editor.chain().focus().toggleStrike().run()"
:disabled="!editor.can().chain().focus().toggleStrike().run()"
:class="{ 'is-active': editor.isActive('strike') }"
>
Strike
</button>
<button @click="editor.chain().focus().toggleCode().run()" :disabled="!editor.can().chain().focus().toggleCode().run()" :class="{ 'is-active': editor.isActive('code') }">
<button
@click="editor.chain().focus().toggleCode().run()"
:disabled="!editor.can().chain().focus().toggleCode().run()"
:class="{ 'is-active': editor.isActive('code') }"
>
Code
</button>
<button @click="editor.chain().focus().unsetAllMarks().run()">
Clear marks
</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().unsetAllMarks().run()">Clear marks</button>
<button @click="editor.chain().focus().clearNodes().run()">Clear nodes</button>
<button
@click="editor.chain().focus().setParagraph().run()"
:class="{ 'is-active': editor.isActive('paragraph') }"
>
Paragraph
</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
</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
</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
</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
</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
</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
</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
</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
</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
</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
</button>
<button @click="editor.chain().focus().setHorizontalRule().run()">
Horizontal rule
</button>
<button @click="editor.chain().focus().setHardBreak().run()">
Hard break
</button>
<button @click="editor.chain().focus().setHorizontalRule().run()">Horizontal rule</button>
<button @click="editor.chain().focus().setHardBreak().run()">Hard break</button>
<button @click="editor.chain().focus().undo().run()" :disabled="!editor.can().chain().focus().undo().run()">
Undo
</button>
<button @click="editor.chain().focus().redo().run()" :disabled="!editor.can().chain().focus().redo().run()">
Redo
</button>
<button @click="editor.chain().focus().setColor('#958DF1').run()" :class="{ 'is-active': editor.isActive('textStyle', { color: '#958DF1' }) }">
<button
@click="editor.chain().focus().setColor('#958DF1').run()"
:class="{ 'is-active': editor.isActive('textStyle', { color: '#958DF1' }) }"
>
Purple
</button>
</div>

View File

@ -2,16 +2,9 @@
<node-view-wrapper class="draw">
<div class="control-group">
<div class="button-group">
<input type="color" v-model="color">
<input
type="number"
min="1"
max="10"
v-model="size"
>
<button @click="clear">
Clear
</button>
<input type="color" v-model="color" />
<input type="number" min="1" max="10" v-model="size" />
<button @click="clear">Clear</button>
</div>
<svg viewBox="0 0 500 250" ref="canvas">
<template v-for="item in node.attrs.lines">
@ -49,15 +42,7 @@ export default {
data() {
return {
color: getRandomElement([
'#A975FF',
'#FB5151',
'#FD9170',
'#FFCB6B',
'#68CEF8',
'#80CBC4',
'#9DEF8F',
]),
color: getRandomElement(['#A975FF', '#FB5151', '#FD9170', '#FFCB6B', '#68CEF8', '#80CBC4', '#9DEF8F']),
size: Math.ceil(Math.random() * Math.floor(10)),
svg: null,
path: null,
@ -78,9 +63,7 @@ export default {
.attr('stroke', this.color)
.attr('stroke-width', this.size)
const moveEvent = event.type === 'mousedown'
? 'mousemove'
: 'touchmove'
const moveEvent = event.type === 'mousedown' ? 'mousemove' : 'touchmove'
this.svg.on(moveEvent, this.onMove)
},

View File

@ -14,40 +14,76 @@ const MenuBar = ({ editor }) => {
return (
<div className="control-group">
<div className="button-group">
<button onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()} className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''}>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''}
>
H1
</button>
<button onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''}>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''}
>
H2
</button>
<button onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''}>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''}
>
H3
</button>
<button onClick={() => editor.chain().focus().setParagraph().run()} className={editor.isActive('paragraph') ? 'is-active' : ''}>
<button
onClick={() => editor.chain().focus().setParagraph().run()}
className={editor.isActive('paragraph') ? 'is-active' : ''}
>
Paragraph
</button>
<button onClick={() => editor.chain().focus().toggleBold().run()} className={editor.isActive('bold') ? 'is-active' : ''}>
<button
onClick={() => editor.chain().focus().toggleBold().run()}
className={editor.isActive('bold') ? 'is-active' : ''}
>
Bold
</button>
<button onClick={() => editor.chain().focus().toggleItalic().run()} className={editor.isActive('italic') ? 'is-active' : ''}>
<button
onClick={() => editor.chain().focus().toggleItalic().run()}
className={editor.isActive('italic') ? 'is-active' : ''}
>
Italic
</button>
<button onClick={() => editor.chain().focus().toggleStrike().run()} className={editor.isActive('strike') ? 'is-active' : ''}>
<button
onClick={() => editor.chain().focus().toggleStrike().run()}
className={editor.isActive('strike') ? 'is-active' : ''}
>
Strike
</button>
<button onClick={() => editor.chain().focus().toggleHighlight().run()} className={editor.isActive('highlight') ? 'is-active' : ''}>
<button
onClick={() => editor.chain().focus().toggleHighlight().run()}
className={editor.isActive('highlight') ? 'is-active' : ''}
>
Highlight
</button>
<button onClick={() => editor.chain().focus().setTextAlign('left').run()} className={editor.isActive({ textAlign: 'left' }) ? 'is-active' : ''}>
<button
onClick={() => editor.chain().focus().setTextAlign('left').run()}
className={editor.isActive({ textAlign: 'left' }) ? 'is-active' : ''}
>
Left
</button>
<button onClick={() => editor.chain().focus().setTextAlign('center').run()} className={editor.isActive({ textAlign: 'center' }) ? 'is-active' : ''}>
<button
onClick={() => editor.chain().focus().setTextAlign('center').run()}
className={editor.isActive({ textAlign: 'center' }) ? 'is-active' : ''}
>
Center
</button>
<button onClick={() => editor.chain().focus().setTextAlign('right').run()} className={editor.isActive({ textAlign: 'right' }) ? 'is-active' : ''}>
<button
onClick={() => editor.chain().focus().setTextAlign('right').run()}
className={editor.isActive({ textAlign: 'right' }) ? 'is-active' : ''}
>
Right
</button>
<button onClick={() => editor.chain().focus().setTextAlign('justify').run()} className={editor.isActive({ textAlign: 'justify' }) ? 'is-active' : ''}>
<button
onClick={() => editor.chain().focus().setTextAlign('justify').run()}
className={editor.isActive({ textAlign: 'justify' }) ? 'is-active' : ''}
>
Justify
</button>
</div>

View File

@ -7,9 +7,7 @@ context('/src/Examples/Formatting/React/', () => {
cy.get('.tiptap').type('{selectall}{backspace}')
})
const marks = [
{ label: 'Highlight', mark: 'mark' },
]
const marks = [{ label: 'Highlight', mark: 'mark' }]
marks.forEach(m => {
it(`sets ${m.label}`, () => {

View File

@ -78,7 +78,7 @@
}
mark {
background-color: #FAF594;
background-color: #faf594;
border-radius: 0.4rem;
box-decoration-break: clone;
padding: 0.1rem 0.3rem;

View File

@ -7,9 +7,7 @@ context('/src/Examples/Formatting/Vue/', () => {
cy.get('.tiptap').type('{selectall}{backspace}')
})
const marks = [
{ label: 'Highlight', mark: 'mark' },
]
const marks = [{ label: 'Highlight', mark: 'mark' }]
marks.forEach(m => {
it(`sets ${m.label}`, () => {

View File

@ -2,40 +2,73 @@
<div v-if="editor" class="container">
<div class="control-group">
<div class="button-group">
<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
</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
</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
</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
</button>
<button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }">
Bold
</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
</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
</button>
<button @click="editor.chain().focus().toggleHighlight().run()" :class="{ 'is-active': editor.isActive('highlight') }">
<button
@click="editor.chain().focus().toggleHighlight().run()"
:class="{ 'is-active': editor.isActive('highlight') }"
>
Highlight
</button>
<button @click="editor.chain().focus().setTextAlign('left').run()" :class="{ 'is-active': editor.isActive({ textAlign: 'left' }) }">
<button
@click="editor.chain().focus().setTextAlign('left').run()"
:class="{ 'is-active': editor.isActive({ textAlign: 'left' }) }"
>
Left
</button>
<button @click="editor.chain().focus().setTextAlign('center').run()" :class="{ 'is-active': editor.isActive({ textAlign: 'center' }) }">
<button
@click="editor.chain().focus().setTextAlign('center').run()"
:class="{ 'is-active': editor.isActive({ textAlign: 'center' }) }"
>
Center
</button>
<button @click="editor.chain().focus().setTextAlign('right').run()" :class="{ 'is-active': editor.isActive({ textAlign: 'right' }) }">
<button
@click="editor.chain().focus().setTextAlign('right').run()"
:class="{ 'is-active': editor.isActive({ textAlign: 'right' }) }"
>
Right
</button>
<button @click="editor.chain().focus().setTextAlign('justify').run()" :class="{ 'is-active': editor.isActive({ textAlign: 'justify' }) }">
<button
@click="editor.chain().focus().setTextAlign('justify').run()"
:class="{ 'is-active': editor.isActive({ textAlign: 'justify' }) }"
>
Justify
</button>
</div>
@ -186,7 +219,7 @@ export default {
}
mark {
background-color: #FAF594;
background-color: #faf594;
border-radius: 0.4rem;
box-decoration-break: clone;
padding: 0.1rem 0.3rem;

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