Merge branch 'develop' into feature/once

This commit is contained in:
Nick Perez 2024-11-08 10:39:27 +01:00 committed by GitHub
commit b873b36f11
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
892 changed files with 44478 additions and 74188 deletions

8
.changeset/README.md Normal file
View File

@ -0,0 +1,8 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

View File

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

View File

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

14
.changeset/config.json Normal file
View File

@ -0,0 +1,14 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.0.1/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [["@tiptap/*"]],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [],
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"onlyUpdatePeerDependentsWhenOutOfRange": true
}
}

View File

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

View File

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

View File

@ -0,0 +1,5 @@
---
"@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

@ -0,0 +1,5 @@
---
"@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

@ -0,0 +1,5 @@
---
"@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

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

View File

@ -0,0 +1,5 @@
---
"@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

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

View File

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

View File

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

View File

@ -0,0 +1,6 @@
---
"@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

@ -8,6 +8,15 @@ module.exports = {
node: true,
},
overrides: [
{
files: [
'./**/*.ts',
'./**/*.tsx',
'./**/*.js',
'./**/*.jsx',
],
extends: ['plugin:react-hooks/recommended'],
},
{
files: [
'./**/*.ts',

3
.github/CODEOWNERS vendored
View File

@ -1,9 +1,6 @@
# Global
* @bdbch @svenadlung
# docs
/docs/ @svenadlung
# demos
/demos/ @bdbch

View File

@ -14,7 +14,7 @@
<!-- Add any other notes or screenshots about the PR here. -->
## Checklist
- [ ] I have renamed my PR according to the naming conventions. (e.g. `feat: Implement new feature` or `chore(deps): Update dependencies`)
- [ ] 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.
- [ ] I have followed the project guidelines.

View File

@ -3,16 +3,22 @@
name: build
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches:
- main
- develop
- next
- release/*
pull_request:
branches:
- main
- develop
- next
jobs:
lint:
@ -20,7 +26,7 @@ jobs:
strategy:
matrix:
node-version: [16]
node-version: [20]
steps:
- uses: actions/checkout@v4.1.4
@ -31,11 +37,12 @@ jobs:
node-version: ${{ matrix.node-version }}
- name: Load cached dependencies
uses: actions/cache@v4.0.2
uses: actions/cache@v4.1.2
id: cache
with:
path: |
**/node_modules
**/.turbo
/home/runner/.cache/Cypress
key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
@ -64,7 +71,7 @@ jobs:
strategy:
matrix:
node-version: [16]
node-version: [20]
test-spec:
- { name: "Integration", spec: "./tests/cypress/integration/**/*.spec.{js,ts}" }
#- { name: "Demos/Commands", spec: "./demos/src/Commands/**/*.spec.{js,ts}" }
@ -96,10 +103,10 @@ jobs:
- name: Test ${{ matrix.test-spec.name }}
id: cypress
uses: cypress-io/github-action@v6.6.0
uses: cypress-io/github-action@v6.7.6
with:
cache-key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
start: npm run start
start: npm run serve
wait-on: 'http://localhost:3000'
spec: ${{ matrix.test-spec.spec }}
project: ./tests
@ -107,7 +114,7 @@ jobs:
quiet: true
- name: Export screenshots (on failure only)
uses: actions/upload-artifact@v4.3.3
uses: actions/upload-artifact@v4.4.3
if: failure()
with:
name: cypress-screenshots
@ -115,7 +122,7 @@ jobs:
retention-days: 7
- name: Export screen recordings (on failure only)
uses: actions/upload-artifact@v4.3.3
uses: actions/upload-artifact@v4.4.3
if: failure()
with:
name: cypress-videos
@ -129,7 +136,7 @@ jobs:
strategy:
matrix:
node-version: [16]
node-version: [20]
steps:
- uses: actions/checkout@v4.1.4
@ -140,7 +147,7 @@ jobs:
node-version: ${{ matrix.node-version }}
- name: Load cached dependencies
uses: actions/cache@v4.0.2
uses: actions/cache@v4.1.2
id: cache
with:
path: |

View File

@ -1,21 +0,0 @@
# Automate, customize, and execute your software development workflows right in your repository with GitHub Actions.
# Documentation: https://docs.github.com/en/actions
name: deploy
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Update the documentation
run: curl ${{ secrets.TRIGGER_DEPLOYMENT }}

View File

@ -1,27 +0,0 @@
# Automate, customize, and execute your software development workflows right in your repository with GitHub Actions.
# Documentation: https://docs.github.com/en/actions
name: docsearch
on:
workflow_dispatch:
schedule:
- cron: '5 0 * * *'
jobs:
docsearch:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.4
- name: Run DocSearch Scraper
shell: bash
run: |
docker run \
-e TYPESENSE_API_KEY=${{ secrets.TYPESENSE_API_KEY }} \
-e TYPESENSE_HOST="${{ secrets.TYPESENSE_HOST }}" \
-e TYPESENSE_PORT="${{ secrets.TYPESENSE_PORT }}" \
-e TYPESENSE_PROTOCOL="${{ secrets.TYPESENSE_PROTOCOL }}" \
-e CONFIG="$(cat docsearch.config.json | jq -r tostring)" \
typesense/docsearch-scraper

88
.github/workflows/publish.yml vendored Normal file
View File

@ -0,0 +1,88 @@
name: Publish
on:
push:
branches:
- main
- develop
# manual trigger for other branches
workflow_dispatch:
permissions:
id-token: write
contents: write
pull-requests: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
release:
name: Release
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20]
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Setup Node ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
registry-url: 'https://registry.npmjs.org/'
- name: Load cached dependencies
uses: actions/cache@v4.1.2
id: cache
with:
path: |
**/node_modules
**/.turbo
/home/runner/.cache/Cypress
key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
- name: Install Dependencies
run: npm ci
- name: Create Release PR or publish stable version to npm
id: changesets
uses: changesets/action@v1
with:
createGithubReleases: false
publish: npm run publish
version: npm run version
title: ${{ github.ref_name == 'main' && 'Publish a new stable version' || 'Publish a new pre-release version' }}
commit: >-
${{ github.ref_name == 'main' && 'chore(release): publish a new release version' || 'chore(release): publish a new pre-release version' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
- name: Send release notification
if: steps.changesets.outputs.published == 'true'
id: slack
uses: slackapi/slack-github-action@v1.27.0
with:
payload: |
{
"message": "[Tiptap Editor Release]: New Tiptap Editor version has been released to NPM."
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
- name: Send failure notification
if: failure()
id: slack_failure
uses: slackapi/slack-github-action@v1.27.0
with:
payload: |
{
"message": "[Tiptap Editor Release]: There was an issue publishing a new version. You can find the logs here: https://github.com/ueberdosis/tiptap/actions/runs/${{ github.run_id }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

View File

@ -1,31 +0,0 @@
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
name: Release new version
# on github release published or workflow_dispatch
on:
workflow_dispatch:
release:
types: [published]
jobs:
publish-npm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.4
- uses: actions/setup-node@v4.0.0
with:
node-version: 16
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm run publish
name: "Publish release (current) to NPM"
if: "!github.event.release.prerelease"
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
- run: npm run publish:pre
name: "Publish release (next) to NPM"
if: "github.event.release.prerelease"
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}

3
.gitignore vendored
View File

@ -17,6 +17,9 @@ yarn-error.log*
# parcel-bundler cache (https://parceljs.org/)
.cache
# Turbo cache
.turbo
.rpt2_cache
.rts2_cache
.rts2_cache_cjs

View File

@ -1,2 +0,0 @@
#!/usr/bin/env sh
npx --no -- commitlint --edit "$1"

View File

@ -1,5 +1,11 @@
# Change Log
> **Important information**
>
> As of version 2.4.1 Tiptap uses **Changesets** which don't allow the generation of one generic CHANGELOG file.
> If you want to check changes of a specific package version, check the **CHANGELOG.md** file in the specific package
> directory or out [Github Releases](https://github.com/ueberdosis/tiptap/releases)
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

View File

@ -36,10 +36,10 @@ Before submitting a pull request:
- Check the codebase to ensure that your feature doesn't already exist.
- Check the pull requests to ensure that another person hasn't already submitted the feature or fix.
Before commiting:
Before committing:
- Make sure to run the tests and linter before committing your changes.
- Write [conventional commit messages](https://www.conventionalcommits.org/en). You can use `npm run cz` for that.
- If you are making changes to one of the packages, make sure to **always** include a [changeset](https://github.com/changesets/changesets) in your PR describing **what changed** with a **description** of the change. Those are responsible for changelog creation
## Requirements

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2023, Tiptap GmbH
Copyright (c) 2024, Tiptap GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -86,6 +86,12 @@ For help, discussion about best practices, or any other conversation that would
<strong>Basewell</strong>
</a>
</td>
<td align="center" width="100">
<a href="https://poggio.io">
<img src="https://unavatar.io/github/poggiolabs" width="25"><br>
<strong>Poggio</strong>
</a>
</td>
</tr>
</table>
@ -93,7 +99,7 @@ For help, discussion about best practices, or any other conversation that would
</table>
[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 inviduals](https://github.com/sponsors/ueberdosis).
[iFixit](https://www.ifixit.com/), [ApostropheCMS](https://apostrophecms.com/), [Novadiscovery](http://www.novadiscovery.com/), [Omics Data Automation](https://www.omicsautomation.com), [Flow Mobile](https://www.flowmobile.app/), [DocIQ](https://www.dociq.io/) and [hundreds of awesome individuals](https://github.com/sponsors/ueberdosis).
### Contributing
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.

View File

@ -1,5 +1,31 @@
# Change Log
## 2.5.0
### Minor Changes
- 6834a7f: Bundling of packages no longer includes tiptap dependency type definitions
## 2.4.2
### Patch Changes
- d6e56c4: declare lowlight to be a peer dep of extension-code-block-lowlight, update usage to v3
## 2.4.1
### Patch Changes
- 85d21ca: Updated demos and reverted vue specific performance enhancements until we know they work
> in commit ff04353b3ee0e6fc63733a673e2b27d2272a3355 revert: "fix(vue-3): faster component rendering (#5206)"
> This reverts commit 31f37464912b7b21f3a565ca63222b9f5b6cce00.
and
> in commit dbab8e42eac893a0237566fb30c14b4ed0f3674a revert: "fix(vue-3): fix editor.state updating too late during a transaction due to reactiveState fixes #4870 (#5252)"
> This reverts commit 509676ed4a63b84b904a98c1e34d18449d25c2a7.
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
@ -7,868 +33,480 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
**Note:** Version bump only for package tiptap-demos
## [2.3.2](https://github.com/ueberdosis/tiptap/compare/v2.3.1...v2.3.2) (2024-05-08)
### Bug Fixes
* NodePos querySelectorAll function ([#5094](https://github.com/ueberdosis/tiptap/issues/5094)) ([4900a27](https://github.com/ueberdosis/tiptap/commit/4900a27c5389d9a2d0d69f407ca3db0155304315))
- NodePos querySelectorAll function ([#5094](https://github.com/ueberdosis/tiptap/issues/5094)) ([4900a27](https://github.com/ueberdosis/tiptap/commit/4900a27c5389d9a2d0d69f407ca3db0155304315))
## [2.3.1](https://github.com/ueberdosis/tiptap/compare/v2.3.0...v2.3.1) (2024-04-30)
**Note:** Version bump only for package tiptap-demos
# [2.3.0](https://github.com/ueberdosis/tiptap/compare/v2.2.6...v2.3.0) (2024-04-09)
### Bug Fixes
* **core:** fix nodepos child lookup ([#5038](https://github.com/ueberdosis/tiptap/issues/5038)) ([22ced31](https://github.com/ueberdosis/tiptap/commit/22ced318723003365fbfd8f59b8dac79c7563017))
- **core:** fix nodepos child lookup ([#5038](https://github.com/ueberdosis/tiptap/issues/5038)) ([22ced31](https://github.com/ueberdosis/tiptap/commit/22ced318723003365fbfd8f59b8dac79c7563017))
### Features
* **core:** apply input and paste rules when using insertContent methods ([#5046](https://github.com/ueberdosis/tiptap/issues/5046)) ([96b6abc](https://github.com/ueberdosis/tiptap/commit/96b6abcf6edbc6cac03a391130d9feebb6de3a04))
- **core:** apply input and paste rules when using insertContent methods ([#5046](https://github.com/ueberdosis/tiptap/issues/5046)) ([96b6abc](https://github.com/ueberdosis/tiptap/commit/96b6abcf6edbc6cac03a391130d9feebb6de3a04))
## [2.2.6](https://github.com/ueberdosis/tiptap/compare/v2.2.5...v2.2.6) (2024-04-06)
**Note:** Version bump only for package tiptap-demos
## [2.2.5](https://github.com/ueberdosis/tiptap/compare/v2.2.4...v2.2.5) (2024-04-05)
**Note:** Version bump only for package tiptap-demos
## [2.2.4](https://github.com/ueberdosis/tiptap/compare/v2.2.3...v2.2.4) (2024-02-23)
**Note:** Version bump only for package tiptap-demos
## [2.2.3](https://github.com/ueberdosis/tiptap/compare/v2.2.2...v2.2.3) (2024-02-15)
### Bug Fixes
* fix test path ([21aa96d](https://github.com/ueberdosis/tiptap/commit/21aa96dee8deab1f439b7f655b8ed266a516a4cd))
- fix test path ([21aa96d](https://github.com/ueberdosis/tiptap/commit/21aa96dee8deab1f439b7f655b8ed266a516a4cd))
## [2.2.2](https://github.com/ueberdosis/tiptap/compare/v2.2.1...v2.2.2) (2024-02-07)
**Note:** Version bump only for package tiptap-demos
## [2.2.1](https://github.com/ueberdosis/tiptap/compare/v2.2.0...v2.2.1) (2024-01-31)
**Note:** Version bump only for package tiptap-demos
# [2.2.0](https://github.com/ueberdosis/tiptap/compare/v2.1.16...v2.2.0) (2024-01-29)
### Bug Fixes
* **core:** fix new lines being added via elementFromString ([#4767](https://github.com/ueberdosis/tiptap/issues/4767)) ([b7a2504](https://github.com/ueberdosis/tiptap/commit/b7a2504f16f46563537c890930cb2c332c256175))
* fix imports, fix demos, unpin y-prosemirror ([681aa57](https://github.com/ueberdosis/tiptap/commit/681aa577bff500015c3f925e300c55a71c73efaf))
* fix newline stripping via insertContent ([8954007](https://github.com/ueberdosis/tiptap/commit/8954007b2b92b040d69b26a0866ae58fabf5e512))
- **core:** fix new lines being added via elementFromString ([#4767](https://github.com/ueberdosis/tiptap/issues/4767)) ([b7a2504](https://github.com/ueberdosis/tiptap/commit/b7a2504f16f46563537c890930cb2c332c256175))
- fix imports, fix demos, unpin y-prosemirror ([681aa57](https://github.com/ueberdosis/tiptap/commit/681aa577bff500015c3f925e300c55a71c73efaf))
- fix newline stripping via insertContent ([8954007](https://github.com/ueberdosis/tiptap/commit/8954007b2b92b040d69b26a0866ae58fabf5e512))
# [2.2.0-rc.8](https://github.com/ueberdosis/tiptap/compare/v2.1.14...v2.2.0-rc.8) (2024-01-08)
# [2.2.0-rc.7](https://github.com/ueberdosis/tiptap/compare/v2.2.0-rc.6...v2.2.0-rc.7) (2023-11-27)
# [2.2.0-rc.6](https://github.com/ueberdosis/tiptap/compare/v2.2.0-rc.5...v2.2.0-rc.6) (2023-11-23)
# [2.2.0-rc.4](https://github.com/ueberdosis/tiptap/compare/v2.1.11...v2.2.0-rc.4) (2023-10-10)
# [2.2.0-rc.3](https://github.com/ueberdosis/tiptap/compare/v2.2.0-rc.2...v2.2.0-rc.3) (2023-08-18)
# [2.2.0-rc.1](https://github.com/ueberdosis/tiptap/compare/v2.2.0-rc.0...v2.2.0-rc.1) (2023-08-18)
# [2.2.0-rc.0](https://github.com/ueberdosis/tiptap/compare/v2.1.5...v2.2.0-rc.0) (2023-08-18)
### Features
* **placeholder:** allow editor-is-empty class on any node ([#4335](https://github.com/ueberdosis/tiptap/issues/4335)) ([ff929b1](https://github.com/ueberdosis/tiptap/commit/ff929b179de930619005a773bb4186ae2aa2ec58))
- **placeholder:** allow editor-is-empty class on any node ([#4335](https://github.com/ueberdosis/tiptap/issues/4335)) ([ff929b1](https://github.com/ueberdosis/tiptap/commit/ff929b179de930619005a773bb4186ae2aa2ec58))
## [2.1.16](https://github.com/ueberdosis/tiptap/compare/v2.1.15...v2.1.16) (2024-01-10)
### Bug Fixes
* **core:** fix new lines being added via elementFromString ([#4767](https://github.com/ueberdosis/tiptap/issues/4767)) ([2235908](https://github.com/ueberdosis/tiptap/commit/2235908c28f388eda041d1d5d017554d513fe909))
- **core:** fix new lines being added via elementFromString ([#4767](https://github.com/ueberdosis/tiptap/issues/4767)) ([2235908](https://github.com/ueberdosis/tiptap/commit/2235908c28f388eda041d1d5d017554d513fe909))
## [2.1.15](https://github.com/ueberdosis/tiptap/compare/v2.1.14...v2.1.15) (2024-01-08)
### Bug Fixes
* **core:** fix insertContentAt keeping new lines in html content ([#4465](https://github.com/ueberdosis/tiptap/issues/4465)) ([135a12f](https://github.com/ueberdosis/tiptap/commit/135a12f7aa2df839a0b619704110a360b980c738))
* **link:** fix tests ([d495d92](https://github.com/ueberdosis/tiptap/commit/d495d92a1f7b1c51e09ac8f4934e15a2d1cf070d))
- **core:** fix insertContentAt keeping new lines in html content ([#4465](https://github.com/ueberdosis/tiptap/issues/4465)) ([135a12f](https://github.com/ueberdosis/tiptap/commit/135a12f7aa2df839a0b619704110a360b980c738))
- **link:** fix tests ([d495d92](https://github.com/ueberdosis/tiptap/commit/d495d92a1f7b1c51e09ac8f4934e15a2d1cf070d))
## [2.1.14](https://github.com/ueberdosis/tiptap/compare/v2.1.13...v2.1.14) (2024-01-08)
### Bug Fixes
* **typography:** require spaces after divisions to not break date formats ([#4696](https://github.com/ueberdosis/tiptap/issues/4696)) ([f6d7e00](https://github.com/ueberdosis/tiptap/commit/f6d7e00a746a67fa440a3fa0f5362295959873d2))
- **typography:** require spaces after divisions to not break date formats ([#4696](https://github.com/ueberdosis/tiptap/issues/4696)) ([f6d7e00](https://github.com/ueberdosis/tiptap/commit/f6d7e00a746a67fa440a3fa0f5362295959873d2))
## [2.1.13](https://github.com/ueberdosis/tiptap/compare/v2.1.12...v2.1.13) (2023-11-30)
**Note:** Version bump only for package tiptap-demos
## [2.1.12](https://github.com/ueberdosis/tiptap/compare/v2.1.11...v2.1.12) (2023-10-11)
**Note:** Version bump only for package tiptap-demos
## [2.1.11](https://github.com/ueberdosis/tiptap/compare/v2.1.10...v2.1.11) (2023-09-20)
### Reverts
* Revert "v2.2.11" ([6aa755a](https://github.com/ueberdosis/tiptap/commit/6aa755a04b9955fc175c7ab33dee527d0d5deef0))
- Revert "v2.2.11" ([6aa755a](https://github.com/ueberdosis/tiptap/commit/6aa755a04b9955fc175c7ab33dee527d0d5deef0))
## [2.1.10](https://github.com/ueberdosis/tiptap/compare/v2.1.9...v2.1.10) (2023-09-15)
**Note:** Version bump only for package tiptap-demos
## [2.1.9](https://github.com/ueberdosis/tiptap/compare/v2.1.8...v2.1.9) (2023-09-14)
**Note:** Version bump only for package tiptap-demos
## [2.1.8](https://github.com/ueberdosis/tiptap-workspace/compare/v2.1.7...v2.1.8) (2023-09-04)
**Note:** Version bump only for package tiptap-demos
## [2.1.7](https://github.com/ueberdosis/tiptap-workspace/compare/v2.1.6...v2.1.7) (2023-09-04)
**Note:** Version bump only for package tiptap-demos
## [2.1.6](https://github.com/ueberdosis/tiptap/compare/v2.1.5...v2.1.6) (2023-08-18)
**Note:** Version bump only for package tiptap-demos
# [2.2.0-rc.2](https://github.com/ueberdosis/tiptap/compare/v2.1.6...v2.2.0-rc.2) (2023-08-18)
# [2.2.0-rc.1](https://github.com/ueberdosis/tiptap/compare/v2.2.0-rc.0...v2.2.0-rc.1) (2023-08-18)
# [2.2.0-rc.0](https://github.com/ueberdosis/tiptap/compare/v2.1.5...v2.2.0-rc.0) (2023-08-18)
### Features
* **placeholder:** allow editor-is-empty class on any node ([#4335](https://github.com/ueberdosis/tiptap/issues/4335)) ([ff929b1](https://github.com/ueberdosis/tiptap/commit/ff929b179de930619005a773bb4186ae2aa2ec58))
- **placeholder:** allow editor-is-empty class on any node ([#4335](https://github.com/ueberdosis/tiptap/issues/4335)) ([ff929b1](https://github.com/ueberdosis/tiptap/commit/ff929b179de930619005a773bb4186ae2aa2ec58))
# [2.2.0-rc.1](https://github.com/ueberdosis/tiptap/compare/v2.2.0-rc.0...v2.2.0-rc.1) (2023-08-18)
**Note:** Version bump only for package tiptap-demos
# [2.2.0-rc.0](https://github.com/ueberdosis/tiptap/compare/v2.1.5...v2.2.0-rc.0) (2023-08-18)
### Features
* **placeholder:** allow editor-is-empty class on any node ([#4335](https://github.com/ueberdosis/tiptap/issues/4335)) ([ff929b1](https://github.com/ueberdosis/tiptap/commit/ff929b179de930619005a773bb4186ae2aa2ec58))
- **placeholder:** allow editor-is-empty class on any node ([#4335](https://github.com/ueberdosis/tiptap/issues/4335)) ([ff929b1](https://github.com/ueberdosis/tiptap/commit/ff929b179de930619005a773bb4186ae2aa2ec58))
## [2.1.6](https://github.com/ueberdosis/tiptap/compare/v2.1.5...v2.1.6) (2023-08-18)
**Note:** Version bump only for package tiptap-demos
## [2.1.5](https://github.com/ueberdosis/tiptap/compare/v2.1.4...v2.1.5) (2023-08-18)
**Note:** Version bump only for package tiptap-demos
## [2.1.4](https://github.com/ueberdosis/tiptap/compare/v2.1.3...v2.1.4) (2023-08-18)
**Note:** Version bump only for package tiptap-demos
## [2.1.3](https://github.com/ueberdosis/tiptap/compare/v2.1.2...v2.1.3) (2023-08-18)
**Note:** Version bump only for package tiptap-demos
## [2.1.2](https://github.com/ueberdosis/tiptap/compare/v2.1.1...v2.1.2) (2023-08-17)
### Bug Fixes
* **core:** fix error when merging class attributes ([#4340](https://github.com/ueberdosis/tiptap/issues/4340)) ([a251946](https://github.com/ueberdosis/tiptap/commit/a2519468589e2baa44901a66a3a06b24dc8626d6))
- **core:** fix error when merging class attributes ([#4340](https://github.com/ueberdosis/tiptap/issues/4340)) ([a251946](https://github.com/ueberdosis/tiptap/commit/a2519468589e2baa44901a66a3a06b24dc8626d6))
## [2.1.1](https://github.com/ueberdosis/tiptap/compare/v2.1.0...v2.1.1) (2023-08-16)
**Note:** Version bump only for package tiptap-demos
# [2.1.0](https://github.com/ueberdosis/tiptap-workspace/compare/v2.1.0-rc.14...v2.1.0) (2023-08-16)
**Note:** Version bump only for package tiptap-demos
# [2.1.0-rc.14](https://github.com/ueberdosis/tiptap-workspace/compare/v2.1.0-rc.13...v2.1.0-rc.14) (2023-08-11)
**Note:** Version bump only for package tiptap-demos
# [2.1.0-rc.13](https://github.com/ueberdosis/tiptap-workspace/compare/v2.0.4...v2.1.0-rc.13) (2023-08-11)
### Bug Fixes
* **demos:** add missing extensions ([6383fd5](https://github.com/ueberdosis/tiptap-workspace/commit/6383fd54080b2ad555286cd0e7c4ad880200200f))
* **demos:** update deps ([05a2edf](https://github.com/ueberdosis/tiptap-workspace/commit/05a2edfc16e297effa86d1583fb1680be0320f25))
* **strikethrough:** update strikethrough shortcut ([#4288](https://github.com/ueberdosis/tiptap-workspace/issues/4288)) ([fd35db4](https://github.com/ueberdosis/tiptap-workspace/commit/fd35db4d090d9fdfef1196fb1f6f858f13cf53d1))
- **demos:** add missing extensions ([6383fd5](https://github.com/ueberdosis/tiptap-workspace/commit/6383fd54080b2ad555286cd0e7c4ad880200200f))
- **demos:** update deps ([05a2edf](https://github.com/ueberdosis/tiptap-workspace/commit/05a2edfc16e297effa86d1583fb1680be0320f25))
- **strikethrough:** update strikethrough shortcut ([#4288](https://github.com/ueberdosis/tiptap-workspace/issues/4288)) ([fd35db4](https://github.com/ueberdosis/tiptap-workspace/commit/fd35db4d090d9fdfef1196fb1f6f858f13cf53d1))
# [2.1.0-rc.12](https://github.com/ueberdosis/tiptap-workspace/compare/v2.1.0-rc.11...v2.1.0-rc.12) (2023-07-14)
# [2.1.0-rc.11](https://github.com/ueberdosis/tiptap-workspace/compare/v2.1.0-rc.10...v2.1.0-rc.11) (2023-07-07)
### Bug Fixes
* **tests:** fix link rel tests ([c1d1854](https://github.com/ueberdosis/tiptap-workspace/commit/c1d18543b03b1fb6b99a2f3546aa5da10c919920))
- **tests:** fix link rel tests ([c1d1854](https://github.com/ueberdosis/tiptap-workspace/commit/c1d18543b03b1fb6b99a2f3546aa5da10c919920))
# [2.1.0-rc.10](https://github.com/ueberdosis/tiptap-workspace/compare/v2.1.0-rc.9...v2.1.0-rc.10) (2023-07-07)
### Bug Fixes
* **react:** check props.clientRect before creating ReactRenderer ([#4138](https://github.com/ueberdosis/tiptap-workspace/issues/4138)) ([d710846](https://github.com/ueberdosis/tiptap-workspace/commit/d710846ecb6a3059dfbc21300b9a4b887a8defa3))
- **react:** check props.clientRect before creating ReactRenderer ([#4138](https://github.com/ueberdosis/tiptap-workspace/issues/4138)) ([d710846](https://github.com/ueberdosis/tiptap-workspace/commit/d710846ecb6a3059dfbc21300b9a4b887a8defa3))
# [2.1.0-rc.9](https://github.com/ueberdosis/tiptap-workspace/compare/v2.1.0-rc.8...v2.1.0-rc.9) (2023-06-15)
# [2.1.0-rc.8](https://github.com/ueberdosis/tiptap-workspace/compare/v2.1.0-rc.7...v2.1.0-rc.8) (2023-05-25)
# [2.1.0-rc.5](https://github.com/ueberdosis/tiptap-workspace/compare/v2.1.0-rc.4...v2.1.0-rc.5) (2023-05-25)
### Features
* add tiptap class ([614fc80](https://github.com/ueberdosis/tiptap-workspace/commit/614fc8082c376bf3c40a05c23ceda6b4a6fbf8d0))
- add tiptap class ([614fc80](https://github.com/ueberdosis/tiptap-workspace/commit/614fc8082c376bf3c40a05c23ceda6b4a6fbf8d0))
# [2.1.0-rc.4](https://github.com/ueberdosis/tiptap-workspace/compare/v2.1.0-rc.3...v2.1.0-rc.4) (2023-04-27)
# [2.1.0-rc.3](https://github.com/ueberdosis/tiptap-workspace/compare/v2.1.0-rc.2...v2.1.0-rc.3) (2023-04-26)
# [2.1.0-rc.2](https://github.com/ueberdosis/tiptap-workspace/compare/v2.0.3...v2.1.0-rc.2) (2023-04-26)
### Bug Fixes
* **extension-link:** fix link not being kept when pasting url with link ([#3975](https://github.com/ueberdosis/tiptap-workspace/issues/3975)) ([e7d7d49](https://github.com/ueberdosis/tiptap-workspace/commit/e7d7d496376c8c11e24c342e20bd179a6ea7dcee))
- **extension-link:** fix link not being kept when pasting url with link ([#3975](https://github.com/ueberdosis/tiptap-workspace/issues/3975)) ([e7d7d49](https://github.com/ueberdosis/tiptap-workspace/commit/e7d7d496376c8c11e24c342e20bd179a6ea7dcee))
# [2.1.0-rc.1](https://github.com/ueberdosis/tiptap-workspace/compare/v2.1.0-rc.0...v2.1.0-rc.1) (2023-04-12)
### Bug Fixes
* **bubble-menu:** fix debounce not working with collab/collaboration cursor ([#3956](https://github.com/ueberdosis/tiptap-workspace/issues/3956)) ([e8cef04](https://github.com/ueberdosis/tiptap-workspace/commit/e8cef0404b5039ec2657536976b8b31931afd337))
- **bubble-menu:** fix debounce not working with collab/collaboration cursor ([#3956](https://github.com/ueberdosis/tiptap-workspace/issues/3956)) ([e8cef04](https://github.com/ueberdosis/tiptap-workspace/commit/e8cef0404b5039ec2657536976b8b31931afd337))
# [2.1.0-rc.0](https://github.com/ueberdosis/tiptap-workspace/compare/v2.0.2...v2.1.0-rc.0) (2023-04-05)
### Bug Fixes
* clear nodes when cursor at start of empty isolating parent ([#3943](https://github.com/ueberdosis/tiptap-workspace/issues/3943)) ([7278ee2](https://github.com/ueberdosis/tiptap-workspace/commit/7278ee2b05de2f96efddf3b1dc3bfd3d52262cbb))
* Update peerDependencies to fix lerna version tasks ([#3914](https://github.com/ueberdosis/tiptap-workspace/issues/3914)) ([0c1bba3](https://github.com/ueberdosis/tiptap-workspace/commit/0c1bba3137b535776bcef95ff3c55e13f5a2db46))
- clear nodes when cursor at start of empty isolating parent ([#3943](https://github.com/ueberdosis/tiptap-workspace/issues/3943)) ([7278ee2](https://github.com/ueberdosis/tiptap-workspace/commit/7278ee2b05de2f96efddf3b1dc3bfd3d52262cbb))
- Update peerDependencies to fix lerna version tasks ([#3914](https://github.com/ueberdosis/tiptap-workspace/issues/3914)) ([0c1bba3](https://github.com/ueberdosis/tiptap-workspace/commit/0c1bba3137b535776bcef95ff3c55e13f5a2db46))
# [2.1.0-rc.12](https://github.com/ueberdosis/tiptap-workspace/compare/v2.1.0-rc.11...v2.1.0-rc.12) (2023-07-14)
**Note:** Version bump only for package tiptap-demos
# [2.1.0-rc.11](https://github.com/ueberdosis/tiptap/compare/v2.1.0-rc.10...v2.1.0-rc.11) (2023-07-07)
### Bug Fixes
* **tests:** fix link rel tests ([c1d1854](https://github.com/ueberdosis/tiptap/commit/c1d18543b03b1fb6b99a2f3546aa5da10c919920))
- **tests:** fix link rel tests ([c1d1854](https://github.com/ueberdosis/tiptap/commit/c1d18543b03b1fb6b99a2f3546aa5da10c919920))
# [2.1.0-rc.10](https://github.com/ueberdosis/tiptap/compare/v2.1.0-rc.9...v2.1.0-rc.10) (2023-07-07)
### Bug Fixes
* **react:** check props.clientRect before creating ReactRenderer ([#4138](https://github.com/ueberdosis/tiptap/issues/4138)) ([d710846](https://github.com/ueberdosis/tiptap/commit/d710846ecb6a3059dfbc21300b9a4b887a8defa3))
- **react:** check props.clientRect before creating ReactRenderer ([#4138](https://github.com/ueberdosis/tiptap/issues/4138)) ([d710846](https://github.com/ueberdosis/tiptap/commit/d710846ecb6a3059dfbc21300b9a4b887a8defa3))
# [2.1.0-rc.9](https://github.com/ueberdosis/tiptap/compare/v2.1.0-rc.8...v2.1.0-rc.9) (2023-06-15)
**Note:** Version bump only for package tiptap-demos
# [2.1.0-rc.8](https://github.com/ueberdosis/tiptap/compare/v2.1.0-rc.7...v2.1.0-rc.8) (2023-05-25)
**Note:** Version bump only for package tiptap-demos
# [2.1.0-rc.7](https://github.com/ueberdosis/tiptap/compare/v2.1.0-rc.6...v2.1.0-rc.7) (2023-05-25)
**Note:** Version bump only for package tiptap-demos
# [2.1.0-rc.6](https://github.com/ueberdosis/tiptap/compare/v2.1.0-rc.5...v2.1.0-rc.6) (2023-05-25)
**Note:** Version bump only for package tiptap-demos
# [2.1.0-rc.5](https://github.com/ueberdosis/tiptap/compare/v2.1.0-rc.4...v2.1.0-rc.5) (2023-05-25)
### Features
* add tiptap class ([614fc80](https://github.com/ueberdosis/tiptap/commit/614fc8082c376bf3c40a05c23ceda6b4a6fbf8d0))
- add tiptap class ([614fc80](https://github.com/ueberdosis/tiptap/commit/614fc8082c376bf3c40a05c23ceda6b4a6fbf8d0))
# [2.1.0-rc.4](https://github.com/ueberdosis/tiptap/compare/v2.1.0-rc.3...v2.1.0-rc.4) (2023-04-27)
**Note:** Version bump only for package tiptap-demos
# [2.1.0-rc.3](https://github.com/ueberdosis/tiptap/compare/v2.1.0-rc.2...v2.1.0-rc.3) (2023-04-26)
**Note:** Version bump only for package tiptap-demos
# [2.1.0-rc.2](https://github.com/ueberdosis/tiptap/compare/v2.0.3...v2.1.0-rc.2) (2023-04-26)
### Bug Fixes
* **extension-link:** fix link not being kept when pasting url with link ([#3975](https://github.com/ueberdosis/tiptap/issues/3975)) ([e7d7d49](https://github.com/ueberdosis/tiptap/commit/e7d7d496376c8c11e24c342e20bd179a6ea7dcee))
- **extension-link:** fix link not being kept when pasting url with link ([#3975](https://github.com/ueberdosis/tiptap/issues/3975)) ([e7d7d49](https://github.com/ueberdosis/tiptap/commit/e7d7d496376c8c11e24c342e20bd179a6ea7dcee))
# [2.1.0-rc.1](https://github.com/ueberdosis/tiptap/compare/v2.1.0-rc.0...v2.1.0-rc.1) (2023-04-12)
### Bug Fixes
* **bubble-menu:** fix debounce not working with collab/collaboration cursor ([#3956](https://github.com/ueberdosis/tiptap/issues/3956)) ([e8cef04](https://github.com/ueberdosis/tiptap/commit/e8cef0404b5039ec2657536976b8b31931afd337))
- **bubble-menu:** fix debounce not working with collab/collaboration cursor ([#3956](https://github.com/ueberdosis/tiptap/issues/3956)) ([e8cef04](https://github.com/ueberdosis/tiptap/commit/e8cef0404b5039ec2657536976b8b31931afd337))
# [2.1.0-rc.0](https://github.com/ueberdosis/tiptap/compare/v2.0.2...v2.1.0-rc.0) (2023-04-05)
### Bug Fixes
* clear nodes when cursor at start of empty isolating parent ([#3943](https://github.com/ueberdosis/tiptap/issues/3943)) ([7278ee2](https://github.com/ueberdosis/tiptap/commit/7278ee2b05de2f96efddf3b1dc3bfd3d52262cbb))
* Update peerDependencies to fix lerna version tasks ([#3914](https://github.com/ueberdosis/tiptap/issues/3914)) ([0c1bba3](https://github.com/ueberdosis/tiptap/commit/0c1bba3137b535776bcef95ff3c55e13f5a2db46))
- clear nodes when cursor at start of empty isolating parent ([#3943](https://github.com/ueberdosis/tiptap/issues/3943)) ([7278ee2](https://github.com/ueberdosis/tiptap/commit/7278ee2b05de2f96efddf3b1dc3bfd3d52262cbb))
- Update peerDependencies to fix lerna version tasks ([#3914](https://github.com/ueberdosis/tiptap/issues/3914)) ([0c1bba3](https://github.com/ueberdosis/tiptap/commit/0c1bba3137b535776bcef95ff3c55e13f5a2db46))
# [2.1.0-rc.0](https://github.com/ueberdosis/tiptap/compare/v2.0.2...v2.1.0-rc.0) (2023-04-05)
### Bug Fixes
* clear nodes when cursor at start of empty isolating parent ([#3943](https://github.com/ueberdosis/tiptap/issues/3943)) ([7278ee2](https://github.com/ueberdosis/tiptap/commit/7278ee2b05de2f96efddf3b1dc3bfd3d52262cbb))
* **bubble-menu:** fix debounce not working with collab/collaboration cursor ([#3956](https://github.com/ueberdosis/tiptap/issues/3956)) ([a78f8cd](https://github.com/ueberdosis/tiptap/commit/a78f8cd9646008e4db938fa3c22b0714c8bb5849))
- clear nodes when cursor at start of empty isolating parent ([#3943](https://github.com/ueberdosis/tiptap/issues/3943)) ([7278ee2](https://github.com/ueberdosis/tiptap/commit/7278ee2b05de2f96efddf3b1dc3bfd3d52262cbb))
- **bubble-menu:** fix debounce not working with collab/collaboration cursor ([#3956](https://github.com/ueberdosis/tiptap/issues/3956)) ([a78f8cd](https://github.com/ueberdosis/tiptap/commit/a78f8cd9646008e4db938fa3c22b0714c8bb5849))
## [2.0.3](https://github.com/ueberdosis/tiptap/compare/v2.0.2...v2.0.3) (2023-04-13)
### Bug Fixes
* **bubble-menu:** fix debounce not working with collab/collaboration cursor ([#3956](https://github.com/ueberdosis/tiptap/issues/3956)) ([e8cef04](https://github.com/ueberdosis/tiptap/commit/e8cef0404b5039ec2657536976b8b31931afd337))
- **bubble-menu:** fix debounce not working with collab/collaboration cursor ([#3956](https://github.com/ueberdosis/tiptap/issues/3956)) ([e8cef04](https://github.com/ueberdosis/tiptap/commit/e8cef0404b5039ec2657536976b8b31931afd337))
## [2.0.2](https://github.com/ueberdosis/tiptap/compare/v2.0.1...v2.0.2) (2023-04-03)
### Features
* add box-shadow to collab demo ([c5496c1](https://github.com/ueberdosis/tiptap/commit/c5496c1b27783150dafb5ebdf6bda43648a46316))
* landingpage demo ([#3925](https://github.com/ueberdosis/tiptap/issues/3925)) ([958925f](https://github.com/ueberdosis/tiptap/commit/958925f2560ca786cd0cf52b83b7ae51deb7dd77))
* Tiptap collab demo styling ([87840b0](https://github.com/ueberdosis/tiptap/commit/87840b0f0821ca65d9f104d9c90512021aa70113))
- add box-shadow to collab demo ([c5496c1](https://github.com/ueberdosis/tiptap/commit/c5496c1b27783150dafb5ebdf6bda43648a46316))
- landingpage demo ([#3925](https://github.com/ueberdosis/tiptap/issues/3925)) ([958925f](https://github.com/ueberdosis/tiptap/commit/958925f2560ca786cd0cf52b83b7ae51deb7dd77))
- Tiptap collab demo styling ([87840b0](https://github.com/ueberdosis/tiptap/commit/87840b0f0821ca65d9f104d9c90512021aa70113))
## [2.0.1](https://github.com/ueberdosis/tiptap/compare/v2.0.0...v2.0.1) (2023-03-30)
### Bug Fixes
* Update peerDependencies to fix lerna version tasks ([#3914](https://github.com/ueberdosis/tiptap/issues/3914)) ([0534f76](https://github.com/ueberdosis/tiptap/commit/0534f76401bf5399c01ca7f39d87f7221d91b4f7))
- Update peerDependencies to fix lerna version tasks ([#3914](https://github.com/ueberdosis/tiptap/issues/3914)) ([0534f76](https://github.com/ueberdosis/tiptap/commit/0534f76401bf5399c01ca7f39d87f7221d91b4f7))
# [2.0.0-beta.220](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.219...v2.0.0-beta.220) (2023-02-28)
### Bug Fixes
* **core:** fix destroyed view causing errors on dispatchTransaction ([#3799](https://github.com/ueberdosis/tiptap/issues/3799)) ([3c07ca0](https://github.com/ueberdosis/tiptap/commit/3c07ca0b9c48cef60d56acdd44812e20e05fc928))
* **tests:** fix tests for lists ([02eec8a](https://github.com/ueberdosis/tiptap/commit/02eec8aaefc2709dc20f91c3c8f9eca84cddc12d))
- **core:** fix destroyed view causing errors on dispatchTransaction ([#3799](https://github.com/ueberdosis/tiptap/issues/3799)) ([3c07ca0](https://github.com/ueberdosis/tiptap/commit/3c07ca0b9c48cef60d56acdd44812e20e05fc928))
- **tests:** fix tests for lists ([02eec8a](https://github.com/ueberdosis/tiptap/commit/02eec8aaefc2709dc20f91c3c8f9eca84cddc12d))
# [2.0.0-beta.219](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.218...v2.0.0-beta.219) (2023-02-27)
### Bug Fixes
* **core:** allow insertContentAt and insertContent text node arrays ([#3790](https://github.com/ueberdosis/tiptap/issues/3790)) ([0300630](https://github.com/ueberdosis/tiptap/commit/0300630a5b04b61d4eef8155f24ca0ef2d683966))
- **core:** allow insertContentAt and insertContent text node arrays ([#3790](https://github.com/ueberdosis/tiptap/issues/3790)) ([0300630](https://github.com/ueberdosis/tiptap/commit/0300630a5b04b61d4eef8155f24ca0ef2d683966))
### Features
* [#3540](https://github.com/ueberdosis/tiptap/issues/3540) Ability to preserve marks on lists ([#3541](https://github.com/ueberdosis/tiptap/issues/3541)) ([36bb1e1](https://github.com/ueberdosis/tiptap/commit/36bb1e1041f91da6437272e7196702df868eae0f))
- [#3540](https://github.com/ueberdosis/tiptap/issues/3540) Ability to preserve marks on lists ([#3541](https://github.com/ueberdosis/tiptap/issues/3541)) ([36bb1e1](https://github.com/ueberdosis/tiptap/commit/36bb1e1041f91da6437272e7196702df868eae0f))
# [2.0.0-beta.218](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.217...v2.0.0-beta.218) (2023-02-18)
**Note:** Version bump only for package tiptap-demos
# [2.0.0-beta.217](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.216...v2.0.0-beta.217) (2023-02-09)
**Note:** Version bump only for package tiptap-demos
# [2.0.0-beta.216](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.215...v2.0.0-beta.216) (2023-02-08)
**Note:** Version bump only for package tiptap-demos
# [2.0.0-beta.215](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.214...v2.0.0-beta.215) (2023-02-08)
**Note:** Version bump only for package tiptap-demos
# [2.0.0-beta.214](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.213...v2.0.0-beta.214) (2023-02-08)
**Note:** Version bump only for package tiptap-demos
# [2.0.0-beta.213](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.212...v2.0.0-beta.213) (2023-02-07)
**Note:** Version bump only for package tiptap-demos
# [2.0.0-beta.212](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.211...v2.0.0-beta.212) (2023-02-03)
**Note:** Version bump only for package tiptap-demos
# [2.0.0-beta.211](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.210...v2.0.0-beta.211) (2023-02-02)
**Note:** Version bump only for package tiptap-demos
# [2.0.0-beta.210](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.209...v2.0.0-beta.210) (2023-02-02)
### Features
* **pm:** new prosemirror package for dependency resolving ([f387ad3](https://github.com/ueberdosis/tiptap/commit/f387ad3dd4c2b30eaea33fb0ba0b42e0cd39263b))
- **pm:** new prosemirror package for dependency resolving ([f387ad3](https://github.com/ueberdosis/tiptap/commit/f387ad3dd4c2b30eaea33fb0ba0b42e0cd39263b))
# [2.0.0-beta.209](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.208...v2.0.0-beta.209) (2022-12-16)
**Note:** Version bump only for package tiptap-demos
# [2.0.0-beta.208](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.207...v2.0.0-beta.208) (2022-12-16)
**Note:** Version bump only for package tiptap-demos
# [2.0.0-beta.207](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.206...v2.0.0-beta.207) (2022-12-08)
**Note:** Version bump only for package tiptap-demos
# [2.0.0-beta.206](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.205...v2.0.0-beta.206) (2022-12-08)
**Note:** Version bump only for package tiptap-demos
# [2.0.0-beta.205](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.204...v2.0.0-beta.205) (2022-12-05)
**Note:** Version bump only for package tiptap-demos
# [2.0.0-beta.204](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.203...v2.0.0-beta.204) (2022-11-25)
### Bug Fixes
* **tests:** fix autolink validation test ([5150095](https://github.com/ueberdosis/tiptap/commit/5150095c6b510c080f4aa35f54d2387543f86da8))
- **tests:** fix autolink validation test ([5150095](https://github.com/ueberdosis/tiptap/commit/5150095c6b510c080f4aa35f54d2387543f86da8))
# [2.0.0-beta.203](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.202...v2.0.0-beta.203) (2022-11-24)
### Bug Fixes
* **extension/table:** move dependency from @_ueberdosis to [@tiptap](https://github.com/tiptap) ([#3448](https://github.com/ueberdosis/tiptap/issues/3448)) ([31c3a9a](https://github.com/ueberdosis/tiptap/commit/31c3a9aad9eb37f445eadcd27135611291178ca6))
- **extension/table:** move dependency from @\_ueberdosis to [@tiptap](https://github.com/tiptap) ([#3448](https://github.com/ueberdosis/tiptap/issues/3448)) ([31c3a9a](https://github.com/ueberdosis/tiptap/commit/31c3a9aad9eb37f445eadcd27135611291178ca6))
# [2.0.0-beta.202](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.201...v2.0.0-beta.202) (2022-11-04)
**Note:** Version bump only for package tiptap-demos
# [2.0.0-beta.201](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.200...v2.0.0-beta.201) (2022-11-04)
**Note:** Version bump only for package tiptap-demos
# [2.0.0-beta.200](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.199...v2.0.0-beta.200) (2022-11-04)
**Note:** Version bump only for package tiptap-demos
# [2.0.0-beta.199](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.198...v2.0.0-beta.199) (2022-09-30)
**Note:** Version bump only for package tiptap-demos
# [2.0.0-beta.198](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.197...v2.0.0-beta.198) (2022-09-29)
**Note:** Version bump only for package tiptap-demos
# [2.0.0-beta.197](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.196...v2.0.0-beta.197) (2022-09-26)
### Bug Fixes
* **core:** Can() does not work for setting marks ([#3223](https://github.com/ueberdosis/tiptap/issues/3223)) ([17a41da](https://github.com/ueberdosis/tiptap/commit/17a41da5a7a14879cf490c81914084791c4c494c))
- **core:** Can() does not work for setting marks ([#3223](https://github.com/ueberdosis/tiptap/issues/3223)) ([17a41da](https://github.com/ueberdosis/tiptap/commit/17a41da5a7a14879cf490c81914084791c4c494c))
# [2.0.0-beta.196](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.195...v2.0.0-beta.196) (2022-09-20)
**Note:** Version bump only for package tiptap-demos
# [2.0.0-beta.195](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.194...v2.0.0-beta.195) (2022-09-14)
### Bug Fixes
* **extension/bubble-menu:** :bug: fix bubble menu and floating menu being available when editor not editable ([#3195](https://github.com/ueberdosis/tiptap/issues/3195)) ([fa96749](https://github.com/ueberdosis/tiptap/commit/fa96749ce22ec67125da491cfeeb38623b9f0d6e))
- **extension/bubble-menu:** :bug: fix bubble menu and floating menu being available when editor not editable ([#3195](https://github.com/ueberdosis/tiptap/issues/3195)) ([fa96749](https://github.com/ueberdosis/tiptap/commit/fa96749ce22ec67125da491cfeeb38623b9f0d6e))
# [2.0.0-beta.194](https://github.com/ueberdosis/tiptap/compare/v2.0.0-beta.193...v2.0.0-beta.194) (2022-09-11)
**Note:** Version bump only for package tiptap-demos
# [2.0.0-beta.193](https://github.com/ueberdosis/tiptap/compare/v0.1.2...v2.0.0-beta.193) (2022-09-10)
### Bug Fixes
* bump documents ([43611ea](https://github.com/ueberdosis/tiptap/commit/43611ea2e70d3dc66ff907ba7ca377bf74814543))
* disable broken tests for experiements with further todo message ([b8ae9e2](https://github.com/ueberdosis/tiptap/commit/b8ae9e27622857093c6ca539901956da5cc291e5))
* dont check for active node in wrapIn command, fix [#1059](https://github.com/ueberdosis/tiptap/issues/1059) ([170ec4b](https://github.com/ueberdosis/tiptap/commit/170ec4be5b3c8362890ca3100a223b505f788381))
* **extension/collaboration:** :ambulance: pin y-prosemirror version to 1.0.20 to fix broken functionality with vue ([5989f3b](https://github.com/ueberdosis/tiptap/commit/5989f3b780bb64b2884d81dcd41a95d98a0714b2))
* fix RangeError bug when selecting all text, fix [#2490](https://github.com/ueberdosis/tiptap/issues/2490) ([70422dd](https://github.com/ueberdosis/tiptap/commit/70422dd107ed1ecdd8dfe41a8a93297124d2f1e0))
* **maintainment:** fix cjs issues with prosemirror-tables ([eb92597](https://github.com/ueberdosis/tiptap/commit/eb925976038fbf59f6ba333ccc57ea84113da00e))
* remove some magic strings ([6c34dec](https://github.com/ueberdosis/tiptap/commit/6c34dec33ac39c9f037a0a72e4525f3fc6d422bf))
* **suggestion:** :bug: make clientrect prop optional as it can potentially be undefined ([#2813](https://github.com/ueberdosis/tiptap/issues/2813)) ([f019f70](https://github.com/ueberdosis/tiptap/commit/f019f70a19c34715e2d5c3921d348e11c7ac51a3)), closes [#2795](https://github.com/ueberdosis/tiptap/issues/2795)
* temp fix collaboration demo ([4528756](https://github.com/ueberdosis/tiptap/commit/45287563f3cfb389095a2794cb2001d65e56d633))
- bump documents ([43611ea](https://github.com/ueberdosis/tiptap/commit/43611ea2e70d3dc66ff907ba7ca377bf74814543))
- disable broken tests for experiements with further todo message ([b8ae9e2](https://github.com/ueberdosis/tiptap/commit/b8ae9e27622857093c6ca539901956da5cc291e5))
- dont check for active node in wrapIn command, fix [#1059](https://github.com/ueberdosis/tiptap/issues/1059) ([170ec4b](https://github.com/ueberdosis/tiptap/commit/170ec4be5b3c8362890ca3100a223b505f788381))
- **extension/collaboration:** :ambulance: pin y-prosemirror version to 1.0.20 to fix broken functionality with vue ([5989f3b](https://github.com/ueberdosis/tiptap/commit/5989f3b780bb64b2884d81dcd41a95d98a0714b2))
- fix RangeError bug when selecting all text, fix [#2490](https://github.com/ueberdosis/tiptap/issues/2490) ([70422dd](https://github.com/ueberdosis/tiptap/commit/70422dd107ed1ecdd8dfe41a8a93297124d2f1e0))
- **maintainment:** fix cjs issues with prosemirror-tables ([eb92597](https://github.com/ueberdosis/tiptap/commit/eb925976038fbf59f6ba333ccc57ea84113da00e))
- remove some magic strings ([6c34dec](https://github.com/ueberdosis/tiptap/commit/6c34dec33ac39c9f037a0a72e4525f3fc6d422bf))
- **suggestion:** :bug: make clientrect prop optional as it can potentially be undefined ([#2813](https://github.com/ueberdosis/tiptap/issues/2813)) ([f019f70](https://github.com/ueberdosis/tiptap/commit/f019f70a19c34715e2d5c3921d348e11c7ac51a3)), closes [#2795](https://github.com/ueberdosis/tiptap/issues/2795)
- temp fix collaboration demo ([4528756](https://github.com/ueberdosis/tiptap/commit/45287563f3cfb389095a2794cb2001d65e56d633))
### Features
* Add extension storage ([#2069](https://github.com/ueberdosis/tiptap/issues/2069)) ([7ffabf2](https://github.com/ueberdosis/tiptap/commit/7ffabf251c408a652eec1931cc78a8bd43cccb67))
* add getText() and generateText() methods (fix [#1428](https://github.com/ueberdosis/tiptap/issues/1428)) ([#1875](https://github.com/ueberdosis/tiptap/issues/1875)) ([fe6a3e7](https://github.com/ueberdosis/tiptap/commit/fe6a3e7491f6a42123d3d8a92ab588f2a40d7799))
* add some improvements to `CharacterCount` extension ([#2256](https://github.com/ueberdosis/tiptap/issues/2256)), fix [#1049](https://github.com/ueberdosis/tiptap/issues/1049), fix [#1550](https://github.com/ueberdosis/tiptap/issues/1550), fix [#1839](https://github.com/ueberdosis/tiptap/issues/1839), fix [#2245](https://github.com/ueberdosis/tiptap/issues/2245) ([5daa870](https://github.com/ueberdosis/tiptap/commit/5daa870b0906f0387fe07041681bc6f5b3774617))
* Add support for autolink ([#2226](https://github.com/ueberdosis/tiptap/issues/2226)) ([3d68981](https://github.com/ueberdosis/tiptap/commit/3d68981b47d087fff40549d2143eb952fc9e0a50))
* **extension-link:** :sparkles: add validate option to link extension ([23e67ad](https://github.com/ueberdosis/tiptap/commit/23e67adfa730df7364bc31220d0ed0e8ea522593)), closes [#2779](https://github.com/ueberdosis/tiptap/issues/2779)
* **extension/youtube:** :sparkles: new youtube embed extension ([#2814](https://github.com/ueberdosis/tiptap/issues/2814)) ([1c0554b](https://github.com/ueberdosis/tiptap/commit/1c0554b7c06d80145274353e58d56608b097fbe4))
* Integrate input rules and paste rules into the core ([#1997](https://github.com/ueberdosis/tiptap/issues/1997)) ([723b955](https://github.com/ueberdosis/tiptap/commit/723b955cecc5c92c8aad897ce16c60fb62976571))
* parseHTML for attributes should return the value instead of an object now, fix [#1863](https://github.com/ueberdosis/tiptap/issues/1863) ([8a3b47a](https://github.com/ueberdosis/tiptap/commit/8a3b47a529d28b28b50d634c6ff69b8e5aad3080))
- Add extension storage ([#2069](https://github.com/ueberdosis/tiptap/issues/2069)) ([7ffabf2](https://github.com/ueberdosis/tiptap/commit/7ffabf251c408a652eec1931cc78a8bd43cccb67))
- add getText() and generateText() methods (fix [#1428](https://github.com/ueberdosis/tiptap/issues/1428)) ([#1875](https://github.com/ueberdosis/tiptap/issues/1875)) ([fe6a3e7](https://github.com/ueberdosis/tiptap/commit/fe6a3e7491f6a42123d3d8a92ab588f2a40d7799))
- add some improvements to `CharacterCount` extension ([#2256](https://github.com/ueberdosis/tiptap/issues/2256)), fix [#1049](https://github.com/ueberdosis/tiptap/issues/1049), fix [#1550](https://github.com/ueberdosis/tiptap/issues/1550), fix [#1839](https://github.com/ueberdosis/tiptap/issues/1839), fix [#2245](https://github.com/ueberdosis/tiptap/issues/2245) ([5daa870](https://github.com/ueberdosis/tiptap/commit/5daa870b0906f0387fe07041681bc6f5b3774617))
- Add support for autolink ([#2226](https://github.com/ueberdosis/tiptap/issues/2226)) ([3d68981](https://github.com/ueberdosis/tiptap/commit/3d68981b47d087fff40549d2143eb952fc9e0a50))
- **extension-link:** :sparkles: add validate option to link extension ([23e67ad](https://github.com/ueberdosis/tiptap/commit/23e67adfa730df7364bc31220d0ed0e8ea522593)), closes [#2779](https://github.com/ueberdosis/tiptap/issues/2779)
- **extension/youtube:** :sparkles: new youtube embed extension ([#2814](https://github.com/ueberdosis/tiptap/issues/2814)) ([1c0554b](https://github.com/ueberdosis/tiptap/commit/1c0554b7c06d80145274353e58d56608b097fbe4))
- Integrate input rules and paste rules into the core ([#1997](https://github.com/ueberdosis/tiptap/issues/1997)) ([723b955](https://github.com/ueberdosis/tiptap/commit/723b955cecc5c92c8aad897ce16c60fb62976571))
- parseHTML for attributes should return the value instead of an object now, fix [#1863](https://github.com/ueberdosis/tiptap/issues/1863) ([8a3b47a](https://github.com/ueberdosis/tiptap/commit/8a3b47a529d28b28b50d634c6ff69b8e5aad3080))

View File

@ -1,7 +1,11 @@
d3
highlight.js/lib/languages/css
highlight.js/lib/languages/javascript
highlight.js/lib/languages/typescript
highlight.js/lib/languages/xml
highlight.js/lib/core
linkifyjs
lowlight
lowlight/lib/core
prosemirror-commands
prosemirror-dropcursor
prosemirror-gapcursor
@ -16,9 +20,13 @@ prosemirror-view
react
react-dom
react-dom/client
use-sync-external-store/shim
use-sync-external-store/shim/with-selector
shiki
simplify-js
tippy.js
uuid
y-webrtc
yjs
@hocuspocus/provider
@lifeomic/attempt

7330
demos/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,8 @@
{
"name": "tiptap-demos",
"version": "2.4.0",
"version": "2.5.0",
"private": true,
"type": "module",
"scripts": {
"start": "vite --host",
"build:demos": "npm run ts && vite build",
@ -9,26 +10,28 @@
"ts": "tsc --project tsconfig.base.json --noEmit && tsc --project tsconfig.react.json --noEmit && tsc --project tsconfig.vue-2.json --noEmit && tsc --project tsconfig.vue-3.json --noEmit"
},
"dependencies": {
"@hocuspocus/provider": "^2.9.0",
"@hocuspocus/provider": "2.13.5",
"@lexical/react": "^0.11.1",
"@shikijs/core": "1.10.3",
"d3": "^7.3.0",
"fast-glob": "^3.2.11",
"highlight.js": "^11.6.0",
"highlight.js": "^11.10.0",
"lexical": "^0.11.1",
"lowlight": "^2.7.0",
"lowlight": "^3.1.0",
"remixicon": "^2.5.0",
"shiki": "^0.10.0",
"shiki": "^1.10.3",
"simplify-js": "^1.2.4",
"y-prosemirror": "^1.2.5",
"y-prosemirror": "1.2.11",
"y-webrtc": "^10.3.0",
"yjs": "^13.6.11"
"yjs": "13.6.18"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.49",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@types/uuid": "^8.3.4",
"@vitejs/plugin-react": "^1.3.1",
"@vitejs/plugin-vue": "^1.10.2",
"@vitejs/plugin-react": "^1.3.2",
"@vitejs/plugin-vue": "^5.0.0",
"autoprefixer": "^10.4.2",
"esbuild": "0.21.5",
"iframe-resizer": "^4.3.2",
"postcss": "^8.4.31",
"postcss-import": "^15.1.0",
@ -36,13 +39,13 @@
"react": "^18.0.0",
"react-dom": "^18.0.0",
"sass": "^1.49.7",
"svelte": "^3.49.0",
"svelte": "^4.2.19",
"tailwindcss": "^3.3.2",
"typescript": "4.7.4",
"typescript": "^5.4.5",
"uuid": "^8.3.2",
"vite": "^2.9.18",
"vite-plugin-checker": "^0.3.4",
"vue": "^3.0.5",
"vite": "^5.4.6",
"vite-plugin-checker": "^0.6.4",
"vue": "^3.0.0",
"vue-router": "^4.0.11"
}
}

View File

@ -17,6 +17,7 @@
<div class="overflow-hidden">
<div
class="bg-white"
:class="[hidePreview ? 'hidden' : '']"
>
<demo-frame
:src="currentIframeUrl"
@ -140,6 +141,10 @@ export default {
return this.query.hideSource || false
},
hidePreview() {
return this.query.hidePreview || false
},
githubUrl() {
return `https://github.com/ueberdosis/tiptap/tree/main/demos/src/${this.name}`
},

View File

@ -1,12 +1,26 @@
<script>
<script setup>
import { ref } from 'vue'
// @ts-nocheck
const showDemoList = process.env.NODE_ENV === 'development'
const searchValue = ref('')
</script>
<template>
<ul v-if="$route.path === '/'">
<template v-if="$route.path === '/'">
<input
class="w-full p-3 my-3 focus:outline-none border-b"
type="search"
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"
v-for="route in $router.options.routes.filter(route => searchValue === ''? true : route.props.name.toLowerCase().includes(searchValue.toLowerCase()))"
:key="route.path"
>
<router-link
@ -28,5 +42,49 @@
</div>
</li>
</ul>
<div v-else>Nothing to see here :-)</div>
</template>
<router-view v-else />
</template>
<script>
export default {
methods: {
fromString(value) {
if (value === null) {
return true
}
if (value.match(/^\d*(\.\d+)?$/)) {
return Number(value)
}
if (value === 'true') {
return true
}
if (value === 'false') {
return false
}
if (value === 'null') {
return null
}
return value
},
},
computed: {
query() {
return Object.fromEntries(Object
.entries(this.$route.query)
.map(([key, value]) => [key, this.fromString(value)]))
},
listing() {
return this.query.listing || false
},
},
}
</script>

View File

@ -1,15 +1,4 @@
import * as shiki from 'shiki'
import onigasm from 'shiki/dist/onig.wasm?url'
import langCSS from 'shiki/languages/css.tmLanguage.json'
import langHTML from 'shiki/languages/html.tmLanguage.json'
import langJS from 'shiki/languages/javascript.tmLanguage.json'
import langJSX from 'shiki/languages/jsx.tmLanguage.json'
import langSCSS from 'shiki/languages/scss.tmLanguage.json'
import langTSX from 'shiki/languages/tsx.tmLanguage.json'
import langTS from 'shiki/languages/typescript.tmLanguage.json'
import langVue from 'shiki/languages/vue.tmLanguage.json'
import langVueHTML from 'shiki/languages/vue-html.tmLanguage.json'
import theme from 'shiki/themes/material-darker.json'
let highlighter = null
@ -18,64 +7,18 @@ async function init() {
return highlighter
}
const arrayBuffer = await fetch(onigasm).then(response => response.arrayBuffer())
shiki.setOnigasmWASM(arrayBuffer)
highlighter = await shiki.getHighlighter({
theme,
highlighter = await shiki.createHighlighter({
themes: ['material-theme-darker'],
langs: [
{
id: 'html',
scopeName: langHTML.scopeName,
grammar: langHTML,
embeddedLangs: ['javascript', 'css'],
},
{
id: 'javascript',
scopeName: langJS.scopeName,
grammar: langJS,
aliases: ['js'],
},
{
id: 'jsx',
scopeName: langJSX.scopeName,
grammar: langJSX,
},
{
id: 'typescript',
scopeName: langTS.scopeName,
grammar: langTS,
aliases: ['ts'],
},
{
id: 'tsx',
scopeName: langTSX.scopeName,
grammar: langTSX,
},
{
id: 'vue-html',
scopeName: langVueHTML.scopeName,
grammar: langVueHTML,
embeddedLangs: ['vue', 'javascript'],
},
{
id: 'vue',
scopeName: langVue.scopeName,
grammar: langVue,
embeddedLangs: ['json', 'markdown', 'pug', 'haml', 'vue-html', 'sass', 'scss', 'less', 'stylus', 'postcss', 'css', 'typescript', 'coffee', 'javascript'],
},
{
id: 'css',
scopeName: langCSS.scopeName,
grammar: langCSS,
},
{
id: 'scss',
scopeName: langSCSS.scopeName,
grammar: langSCSS,
embeddedLangs: ['css'],
},
'html',
'js',
'jsx',
'ts',
'tsx',
'css',
'vue-html',
'vue',
'scss',
],
})
@ -84,9 +27,12 @@ async function init() {
// eslint-disable-next-line
self.addEventListener('message', async event => {
init().then(() => {
init().then(async () => {
const { code, language } = event.data
const html = highlighter.codeToHtml(code, language)
await highlighter.loadLanguage(language)
const html = highlighter.codeToHtml(code, { lang: language, theme: 'material-theme-darker' })
// eslint-disable-next-line
self.postMessage({ code, language, html })

View File

@ -1,4 +1,24 @@
$colorBlack: #000;
/* Base HTML and global element styles*/
:root {
--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;
--purple-light: rgba(88, 5, 255, 0.05);
--yellow-contrast: #FACC15;
--yellow: rgba(250, 204, 21, 0.4);
--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);
}
*,
*::before,
@ -9,32 +29,46 @@ $colorBlack: #000;
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";
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
}
body {
margin: 1rem;
min-height: 10rem;
min-height: 25rem;
margin: 0;
}
:first-child {
margin-top: 0;
}
.tiptap {
caret-color: var(--purple);
margin: 1.5rem;
&:focus {
outline: none;
}
}
/* Custom scrollbar styles */
::-webkit-scrollbar {
width: 14px;
height: 14px;
width: 14px;
}
::-webkit-scrollbar-track {
border: 4px solid transparent;
background-clip: padding-box;
border-radius: 8px;
background-color: transparent;
border: 4px solid transparent;
border-radius: 8px;
}
::-webkit-scrollbar-thumb {
border: 4px solid rgba(0, 0, 0, 0);
background-clip: padding-box;
border-radius: 8px;
background-color: rgba(0, 0, 0, 0);
border: 4px solid rgba(0, 0, 0, 0);
border-radius: 8px;
}
:hover::-webkit-scrollbar-thumb {
@ -47,37 +81,302 @@ body {
::-webkit-scrollbar-button {
display: none;
width: 0;
height: 0;
width: 0;
}
::-webkit-scrollbar-corner {
background-color: transparent;
}
/* Specific element and component styles */
button,
input,
select {
font-size: inherit;
select,
textarea {
background: var(--gray-2);
border-radius: 0.5rem;
border: none;
color: var(--black);
font-family: inherit;
color: black;
margin: 0.1rem;
border: 1px solid black;
border-radius: 0.3rem;
padding: 0.1rem 0.4rem;
background: white;
accent-color: black;
font-size: 0.875rem;
font-weight: 500;
line-height: 1.15;
margin: none;
padding: 0.375rem 0.625rem;
transition: all 0.2s cubic-bezier(0.65,0.05,0.36,1);
&:hover {
background-color: var(--gray-3);
color: var(--black-contrast);
}
&[disabled] {
opacity: 0.3;
background: var(--gray-1);
color: var(--gray-4);
}
&:checked {
accent-color: var(--purple);
}
&.primary {
background: var(--black);
color: var(--white);
&:hover {
background-color: var(--black-contrast);
}
&[disabled] {
background: var(--gray-1);
color: var(--gray-4);
}
}
.tiptap:focus {
&.is-active {
background: var(--purple);
color: var(--white);
&:hover {
background-color: var(--purple-contrast);
color: var(--white);
}
}
}
button:not([disabled]),
select:not([disabled]) {
cursor: pointer;
}
input[type="text"],
textarea {
background-color: unset;
border: 1px solid var(--gray-3);
border-radius: 0.5rem;
color: var(--black);
&::placeholder {
color: var(--gray-4);
}
&:hover {
background-color: unset;
border-color: var(--gray-4);
}
&:focus-visible, &:focus {
border-color: var(--purple);
outline: none;
}
.is-active {
background: black;
color: white;
}
select {
/* reset */
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="Gray" d="M7 10l5 5 5-5z"/></svg>');
background-repeat: no-repeat;
background-position: right 0.1rem center;
background-size: 1.25rem 1.25rem;
padding-right: 1.25rem;
&:focus {
outline: 0;
}
}
form {
align-items: flex-start;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.hint {
align-items: center;
background-color: var(--yellow-light);
border-radius: 0.5rem;
border: 1px solid var(--gray-2);
display: flex;
flex-direction: row;
font-size: 0.75rem;
gap: 0.25rem;
line-height: 1.15;
padding: 0.3rem 0.5rem;
&.purple-spinner,
&.error {
justify-content: center;
text-align: center;
width: 100%;
}
.badge {
background-color: var(--gray-1);
border: 1px solid var(--gray-3);
border-radius: 2rem;
color: var(--gray-5);
font-size: 0.625rem;
font-weight: 700;
line-height: 1;
padding: 0.25rem 0.5rem;
}
&.purple-spinner {
background-color: var(--purple-light);
&::after {
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;
background-position: center;
height: 1rem;
width: 1rem;
}
}
&.error {
background-color: var(--red-light);
}
}
.label,
.label-small,
.label-large {
color: var(--black);
font-size: 0.8125rem;
font-weight: 500;
line-height: 1.15;
}
.label-small {
color: var(--gray-5);
font-size: 0.75rem;
font-weight: 400;
}
.label-large {
font-size: 0.875rem;
font-weight: 700;
}
hr {
border: none;
border-top: 1px solid var(--gray-3);
margin: 0;
width: 100%;
}
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;
}
/* Layout and structure */
#app,
.container {
display: flex;
flex-direction: column;
}
.button-group {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
}
.control-group {
align-items: flex-start;
background-color: var(--white);
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1.5rem;
.sticky {
position: sticky;
top: 0;
}
}
[data-node-view-wrapper] > .control-group {
padding: 0;
}
.flex-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 1rem;
justify-content: space-between;
width: 100%;
}
.switch-group {
align-items: center;
background: var(--gray-2);
border-radius: 0.5rem;
display: flex;
flex-direction: row;
flex-wrap: wrap;
flex: 0 1 auto;
justify-content: flex-start;
padding: 0.125rem;
label {
align-items: center;
border-radius: 0.375rem;
color: var(--gray-5);
cursor: pointer;
display: flex;
flex-direction: row;
font-size: 0.75rem;
font-weight: 500;
gap: 0.25rem;
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);
&:has(input:checked) {
background-color: var(--white);
color: var(--black-contrast);
}
&:hover {
color: var(--black);
}
input {
display: none;
margin: unset;
}
}
}
.output-group {
background-color: var(--gray-1);
display: flex;
flex-direction: column;
font-family: 'JetBrainsMono', monospace;
font-size: 0.75rem;
gap: 1rem;
margin-top: 2.5rem;
padding: 1.5rem;
label {
color: var(--black);
font-size: 0.875rem;
font-weight: 700;
line-height: 1.15;
}
}

View File

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

View File

@ -8,10 +8,6 @@ import StarterKit from '@tiptap/starter-kit'
import React, { useCallback } from 'react'
const MenuBar = ({ editor }) => {
if (!editor) {
return null
}
const onCutToStart = useCallback(() => {
editor.chain().cut({ from: editor.state.selection.$from.pos, to: editor.state.selection.$to.pos }, 1).run()
}, [editor])
@ -20,11 +16,17 @@ const MenuBar = ({ editor }) => {
editor.chain().cut({ from: editor.state.selection.$from.pos, to: editor.state.selection.$to.pos }, editor.state.doc.nodeSize - 2).run()
}, [editor])
if (!editor) {
return null
}
return (
<>
<div className="control-group">
<div className="button-group">
<button onClick={onCutToStart}>Cut content to start of document</button>
<button onClick={onCutToEnd}>Cut content to end of document</button>
</>
</div>
</div>
)
}
@ -49,7 +51,7 @@ export default () => {
Hi there,
</h2>
<p>
this is a <em>basic</em> example of <strong>tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists:
this is a <em>basic</em> example of <strong>Tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists:
</p>
<ul>
<li>
@ -77,9 +79,9 @@ export default () => {
})
return (
<div>
<>
<MenuBar editor={editor} />
<EditorContent editor={editor} />
</div>
</>
)
}

View File

@ -21,41 +21,41 @@ context('/src/Examples/Default/React/', () => {
})
const buttonMarks = [
{ label: 'bold', tag: 'strong' },
{ label: 'italic', tag: 'em' },
{ label: 'strike', tag: 's' },
{ label: 'Bold', tag: 'strong' },
{ label: 'Italic', tag: 'em' },
{ label: 'Strike', tag: 's' },
]
buttonMarks.forEach(m => {
it(`should disable ${m.label} when the code tag is enabled for cursor`, () => {
cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('code').click()
cy.get('button').contains('Code').click()
cy.get('button').contains(m.label).should('be.disabled')
})
it(`should enable ${m.label} when the code tag is disabled for cursor`, () => {
cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('code').click()
cy.get('button').contains('code').click()
cy.get('button').contains('Code').click()
cy.get('button').contains('Code').click()
cy.get('button').contains(m.label).should('not.be.disabled')
})
it(`should disable ${m.label} when the code tag is enabled for selection`, () => {
cy.get('.tiptap').type('{selectall}Hello world{selectall}')
cy.get('button').contains('code').click()
cy.get('button').contains('Code').click()
cy.get('button').contains(m.label).should('be.disabled')
})
it(`should enable ${m.label} when the code tag is disabled for selection`, () => {
cy.get('.tiptap').type('{selectall}Hello world{selectall}')
cy.get('button').contains('code').click()
cy.get('button').contains('code').click()
cy.get('button').contains('Code').click()
cy.get('button').contains('Code').click()
cy.get('button').contains(m.label).should('not.be.disabled')
})
it(`should apply ${m.label} when the button is pressed`, () => {
cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('paragraph').click()
cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}')
cy.get('button').contains(m.label).click()
cy.get(`.tiptap ${m.tag}`).should('exist').should('have.text', 'Hello world')
@ -64,40 +64,40 @@ context('/src/Examples/Default/React/', () => {
it('should clear marks when the button is pressed', () => {
cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('paragraph').click()
cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}')
cy.get('button').contains('bold').click()
cy.get('button').contains('Bold').click()
cy.get('.tiptap strong').should('exist').should('have.text', 'Hello world')
cy.get('button').contains('clear marks').click()
cy.get('button').contains('Clear marks').click()
cy.get('.tiptap strong').should('not.exist')
})
it('should clear nodes when the button is pressed', () => {
cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('bullet list').click()
cy.get('button').contains('Bullet list').click()
cy.get('.tiptap ul').should('exist').should('have.text', 'Hello world')
cy.get('.tiptap').type('{enter}A second item{enter}A third item{selectall}')
cy.get('button').contains('clear nodes').click()
cy.get('button').contains('Clear nodes').click()
cy.get('.tiptap ul').should('not.exist')
cy.get('.tiptap p').should('have.length', 3)
})
const buttonNodes = [
{ label: 'h1', tag: 'h1' },
{ label: 'h2', tag: 'h2' },
{ label: 'h3', tag: 'h3' },
{ label: 'h4', tag: 'h4' },
{ label: 'h5', tag: 'h5' },
{ label: 'h6', tag: 'h6' },
{ label: 'bullet list', tag: 'ul' },
{ label: 'ordered list', tag: 'ol' },
{ label: 'code block', tag: 'pre code' },
{ label: 'blockquote', tag: 'blockquote' },
{ label: 'H1', tag: 'h1' },
{ label: 'H2', tag: 'h2' },
{ label: 'H3', tag: 'h3' },
{ label: 'H4', tag: 'h4' },
{ label: 'H5', tag: 'h5' },
{ label: 'H6', tag: 'h6' },
{ label: 'Bullet list', tag: 'ul' },
{ label: 'Ordered list', tag: 'ol' },
{ label: 'Code block', tag: 'pre code' },
{ label: 'Blockquote', tag: 'blockquote' },
]
buttonNodes.forEach(n => {
it(`should set ${n.label} when the button is pressed`, () => {
cy.get('button').contains('paragraph').click()
cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}Hello world{selectall}')
cy.get('button').contains(n.label).click()
@ -109,35 +109,35 @@ context('/src/Examples/Default/React/', () => {
it('should add a hr when on the same line as a node', () => {
cy.get('.tiptap').type('{rightArrow}')
cy.get('button').contains('horizontal rule').click()
cy.get('button').contains('Horizontal rule').click()
cy.get('.tiptap hr').should('exist')
cy.get('.tiptap h1').should('exist')
})
it('should add a hr when on a new line', () => {
cy.get('.tiptap').type('{rightArrow}{enter}')
cy.get('button').contains('horizontal rule').click()
cy.get('button').contains('Horizontal rule').click()
cy.get('.tiptap hr').should('exist')
cy.get('.tiptap h1').should('exist')
})
it('should add a br', () => {
cy.get('.tiptap').type('{rightArrow}')
cy.get('button').contains('hard break').click()
cy.get('button').contains('Hard break').click()
cy.get('.tiptap h1 br').should('exist')
})
it('should undo', () => {
cy.get('.tiptap').type('{selectall}{backspace}')
cy.get('button').contains('undo').click()
cy.get('button').contains('Undo').click()
cy.get('.tiptap').should('contain', 'Hello world')
})
it('should redo', () => {
cy.get('.tiptap').type('{selectall}{backspace}')
cy.get('button').contains('undo').click()
cy.get('button').contains('Undo').click()
cy.get('.tiptap').should('contain', 'Hello world')
cy.get('button').contains('redo').click()
cy.get('button').contains('Redo').click()
cy.get('.tiptap').should('not.contain', 'Hello world')
})
})

View File

@ -1,14 +1,22 @@
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
:first-child {
margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
@ -16,41 +24,68 @@
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: rgba(#616161, 0.1);
color: #616161;
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
color: inherit;
padding: 0;
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
img {
max-width: 100%;
height: auto;
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
hr {
border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1);
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
}

View File

@ -1,6 +1,7 @@
import './styles.scss'
import { Color } from '@tiptap/extension-color'
import Link from '@tiptap/extension-link'
import ListItem from '@tiptap/extension-list-item'
import TextStyle from '@tiptap/extension-text-style'
import { EditorProvider, useCurrentEditor } from '@tiptap/react'
@ -29,23 +30,26 @@ const MenuBar = () => {
}
return (
<>
<div className="control-group">
<div className="button-group">
<button data-test-id="html-content" onClick={() => editor.chain().insertContent(htmlContent).focus().run()}>
Insert html content
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()}>
Insert html with span tags content
Insert HTML with span tags content
</button>
<button data-test-id="text-content" onClick={() => editor.chain().insertContent(textContent).focus().run()}>
Insert text content
</button>
</>
</div>
</div>
)
}
const extensions = [
Color.configure({ types: [TextStyle.name, ListItem.name] }),
TextStyle.configure({ types: [ListItem.name] }),
Link,
StarterKit.configure({
bulletList: {
keepMarks: true,

View File

@ -11,7 +11,7 @@ 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>Tiptap</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', () => {
@ -41,4 +41,78 @@ context('/src/Commands/InsertContent/React/', () => {
cy.get('.tiptap').should('contain.html', '<pre><code>foo\nbar</code></pre>')
})
})
it('should keep newlines and tabs', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.insertContent('<p>Hello\n\tworld\n\t\thow\n\t\t\tnice.\ntest\tOK</p>')
cy.get('.tiptap').should('contain.html', '<p>Hello\n\tworld\n\t\thow\n\t\t\tnice.\ntest\tOK</p>')
})
})
it('should keep newlines and tabs', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.insertContent('<h1>Tiptap</h1>\n<p><strong>Hello World</strong></p>')
cy.get('.tiptap').should('contain.html', '<h1>Tiptap</h1><p><strong>Hello World</strong></p>')
})
})
it('should allow inserting nothing', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.insertContent('')
cy.get('.tiptap').should('contain.html', '')
})
})
it('should allow inserting a partial HTML tag', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.insertContent('<p>foo')
cy.get('.tiptap').should('contain.html', '<p>foo</p>')
})
})
it('should allow inserting an incomplete HTML tag', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.insertContent('foo<p')
cy.get('.tiptap').should('contain.html', '<p>foo&lt;p</p>')
})
})
it('should allow inserting a list', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.insertContent('<ul><li>ABC</li><li>123</li></ul>')
cy.get('.tiptap').should('contain.html', '<ul><li><p>ABC</p></li><li><p>123</p></li></ul>')
})
})
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 } })
cy.get('.tiptap').should('contain.html', '<h1>Tiptap</h1><p><strong>Hello World</strong></p>')
})
})
it('should respect editor.options.parseOptions if defined to be `false`', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.options.parseOptions = { preserveWhitespace: false }
editor.commands.insertContent('\n<h1>Tiptap</h1><p><strong>Hello\n World</strong>\n</p>\n')
cy.get('.tiptap').should('contain.html', '<h1>Tiptap</h1><p><strong>Hello World</strong></p>')
})
})
it('should respect editor.options.parseOptions if defined to be `full`', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.options.parseOptions = { preserveWhitespace: 'full' }
editor.commands.insertContent('\n<h1>Tiptap</h1><p><strong>Hello\n World</strong>\n</p>\n')
cy.get('.tiptap').should('contain.html', '<h1>Tiptap</h1><p><strong>Hello\n World</strong></p>')
})
})
it('should respect editor.options.parseOptions if defined to be `true`', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.options.parseOptions = { preserveWhitespace: true }
editor.commands.insertContent('<h1>Tiptap</h1><p><strong>Hello\n World</strong>\n</p>')
cy.get('.tiptap').should('contain.html', '<h1>Tiptap</h1><p><strong>Hello World</strong></p>')
})
})
})

View File

@ -1,14 +1,22 @@
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
:first-child {
margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
@ -16,41 +24,68 @@
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: rgba(#616161, 0.1);
color: #616161;
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
color: inherit;
padding: 0;
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
img {
max-width: 100%;
height: auto;
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
hr {
border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1);
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
}

View File

@ -14,30 +14,27 @@ const MenuBar = () => {
}
return (
<>
<div className="control-group">
<div>
<button
onClick={() => setUseInputRules(prev => !prev)}
style={{
color: useInputRules ? 'white' : 'inherit',
backgroundColor: useInputRules ? 'black' : 'transparent',
}}
>
Apply Input Rules
</button>
<button
onClick={() => setUsePasteRules(prev => !prev)}
style={{
color: usePasteRules ? 'white' : 'inherit',
backgroundColor: usePasteRules ? 'black' : 'transparent',
}}
>
Apply Paste Rules
</button>
<label>
<input
type="checkbox"
checked={useInputRules}
onChange={() => setUseInputRules(prev => !prev)}
/>
Apply input rules
</label>
<label>
<input
type="checkbox"
checked={usePasteRules}
onChange={() => setUsePasteRules(prev => !prev)}
/>
Apply paste rules
</label>
</div>
<br />
<div className="button-group">
<button
onClick={() => {
editor
@ -161,7 +158,8 @@ const MenuBar = () => {
>
Insert "*this is a test*"
</button>
</>
</div>
</div>
)
}

View File

@ -1,14 +1,22 @@
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
:first-child {
margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
@ -16,41 +24,68 @@
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: rgba(#616161, 0.1);
color: #616161;
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
color: inherit;
padding: 0;
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
img {
max-width: 100%;
height: auto;
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
hr {
border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1);
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
}

View File

@ -0,0 +1,31 @@
import './styles.scss'
import { Color } from '@tiptap/extension-color'
import ListItem from '@tiptap/extension-list-item'
import Mentions from '@tiptap/extension-mention'
import TextStyle from '@tiptap/extension-text-style'
import { EditorProvider } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import React from 'react'
const extensions = [
Color.configure({ types: [TextStyle.name, ListItem.name] }),
TextStyle.configure({ types: [ListItem.name] }),
StarterKit.configure({
bulletList: {
keepMarks: true,
},
orderedList: {
keepMarks: true,
},
}),
Mentions,
]
const content = ''
export default () => {
return (
<EditorProvider extensions={extensions} content={content}></EditorProvider>
)
}

View File

@ -0,0 +1,181 @@
context('/src/Commands/SetContent/React/', () => {
before(() => {
cy.visit('/src/Commands/SetContent/React/')
})
beforeEach(() => {
cy.get('.tiptap').type('{selectall}{backspace}')
})
it('should insert raw text content', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('Hello World.')
cy.get('.tiptap').should('contain.html', '<p>Hello World.</p>')
})
})
it('should insert raw JSON content', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent({ type: 'paragraph', content: [{ type: 'text', text: 'Hello World.' }] })
cy.get('.tiptap').should('contain.html', '<p>Hello World.</p>')
})
})
it('should insert a Prosemirror Node as content', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent(editor.schema.node('paragraph', null, editor.schema.text('Hello World.')))
cy.get('.tiptap').should('contain.html', '<p>Hello World.</p>')
})
})
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)
cy.get('.tiptap').should('contain.html', '<p>Hello World.</p>')
})
})
it('should emit updates', () => {
cy.get('.tiptap').then(([{ editor }]) => {
let updateCount = 0
const callback = () => {
updateCount += 1
}
editor.on('update', callback)
// emit an update
editor.commands.setContent('Hello World.', true)
expect(updateCount).to.equal(1)
updateCount = 0
// do not emit an update
editor.commands.setContent('Hello World again.', false)
expect(updateCount).to.equal(0)
editor.off('update', callback)
})
})
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>')
})
})
it('should remove newlines and tabs', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('<p>Hello\n\tworld\n\t\thow\n\t\t\tnice.</p>')
cy.get('.tiptap').should('contain.html', '<p>Hello world how nice.</p>')
})
})
it('should keep newlines and tabs when preserveWhitespace = full', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('<p>Hello\n\tworld\n\t\thow\n\t\t\tnice.</p>', false, { preserveWhitespace: 'full' })
cy.get('.tiptap').should('contain.html', '<p>Hello\n\tworld\n\t\thow\n\t\t\tnice.</p>')
})
})
it('should overwrite existing content', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('<p>Initial Content</p>')
cy.get('.tiptap').should('contain.html', '<p>Initial Content</p>')
})
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('<p>Overwritten Content</p>')
cy.get('.tiptap').should('contain.html', '<p>Overwritten Content</p>')
})
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('Content without tags')
cy.get('.tiptap').should('contain.html', '<p>Content without tags</p>')
})
})
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>')
})
})
it('should remove newlines and tabs between html fragments', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('<h1>Tiptap</h1>\n\t<p><strong>Hello World</strong></p>')
cy.get('.tiptap').should('contain.html', '<h1>Tiptap</h1><p><strong>Hello World</strong></p>')
})
})
// TODO I'm not certain about this behavior and what it should do...
// 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' })
cy.get('.tiptap').should('contain.html', '<h1>Tiptap</h1><p>\n\t</p><p><strong>Hello World</strong></p>')
})
})
it('should allow inserting nothing', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('')
cy.get('.tiptap').should('contain.html', '')
})
})
it('should allow inserting nothing when preserveWhitespace = full', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('', false, { preserveWhitespace: 'full' })
cy.get('.tiptap').should('contain.html', '')
})
})
it('should allow inserting a partial HTML tag', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('<p>foo')
cy.get('.tiptap').should('contain.html', '<p>foo</p>')
})
})
it('should allow inserting a partial HTML tag when preserveWhitespace = full', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('<p>foo', false, { preserveWhitespace: 'full' })
cy.get('.tiptap').should('contain.html', '<p>foo</p>')
})
})
it('will remove an incomplete HTML tag', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('foo<p')
cy.get('.tiptap').should('contain.html', '<p>foo</p>')
})
})
// TODO I'm not certain about this behavior and what it should do...
// This exists in insertContentAt as well
it('should allow inserting an incomplete HTML tag when preserveWhitespace = full', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('foo<p', false, { preserveWhitespace: 'full' })
cy.get('.tiptap').should('contain.html', '<p>foo&lt;p</p>')
})
})
it('should allow inserting a list', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('<ul><li>ABC</li><li>123</li></ul>')
cy.get('.tiptap').should('contain.html', '<ul><li><p>ABC</p></li><li><p>123</p></li></ul>')
})
})
it('should allow inserting a list when preserveWhitespace = full', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('<ul><li>ABC</li><li>123</li></ul>', false, { preserveWhitespace: 'full' })
cy.get('.tiptap').should('contain.html', '<ul><li><p>ABC</p></li><li><p>123</p></li></ul>')
})
})
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 })
cy.get('.tiptap').should('contain.html', '<h1>Tiptap</h1><p><strong>Hello World</strong></p>')
})
})
})

View File

@ -0,0 +1,56 @@
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
}
ul,
ol {
padding: 0 1rem;
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
}
code {
background-color: rgba(#616161, 0.1);
color: #616161;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
code {
color: inherit;
padding: 0;
background: none;
font-size: 0.8rem;
}
}
img {
max-width: 100%;
height: auto;
}
blockquote {
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
hr {
border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1);
margin: 2rem 0;
}
}

View File

@ -0,0 +1,209 @@
import CharacterCount from '@tiptap/extension-character-count'
import Collaboration from '@tiptap/extension-collaboration'
import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
import Highlight from '@tiptap/extension-highlight'
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'
const colors = [
'#958DF1',
'#F98181',
'#FBBC88',
'#FAF594',
'#70CFF8',
'#94FADB',
'#B9F18D',
'#C3E2C2',
'#EAECCC',
'#AFC8AD',
'#EEC759',
'#9BB8CD',
'#FF90BC',
'#FFC0D9',
'#DC8686',
'#7ED7C1',
'#F3EEEA',
'#89B9AD',
'#D0BFFF',
'#FFF8C9',
'#CBFFA9',
'#9BABB8',
'#E3F4F4',
]
const names = [
'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',
]
const defaultContent = `
<p>Hi 👋, this is a collaborative document.</p>
<p>Feel free to edit and collaborate in real-time!</p>
`
const getRandomElement = list => list[Math.floor(Math.random() * list.length)]
const getRandomColor = () => getRandomElement(colors)
const getRandomName = () => getRandomElement(names)
const getInitialUser = () => {
return {
name: getRandomName(),
color: getRandomColor(),
}
}
const Editor = ({
ydoc, provider, room,
}) => {
const [status, setStatus] = useState('connecting')
const [currentUser, setCurrentUser] = useState(getInitialUser)
const editor = useEditor({
enableContentCheck: true,
onContentError: ({ disableCollaboration }) => {
disableCollaboration()
},
onCreate: ({ editor: currentEditor }) => {
provider.on('synced', () => {
if (currentEditor.isEmpty) {
currentEditor.commands.setContent(defaultContent)
}
})
},
extensions: [
StarterKit.configure({
history: false,
}),
Highlight,
TaskList,
TaskItem,
CharacterCount.extend().configure({
limit: 10000,
}),
Collaboration.extend().configure({
document: ydoc,
}),
CollaborationCursor.extend().configure({
provider,
}),
],
})
useEffect(() => {
// Update status changes
const statusHandler = event => {
setStatus(event.status)
}
provider.on('status', statusHandler)
return () => {
provider.off('status', statusHandler)
}
}, [provider])
// Save current user to localStorage and emit to editor
useEffect(() => {
if (editor && currentUser) {
localStorage.setItem('currentUser', JSON.stringify(currentUser))
editor.chain().focus().updateUser(currentUser).run()
}
}, [editor, currentUser])
const setName = useCallback(() => {
const name = (window.prompt('Name', currentUser.name) || '').trim().substring(0, 32)
if (name) {
return setCurrentUser({ ...currentUser, name })
}
}, [currentUser])
if (!editor) {
return null
}
return (
<div className="column-half">
<div className="control-group">
<div className="button-group">
<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' : ''}
>
Italic
</button>
<button
onClick={() => editor.chain().focus().toggleStrike().run()}
className={editor.isActive('strike') ? 'is-active' : ''}
>
Strike
</button>
<button
onClick={() => editor.chain().focus().toggleBulletList().run()}
className={editor.isActive('bulletList') ? 'is-active' : ''}
>
Bullet list
</button>
<button
onClick={() => editor.chain().focus().toggleCode().run()}
className={editor.isActive('code') ? 'is-active' : ''}
>
Code
</button>
</div>
</div>
<EditorContent editor={editor} className="main-group" />
<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}`
: 'offline'}
</label>
<button style={{ '--color': currentUser.color }} onClick={setName}>
{currentUser.name}
</button>
</div>
</div>
)
}
export default Editor

View File

@ -0,0 +1,39 @@
import './styles.scss'
import { TiptapCollabProvider } from '@hocuspocus/provider'
import * as Y from 'yjs'
import Editor from './Editor.jsx'
const appId = '7j9y6m10'
const room = `room.${new Date()
.getFullYear()
.toString()
.slice(-2)}${new Date().getMonth() + 1}${new Date().getDate()}-ok`
// ydoc and provider for Editor A
const ydocA = new Y.Doc()
const providerA = new TiptapCollabProvider({
appId,
name: room,
document: ydocA,
})
// ydoc and provider for Editor B
const ydocB = new Y.Doc()
const providerB = new TiptapCollabProvider({
appId,
name: room,
document: ydocB,
})
const App = () => {
return (
<div className="col-group">
<Editor provider={providerA} ydoc={ydocA} room={room} />
<Editor provider={providerB} ydoc={ydocB} room={room} />
</div>
)
}
export default App

View File

@ -0,0 +1,24 @@
context('/src/Demos/CollaborationSplitPane/React/', () => {
beforeEach(() => {
cy.visit('/src/Demos/CollaborationSplitPane/React/')
})
it('should have a working tiptap instance', () => {
cy.get('.tiptap').then(([{ editor }]) => {
// eslint-disable-next-line
expect(editor).to.not.be.null
})
})
it('should have a ydoc', () => {
cy.get('.tiptap').then(([{ editor }]) => {
/**
* @type {import('yjs').Doc}
*/
const yDoc = editor.extensionManager.extensions.find(a => a.name === 'collaboration').options.document
// eslint-disable-next-line
expect(yDoc).to.not.be.null
})
})
})

View File

@ -0,0 +1,280 @@
/* Basic editor styles */
.tiptap {
:first-child {
margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
/* Highlight specific styles */
mark {
background-color: #FAF594;
border-radius: 0.4rem;
box-decoration-break: clone;
padding: 0.1rem 0.3rem;
}
/* Task list specific styles */
ul[data-type="taskList"] {
list-style: none;
margin-left: 0;
padding: 0;
li {
align-items: flex-start;
display: flex;
> label {
flex: 0 0 auto;
margin-right: 0.5rem;
user-select: none;
}
> div {
flex: 1 1 auto;
}
}
input[type="checkbox"] {
cursor: pointer;
}
ul[data-type="taskList"] {
margin: 0;
}
}
p {
word-break: break-all;
}
/* Give a remote user a caret */
.collaboration-cursor__caret {
border-left: 1px solid #0d0d0d;
border-right: 1px solid #0d0d0d;
margin-left: -1px;
margin-right: -1px;
pointer-events: none;
position: relative;
word-break: normal;
}
/* Render the username above the caret */
.collaboration-cursor__label {
border-radius: 3px 3px 3px 0;
color: #0d0d0d;
font-size: 12px;
font-style: normal;
font-weight: 600;
left: -1px;
line-height: normal;
padding: 0.1rem 0.3rem;
position: absolute;
top: -1.4em;
user-select: none;
white-space: nowrap;
}
}
.col-group {
display: flex;
flex-direction: row;
height: 100vh;
@media (max-width: 540px) {
flex-direction: column;
}
}
/* Column-half */
body {
overflow: hidden;
}
.column-half {
display: flex;
flex-direction: column;
flex: 1;
overflow: auto;
&:last-child {
border-left: 1px solid var(--gray-3);
@media (max-width: 540px) {
border-left: none;
border-top: 1px solid var(--gray-3);
}
}
& > .main-group {
flex-grow: 1;
}
}
/* Collaboration status */
.collab-status-group {
align-items: center;
background-color: var(--white);
border-top: 1px solid var(--gray-3);
bottom: 0;
color: var(--gray-5);
display: flex;
flex-direction: row;
font-size: 0.75rem;
font-weight: 400;
gap: 1rem;
justify-content: space-between;
padding: 0.375rem 0.5rem 0.375rem 1rem;
position: sticky;
width: 100%;
z-index: 100;
button {
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
align-self: stretch;
background: none;
display: -webkit-box;
flex-shrink: 1;
font-size: 0.75rem;
max-width: 100%;
padding: 0.25rem 0.375rem;
overflow: hidden;
position: relative;
text-overflow: ellipsis;
white-space: nowrap;
&::before {
background-color: var(--color);
border-radius: 0.375rem;
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);
width: 100%;
z-index: -1;
}
&:hover::before {
opacity: 1;
}
}
label {
align-items: center;
display: flex;
flex-direction: row;
flex-shrink: 0;
gap: 0.375rem;
line-height: 1.1;
&::before {
border-radius: 50%;
content: " ";
height: 0.35rem;
width: 0.35rem;
}
}
&[data-state="online"] {
label {
&::before {
background-color: var(--green);
}
}
}
&[data-state="offline"] {
label {
&::before {
background-color: var(--red);
}
}
}
}

View File

@ -1,6 +1,6 @@
context('/src/Examples/CollaborativeEditing/React/', () => {
context('/src/Demos/SingleRoomCollab/React/', () => {
beforeEach(() => {
cy.visit('/src/Examples/CollaborativeEditing/React/')
cy.visit('/src/Demos/SingleRoomCollab/React/')
})
/* it('should show the current room with participants', () => {

View File

@ -50,22 +50,26 @@ export default () => {
}
return (
<div>
<>
<div className="control-group">
<div className="button-group">
<button
onClick={setLink}
className={editor.isActive('link') ? 'is-active' : ''}
data-testid="setLink"
>
setLink
Set link
</button>
<button
onClick={() => editor.chain().focus().unsetLink().run()}
disabled={!editor.isActive('link')}
data-testid="unsetLink"
>
unsetLink
Unset link
</button>
<EditorContent editor={editor} />
</div>
</div>
<EditorContent editor={editor} />
</>
)
}

View File

@ -1,14 +1,22 @@
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
:first-child {
margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
@ -16,39 +24,78 @@
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: rgba(#616161, 0.1);
color: #616161;
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
color: inherit;
padding: 0;
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
img {
max-width: 100%;
height: auto;
}
hr {
margin: 1rem 0;
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
/* Link styles */
a {
color: var(--purple);
cursor: pointer;
&:hover {
color: var(--purple-contrast);
}
}
}

View File

@ -12,116 +12,118 @@ const MenuBar = ({ editor }) => {
}
return (
<>
<div className="control-group">
<div className="button-group">
<button
onClick={() => editor.chain().focus().toggleBold().run()}
className={editor.isActive('bold') ? 'is-active' : ''}
>
bold
Bold
</button>
<button
onClick={() => editor.chain().focus().toggleItalic().run()}
className={editor.isActive('italic') ? 'is-active' : ''}
>
italic
Italic
</button>
<button
onClick={() => editor.chain().focus().toggleStrike().run()}
className={editor.isActive('strike') ? 'is-active' : ''}
>
strike
Strike
</button>
<button
onClick={() => editor.chain().focus().toggleCode().run()}
className={editor.isActive('code') ? 'is-active' : ''}
>
code
Code
</button>
<button onClick={() => editor.chain().focus().unsetAllMarks().run()}>
clear marks
Clear marks
</button>
<button onClick={() => editor.chain().focus().clearNodes().run()}>
clear nodes
Clear nodes
</button>
<button
onClick={() => editor.chain().focus().setParagraph().run()}
className={editor.isActive('paragraph') ? 'is-active' : ''}
>
paragraph
Paragraph
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''}
>
h1
H1
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''}
>
h2
H2
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''}
>
h3
H3
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()}
className={editor.isActive('heading', { level: 4 }) ? 'is-active' : ''}
>
h4
H4
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()}
className={editor.isActive('heading', { level: 5 }) ? 'is-active' : ''}
>
h5
H5
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()}
className={editor.isActive('heading', { level: 6 }) ? 'is-active' : ''}
>
h6
H6
</button>
<button
onClick={() => editor.chain().focus().toggleBulletList().run()}
className={editor.isActive('bulletList') ? 'is-active' : ''}
>
bullet list
Bullet list
</button>
<button
onClick={() => editor.chain().focus().toggleOrderedList().run()}
className={editor.isActive('orderedList') ? 'is-active' : ''}
>
ordered list
Ordered list
</button>
<button
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
className={editor.isActive('codeBlock') ? 'is-active' : ''}
>
code block
Code block
</button>
<button
onClick={() => editor.chain().focus().toggleBlockquote().run()}
className={editor.isActive('blockquote') ? 'is-active' : ''}
>
blockquote
Blockquote
</button>
<button onClick={() => editor.chain().focus().setHorizontalRule().run()}>
horizontal rule
Horizontal rule
</button>
<button onClick={() => editor.chain().focus().setHardBreak().run()}>
hard break
Hard break
</button>
<button onClick={() => editor.chain().focus().undo().run()}>
undo
Undo
</button>
<button onClick={() => editor.chain().focus().redo().run()}>
redo
Redo
</button>
</>
</div>
</div>
)
}
@ -139,9 +141,9 @@ export default () => {
})
return (
<div>
<>
<MenuBar editor={editor} />
<EditorContent editor={editor} />
</div>
</>
)
}

View File

@ -1,14 +1,22 @@
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
:first-child {
margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
@ -16,39 +24,68 @@
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: rgba(#616161, 0.1);
color: #616161;
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
color: inherit;
padding: 0;
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
img {
max-width: 100%;
height: auto;
}
hr {
margin: 1rem 0;
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
}

View File

@ -1,70 +1,74 @@
<template>
<div v-if="editor">
<div v-if="editor" class="container">
<div class="control-group">
<div class="button-group">
<button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }">
bold
Bold
</button>
<button @click="editor.chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }">
italic
Italic
</button>
<button @click="editor.chain().focus().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }">
strike
Strike
</button>
<button @click="editor.chain().focus().toggleCode().run()" :class="{ 'is-active': editor.isActive('code') }">
code
Code
</button>
<button @click="editor.chain().focus().unsetAllMarks().run()">
clear marks
Clear marks
</button>
<button @click="editor.chain().focus().clearNodes().run()">
clear nodes
Clear nodes
</button>
<button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }">
paragraph
Paragraph
</button>
<button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }">
h1
H1
</button>
<button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }">
h2
H2
</button>
<button @click="editor.chain().focus().toggleHeading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }">
h3
H3
</button>
<button @click="editor.chain().focus().toggleHeading({ level: 4 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 4 }) }">
h4
H4
</button>
<button @click="editor.chain().focus().toggleHeading({ level: 5 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 5 }) }">
h5
H5
</button>
<button @click="editor.chain().focus().toggleHeading({ level: 6 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 6 }) }">
h6
H6
</button>
<button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }">
bullet list
Bullet list
</button>
<button @click="editor.chain().focus().toggleOrderedList().run()" :class="{ 'is-active': editor.isActive('orderedList') }">
ordered list
Ordered list
</button>
<button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }">
code block
Code block
</button>
<button @click="editor.chain().focus().toggleBlockquote().run()" :class="{ 'is-active': editor.isActive('blockquote') }">
blockquote
Blockquote
</button>
<button @click="editor.chain().focus().setHorizontalRule().run()">
horizontal rule
Horizontal rule
</button>
<button @click="editor.chain().focus().setHardBreak().run()">
hard break
Hard break
</button>
<button @click="editor.chain().focus().undo().run()">
undo
Undo
</button>
<button @click="editor.chain().focus().redo().run()">
redo
Redo
</button>
</div>
</div>
<editor-content :editor="editor" />
</div>
</template>
<script>
@ -107,15 +111,23 @@ export default {
<style lang="scss">
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
:first-child {
margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
@ -123,40 +135,69 @@ export default {
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: rgba(#616161, 0.1);
color: #616161;
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
color: inherit;
padding: 0;
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
img {
max-width: 100%;
height: auto;
}
hr {
margin: 1rem 0;
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
}
</style>

View File

@ -17,109 +17,109 @@ const MenuBar = ({ editor }) => {
onClick={() => editor.chain().focus().toggleBold().run()}
className={editor.isActive('bold') ? 'is-active' : ''}
>
bold
Bold
</button>
<button
onClick={() => editor.chain().focus().toggleItalic().run()}
className={editor.isActive('italic') ? 'is-active' : ''}
>
italic
Italic
</button>
<button
onClick={() => editor.chain().focus().toggleStrike().run()}
className={editor.isActive('strike') ? 'is-active' : ''}
>
strike
Strike
</button>
<button
onClick={() => editor.chain().focus().toggleCode().run()}
className={editor.isActive('code') ? 'is-active' : ''}
>
code
Code
</button>
<button onClick={() => editor.chain().focus().unsetAllMarks().run()}>
clear marks
Clear marks
</button>
<button onClick={() => editor.chain().focus().clearNodes().run()}>
clear nodes
Clear nodes
</button>
<button
onClick={() => editor.chain().focus().setParagraph().run()}
className={editor.isActive('paragraph') ? 'is-active' : ''}
>
paragraph
Paragraph
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''}
>
h1
H1
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''}
>
h2
H2
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''}
>
h3
H3
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()}
className={editor.isActive('heading', { level: 4 }) ? 'is-active' : ''}
>
h4
H4
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()}
className={editor.isActive('heading', { level: 5 }) ? 'is-active' : ''}
>
h5
H5
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()}
className={editor.isActive('heading', { level: 6 }) ? 'is-active' : ''}
>
h6
H6
</button>
<button
onClick={() => editor.chain().focus().toggleBulletList().run()}
className={editor.isActive('bulletList') ? 'is-active' : ''}
>
bullet list
Bullet list
</button>
<button
onClick={() => editor.chain().focus().toggleOrderedList().run()}
className={editor.isActive('orderedList') ? 'is-active' : ''}
>
ordered list
Ordered list
</button>
<button
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
className={editor.isActive('codeBlock') ? 'is-active' : ''}
>
code block
Code block
</button>
<button
onClick={() => editor.chain().focus().toggleBlockquote().run()}
className={editor.isActive('blockquote') ? 'is-active' : ''}
>
blockquote
Blockquote
</button>
<button onClick={() => editor.chain().focus().setHorizontalRule().run()}>
horizontal rule
Horizontal rule
</button>
<button onClick={() => editor.chain().focus().setHardBreak().run()}>
hard break
Hard break
</button>
<button onClick={() => editor.chain().focus().undo().run()}>
undo
Undo
</button>
<button onClick={() => editor.chain().focus().redo().run()}>
redo
Redo
</button>
</div>
)
@ -135,7 +135,7 @@ export default () => {
Hi there,
</h2>
<p>
this is a <em>basic</em> example of <strong>tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists:
this is a <em>basic</em> example of <strong>Tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists:
</p>
<ul>
<li>
@ -163,9 +163,9 @@ export default () => {
})
return (
<div>
<>
<MenuBar editor={editor} />
<EditorContent editor={editor} />
</div>
</>
)
}

View File

@ -1,14 +1,22 @@
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
:first-child {
margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
@ -16,41 +24,68 @@
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: rgba(#616161, 0.1);
color: #616161;
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
color: inherit;
padding: 0;
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
img {
max-width: 100%;
height: auto;
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
hr {
border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1);
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
}

View File

@ -1,67 +1,67 @@
<template>
<div v-if="editor" class="toolbar" :class="styles.toolbar">
<button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }">
bold
Bold
</button>
<button @click="editor.chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }">
italic
Italic
</button>
<button @click="editor.chain().focus().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }">
strike
Strike
</button>
<button @click="editor.chain().focus().toggleCode().run()" :class="{ 'is-active': editor.isActive('code') }">
code
Code
</button>
<button @click="editor.chain().focus().unsetAllMarks().run()">
clear marks
Clear marks
</button>
<button @click="editor.chain().focus().clearNodes().run()">
clear nodes
Clear nodes
</button>
<button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }">
paragraph
Paragraph
</button>
<button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }">
h1
H1
</button>
<button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }">
h2
H2
</button>
<button @click="editor.chain().focus().toggleHeading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }">
h3
H3
</button>
<button @click="editor.chain().focus().toggleHeading({ level: 4 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 4 }) }">
h4
H4
</button>
<button @click="editor.chain().focus().toggleHeading({ level: 5 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 5 }) }">
h5
H5
</button>
<button @click="editor.chain().focus().toggleHeading({ level: 6 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 6 }) }">
h6
H6
</button>
<button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }">
bullet list
Bullet list
</button>
<button @click="editor.chain().focus().toggleOrderedList().run()" :class="{ 'is-active': editor.isActive('orderedList') }">
ordered list
Ordered list
</button>
<button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }">
code block
Code block
</button>
<button @click="editor.chain().focus().toggleBlockquote().run()" :class="{ 'is-active': editor.isActive('blockquote') }">
blockquote
Blockquote
</button>
<button @click="editor.chain().focus().setHorizontalRule().run()">
horizontal rule
Horizontal rule
</button>
<button @click="editor.chain().focus().setHardBreak().run()">
hard break
Hard break
</button>
<button @click="editor.chain().focus().undo().run()">
undo
Undo
</button>
<button @click="editor.chain().focus().redo().run()">
redo
Redo
</button>
</div>
<editor-content :editor="editor" />
@ -95,7 +95,7 @@ export default {
This is a red headline
</h1>
<p>
this is a <em>basic</em> example of <strong>tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists:
this is a <em>basic</em> example of <strong>Tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists:
</p>
<ul>
<li>
@ -132,15 +132,23 @@ export default {
<style lang="scss">
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
:first-child {
margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
@ -148,41 +156,68 @@ export default {
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: rgba(#616161, 0.1);
color: #616161;
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
color: inherit;
padding: 0;
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
img {
max-width: 100%;
height: auto;
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
hr {
border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1);
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
}

View File

@ -1,9 +1,13 @@
.tiptap {
.code-block {
position: relative;
select {
position: absolute;
background-color: var(--white);
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="Black" d="M7 10l5 5 5-5z"/></svg>');
right: 0.5rem;
top: 0.5rem;
}
}
}

View File

@ -1,7 +1,3 @@
// load specific languages only
// import { lowlight } from 'lowlight/lib/core'
// import javascript from 'highlight.js/lib/languages/javascript'
// lowlight.registerLanguage('javascript', javascript)
import './styles.scss'
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
@ -13,16 +9,21 @@ import css from 'highlight.js/lib/languages/css'
import js from 'highlight.js/lib/languages/javascript'
import ts from 'highlight.js/lib/languages/typescript'
import html from 'highlight.js/lib/languages/xml'
// load all highlight.js languages
import { lowlight } from 'lowlight'
// load all languages with "all" or common languages with "common"
import { all, createLowlight } from 'lowlight'
import React from 'react'
import CodeBlockComponent from './CodeBlockComponent.jsx'
// eslint-disable-next-line
import CodeBlockComponent from './CodeBlockComponent'
lowlight.registerLanguage('html', html)
lowlight.registerLanguage('css', css)
lowlight.registerLanguage('js', js)
lowlight.registerLanguage('ts', ts)
// create a lowlight instance
const lowlight = createLowlight(all)
// you can also register individual languages
lowlight.register('html', html)
lowlight.register('css', css)
lowlight.register('js', js)
lowlight.register('ts', ts)
const MenuBar = ({ editor }) => {
if (!editor) {
@ -30,9 +31,13 @@ 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' : ''}>
code block
Toggle code block
</button>
</div>
</div>
)
}
@ -52,7 +57,7 @@ export default () => {
],
content: `
<p>
Thats a boring paragraph followed by a fenced code block:
That's a boring paragraph followed by a fenced code block:
</p>
<pre><code class="language-javascript">for (var i=1; i <= 20; i++)
{
@ -72,9 +77,9 @@ export default () => {
})
return (
<div>
<>
<MenuBar editor={editor} />
<EditorContent editor={editor} />
</div>
</>
)
}

View File

@ -15,6 +15,7 @@ context('/src/Examples/CodeBlockLanguage/React/', () => {
expect(initialCount).to.be.greaterThan(0)
cy.wait(100)
cy.get('.tiptap select').select('java')
cy.wait(500)

View File

@ -1,23 +1,25 @@
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
:first-child {
margin-top: 0;
}
pre {
background: #0d0d0d;
color: #fff;
font-family: "JetBrainsMono", monospace;
padding: 0.75rem 1rem;
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
color: inherit;
padding: 0;
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
/* Code styling */
.hljs-comment,
.hljs-quote {
color: #616161;

View File

@ -46,13 +46,17 @@ export default {
</script>
<style lang="scss">
.tiptap {
.code-block {
position: relative;
select {
position: absolute;
top: 0.5rem;
background-color: var(--white);
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="Black" d="M7 10l5 5 5-5z"/></svg>');
right: 0.5rem;
top: 0.5rem;
}
}
}
</style>

View File

@ -15,6 +15,7 @@ context('/src/Examples/CodeBlockLanguage/Vue/', () => {
expect(initialCount).to.be.greaterThan(0)
cy.wait(100)
cy.get('.tiptap select').select('java')
cy.wait(500)

View File

@ -1,9 +1,13 @@
<template>
<div v-if="editor">
<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') }">
code block
Toggle code block
</button>
</div>
</div>
</div>
<editor-content :editor="editor" />
</template>
@ -17,20 +21,19 @@ import css from 'highlight.js/lib/languages/css'
import js from 'highlight.js/lib/languages/javascript'
import ts from 'highlight.js/lib/languages/typescript'
import html from 'highlight.js/lib/languages/xml'
// load all highlight.js languages
import { lowlight } from 'lowlight'
// load all languages with "all" or common languages with "common"
import { all, createLowlight } from 'lowlight'
import CodeBlockComponent from './CodeBlockComponent.vue'
lowlight.registerLanguage('html', html)
lowlight.registerLanguage('css', css)
lowlight.registerLanguage('js', js)
lowlight.registerLanguage('ts', ts)
// create a lowlight instance
const lowlight = createLowlight(all)
// load specific languages only
// import { lowlight } from 'lowlight/lib/core'
// import javascript from 'highlight.js/lib/languages/javascript'
// lowlight.registerLanguage('javascript', javascript)
// you can also register languages
lowlight.register('html', html)
lowlight.register('css', css)
lowlight.register('js', js)
lowlight.register('ts', ts)
export default {
components: {
@ -59,7 +62,7 @@ export default {
],
content: `
<p>
Thats a boring paragraph followed by a fenced code block:
That's a boring paragraph followed by a fenced code block:
</p>
<pre><code class="language-javascript">for (var i=1; i <= 20; i++)
{
@ -88,24 +91,26 @@ export default {
<style lang="scss">
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
:first-child {
margin-top: 0;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
color: inherit;
padding: 0;
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
/* Code styling */
.hljs-comment,
.hljs-quote {
color: #616161;
@ -121,7 +126,7 @@ export default {
.hljs-name,
.hljs-selector-id,
.hljs-selector-class {
color: #F98181;
color: #f98181;
}
.hljs-number,
@ -131,23 +136,23 @@ export default {
.hljs-literal,
.hljs-type,
.hljs-params {
color: #FBBC88;
color: #fbbc88;
}
.hljs-string,
.hljs-symbol,
.hljs-bullet {
color: #B9F18D;
color: #b9f18d;
}
.hljs-title,
.hljs-section {
color: #FAF594;
color: #faf594;
}
.hljs-keyword,
.hljs-selector-tag {
color: #70CFF8;
color: #70cff8;
}
.hljs-emphasis {

View File

@ -59,25 +59,25 @@ export default ({ editor }) => {
},
{
icon: 'list-unordered',
title: 'Bullet List',
title: 'Bullet list',
action: () => editor.chain().focus().toggleBulletList().run(),
isActive: () => editor.isActive('bulletList'),
},
{
icon: 'list-ordered',
title: 'Ordered List',
title: 'Ordered list',
action: () => editor.chain().focus().toggleOrderedList().run(),
isActive: () => editor.isActive('orderedList'),
},
{
icon: 'list-check-2',
title: 'Task List',
title: 'Task list',
action: () => editor.chain().focus().toggleTaskList().run(),
isActive: () => editor.isActive('taskList'),
},
{
icon: 'code-box-line',
title: 'Code Block',
title: 'Code block',
action: () => editor.chain().focus().toggleCodeBlock().run(),
isActive: () => editor.isActive('codeBlock'),
},
@ -92,7 +92,7 @@ export default ({ editor }) => {
},
{
icon: 'separator',
title: 'Horizontal Rule',
title: 'Horizontal rule',
action: () => editor.chain().focus().setHorizontalRule().run(),
},
{
@ -100,12 +100,12 @@ export default ({ editor }) => {
},
{
icon: 'text-wrap',
title: 'Hard Break',
title: 'Hard break',
action: () => editor.chain().focus().setHardBreak().run(),
},
{
icon: 'format-clear',
title: 'Clear Format',
title: 'Clear format',
action: () => editor.chain().focus().clearNodes().unsetAllMarks()
.run(),
},

View File

@ -78,25 +78,25 @@ export default {
},
{
icon: 'list-unordered',
title: 'Bullet List',
title: 'Bullet list',
action: () => this.editor.chain().focus().toggleBulletList().run(),
isActive: () => this.editor.isActive('bulletList'),
},
{
icon: 'list-ordered',
title: 'Ordered List',
title: 'Ordered list',
action: () => this.editor.chain().focus().toggleOrderedList().run(),
isActive: () => this.editor.isActive('orderedList'),
},
{
icon: 'list-check-2',
title: 'Task List',
title: 'Task list',
action: () => this.editor.chain().focus().toggleTaskList().run(),
isActive: () => this.editor.isActive('taskList'),
},
{
icon: 'code-box-line',
title: 'Code Block',
title: 'Code block',
action: () => this.editor.chain().focus().toggleCodeBlock().run(),
isActive: () => this.editor.isActive('codeBlock'),
},
@ -111,7 +111,7 @@ export default {
},
{
icon: 'separator',
title: 'Horizontal Rule',
title: 'Horizontal rule',
action: () => this.editor.chain().focus().setHorizontalRule().run(),
},
{
@ -119,12 +119,12 @@ export default {
},
{
icon: 'text-wrap',
title: 'Hard Break',
title: 'Hard break',
action: () => this.editor.chain().focus().setHardBreak().run(),
},
{
icon: 'format-clear',
title: 'Clear Format',
title: 'Clear format',
action: () => this.editor.chain()
.focus()
.clearNodes()

View File

@ -54,11 +54,11 @@ export const MentionList = forwardRef((props, ref) => {
}))
return (
<div className="items">
<div className="dropdown-menu">
{props.items.length
? props.items.map((item, index) => (
<button
className={`item ${index === selectedIndex ? 'is-selected' : ''}`}
className={index === selectedIndex ? 'is-selected' : ''}
key={index}
onClick={() => selectItem(index)}
>

View File

@ -1,28 +1,31 @@
.items {
padding: 0.2rem;
/* Dropdown menu */
.dropdown-menu {
background: var(--white);
border: 1px solid var(--gray-1);
border-radius: 0.7rem;
box-shadow: var(--shadow);
display: flex;
flex-direction: column;
gap: 0.1rem;
overflow: auto;
padding: 0.4rem;
position: relative;
border-radius: 0.5rem;
background: #FFF;
color: rgba(0, 0, 0, 0.8);
overflow: hidden;
font-size: 0.9rem;
box-shadow:
0 0 0 1px rgba(0, 0, 0, 0.05),
0px 10px 20px rgba(0, 0, 0, 0.1),
;
}
.item {
display: block;
margin: 0;
width: 100%;
button {
align-items: center;
background-color: transparent;
display: flex;
gap: 0.25rem;
text-align: left;
background: transparent;
border-radius: 0.4rem;
border: 1px solid transparent;
padding: 0.2rem 0.4rem;
width: 100%;
&:hover,
&:hover.is-selected {
background-color: var(--gray-3);
}
&.is-selected {
border-color: #000;
background-color: var(--gray-2);
}
}
}

View File

@ -40,7 +40,7 @@ export default () => {
: 0
return (
<div>
<>
<EditorContent editor={editor} />
{editor
&& <div className={`character-count ${editor.storage.characterCount.characters() === limit ? 'character-count--warning' : ''}`}>
@ -48,7 +48,6 @@ export default () => {
height="20"
width="20"
viewBox="0 0 20 20"
className="character-count__graph"
>
<circle
r="10"
@ -74,11 +73,9 @@ export default () => {
/>
</svg>
<div className="character-count__text">
{editor.storage.characterCount.characters()} / {limit} characters
</div>
</div>
}
</div>
</>
)
}

View File

@ -5,32 +5,32 @@ context('/src/Examples/Community/React/', () => {
it('should count the characters correctly', () => {
// check if count text is "44 / 280 characters"
cy.get('.character-count__text').should('have.text', '44/280 characters')
cy.get('.character-count').should('contain', '44 / 280 characters')
// type in .tiptap
cy.get('.tiptap').type(' Hello World')
cy.get('.character-count__text').should('have.text', '56/280 characters')
cy.get('.character-count').should('contain', '56 / 280 characters')
// remove content from .tiptap and enter text
cy.get('.tiptap').type('{selectall}{backspace}Hello World')
cy.get('.character-count__text').should('have.text', '11/280 characters')
cy.get('.character-count').should('contain', '11 / 280 characters')
})
it('should mention a user', () => {
cy.get('.tiptap').type('{selectall}{backspace}@')
// check if the mention autocomplete is visible
cy.get('.tippy-content .items').should('be.visible')
cy.get('.tippy-content .dropdown-menu').should('be.visible')
// select the first user
cy.get('.tippy-content .items .item').first().then($el => {
cy.get('.tippy-content .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__text').should('have.text', '2/280 characters')
cy.get('.character-count').should('contain', '2 / 280 characters')
})
})

View File

@ -1,41 +1,33 @@
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
}
:first-child {
margin-top: 0;
}
.mention {
border: 1px solid #000;
background-color: var(--purple-light);
border-radius: 0.4rem;
padding: 0.1rem 0.3rem;
box-decoration-break: clone;
color: var(--purple);
padding: 0.1rem 0.3rem;
}
}
/* Character count */
.character-count {
margin-top: 1rem;
display: flex;
align-items: center;
color: #68CEF8;
color: var(--gray-5);
display: flex;
font-size: 0.75rem;
gap: .5rem;
margin: 1.5rem;
&--warning {
color: #FB5151;
svg {
color: var(--purple);
}
&__graph {
margin-right: 0.5rem;
}
&__text {
color: #868e96;
&--warning,
&--warning svg {
color: var(--red);
}
}

View File

@ -1,8 +1,7 @@
<template>
<div class="items">
<div class="dropdown-menu">
<template v-if="items.length">
<button
class="item"
:class="{ 'is-selected': index === selectedIndex }"
v-for="(item, index) in items"
:key="index"
@ -87,32 +86,35 @@ export default {
</script>
<style lang="scss">
.items {
padding: 0.2rem;
/* Dropdown menu */
.dropdown-menu {
background: var(--white);
border: 1px solid var(--gray-1);
border-radius: 0.7rem;
box-shadow: var(--shadow);
display: flex;
flex-direction: column;
gap: 0.1rem;
overflow: auto;
padding: 0.4rem;
position: relative;
border-radius: 0.5rem;
background: #FFF;
color: rgba(0, 0, 0, 0.8);
overflow: hidden;
font-size: 0.9rem;
box-shadow:
0 0 0 1px rgba(0, 0, 0, 0.05),
0px 10px 20px rgba(0, 0, 0, 0.1),
;
button {
align-items: center;
background-color: transparent;
display: flex;
gap: 0.25rem;
text-align: left;
width: 100%;
&:hover,
&:hover.is-selected {
background-color: var(--gray-3);
}
.item {
display: block;
margin: 0;
width: 100%;
text-align: left;
background: transparent;
border-radius: 0.4rem;
border: 1px solid transparent;
padding: 0.2rem 0.4rem;
&.is-selected {
border-color: #000;
background-color: var(--gray-2);
}
}
}
</style>

View File

@ -5,32 +5,32 @@ context('/src/Examples/Community/Vue/', () => {
it('should count the characters correctly', () => {
// check if count text is "44 / 280 characters"
cy.get('.character-count__text').should('have.text', '44/280 characters')
cy.get('.character-count').should('contain', '44 / 280 characters')
// type in .tiptap
cy.get('.tiptap').type(' Hello World')
cy.get('.character-count__text').should('have.text', '56/280 characters')
cy.get('.character-count').should('contain', '56 / 280 characters')
// remove content from .tiptap and enter text
cy.get('.tiptap').type('{selectall}{backspace}Hello World')
cy.get('.character-count__text').should('have.text', '11/280 characters')
cy.get('.character-count').should('contain', '11 / 280 characters')
})
it('should mention a user', () => {
cy.get('.tiptap').type('{selectall}{backspace}@')
// check if the mention autocomplete is visible
cy.get('.tippy-content .items').should('be.visible')
cy.get('.tippy-content .dropdown-menu').should('be.visible')
// select the first user
cy.get('.tippy-content .items .item').first().then($el => {
cy.get('.tippy-content .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__text').should('have.text', '2/280 characters')
cy.get('.character-count').should('contain', '2 / 280 characters')
})
})

View File

@ -6,7 +6,6 @@
height="20"
width="20"
viewBox="0 0 20 20"
class="character-count__graph"
>
<circle
r="10"
@ -32,7 +31,7 @@
/>
</svg>
<div class="character-count__text">{{ editor.storage.characterCount.characters() }}/{{ limit }} characters</div>
{{ editor.storage.characterCount.characters() }} / {{ limit }} characters
</div>
</template>
@ -97,43 +96,35 @@ export default {
<style lang="scss">
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
}
:first-child {
margin-top: 0;
}
.mention {
border: 1px solid #000;
background-color: var(--purple-light);
border-radius: 0.4rem;
padding: 0.1rem 0.3rem;
box-decoration-break: clone;
color: var(--purple);
padding: 0.1rem 0.3rem;
}
}
/* Character count */
.character-count {
margin-top: 1rem;
display: flex;
align-items: center;
color: #68CEF8;
color: var(--gray-5);
display: flex;
font-size: 0.75rem;
gap: .5rem;
margin: 1.5rem;
&--warning {
color: #FB5151;
svg {
color: var(--purple);
}
&__graph {
margin-right: 0.5rem;
}
&__text {
color: #868e96;
&--warning,
&--warning svg {
color: var(--red);
}
}
</style>

View File

@ -1,24 +1,109 @@
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
:first-child {
margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
/* Placeholder (at the top) */
/*.tiptap p.is-editor-empty:first-child::before {
/* p.is-editor-empty:first-child::before {
color: var(--gray-4);
content: attr(data-placeholder);
float: left;
color: #ced4da;
pointer-events: none;
height: 0;
pointer-events: none;
}*/
/* Placeholder (on every new line) */
.tiptap .is-empty::before {
.is-empty::before {
color: var(--gray-4);
content: attr(data-placeholder);
float: left;
color: #ced4da;
pointer-events: none;
height: 0;
pointer-events: none;
}
}

View File

@ -60,26 +60,111 @@ export default {
<style lang="scss">
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
:first-child {
margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
/* Placeholder (at the top) */
/*.tiptap p.is-editor-empty:first-child::before {
/* p.is-editor-empty:first-child::before {
color: var(--gray-4);
content: attr(data-placeholder);
float: left;
color: #ced4da;
pointer-events: none;
height: 0;
pointer-events: none;
}*/
/* Placeholder (on every new line) */
.tiptap .is-empty::before {
.is-empty::before {
color: var(--gray-4);
content: attr(data-placeholder);
float: left;
color: #ced4da;
pointer-events: none;
height: 0;
pointer-events: none;
}
}
</style>

View File

@ -1,24 +1,91 @@
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
:first-child {
margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Placeholder (at the top) */
/*.tiptap p.is-editor-empty:first-child::before {
content: attr(data-placeholder);
float: left;
color: #ced4da;
pointer-events: none;
height: 0;
}*/
/* Placeholder (on every new line) */
.tiptap .is-empty::before {
content: attr(data-placeholder);
float: left;
color: #ced4da;
pointer-events: none;
height: 0;
/* Heading styles */
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
}

View File

@ -15,7 +15,8 @@ const MenuBar = () => {
}
return (
<>
<div className="control-group">
<div className="button-group">
<button
onClick={() => editor.chain().focus().toggleBold().run()}
disabled={
@ -27,7 +28,7 @@ const MenuBar = () => {
}
className={editor.isActive('bold') ? 'is-active' : ''}
>
bold
Bold
</button>
<button
onClick={() => editor.chain().focus().toggleItalic().run()}
@ -40,7 +41,7 @@ const MenuBar = () => {
}
className={editor.isActive('italic') ? 'is-active' : ''}
>
italic
Italic
</button>
<button
onClick={() => editor.chain().focus().toggleStrike().run()}
@ -53,7 +54,7 @@ const MenuBar = () => {
}
className={editor.isActive('strike') ? 'is-active' : ''}
>
strike
Strike
</button>
<button
onClick={() => editor.chain().focus().toggleCode().run()}
@ -66,85 +67,85 @@ const MenuBar = () => {
}
className={editor.isActive('code') ? 'is-active' : ''}
>
code
Code
</button>
<button onClick={() => editor.chain().focus().unsetAllMarks().run()}>
clear marks
Clear marks
</button>
<button onClick={() => editor.chain().focus().clearNodes().run()}>
clear nodes
Clear nodes
</button>
<button
onClick={() => editor.chain().focus().setParagraph().run()}
className={editor.isActive('paragraph') ? 'is-active' : ''}
>
paragraph
Paragraph
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''}
>
h1
H1
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''}
>
h2
H2
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''}
>
h3
H3
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()}
className={editor.isActive('heading', { level: 4 }) ? 'is-active' : ''}
>
h4
H4
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()}
className={editor.isActive('heading', { level: 5 }) ? 'is-active' : ''}
>
h5
H5
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()}
className={editor.isActive('heading', { level: 6 }) ? 'is-active' : ''}
>
h6
H6
</button>
<button
onClick={() => editor.chain().focus().toggleBulletList().run()}
className={editor.isActive('bulletList') ? 'is-active' : ''}
>
bullet list
Bullet list
</button>
<button
onClick={() => editor.chain().focus().toggleOrderedList().run()}
className={editor.isActive('orderedList') ? 'is-active' : ''}
>
ordered list
Ordered list
</button>
<button
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
className={editor.isActive('codeBlock') ? 'is-active' : ''}
>
code block
Code block
</button>
<button
onClick={() => editor.chain().focus().toggleBlockquote().run()}
className={editor.isActive('blockquote') ? 'is-active' : ''}
>
blockquote
Blockquote
</button>
<button onClick={() => editor.chain().focus().setHorizontalRule().run()}>
horizontal rule
Horizontal rule
</button>
<button onClick={() => editor.chain().focus().setHardBreak().run()}>
hard break
Hard break
</button>
<button
onClick={() => editor.chain().focus().undo().run()}
@ -156,7 +157,7 @@ const MenuBar = () => {
.run()
}
>
undo
Undo
</button>
<button
onClick={() => editor.chain().focus().redo().run()}
@ -168,15 +169,16 @@ const MenuBar = () => {
.run()
}
>
redo
Redo
</button>
<button
onClick={() => editor.chain().focus().setColor('#958DF1').run()}
className={editor.isActive('textStyle', { color: '#958DF1' }) ? 'is-active' : ''}
>
purple
Purple
</button>
</>
</div>
</div>
)
}
@ -200,7 +202,7 @@ const content = `
Hi there,
</h2>
<p>
this is a <em>basic</em> example of <strong>tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists:
this is a <em>basic</em> example of <strong>Tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists:
</p>
<ul>
<li>

View File

@ -21,41 +21,41 @@ context('/src/Examples/Default/React/', () => {
})
const buttonMarks = [
{ label: 'bold', tag: 'strong' },
{ label: 'italic', tag: 'em' },
{ label: 'strike', tag: 's' },
{ label: 'Bold', tag: 'strong' },
{ label: 'Italic', tag: 'em' },
{ label: 'Strike', tag: 's' },
]
buttonMarks.forEach(m => {
it(`should disable ${m.label} when the code tag is enabled for cursor`, () => {
cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('code').click()
cy.get('button').contains('Code').click()
cy.get('button').contains(m.label).should('be.disabled')
})
it(`should enable ${m.label} when the code tag is disabled for cursor`, () => {
cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('code').click()
cy.get('button').contains('code').click()
cy.get('button').contains('Code').click()
cy.get('button').contains('Code').click()
cy.get('button').contains(m.label).should('not.be.disabled')
})
it(`should disable ${m.label} when the code tag is enabled for selection`, () => {
cy.get('.tiptap').type('{selectall}Hello world{selectall}')
cy.get('button').contains('code').click()
cy.get('button').contains('Code').click()
cy.get('button').contains(m.label).should('be.disabled')
})
it(`should enable ${m.label} when the code tag is disabled for selection`, () => {
cy.get('.tiptap').type('{selectall}Hello world{selectall}')
cy.get('button').contains('code').click()
cy.get('button').contains('code').click()
cy.get('button').contains('Code').click()
cy.get('button').contains('Code').click()
cy.get('button').contains(m.label).should('not.be.disabled')
})
it(`should apply ${m.label} when the button is pressed`, () => {
cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('paragraph').click()
cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}')
cy.get('button').contains(m.label).click()
cy.get(`.tiptap ${m.tag}`).should('exist').should('have.text', 'Hello world')
@ -64,40 +64,40 @@ context('/src/Examples/Default/React/', () => {
it('should clear marks when the button is pressed', () => {
cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('paragraph').click()
cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}')
cy.get('button').contains('bold').click()
cy.get('button').contains('Bold').click()
cy.get('.tiptap strong').should('exist').should('have.text', 'Hello world')
cy.get('button').contains('clear marks').click()
cy.get('button').contains('Clear marks').click()
cy.get('.tiptap strong').should('not.exist')
})
it('should clear nodes when the button is pressed', () => {
cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('bullet list').click()
cy.get('button').contains('Bullet list').click()
cy.get('.tiptap ul').should('exist').should('have.text', 'Hello world')
cy.get('.tiptap').type('{enter}A second item{enter}A third item{selectall}')
cy.get('button').contains('clear nodes').click()
cy.get('button').contains('Clear nodes').click()
cy.get('.tiptap ul').should('not.exist')
cy.get('.tiptap p').should('have.length', 3)
})
const buttonNodes = [
{ label: 'h1', tag: 'h1' },
{ label: 'h2', tag: 'h2' },
{ label: 'h3', tag: 'h3' },
{ label: 'h4', tag: 'h4' },
{ label: 'h5', tag: 'h5' },
{ label: 'h6', tag: 'h6' },
{ label: 'bullet list', tag: 'ul' },
{ label: 'ordered list', tag: 'ol' },
{ label: 'code block', tag: 'pre code' },
{ label: 'blockquote', tag: 'blockquote' },
{ label: 'H1', tag: 'h1' },
{ label: 'H2', tag: 'h2' },
{ label: 'H3', tag: 'h3' },
{ label: 'H4', tag: 'h4' },
{ label: 'H5', tag: 'h5' },
{ label: 'H6', tag: 'h6' },
{ label: 'Bullet list', tag: 'ul' },
{ label: 'Ordered list', tag: 'ol' },
{ label: 'Code block', tag: 'pre code' },
{ label: 'Blockquote', tag: 'blockquote' },
]
buttonNodes.forEach(n => {
it(`should set ${n.label} when the button is pressed`, () => {
cy.get('button').contains('paragraph').click()
cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}Hello world{selectall}')
cy.get('button').contains(n.label).click()
@ -109,35 +109,35 @@ context('/src/Examples/Default/React/', () => {
it('should add a hr when on the same line as a node', () => {
cy.get('.tiptap').type('{rightArrow}')
cy.get('button').contains('horizontal rule').click()
cy.get('button').contains('Horizontal rule').click()
cy.get('.tiptap hr').should('exist')
cy.get('.tiptap h1').should('exist')
})
it('should add a hr when on a new line', () => {
cy.get('.tiptap').type('{rightArrow}{enter}')
cy.get('button').contains('horizontal rule').click()
cy.get('button').contains('Horizontal rule').click()
cy.get('.tiptap hr').should('exist')
cy.get('.tiptap h1').should('exist')
})
it('should add a br', () => {
cy.get('.tiptap').type('{rightArrow}')
cy.get('button').contains('hard break').click()
cy.get('button').contains('Hard break').click()
cy.get('.tiptap h1 br').should('exist')
})
it('should undo', () => {
cy.get('.tiptap').type('{selectall}{backspace}')
cy.get('button').contains('undo').click()
cy.get('button').contains('Undo').click()
cy.get('.tiptap').should('contain', 'Hello world')
})
it('should redo', () => {
cy.get('.tiptap').type('{selectall}{backspace}')
cy.get('button').contains('undo').click()
cy.get('button').contains('Undo').click()
cy.get('.tiptap').should('contain', 'Hello world')
cy.get('button').contains('redo').click()
cy.get('button').contains('Redo').click()
cy.get('.tiptap').should('not.contain', 'Hello world')
})
})

View File

@ -1,14 +1,22 @@
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
:first-child {
margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
@ -16,41 +24,68 @@
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: rgba(#616161, 0.1);
color: #616161;
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
color: inherit;
padding: 0;
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
img {
max-width: 100%;
height: auto;
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
hr {
border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1);
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
}

View File

@ -21,15 +21,15 @@ context('/src/Examples/Default/React/', () => {
})
const buttonMarks = [
{ label: 'bold', tag: 'strong' },
{ label: 'italic', tag: 'em' },
{ label: 'strike', tag: 's' },
{ label: 'Bold', tag: 'strong' },
{ label: 'Italic', tag: 'em' },
{ label: 'Strike', tag: 's' },
]
buttonMarks.forEach(m => {
it(`should apply ${m.label} when the button is pressed`, () => {
cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('paragraph').click()
cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}')
cy.get('button').contains(m.label).click()
cy.get(`.tiptap ${m.tag}`).should('exist').should('have.text', 'Hello world')
@ -38,40 +38,40 @@ context('/src/Examples/Default/React/', () => {
it('should clear marks when the button is pressed', () => {
cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('paragraph').click()
cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}')
cy.get('button').contains('bold').click()
cy.get('button').contains('Bold').click()
cy.get('.tiptap strong').should('exist').should('have.text', 'Hello world')
cy.get('button').contains('clear marks').click()
cy.get('button').contains('Clear marks').click()
cy.get('.tiptap strong').should('not.exist')
})
it('should clear nodes when the button is pressed', () => {
cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('bullet list').click()
cy.get('button').contains('Bullet list').click()
cy.get('.tiptap ul').should('exist').should('have.text', 'Hello world')
cy.get('.tiptap').type('{enter}A second item{enter}A third item{selectall}')
cy.get('button').contains('clear nodes').click()
cy.get('button').contains('Clear nodes').click()
cy.get('.tiptap ul').should('not.exist')
cy.get('.tiptap p').should('have.length', 3)
})
const buttonNodes = [
{ label: 'h1', tag: 'h1' },
{ label: 'h2', tag: 'h2' },
{ label: 'h3', tag: 'h3' },
{ label: 'h4', tag: 'h4' },
{ label: 'h5', tag: 'h5' },
{ label: 'h6', tag: 'h6' },
{ label: 'bullet list', tag: 'ul' },
{ label: 'ordered list', tag: 'ol' },
{ label: 'code block', tag: 'pre code' },
{ label: 'blockquote', tag: 'blockquote' },
{ label: 'H1', tag: 'h1' },
{ label: 'H2', tag: 'h2' },
{ label: 'H3', tag: 'h3' },
{ label: 'H4', tag: 'h4' },
{ label: 'H5', tag: 'h5' },
{ label: 'H6', tag: 'h6' },
{ label: 'Bullet list', tag: 'ul' },
{ label: 'Ordered list', tag: 'ol' },
{ label: 'Code block', tag: 'pre code' },
{ label: 'Blockquote', tag: 'blockquote' },
]
buttonNodes.forEach(n => {
it(`should set ${n.label} when the button is pressed`, () => {
cy.get('button').contains('paragraph').click()
cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}Hello world{selectall}')
cy.get('button').contains(n.label).click()
@ -83,35 +83,35 @@ context('/src/Examples/Default/React/', () => {
it('should add a hr when on the same line as a node', () => {
cy.get('.tiptap').type('{rightArrow}')
cy.get('button').contains('horizontal rule').click()
cy.get('button').contains('Horizontal rule').click()
cy.get('.tiptap hr').should('exist')
cy.get('.tiptap h1').should('exist')
})
it('should add a hr when on a new line', () => {
cy.get('.tiptap').type('{rightArrow}{enter}')
cy.get('button').contains('horizontal rule').click()
cy.get('button').contains('Horizontal rule').click()
cy.get('.tiptap hr').should('exist')
cy.get('.tiptap h1').should('exist')
})
it('should add a br', () => {
cy.get('.tiptap').type('{rightArrow}')
cy.get('button').contains('hard break').click()
cy.get('button').contains('Hard break').click()
cy.get('.tiptap h1 br').should('exist')
})
it('should undo', () => {
cy.get('.tiptap').type('{selectall}{backspace}')
cy.get('button').contains('undo').click()
cy.get('button').contains('Undo').click()
cy.get('.tiptap').should('contain', 'Hello world')
})
it('should redo', () => {
cy.get('.tiptap').type('{selectall}{backspace}')
cy.get('button').contains('undo').click()
cy.get('button').contains('Undo').click()
cy.get('.tiptap').should('contain', 'Hello world')
cy.get('button').contains('redo').click()
cy.get('button').contains('Redo').click()
cy.get('.tiptap').should('not.contain', 'Hello world')
})
})

View File

@ -1,6 +1,9 @@
<script>
import "./styles.scss";
import { Color } from '@tiptap/extension-color'
import ListItem from '@tiptap/extension-list-item'
import TextStyle from '@tiptap/extension-text-style'
import StarterKit from "@tiptap/starter-kit";
import { Editor } from "@tiptap/core";
import { onMount } from "svelte";
@ -11,13 +14,17 @@
onMount(() => {
editor = new Editor({
element: element,
extensions: [StarterKit],
extensions: [
Color.configure({ types: [TextStyle.name, ListItem.name] }),
TextStyle.configure({ types: [ListItem.name] }),
StarterKit,
],
content: `
<h2>
Hi there,
</h2>
<p>
this is a <em>basic</em> example of <strong>tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists:
this is a <em>basic</em> example of <strong>Tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists:
</p>
<ul>
<li>
@ -51,119 +58,125 @@
</script>
{#if editor}
<div>
<div>
<div class="control-group">
<div class="button-group">
<button
on:click={() => console.log && editor.chain().focus().toggleBold().run()}
disabled={!editor.can().chain().focus().toggleBold().run()}
class={editor.isActive("bold") ? "is-active" : ""}
>
bold
Bold
</button>
<button
on:click={() => editor.chain().focus().toggleItalic().run()}
disabled={!editor.can().chain().focus().toggleItalic().run()}
class={editor.isActive("italic") ? "is-active" : ""}
>
italic
Italic
</button>
<button
on:click={() => editor.chain().focus().toggleStrike().run()}
disabled={!editor.can().chain().focus().toggleStrike().run()}
class={editor.isActive("strike") ? "is-active" : ""}
>
strike
Strike
</button>
<button
on:click={() => editor.chain().focus().toggleCode().run()}
disabled={!editor.can().chain().focus().toggleCode().run()}
class={editor.isActive("code") ? "is-active" : ""}
>
code
Code
</button>
<button on:click={() => editor.chain().focus().unsetAllMarks().run()}> clear marks </button>
<button on:click={() => editor.chain().focus().clearNodes().run()}> clear nodes </button>
<button on:click={() => editor.chain().focus().unsetAllMarks().run()}>Clear marks</button>
<button on:click={() => editor.chain().focus().clearNodes().run()}>Clear nodes</button>
<button
on:click={() => editor.chain().focus().setParagraph().run()}
class={editor.isActive("paragraph") ? "is-active" : ""}
>
paragraph
Paragraph
</button>
<button
on:click={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
class={editor.isActive("heading", { level: 1 }) ? "is-active" : ""}
>
h1
H1
</button>
<button
on:click={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
class={editor.isActive("heading", { level: 2 }) ? "is-active" : ""}
>
h2
H2
</button>
<button
on:click={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
class={editor.isActive("heading", { level: 3 }) ? "is-active" : ""}
>
h3
H3
</button>
<button
on:click={() => editor.chain().focus().toggleHeading({ level: 4 }).run()}
class={editor.isActive("heading", { level: 4 }) ? "is-active" : ""}
>
h4
H4
</button>
<button
on:click={() => editor.chain().focus().toggleHeading({ level: 5 }).run()}
class={editor.isActive("heading", { level: 5 }) ? "is-active" : ""}
>
h5
H5
</button>
<button
on:click={() => editor.chain().focus().toggleHeading({ level: 6 }).run()}
class={editor.isActive("heading", { level: 6 }) ? "is-active" : ""}
>
h6
H6
</button>
<button
on:click={() => editor.chain().focus().toggleBulletList().run()}
class={editor.isActive("bulletList") ? "is-active" : ""}
>
bullet list
Bullet list
</button>
<button
on:click={() => editor.chain().focus().toggleOrderedList().run()}
class={editor.isActive("orderedList") ? "is-active" : ""}
>
ordered list
Ordered list
</button>
<button
on:click={() => editor.chain().focus().toggleCodeBlock().run()}
class={editor.isActive("codeBlock") ? "is-active" : ""}
>
code block
Code block
</button>
<button
on:click={() => editor.chain().focus().toggleBlockquote().run()}
class={editor.isActive("blockquote") ? "is-active" : ""}
>
blockquote
Blockquote
</button>
<button on:click={() => editor.chain().focus().setHorizontalRule().run()}>
horizontal rule
Horizontal rule
</button>
<button on:click={() => editor.chain().focus().setHardBreak().run()}> hard break </button>
<button on:click={() => editor.chain().focus().setHardBreak().run()}>Hard break</button>
<button
on:click={() => editor.chain().focus().undo().run()}
disabled={!editor.can().chain().focus().undo().run()}
>
undo
Undo
</button>
<button
on:click={() => editor.chain().focus().redo().run()}
disabled={!editor.can().chain().focus().redo().run()}
>
redo
Redo
</button>
<button
on:click={() => editor.chain().focus().setColor('#958DF1').run()}
class={editor.isActive('textStyle', { color: '#958DF1' }) ? 'is-active' : ''}
>
Purple
</button>
</div>
</div>

View File

@ -1,14 +1,22 @@
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
:first-child {
margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
@ -16,41 +24,68 @@
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: rgba(#616161, 0.1);
color: #616161;
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
color: inherit;
padding: 0;
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
img {
max-width: 100%;
height: auto;
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
hr {
border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1);
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
}

View File

@ -21,15 +21,15 @@ context('/src/Examples/Default/Vue/', () => {
})
const buttonMarks = [
{ label: 'bold', tag: 'strong' },
{ label: 'italic', tag: 'em' },
{ label: 'strike', tag: 's' },
{ label: 'Bold', tag: 'strong' },
{ label: 'Italic', tag: 'em' },
{ label: 'Strike', tag: 's' },
]
buttonMarks.forEach(m => {
it(`should apply ${m.label} when the button is pressed`, () => {
cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('paragraph').click()
cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}')
cy.get('button').contains(m.label).click()
cy.get(`.tiptap ${m.tag}`).should('exist').should('have.text', 'Hello world')
@ -38,40 +38,40 @@ context('/src/Examples/Default/Vue/', () => {
it('should clear marks when the button is pressed', () => {
cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('paragraph').click()
cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}')
cy.get('button').contains('bold').click()
cy.get('button').contains('Bold').click()
cy.get('.tiptap strong').should('exist').should('have.text', 'Hello world')
cy.get('button').contains('clear marks').click()
cy.get('button').contains('Clear marks').click()
cy.get('.tiptap strong').should('not.exist')
})
it('should clear nodes when the button is pressed', () => {
cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('bullet list').click()
cy.get('button').contains('Bullet list').click()
cy.get('.tiptap ul').should('exist').should('have.text', 'Hello world')
cy.get('.tiptap').type('{enter}A second item{enter}A third item{selectall}')
cy.get('button').contains('clear nodes').click()
cy.get('button').contains('Clear nodes').click()
cy.get('.tiptap ul').should('not.exist')
cy.get('.tiptap p').should('have.length', 3)
})
const buttonNodes = [
{ label: 'h1', tag: 'h1' },
{ label: 'h2', tag: 'h2' },
{ label: 'h3', tag: 'h3' },
{ label: 'h4', tag: 'h4' },
{ label: 'h5', tag: 'h5' },
{ label: 'h6', tag: 'h6' },
{ label: 'bullet list', tag: 'ul' },
{ label: 'ordered list', tag: 'ol' },
{ label: 'code block', tag: 'pre code' },
{ label: 'blockquote', tag: 'blockquote' },
{ label: 'H1', tag: 'h1' },
{ label: 'H2', tag: 'h2' },
{ label: 'H3', tag: 'h3' },
{ label: 'H4', tag: 'h4' },
{ label: 'H5', tag: 'h5' },
{ label: 'H6', tag: 'h6' },
{ label: 'Bullet list', tag: 'ul' },
{ label: 'Ordered list', tag: 'ol' },
{ label: 'Code block', tag: 'pre code' },
{ label: 'Blockquote', tag: 'blockquote' },
]
buttonNodes.forEach(n => {
it(`should set ${n.label} when the button is pressed`, () => {
cy.get('button').contains('paragraph').click()
cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}Hello world{selectall}')
cy.get('button').contains(n.label).click()
@ -83,35 +83,35 @@ context('/src/Examples/Default/Vue/', () => {
it('should add a hr when on the same line as a node', () => {
cy.get('.tiptap').type('{rightArrow}')
cy.get('button').contains('horizontal rule').click()
cy.get('button').contains('Horizontal rule').click()
cy.get('.tiptap hr').should('exist')
cy.get('.tiptap h1').should('exist')
})
it('should add a hr when on a new line', () => {
cy.get('.tiptap').type('{rightArrow}{enter}')
cy.get('button').contains('horizontal rule').click()
cy.get('button').contains('Horizontal rule').click()
cy.get('.tiptap hr').should('exist')
cy.get('.tiptap h1').should('exist')
})
it('should add a br', () => {
cy.get('.tiptap').type('{rightArrow}')
cy.get('button').contains('hard break').click()
cy.get('button').contains('Hard break').click()
cy.get('.tiptap h1 br').should('exist')
})
it('should undo', () => {
cy.get('.tiptap').type('{selectall}{backspace}')
cy.get('button').contains('undo').click()
cy.get('button').contains('Undo').click()
cy.get('.tiptap').should('contain', 'Hello world')
})
it('should redo', () => {
cy.get('.tiptap').type('{selectall}{backspace}')
cy.get('button').contains('undo').click()
cy.get('button').contains('Undo').click()
cy.get('.tiptap').should('contain', 'Hello world')
cy.get('button').contains('redo').click()
cy.get('button').contains('Redo').click()
cy.get('.tiptap').should('not.contain', 'Hello world')
})
})

View File

@ -1,73 +1,83 @@
<template>
<div v-if="editor">
<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') }">
bold
Bold
</button>
<button @click="editor.chain().focus().toggleItalic().run()" :disabled="!editor.can().chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }">
italic
Italic
</button>
<button @click="editor.chain().focus().toggleStrike().run()" :disabled="!editor.can().chain().focus().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }">
strike
Strike
</button>
<button @click="editor.chain().focus().toggleCode().run()" :disabled="!editor.can().chain().focus().toggleCode().run()" :class="{ 'is-active': editor.isActive('code') }">
code
Code
</button>
<button @click="editor.chain().focus().unsetAllMarks().run()">
clear marks
Clear marks
</button>
<button @click="editor.chain().focus().clearNodes().run()">
clear nodes
Clear nodes
</button>
<button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }">
paragraph
Paragraph
</button>
<button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }">
h1
H1
</button>
<button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }">
h2
H2
</button>
<button @click="editor.chain().focus().toggleHeading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }">
h3
H3
</button>
<button @click="editor.chain().focus().toggleHeading({ level: 4 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 4 }) }">
h4
H4
</button>
<button @click="editor.chain().focus().toggleHeading({ level: 5 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 5 }) }">
h5
H5
</button>
<button @click="editor.chain().focus().toggleHeading({ level: 6 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 6 }) }">
h6
H6
</button>
<button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }">
bullet list
Bullet list
</button>
<button @click="editor.chain().focus().toggleOrderedList().run()" :class="{ 'is-active': editor.isActive('orderedList') }">
ordered list
Ordered list
</button>
<button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }">
code block
Code block
</button>
<button @click="editor.chain().focus().toggleBlockquote().run()" :class="{ 'is-active': editor.isActive('blockquote') }">
blockquote
Blockquote
</button>
<button @click="editor.chain().focus().setHorizontalRule().run()">
horizontal rule
Horizontal rule
</button>
<button @click="editor.chain().focus().setHardBreak().run()">
hard break
Hard break
</button>
<button @click="editor.chain().focus().undo().run()" :disabled="!editor.can().chain().focus().undo().run()">
undo
Undo
</button>
<button @click="editor.chain().focus().redo().run()" :disabled="!editor.can().chain().focus().redo().run()">
redo
Redo
</button>
<button @click="editor.chain().focus().setColor('#958DF1').run()" :class="{ 'is-active': editor.isActive('textStyle', { color: '#958DF1' }) }">
Purple
</button>
</div>
</div>
<editor-content :editor="editor" />
</div>
</template>
<script>
import { Color } from '@tiptap/extension-color'
import ListItem from '@tiptap/extension-list-item'
import TextStyle from '@tiptap/extension-text-style'
import StarterKit from '@tiptap/starter-kit'
import { Editor, EditorContent } from '@tiptap/vue-3'
@ -85,6 +95,8 @@ export default {
mounted() {
this.editor = new Editor({
extensions: [
Color.configure({ types: [TextStyle.name, ListItem.name] }),
TextStyle.configure({ types: [ListItem.name] }),
StarterKit,
],
content: `
@ -92,7 +104,7 @@ export default {
Hi there,
</h2>
<p>
this is a <em>basic</em> example of <strong>tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists:
this is a <em>basic</em> example of <strong>Tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists:
</p>
<ul>
<li>
@ -129,15 +141,23 @@ export default {
<style lang="scss">
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
:first-child {
margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
@ -145,41 +165,68 @@ export default {
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: rgba(#616161, 0.1);
color: #616161;
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
color: inherit;
padding: 0;
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
img {
max-width: 100%;
height: auto;
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
hr {
border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1);
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
}

View File

@ -1,5 +1,7 @@
<template>
<node-view-wrapper class="draw">
<div class="control-group">
<div class="button-group">
<input type="color" v-model="color">
<input
type="number"
@ -8,8 +10,9 @@
v-model="size"
>
<button @click="clear">
clear
Clear
</button>
</div>
<svg viewBox="0 0 500 250" ref="canvas">
<template v-for="item in node.attrs.lines">
<path
@ -22,6 +25,7 @@
/>
</template>
</svg>
</div>
</node-view-wrapper>
</template>

View File

@ -12,44 +12,46 @@ 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' : ''}>
h1
H1
</button>
<button onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''}>
h2
H2
</button>
<button onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''}>
h3
H3
</button>
<button onClick={() => editor.chain().focus().setParagraph().run()} className={editor.isActive('paragraph') ? 'is-active' : ''}>
paragraph
Paragraph
</button>
<button onClick={() => editor.chain().focus().toggleBold().run()} className={editor.isActive('bold') ? 'is-active' : ''}>
bold
Bold
</button>
<button onClick={() => editor.chain().focus().toggleItalic().run()} className={editor.isActive('italic') ? 'is-active' : ''}>
italic
Italic
</button>
<button onClick={() => editor.chain().focus().toggleStrike().run()} className={editor.isActive('strike') ? 'is-active' : ''}>
strike
Strike
</button>
<button onClick={() => editor.chain().focus().toggleHighlight().run()} className={editor.isActive('highlight') ? 'is-active' : ''}>
highlight
Highlight
</button>
<button onClick={() => editor.chain().focus().setTextAlign('left').run()} className={editor.isActive({ textAlign: 'left' }) ? 'is-active' : ''}>
left
Left
</button>
<button onClick={() => editor.chain().focus().setTextAlign('center').run()} className={editor.isActive({ textAlign: 'center' }) ? 'is-active' : ''}>
center
Center
</button>
<button onClick={() => editor.chain().focus().setTextAlign('right').run()} className={editor.isActive({ textAlign: 'right' }) ? 'is-active' : ''}>
right
Right
</button>
<button onClick={() => editor.chain().focus().setTextAlign('justify').run()} className={editor.isActive({ textAlign: 'justify' }) ? 'is-active' : ''}>
justify
Justify
</button>
</>
</div>
</div>
)
}
@ -91,9 +93,9 @@ export default () => {
})
return (
<div>
<>
<MenuBar editor={editor} />
<EditorContent editor={editor} />
</div>
</>
)
}

View File

@ -8,7 +8,7 @@ context('/src/Examples/Formatting/React/', () => {
})
const marks = [
{ label: 'highlight', mark: 'mark' },
{ label: 'Highlight', mark: 'mark' },
]
marks.forEach(m => {
@ -20,10 +20,10 @@ context('/src/Examples/Formatting/React/', () => {
})
const alignments = [
{ label: 'left', alignment: 'left' },
{ label: 'center', alignment: 'center' },
{ label: 'right', alignment: 'right' },
{ label: 'justify', alignment: 'justify' },
{ label: 'Left', alignment: 'left' },
{ label: 'Center', alignment: 'center' },
{ label: 'Right', alignment: 'right' },
{ label: 'Justify', alignment: 'justify' },
]
alignments.forEach(a => {

View File

@ -1,16 +1,22 @@
/* Basic editor styles */
.tiptap {
margin-top: 1rem;
> * + * {
margin-top: 0.75em;
:first-child {
margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
@ -18,45 +24,75 @@
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: rgba(#616161, 0.1);
color: #616161;
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
color: inherit;
padding: 0;
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
mark {
background-color: #FAF594;
}
img {
max-width: 100%;
height: auto;
border-radius: 0.4rem;
box-decoration-break: clone;
padding: 0.1rem 0.3rem;
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
hr {
border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1);
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
}

View File

@ -8,7 +8,7 @@ context('/src/Examples/Formatting/Vue/', () => {
})
const marks = [
{ label: 'highlight', mark: 'mark' },
{ label: 'Highlight', mark: 'mark' },
]
marks.forEach(m => {
@ -20,10 +20,10 @@ context('/src/Examples/Formatting/Vue/', () => {
})
const alignments = [
{ label: 'left', alignment: 'left' },
{ label: 'center', alignment: 'center' },
{ label: 'right', alignment: 'right' },
{ label: 'justify', alignment: 'justify' },
{ label: 'Left', alignment: 'left' },
{ label: 'Center', alignment: 'center' },
{ label: 'Right', alignment: 'right' },
{ label: 'Justify', alignment: 'justify' },
]
alignments.forEach(a => {

View File

@ -1,43 +1,47 @@
<template>
<div v-if="editor">
<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 }) }">
h1
H1
</button>
<button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }">
h2
H2
</button>
<button @click="editor.chain().focus().toggleHeading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }">
h3
H3
</button>
<button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }">
paragraph
Paragraph
</button>
<button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }">
bold
Bold
</button>
<button @click="editor.chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }">
italic
Italic
</button>
<button @click="editor.chain().focus().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }">
strike
Strike
</button>
<button @click="editor.chain().focus().toggleHighlight().run()" :class="{ 'is-active': editor.isActive('highlight') }">
highlight
Highlight
</button>
<button @click="editor.chain().focus().setTextAlign('left').run()" :class="{ 'is-active': editor.isActive({ textAlign: 'left' }) }">
left
Left
</button>
<button @click="editor.chain().focus().setTextAlign('center').run()" :class="{ 'is-active': editor.isActive({ textAlign: 'center' }) }">
center
Center
</button>
<button @click="editor.chain().focus().setTextAlign('right').run()" :class="{ 'is-active': editor.isActive({ textAlign: 'right' }) }">
right
Right
</button>
<button @click="editor.chain().focus().setTextAlign('justify').run()" :class="{ 'is-active': editor.isActive({ textAlign: 'justify' }) }">
justify
Justify
</button>
</div>
</div>
<editor-content :editor="editor" />
</div>
</template>
<script>
@ -104,17 +108,23 @@ export default {
<style lang="scss">
/* Basic editor styles */
.tiptap {
margin-top: 1rem;
> * + * {
margin-top: 0.75em;
:first-child {
margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
@ -122,45 +132,75 @@ export default {
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: rgba(#616161, 0.1);
color: #616161;
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
color: inherit;
padding: 0;
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
mark {
background-color: #FAF594;
}
img {
max-width: 100%;
height: auto;
border-radius: 0.4rem;
box-decoration-break: clone;
padding: 0.1rem 0.3rem;
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
hr {
border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1);
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
}

View File

@ -37,9 +37,13 @@ export default () => {
}
return (
<div>
<button onClick={addImage}>add image from URL</button>
<EditorContent editor={editor} />
<>
<div className="control-group">
<div className="button-group">
<button onClick={addImage}>Add image from URL</button>
</div>
</div>
<EditorContent editor={editor} />
</>
)
}

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