Merge branch 'main' of github.com:ueberdosis/tiptap into add-empty-editor-class-to-root-div
This commit is contained in:
@@ -20,6 +20,7 @@ module.exports = {
|
|||||||
'html',
|
'html',
|
||||||
'cypress',
|
'cypress',
|
||||||
'@typescript-eslint',
|
'@typescript-eslint',
|
||||||
|
'simple-import-sort',
|
||||||
],
|
],
|
||||||
env: {
|
env: {
|
||||||
'cypress/globals': true,
|
'cypress/globals': true,
|
||||||
@@ -90,6 +91,8 @@ module.exports = {
|
|||||||
'@typescript-eslint/ban-types': 'off',
|
'@typescript-eslint/ban-types': 'off',
|
||||||
'@typescript-eslint/comma-dangle': ['error', 'always-multiline'],
|
'@typescript-eslint/comma-dangle': ['error', 'always-multiline'],
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'simple-import-sort/imports': 'error',
|
||||||
|
'simple-import-sort/exports': 'error',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
9
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
9
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -11,6 +11,13 @@ body:
|
|||||||
placeholder: "I’m always frustrated when …"
|
placeholder: "I’m always frustrated when …"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: environment
|
||||||
|
attributes:
|
||||||
|
label: Which browser was this experienced in? Are any special extensions installed?
|
||||||
|
description: Please give us more information about your browser environment so we can reproduce the bug faster.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: reproduction
|
id: reproduction
|
||||||
attributes:
|
attributes:
|
||||||
@@ -46,7 +53,7 @@ body:
|
|||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Did you update your dependencies?
|
label: Did you update your dependencies?
|
||||||
description: "Use `yarn upgrade-interactive` to update your dependencies."
|
description: "Use `npm update` to update your dependencies."
|
||||||
options:
|
options:
|
||||||
- label: Yes, I’ve updated my dependencies to use the latest version of all packages.
|
- label: Yes, I’ve updated my dependencies to use the latest version of all packages.
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/feedback.yml
vendored
2
.github/ISSUE_TEMPLATE/feedback.yml
vendored
@@ -3,7 +3,7 @@ description: Share what we need to explain better
|
|||||||
labels:
|
labels:
|
||||||
- documentation
|
- documentation
|
||||||
assignees:
|
assignees:
|
||||||
- hanspagel
|
- bdbch
|
||||||
body:
|
body:
|
||||||
- type: input
|
- type: input
|
||||||
id: url
|
id: url
|
||||||
|
|||||||
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -11,5 +11,5 @@ updates:
|
|||||||
interval: 'weekly'
|
interval: 'weekly'
|
||||||
day: 'monday'
|
day: 'monday'
|
||||||
reviewers:
|
reviewers:
|
||||||
- 'hanspagel'
|
- 'bdbch'
|
||||||
|
|
||||||
|
|||||||
38
.github/workflows/build.yml
vendored
38
.github/workflows/build.yml
vendored
@@ -24,30 +24,30 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/checkout@v2.4.0
|
- uses: actions/checkout@v3.0.2
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v2.5.1
|
uses: actions/setup-node@v3.4.1
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
- name: Load cached dependencies
|
- name: Load cached dependencies
|
||||||
uses: actions/cache@v2.1.7
|
uses: actions/cache@v3.0.8
|
||||||
id: cache
|
id: cache
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
**/node_modules
|
**/node_modules
|
||||||
/home/runner/.cache/Cypress
|
/home/runner/.cache/Cypress
|
||||||
key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
|
key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
id: install-dependencies
|
id: install-dependencies
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
run: yarn install
|
run: npm install
|
||||||
|
|
||||||
# - name: Fix code style linting errors
|
# - name: Fix code style linting errors
|
||||||
# id: lint-fix
|
# id: lint-fix
|
||||||
# run: yarn lint:fix
|
# run: npm run lint:fix
|
||||||
# continue-on-error: true
|
# continue-on-error: true
|
||||||
#
|
#
|
||||||
# - name: Commit fixed linting errors
|
# - name: Commit fixed linting errors
|
||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Lint code
|
- name: Lint code
|
||||||
id: lint
|
id: lint
|
||||||
run: yarn lint
|
run: npm run lint
|
||||||
|
|
||||||
- name: Send Slack notifications
|
- name: Send Slack notifications
|
||||||
uses: act10ns/slack@v1
|
uses: act10ns/slack@v1
|
||||||
@@ -80,10 +80,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/checkout@v2.4.0
|
- uses: actions/checkout@v3.0.2
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v2.5.1
|
uses: actions/setup-node@v3.4.1
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
@@ -91,15 +91,15 @@ jobs:
|
|||||||
id: cypress
|
id: cypress
|
||||||
uses: cypress-io/github-action@v2
|
uses: cypress-io/github-action@v2
|
||||||
with:
|
with:
|
||||||
cache-key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
|
cache-key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
start: yarn start
|
start: npm run start
|
||||||
wait-on: 'http://localhost:3000'
|
wait-on: 'http://localhost:3000'
|
||||||
project: ./tests
|
project: ./tests
|
||||||
browser: chrome
|
browser: chrome
|
||||||
quiet: true
|
quiet: true
|
||||||
|
|
||||||
- name: Export screenshots (on failure only)
|
- name: Export screenshots (on failure only)
|
||||||
uses: actions/upload-artifact@v2.3.1
|
uses: actions/upload-artifact@v3.1.0
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: cypress-screenshots
|
name: cypress-screenshots
|
||||||
@@ -107,7 +107,7 @@ jobs:
|
|||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
- name: Export screen recordings (on failure only)
|
- name: Export screen recordings (on failure only)
|
||||||
uses: actions/upload-artifact@v2.3.1
|
uses: actions/upload-artifact@v3.1.0
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: cypress-videos
|
name: cypress-videos
|
||||||
@@ -136,30 +136,30 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/checkout@v2.4.0
|
- uses: actions/checkout@v3.0.2
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v2.5.1
|
uses: actions/setup-node@v3.4.1
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
- name: Load cached dependencies
|
- name: Load cached dependencies
|
||||||
uses: actions/cache@v2.1.7
|
uses: actions/cache@v3.0.8
|
||||||
id: cache
|
id: cache
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
**/node_modules
|
**/node_modules
|
||||||
/home/runner/.cache/Cypress
|
/home/runner/.cache/Cypress
|
||||||
key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
|
key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
id: install-dependencies
|
id: install-dependencies
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
run: yarn install
|
run: npm install
|
||||||
|
|
||||||
- name: Try to build the packages
|
- name: Try to build the packages
|
||||||
id: build-packages
|
id: build-packages
|
||||||
run: yarn build:ci
|
run: npm run build:ci
|
||||||
|
|
||||||
- name: Send Slack notifications
|
- name: Send Slack notifications
|
||||||
uses: act10ns/slack@v1
|
uses: act10ns/slack@v1
|
||||||
|
|||||||
19
.github/workflows/issues.yml
vendored
Normal file
19
.github/workflows/issues.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: Add issues to Tiptap project
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
add-to-project:
|
||||||
|
name: Add issue to project
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/add-to-project@main
|
||||||
|
with:
|
||||||
|
project-url: ${{ secrets.ADD_TO_PROJECT_URL }}
|
||||||
|
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
|
||||||
|
- uses: actions-ecosystem/action-add-labels@v1
|
||||||
|
with:
|
||||||
|
labels: needs-triage
|
||||||
16
.github/workflows/prs.yml
vendored
Normal file
16
.github/workflows/prs.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
name: Add pull requests to Tiptap project
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
add-to-project:
|
||||||
|
name: Add pull request to project
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/add-to-project@main
|
||||||
|
with:
|
||||||
|
project-url: ${{ secrets.ADD_TO_PROJECT_URL }}
|
||||||
|
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
|
||||||
18
.github/workflows/stale.yml
vendored
Normal file
18
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
name: 'Close stale issues and PRs'
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@main
|
||||||
|
with:
|
||||||
|
stale-issue-message: 'This issue is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 7 days'
|
||||||
|
days-before-stale: 90
|
||||||
|
days-before-close: 7
|
||||||
|
days-before-pr-stale: 180
|
||||||
|
stale-issue-label: stale
|
||||||
|
stale-pr-label: stale
|
||||||
4
.husky/pre-commit
Executable file
4
.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npm run lint:staged
|
||||||
3
.lintstagedrc.js
Normal file
3
.lintstagedrc.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
"./**/*.{ts,tsx,js,jsx,vue}": ["eslint --fix --quiet --no-error-on-unmatched-pattern"],
|
||||||
|
};
|
||||||
11
.vscode/launch.json
vendored
Normal file
11
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Launch Tiptap demos in Google Chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "chrome",
|
||||||
|
"url": "http://localhost:3000",
|
||||||
|
"webRoot": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
59
.vscode/settings.json
vendored
59
.vscode/settings.json
vendored
@@ -1,3 +1,60 @@
|
|||||||
{
|
{
|
||||||
"typescript.tsdk": "node_modules/typescript/lib"
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
|
"conventionalCommits.scopes": [
|
||||||
|
"ci",
|
||||||
|
"docs",
|
||||||
|
"maintainment",
|
||||||
|
"tests",
|
||||||
|
"core",
|
||||||
|
"extension/blockquote",
|
||||||
|
"extension/bold",
|
||||||
|
"extension/bubble-menu",
|
||||||
|
"extension/bullet-list",
|
||||||
|
"extension/character-count",
|
||||||
|
"extension/code",
|
||||||
|
"extension/code-block",
|
||||||
|
"extension/code-block-lowlight",
|
||||||
|
"extension/collaboration",
|
||||||
|
"extension/collaboration-cursor",
|
||||||
|
"extension/color",
|
||||||
|
"extension/document",
|
||||||
|
"extension/dropcursor",
|
||||||
|
"extension/floating-menu",
|
||||||
|
"extension/focus",
|
||||||
|
"extension/font-family",
|
||||||
|
"extension/gapcursor",
|
||||||
|
"extension/hard-break",
|
||||||
|
"extension/heading",
|
||||||
|
"extension/highlight",
|
||||||
|
"extension/history",
|
||||||
|
"extension/horizontal-rule",
|
||||||
|
"extension/image",
|
||||||
|
"extension/italic",
|
||||||
|
"extension/link",
|
||||||
|
"extension/list-item",
|
||||||
|
"extension/mention",
|
||||||
|
"extension/ordered-list",
|
||||||
|
"extension/paragraph",
|
||||||
|
"extension/placeholder",
|
||||||
|
"extension/strike",
|
||||||
|
"extension/subscript",
|
||||||
|
"extension/table",
|
||||||
|
"extension/table-cell",
|
||||||
|
"extension/table-header",
|
||||||
|
"extension/table-row",
|
||||||
|
"extension/task-item",
|
||||||
|
"extension/task-list",
|
||||||
|
"extension/text",
|
||||||
|
"extension/text-align",
|
||||||
|
"extension/text-style",
|
||||||
|
"extension/typography",
|
||||||
|
"extension/underline",
|
||||||
|
"extension/youtube",
|
||||||
|
"html",
|
||||||
|
"react",
|
||||||
|
"starter-kit",
|
||||||
|
"suggestion",
|
||||||
|
"vue-2",
|
||||||
|
"vue-3"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,15 @@ prosemirror-keymap
|
|||||||
prosemirror-model
|
prosemirror-model
|
||||||
prosemirror-schema-list
|
prosemirror-schema-list
|
||||||
prosemirror-state
|
prosemirror-state
|
||||||
prosemirror-tables
|
@_ueberdosis/prosemirror-tables
|
||||||
prosemirror-transform
|
prosemirror-transform
|
||||||
prosemirror-view
|
prosemirror-view
|
||||||
|
react
|
||||||
|
react-dom
|
||||||
|
react-dom/client
|
||||||
shiki
|
shiki
|
||||||
simplify-js
|
simplify-js
|
||||||
tippy.js
|
tippy.js
|
||||||
uuid
|
uuid
|
||||||
y-prosemirror
|
|
||||||
y-webrtc
|
y-webrtc
|
||||||
yjs
|
yjs
|
||||||
|
|||||||
2118
demos/package-lock.json
generated
2118
demos/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,33 +4,38 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite --host",
|
"start": "vite --host",
|
||||||
"build": "yarn ts && vite build",
|
"build": "npm run ts && vite build",
|
||||||
"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"
|
"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": {
|
"dependencies": {
|
||||||
"@hocuspocus/provider": "^1.0.0-alpha.29",
|
"@hocuspocus/provider": "^1.0.0-alpha.29",
|
||||||
"d3": "^7.3.0",
|
"d3": "^7.3.0",
|
||||||
"fast-glob": "^3.2.11",
|
"fast-glob": "^3.2.11",
|
||||||
|
"highlight.js": "^11.6.0",
|
||||||
|
"lowlight": "^2.7.0",
|
||||||
"remixicon": "^2.5.0",
|
"remixicon": "^2.5.0",
|
||||||
"shiki": "^0.10.0",
|
"shiki": "^0.10.0",
|
||||||
"simplify-js": "^1.2.4",
|
"simplify-js": "^1.2.4",
|
||||||
"y-webrtc": "^10.2.2",
|
"y-prosemirror": "1.0.20",
|
||||||
"yjs": "^13.5.26"
|
"y-webrtc": "^10.2.3",
|
||||||
|
"yjs": "^13.5.39"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.49",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"@vitejs/plugin-react-refresh": "^1.3.6",
|
"@vitejs/plugin-react": "^1.3.1",
|
||||||
"@vitejs/plugin-vue": "^1.10.2",
|
"@vitejs/plugin-vue": "^1.10.2",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.2",
|
||||||
"iframe-resizer": "^4.3.2",
|
"iframe-resizer": "^4.3.2",
|
||||||
"postcss": "^8.4.6",
|
"postcss": "^8.4.6",
|
||||||
"react": "^17.0.2",
|
"react": "^18.0.0",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^18.0.0",
|
||||||
"sass": "^1.49.7",
|
"sass": "^1.49.7",
|
||||||
|
"svelte": "^3.49.0",
|
||||||
"tailwindcss": "^2.2.19",
|
"tailwindcss": "^2.2.19",
|
||||||
"typescript": "^4.5.5",
|
"typescript": "^4.5.5",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"vite": "^2.7.13",
|
"vite": "^2.9.13",
|
||||||
"vite-plugin-checker": "^0.3.4",
|
"vite-plugin-checker": "^0.3.4",
|
||||||
"vue": "^3.0.5",
|
"vue": "^3.0.5",
|
||||||
"vue-router": "^4.0.11"
|
"vue-router": "^4.0.11"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
v-for="(language, index) in sortedTabs"
|
v-for="(language, index) in sortedTabs"
|
||||||
:key="index"
|
:key="index"
|
||||||
@click="setTab(language.name)"
|
@click="setTab(language.name)"
|
||||||
class="px-4 py-2 rounded-t-lg text-xs uppercase font-bold tracking-wide"
|
class="px-4 py-2 text-xs font-bold tracking-wide uppercase rounded-t-lg"
|
||||||
:class="[currentTab === language.name
|
:class="[currentTab === language.name
|
||||||
? 'bg-black text-white'
|
? 'bg-black text-white'
|
||||||
: 'text-black'
|
: 'text-black'
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="overflow-hidden rounded-b-xl">
|
<div class="overflow-hidden rounded-b-xl">
|
||||||
<div
|
<div
|
||||||
class="bg-white border-3 border-black last:rounded-b-xl"
|
class="bg-white border-black border-3 last:rounded-b-xl"
|
||||||
:class="[
|
:class="[
|
||||||
showTabs && firstTabSelected
|
showTabs && firstTabSelected
|
||||||
? 'rounded-tr-xl'
|
? 'rounded-tr-xl'
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-black text-white" v-if="!hideSource && currentFile">
|
<div class="text-white bg-black" v-if="!hideSource && currentFile">
|
||||||
<div class="flex overflow-x-auto">
|
<div class="flex overflow-x-auto">
|
||||||
<div class="flex flex-auto px-4 border-b-2 border-gray-800">
|
<div class="flex flex-auto px-4 border-b-2 border-gray-800">
|
||||||
<button
|
<button
|
||||||
@@ -66,17 +66,17 @@
|
|||||||
|
|
||||||
<div class="overflow-dark overflow-auto max-h-[500px] relative text-white">
|
<div class="overflow-dark overflow-auto max-h-[500px] relative text-white">
|
||||||
<shiki
|
<shiki
|
||||||
class="overflow-visible p-4"
|
class="p-4 overflow-visible"
|
||||||
:language="debugJSON && showDebug ? 'js' : getFileExtension(currentFile.name)"
|
:language="debugJSON && showDebug ? 'js' : getFileExtension(currentFile.name)"
|
||||||
:code="debugJSON && showDebug ? debugJSON : currentFile.content"
|
:code="debugJSON && showDebug ? debugJSON : currentFile.content"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-between px-4 py-2 text-md text-gray-400 border-t border-gray-800">
|
<div class="flex justify-between px-4 py-2 text-gray-400 border-t border-gray-800 text-md">
|
||||||
<a class="flex-shrink min-w-0 overflow-ellipsis overflow-hidden whitespace-nowrap" :href="currentIframeUrl">
|
<a class="flex-shrink min-w-0 overflow-hidden overflow-ellipsis whitespace-nowrap" :href="currentIframeUrl">
|
||||||
{{ name }}/{{ currentTab }}
|
{{ name }}/{{ currentTab }}
|
||||||
</a>
|
</a>
|
||||||
<a class="whitespace-nowrap pl-4" :href="githubUrl" target="_blank">
|
<a class="pl-4 whitespace-nowrap" :href="githubUrl" target="_blank">
|
||||||
Edit on GitHub →
|
Edit on GitHub →
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -87,6 +87,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getDebugJSON } from '@tiptap/core'
|
import { getDebugJSON } from '@tiptap/core'
|
||||||
|
|
||||||
import DemoFrame from './DemoFrame.vue'
|
import DemoFrame from './DemoFrame.vue'
|
||||||
import Shiki from './Shiki.vue'
|
import Shiki from './Shiki.vue'
|
||||||
|
|
||||||
@@ -114,7 +115,7 @@ export default {
|
|||||||
sources: {},
|
sources: {},
|
||||||
currentTab: null,
|
currentTab: null,
|
||||||
currentFile: null,
|
currentFile: null,
|
||||||
tabOrder: ['React', 'Vue', 'JS'],
|
tabOrder: ['React', 'Vue', 'Svelte', 'JS'],
|
||||||
debugJSON: null,
|
debugJSON: null,
|
||||||
showDebug: false,
|
showDebug: false,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Worker from './shiki.worker?worker'
|
|
||||||
// this import is a bugfix
|
// this import is a bugfix
|
||||||
// otherwise the `onig.wasm` file is missing in the dist folder
|
// otherwise the `onig.wasm` file is missing in the dist folder
|
||||||
import 'shiki/dist/onig.wasm?url'
|
import 'shiki/dist/onig.wasm?url'
|
||||||
|
|
||||||
|
import Worker from './shiki.worker?worker'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
code: {
|
code: {
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
|
import 'iframe-resizer/js/iframeResizer.contentWindow'
|
||||||
|
import './style.css'
|
||||||
|
|
||||||
|
import { demos } from '@demos'
|
||||||
|
import iframeResize from 'iframe-resizer/js/iframeResizer'
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import App from './index.vue'
|
|
||||||
import Demo from './Demo.vue'
|
import Demo from './Demo.vue'
|
||||||
import { demos } from '@demos'
|
import App from './index.vue'
|
||||||
import 'iframe-resizer/js/iframeResizer.contentWindow'
|
|
||||||
import iframeResize from 'iframe-resizer/js/iframeResizer'
|
|
||||||
import './style.css'
|
|
||||||
|
|
||||||
const routes = demos
|
const routes = demos
|
||||||
.map(({ name, tabs }) => {
|
.map(({ name, tabs }) => {
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import * as shiki from 'shiki'
|
import * as shiki from 'shiki'
|
||||||
import onigasm from 'shiki/dist/onig.wasm?url'
|
import onigasm from 'shiki/dist/onig.wasm?url'
|
||||||
import theme from 'shiki/themes/material-darker.json'
|
import langCSS from 'shiki/languages/css.tmLanguage.json'
|
||||||
import langHTML from 'shiki/languages/html.tmLanguage.json'
|
import langHTML from 'shiki/languages/html.tmLanguage.json'
|
||||||
import langJS from 'shiki/languages/javascript.tmLanguage.json'
|
import langJS from 'shiki/languages/javascript.tmLanguage.json'
|
||||||
import langJSX from 'shiki/languages/jsx.tmLanguage.json'
|
import langJSX from 'shiki/languages/jsx.tmLanguage.json'
|
||||||
import langTS from 'shiki/languages/typescript.tmLanguage.json'
|
|
||||||
import langTSX from 'shiki/languages/tsx.tmLanguage.json'
|
|
||||||
import langVueHTML from 'shiki/languages/vue-html.tmLanguage.json'
|
|
||||||
import langVue from 'shiki/languages/vue.tmLanguage.json'
|
|
||||||
import langCSS from 'shiki/languages/css.tmLanguage.json'
|
|
||||||
import langSCSS from 'shiki/languages/scss.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
|
let highlighter = null
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import 'iframe-resizer/js/iframeResizer.contentWindow'
|
import 'iframe-resizer/js/iframeResizer.contentWindow'
|
||||||
import { debug } from './helper'
|
|
||||||
import './style.scss'
|
import './style.scss'
|
||||||
|
|
||||||
|
import { debug } from './helper'
|
||||||
|
|
||||||
export default function init(name: string, source: any) {
|
export default function init(name: string, source: any) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.source = source
|
window.source = source
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import React from 'react'
|
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import 'iframe-resizer/js/iframeResizer.contentWindow'
|
import 'iframe-resizer/js/iframeResizer.contentWindow'
|
||||||
import { debug, splitName } from './helper'
|
|
||||||
import './style.scss'
|
import './style.scss'
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import { createRoot } from 'react-dom/client'
|
||||||
|
|
||||||
|
import { debug, splitName } from './helper'
|
||||||
|
|
||||||
export default function init(name: string, source: any) {
|
export default function init(name: string, source: any) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.source = source
|
window.source = source
|
||||||
@@ -13,7 +15,11 @@ export default function init(name: string, source: any) {
|
|||||||
|
|
||||||
import(`../src/${demoCategory}/${demoName}/React/index.jsx`)
|
import(`../src/${demoCategory}/${demoName}/React/index.jsx`)
|
||||||
.then(module => {
|
.then(module => {
|
||||||
ReactDOM.render(React.createElement(module.default), document.getElementById('app'))
|
const root = document.getElementById('app')
|
||||||
|
|
||||||
|
if (root) {
|
||||||
|
createRoot(root).render(React.createElement(module.default))
|
||||||
|
}
|
||||||
debug()
|
debug()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
21
demos/setup/svelte.ts
Normal file
21
demos/setup/svelte.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import 'iframe-resizer/js/iframeResizer.contentWindow'
|
||||||
|
import './style.scss'
|
||||||
|
|
||||||
|
import { debug, splitName } from './helper'
|
||||||
|
|
||||||
|
export default function init(name: string, source: any) {
|
||||||
|
// @ts-ignore
|
||||||
|
window.source = source
|
||||||
|
document.title = name
|
||||||
|
|
||||||
|
const [demoCategory, demoName] = splitName(name)
|
||||||
|
|
||||||
|
import(`../src/${demoCategory}/${demoName}/Svelte/index.svelte`)
|
||||||
|
.then(Module => {
|
||||||
|
const Component = Module.default
|
||||||
|
|
||||||
|
new Component({ target: document.querySelector('#app') }) // eslint-disable-line
|
||||||
|
|
||||||
|
debug()
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import { createApp } from 'vue'
|
|
||||||
import 'iframe-resizer/js/iframeResizer.contentWindow'
|
import 'iframe-resizer/js/iframeResizer.contentWindow'
|
||||||
import { debug, splitName } from './helper'
|
|
||||||
import './style.scss'
|
import './style.scss'
|
||||||
|
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
|
||||||
|
import { debug, splitName } from './helper'
|
||||||
|
|
||||||
export default function init(name: string, source: any) {
|
export default function init(name: string, source: any) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.source = source
|
window.source = source
|
||||||
|
|||||||
31
demos/src/Examples/AutolinkValidation/React/index.jsx
Normal file
31
demos/src/Examples/AutolinkValidation/React/index.jsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import './styles.scss'
|
||||||
|
|
||||||
|
import Link from '@tiptap/extension-link'
|
||||||
|
import { EditorContent, useEditor } from '@tiptap/react'
|
||||||
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const editor = useEditor({
|
||||||
|
extensions: [
|
||||||
|
StarterKit,
|
||||||
|
Link.configure({
|
||||||
|
validate: link => /^https?:\/\//.test(link),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<p>Hey! Try to type in url with and without a http/s protocol. - Links without a protocol should not get auto linked</p>
|
||||||
|
`,
|
||||||
|
editorProps: {
|
||||||
|
attributes: {
|
||||||
|
spellcheck: 'false',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<EditorContent editor={editor} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
42
demos/src/Examples/AutolinkValidation/React/index.spec.js
Normal file
42
demos/src/Examples/AutolinkValidation/React/index.spec.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
context('/src/Examples/AutolinkValidation/React/', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/src/Examples/AutolinkValidation/React/')
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}{backspace}')
|
||||||
|
})
|
||||||
|
|
||||||
|
const validLinks = [
|
||||||
|
'https://tiptap.dev',
|
||||||
|
'http://tiptap.dev',
|
||||||
|
'https://www.tiptap.dev/',
|
||||||
|
'http://www.tiptap.dev/',
|
||||||
|
]
|
||||||
|
|
||||||
|
const invalidLinks = [
|
||||||
|
'tiptap.dev',
|
||||||
|
'www.tiptap.dev',
|
||||||
|
]
|
||||||
|
|
||||||
|
validLinks.forEach(link => {
|
||||||
|
it(`${link} should get autolinked`, () => {
|
||||||
|
cy.get('.ProseMirror').type(link)
|
||||||
|
cy.get('.ProseMirror').should('have.text', link)
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('a')
|
||||||
|
.should('have.length', 1)
|
||||||
|
.should('have.attr', 'href', link)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
invalidLinks.forEach(link => {
|
||||||
|
it(`${link} should NOT get autolinked`, () => {
|
||||||
|
cy.get('.ProseMirror').type(link)
|
||||||
|
cy.get('.ProseMirror').should('have.text', link)
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('a')
|
||||||
|
.should('have.length', 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
54
demos/src/Examples/AutolinkValidation/React/styles.scss
Normal file
54
demos/src/Examples/AutolinkValidation/React/styles.scss
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
/* Basic editor styles */
|
||||||
|
.ProseMirror {
|
||||||
|
> * + * {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
padding-left: 1rem;
|
||||||
|
border-left: 2px solid rgba(#0D0D0D, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
42
demos/src/Examples/AutolinkValidation/Vue/index.spec.js
Normal file
42
demos/src/Examples/AutolinkValidation/Vue/index.spec.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
context('/src/Examples/AutolinkValidation/Vue/', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/src/Examples/AutolinkValidation/Vue/')
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}{backspace}')
|
||||||
|
})
|
||||||
|
|
||||||
|
const validLinks = [
|
||||||
|
'https://tiptap.dev',
|
||||||
|
'http://tiptap.dev',
|
||||||
|
'https://www.tiptap.dev/',
|
||||||
|
'http://www.tiptap.dev/',
|
||||||
|
]
|
||||||
|
|
||||||
|
const invalidLinks = [
|
||||||
|
'tiptap.dev',
|
||||||
|
'www.tiptap.dev',
|
||||||
|
]
|
||||||
|
|
||||||
|
validLinks.forEach(link => {
|
||||||
|
it(`${link} should get autolinked`, () => {
|
||||||
|
cy.get('.ProseMirror').type(link)
|
||||||
|
cy.get('.ProseMirror').should('have.text', link)
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('a')
|
||||||
|
.should('have.length', 1)
|
||||||
|
.should('have.attr', 'href', link)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
invalidLinks.forEach(link => {
|
||||||
|
it(`${link} should NOT get autolinked`, () => {
|
||||||
|
cy.get('.ProseMirror').type(link)
|
||||||
|
cy.get('.ProseMirror').should('have.text', link)
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('a')
|
||||||
|
.should('have.length', 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
101
demos/src/Examples/AutolinkValidation/Vue/index.vue
Normal file
101
demos/src/Examples/AutolinkValidation/Vue/index.vue
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<template>
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Link from '@tiptap/extension-link'
|
||||||
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
StarterKit,
|
||||||
|
Link.configure({
|
||||||
|
validate: link => /^https?:\/\//.test(link),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<p>Hey! Try to type in url with and without a http/s protocol. - Links without a protocol should not get auto linked</p>
|
||||||
|
`,
|
||||||
|
editorProps: {
|
||||||
|
attributes: {
|
||||||
|
spellcheck: 'false',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeUnmount() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/* Basic editor styles */
|
||||||
|
.ProseMirror {
|
||||||
|
> * + * {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
padding-left: 1rem;
|
||||||
|
border-left: 2px solid rgba(#0D0D0D, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import React from 'react'
|
|
||||||
import { useEditor, EditorContent } from '@tiptap/react'
|
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
|
||||||
import { content } from '../content.js'
|
|
||||||
import './styles.scss'
|
import './styles.scss'
|
||||||
|
|
||||||
|
import { EditorContent, useEditor } from '@tiptap/react'
|
||||||
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { content } from '../content.js'
|
||||||
|
|
||||||
const MenuBar = ({ editor }) => {
|
const MenuBar = ({ editor }) => {
|
||||||
if (!editor) {
|
if (!editor) {
|
||||||
return null
|
return null
|
||||||
|
|||||||
12
demos/src/Examples/Book/React/index.spec.js
Normal file
12
demos/src/Examples/Book/React/index.spec.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
context('/src/Examples/Book/React/', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/src/Examples/Book/React/')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have a working tiptap instance', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
expect(editor).to.not.be.null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -3,5 +3,10 @@ context('/src/Examples/Book/Vue/', () => {
|
|||||||
cy.visit('/src/Examples/Book/Vue/')
|
cy.visit('/src/Examples/Book/Vue/')
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: Write tests
|
it('should have a working tiptap instance', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
expect(editor).to.not.be.null
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -68,8 +68,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
|
||||||
import { content } from '../content.js'
|
import { content } from '../content.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
0
demos/src/Examples/CSSModules/React/index.html
Normal file
0
demos/src/Examples/CSSModules/React/index.html
Normal file
171
demos/src/Examples/CSSModules/React/index.jsx
Normal file
171
demos/src/Examples/CSSModules/React/index.jsx
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
import './styles.scss'
|
||||||
|
|
||||||
|
import { EditorContent, useEditor } from '@tiptap/react'
|
||||||
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import styles from './index.module.css'
|
||||||
|
|
||||||
|
const MenuBar = ({ editor }) => {
|
||||||
|
if (!editor) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`toolbar ${styles.toolbar}`}>
|
||||||
|
<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().toggleCode().run()}
|
||||||
|
className={editor.isActive('code') ? 'is-active' : ''}
|
||||||
|
>
|
||||||
|
code
|
||||||
|
</button>
|
||||||
|
<button onClick={() => editor.chain().focus().unsetAllMarks().run()}>
|
||||||
|
clear marks
|
||||||
|
</button>
|
||||||
|
<button onClick={() => editor.chain().focus().clearNodes().run()}>
|
||||||
|
clear nodes
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().setParagraph().run()}
|
||||||
|
className={editor.isActive('paragraph') ? 'is-active' : ''}
|
||||||
|
>
|
||||||
|
paragraph
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
|
||||||
|
className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''}
|
||||||
|
>
|
||||||
|
h1
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
|
||||||
|
className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''}
|
||||||
|
>
|
||||||
|
h2
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
|
||||||
|
className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''}
|
||||||
|
>
|
||||||
|
h3
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()}
|
||||||
|
className={editor.isActive('heading', { level: 4 }) ? 'is-active' : ''}
|
||||||
|
>
|
||||||
|
h4
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()}
|
||||||
|
className={editor.isActive('heading', { level: 5 }) ? 'is-active' : ''}
|
||||||
|
>
|
||||||
|
h5
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()}
|
||||||
|
className={editor.isActive('heading', { level: 6 }) ? 'is-active' : ''}
|
||||||
|
>
|
||||||
|
h6
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
||||||
|
className={editor.isActive('bulletList') ? 'is-active' : ''}
|
||||||
|
>
|
||||||
|
bullet list
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleOrderedList().run()}
|
||||||
|
className={editor.isActive('orderedList') ? 'is-active' : ''}
|
||||||
|
>
|
||||||
|
ordered list
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
|
||||||
|
className={editor.isActive('codeBlock') ? 'is-active' : ''}
|
||||||
|
>
|
||||||
|
code block
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleBlockquote().run()}
|
||||||
|
className={editor.isActive('blockquote') ? 'is-active' : ''}
|
||||||
|
>
|
||||||
|
blockquote
|
||||||
|
</button>
|
||||||
|
<button onClick={() => editor.chain().focus().setHorizontalRule().run()}>
|
||||||
|
horizontal rule
|
||||||
|
</button>
|
||||||
|
<button onClick={() => editor.chain().focus().setHardBreak().run()}>
|
||||||
|
hard break
|
||||||
|
</button>
|
||||||
|
<button onClick={() => editor.chain().focus().undo().run()}>
|
||||||
|
undo
|
||||||
|
</button>
|
||||||
|
<button onClick={() => editor.chain().focus().redo().run()}>
|
||||||
|
redo
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const editor = useEditor({
|
||||||
|
extensions: [
|
||||||
|
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 you’d probably expect from a text editor. But wait until you see the lists:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
That’s a bullet list with one …
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
… or two list items.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Isn’t that great? And all of that is editable. But wait, there’s more. Let’s try a code block:
|
||||||
|
</p>
|
||||||
|
<pre><code class="language-css">body {
|
||||||
|
display: none;
|
||||||
|
}</code></pre>
|
||||||
|
<p>
|
||||||
|
I know, I know, this is impressive. It’s only the tip of the iceberg though. Give it a try and click a little bit around. Don’t forget to check the other examples too.
|
||||||
|
</p>
|
||||||
|
<blockquote>
|
||||||
|
Wow, that’s amazing. Good work, boy! 👏
|
||||||
|
<br />
|
||||||
|
— Mom
|
||||||
|
</blockquote>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<MenuBar editor={editor} />
|
||||||
|
<EditorContent editor={editor} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
4
demos/src/Examples/CSSModules/React/index.module.css
Normal file
4
demos/src/Examples/CSSModules/React/index.module.css
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.toolbar {
|
||||||
|
background-color: red;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
11
demos/src/Examples/CSSModules/React/index.spec.js
Normal file
11
demos/src/Examples/CSSModules/React/index.spec.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
context('/src/Examples/CSSModules/React/', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/src/Examples/CSSModules/React/')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should apply a randomly generated class that adds padding and background color to the toolbar', () => {
|
||||||
|
cy.get('.toolbar').should('exist')
|
||||||
|
cy.get('.toolbar').should('have.css', 'background-color', 'rgb(255, 0, 0)')
|
||||||
|
cy.get('.toolbar').should('have.css', 'padding', '16px')
|
||||||
|
})
|
||||||
|
})
|
||||||
56
demos/src/Examples/CSSModules/React/styles.scss
Normal file
56
demos/src/Examples/CSSModules/React/styles.scss
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/* Basic editor styles */
|
||||||
|
.ProseMirror {
|
||||||
|
> * + * {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
0
demos/src/Examples/CSSModules/Vue/index.html
Normal file
0
demos/src/Examples/CSSModules/Vue/index.html
Normal file
4
demos/src/Examples/CSSModules/Vue/index.module.css
Normal file
4
demos/src/Examples/CSSModules/Vue/index.module.css
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.toolbar {
|
||||||
|
background-color: red;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
11
demos/src/Examples/CSSModules/Vue/index.spec.js
Normal file
11
demos/src/Examples/CSSModules/Vue/index.spec.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
context('/src/Examples/CSSModules/Vue/', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/src/Examples/CSSModules/Vue/')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should apply a randomly generated class that adds padding and background color to the toolbar', () => {
|
||||||
|
cy.get('.toolbar').should('exist')
|
||||||
|
cy.get('.toolbar').should('have.css', 'background-color', 'rgb(255, 0, 0)')
|
||||||
|
cy.get('.toolbar').should('have.css', 'padding', '16px')
|
||||||
|
})
|
||||||
|
})
|
||||||
189
demos/src/Examples/CSSModules/Vue/index.vue
Normal file
189
demos/src/Examples/CSSModules/Vue/index.vue
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="editor" class="toolbar" :class="styles.toolbar">
|
||||||
|
<button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }">
|
||||||
|
bold
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }">
|
||||||
|
italic
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }">
|
||||||
|
strike
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleCode().run()" :class="{ 'is-active': editor.isActive('code') }">
|
||||||
|
code
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().unsetAllMarks().run()">
|
||||||
|
clear marks
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().clearNodes().run()">
|
||||||
|
clear nodes
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }">
|
||||||
|
paragraph
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }">
|
||||||
|
h1
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }">
|
||||||
|
h2
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleHeading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }">
|
||||||
|
h3
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleHeading({ level: 4 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 4 }) }">
|
||||||
|
h4
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleHeading({ level: 5 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 5 }) }">
|
||||||
|
h5
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleHeading({ level: 6 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 6 }) }">
|
||||||
|
h6
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }">
|
||||||
|
bullet list
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleOrderedList().run()" :class="{ 'is-active': editor.isActive('orderedList') }">
|
||||||
|
ordered list
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }">
|
||||||
|
code block
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleBlockquote().run()" :class="{ 'is-active': editor.isActive('blockquote') }">
|
||||||
|
blockquote
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().setHorizontalRule().run()">
|
||||||
|
horizontal rule
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().setHardBreak().run()">
|
||||||
|
hard break
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().undo().run()">
|
||||||
|
undo
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().redo().run()">
|
||||||
|
redo
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
|
||||||
|
import styles from './index.module.css'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
styles,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
StarterKit,
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<h1 class="test">
|
||||||
|
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 you’d probably expect from a text editor. But wait until you see the lists:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
That’s a bullet list with one …
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
… or two list items.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Isn’t that great? And all of that is editable. But wait, there’s more. Let’s try a code block:
|
||||||
|
</p>
|
||||||
|
<pre><code class="language-css">body {
|
||||||
|
display: none;
|
||||||
|
}</code></pre>
|
||||||
|
<p>
|
||||||
|
I know, I know, this is impressive. It’s only the tip of the iceberg though. Give it a try and click a little bit around. Don’t forget to check the other examples too.
|
||||||
|
</p>
|
||||||
|
<blockquote>
|
||||||
|
Wow, that’s amazing. Good work, boy! 👏
|
||||||
|
<br />
|
||||||
|
— Mom
|
||||||
|
</blockquote>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeUnmount() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/* Basic editor styles */
|
||||||
|
.ProseMirror {
|
||||||
|
> * + * {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import React from 'react'
|
|
||||||
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react'
|
|
||||||
import './CodeBlockComponent.scss'
|
import './CodeBlockComponent.scss'
|
||||||
|
|
||||||
|
import { NodeViewContent, NodeViewWrapper } from '@tiptap/react'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
export default ({ node: { attrs: { language: defaultLanguage } }, updateAttributes, extension }) => (
|
export default ({ node: { attrs: { language: defaultLanguage } }, updateAttributes, extension }) => (
|
||||||
<NodeViewWrapper className="code-block">
|
<NodeViewWrapper className="code-block">
|
||||||
<select contentEditable={false} defaultValue={defaultLanguage} onChange={event => updateAttributes({ language: event.target.value })}>
|
<select contentEditable={false} defaultValue={defaultLanguage} onChange={event => updateAttributes({ language: event.target.value })}>
|
||||||
|
|||||||
@@ -1,20 +1,29 @@
|
|||||||
import React from 'react'
|
|
||||||
import { useEditor, EditorContent, ReactNodeViewRenderer } from '@tiptap/react'
|
|
||||||
import Document from '@tiptap/extension-document'
|
|
||||||
import Paragraph from '@tiptap/extension-paragraph'
|
|
||||||
import Text from '@tiptap/extension-text'
|
|
||||||
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
|
|
||||||
import CodeBlockComponent from './CodeBlockComponent'
|
|
||||||
|
|
||||||
// load all highlight.js languages
|
|
||||||
import lowlight from 'lowlight'
|
|
||||||
|
|
||||||
// load specific languages only
|
// load specific languages only
|
||||||
// import lowlight from 'lowlight/lib/core'
|
// import { lowlight } from 'lowlight/lib/core'
|
||||||
// import javascript from 'highlight.js/lib/languages/javascript'
|
// import javascript from 'highlight.js/lib/languages/javascript'
|
||||||
// lowlight.registerLanguage('javascript', javascript)
|
// lowlight.registerLanguage('javascript', javascript)
|
||||||
import './styles.scss'
|
import './styles.scss'
|
||||||
|
|
||||||
|
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
|
||||||
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Text from '@tiptap/extension-text'
|
||||||
|
import { EditorContent, ReactNodeViewRenderer, useEditor } from '@tiptap/react'
|
||||||
|
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'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import CodeBlockComponent from './CodeBlockComponent'
|
||||||
|
|
||||||
|
lowlight.registerLanguage('html', html)
|
||||||
|
lowlight.registerLanguage('css', css)
|
||||||
|
lowlight.registerLanguage('js', js)
|
||||||
|
lowlight.registerLanguage('ts', ts)
|
||||||
|
|
||||||
const MenuBar = ({ editor }) => {
|
const MenuBar = ({ editor }) => {
|
||||||
if (!editor) {
|
if (!editor) {
|
||||||
return null
|
return null
|
||||||
|
|||||||
28
demos/src/Examples/CodeBlockLanguage/React/index.spec.js
Normal file
28
demos/src/Examples/CodeBlockLanguage/React/index.spec.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
context('/src/Examples/CodeBlockLanguage/React/', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/src/Examples/CodeBlockLanguage/React/')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have hljs classes for syntax highlighting', () => {
|
||||||
|
cy.get('[class^=hljs]').then(elements => {
|
||||||
|
expect(elements.length).to.be.greaterThan(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have different count of hljs classes after switching language', () => {
|
||||||
|
cy.get('[class^=hljs]').then(elements => {
|
||||||
|
const initialCount = elements.length
|
||||||
|
|
||||||
|
expect(initialCount).to.be.greaterThan(0)
|
||||||
|
|
||||||
|
cy.get('.ProseMirror select').select('java')
|
||||||
|
cy.wait(500)
|
||||||
|
|
||||||
|
cy.get('[class^=hljs]').then(newElements => {
|
||||||
|
const newCount = newElements.length
|
||||||
|
|
||||||
|
expect(newCount).to.not.equal(initialCount)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { NodeViewWrapper, NodeViewContent, nodeViewProps } from '@tiptap/vue-3'
|
import { NodeViewContent, nodeViewProps, NodeViewWrapper } from '@tiptap/vue-3'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
28
demos/src/Examples/CodeBlockLanguage/Vue/index.spec.js
Normal file
28
demos/src/Examples/CodeBlockLanguage/Vue/index.spec.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
context('/src/Examples/CodeBlockLanguage/Vue/', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/src/Examples/CodeBlockLanguage/Vue/')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have hljs classes for syntax highlighting', () => {
|
||||||
|
cy.get('[class^=hljs]').then(elements => {
|
||||||
|
expect(elements.length).to.be.greaterThan(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have different count of hljs classes after switching language', () => {
|
||||||
|
cy.get('[class^=hljs]').then(elements => {
|
||||||
|
const initialCount = elements.length
|
||||||
|
|
||||||
|
expect(initialCount).to.be.greaterThan(0)
|
||||||
|
|
||||||
|
cy.get('.ProseMirror select').select('java')
|
||||||
|
cy.wait(500)
|
||||||
|
|
||||||
|
cy.get('[class^=hljs]').then(newElements => {
|
||||||
|
const newCount = newElements.length
|
||||||
|
|
||||||
|
expect(newCount).to.not.equal(initialCount)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -8,18 +8,27 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Editor, EditorContent, VueNodeViewRenderer } from '@tiptap/vue-3'
|
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
|
||||||
import Document from '@tiptap/extension-document'
|
import Document from '@tiptap/extension-document'
|
||||||
import Paragraph from '@tiptap/extension-paragraph'
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
import Text from '@tiptap/extension-text'
|
import Text from '@tiptap/extension-text'
|
||||||
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
|
import { Editor, EditorContent, VueNodeViewRenderer } from '@tiptap/vue-3'
|
||||||
|
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'
|
||||||
|
|
||||||
import CodeBlockComponent from './CodeBlockComponent.vue'
|
import CodeBlockComponent from './CodeBlockComponent.vue'
|
||||||
|
|
||||||
// load all highlight.js languages
|
lowlight.registerLanguage('html', html)
|
||||||
import lowlight from 'lowlight'
|
lowlight.registerLanguage('css', css)
|
||||||
|
lowlight.registerLanguage('js', js)
|
||||||
|
lowlight.registerLanguage('ts', ts)
|
||||||
|
|
||||||
// load specific languages only
|
// load specific languages only
|
||||||
// import lowlight from 'lowlight/lib/core'
|
// import { lowlight } from 'lowlight/lib/core'
|
||||||
// import javascript from 'highlight.js/lib/languages/javascript'
|
// import javascript from 'highlight.js/lib/languages/javascript'
|
||||||
// lowlight.registerLanguage('javascript', javascript)
|
// lowlight.registerLanguage('javascript', javascript)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import React, { Fragment } from 'react'
|
|
||||||
import MenuItem from './MenuItem'
|
|
||||||
import './MenuBar.scss'
|
import './MenuBar.scss'
|
||||||
|
|
||||||
|
import React, { Fragment } from 'react'
|
||||||
|
|
||||||
|
import MenuItem from './MenuItem'
|
||||||
|
|
||||||
export default ({ editor }) => {
|
export default ({ editor }) => {
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react'
|
|
||||||
import './MenuItem.scss'
|
import './MenuItem.scss'
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
import remixiconUrl from 'remixicon/fonts/remixicon.symbol.svg'
|
import remixiconUrl from 'remixicon/fonts/remixicon.symbol.svg'
|
||||||
|
|
||||||
export default ({
|
export default ({
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
import React, {
|
import './styles.scss'
|
||||||
useState, useCallback, useEffect,
|
|
||||||
} from 'react'
|
import { HocuspocusProvider } from '@hocuspocus/provider'
|
||||||
import * as Y from 'yjs'
|
|
||||||
import { useEditor, EditorContent } from '@tiptap/react'
|
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
|
||||||
import TaskList from '@tiptap/extension-task-list'
|
|
||||||
import TaskItem from '@tiptap/extension-task-item'
|
|
||||||
import Highlight from '@tiptap/extension-highlight'
|
|
||||||
import CharacterCount from '@tiptap/extension-character-count'
|
import CharacterCount from '@tiptap/extension-character-count'
|
||||||
import Collaboration from '@tiptap/extension-collaboration'
|
import Collaboration from '@tiptap/extension-collaboration'
|
||||||
import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
|
import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
|
||||||
import { HocuspocusProvider } from '@hocuspocus/provider'
|
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'
|
||||||
|
import * as Y from 'yjs'
|
||||||
|
|
||||||
import MenuBar from './MenuBar'
|
import MenuBar from './MenuBar'
|
||||||
import './styles.scss'
|
|
||||||
|
|
||||||
const colors = ['#958DF1', '#F98181', '#FBBC88', '#FAF594', '#70CFF8', '#94FADB', '#B9F18D']
|
const colors = ['#958DF1', '#F98181', '#FBBC88', '#FAF594', '#70CFF8', '#94FADB', '#B9F18D']
|
||||||
const rooms = ['rooms.10', 'rooms.11', 'rooms.12']
|
const rooms = ['rooms.10', 'rooms.11', 'rooms.12']
|
||||||
@@ -56,7 +59,7 @@ const ydoc = new Y.Doc()
|
|||||||
const websocketProvider = new HocuspocusProvider({
|
const websocketProvider = new HocuspocusProvider({
|
||||||
url: 'wss://connect.hocuspocus.cloud',
|
url: 'wss://connect.hocuspocus.cloud',
|
||||||
parameters: {
|
parameters: {
|
||||||
key: 'write_B0sHbuV5xwYl6WzGjoqL',
|
key: 'write_bqgvQ3Zwl34V4Nxt43zR',
|
||||||
},
|
},
|
||||||
name: room,
|
name: room,
|
||||||
document: ydoc,
|
document: ydoc,
|
||||||
|
|||||||
@@ -1,7 +1,21 @@
|
|||||||
context('/src/Examples/CollaborativeEditing/React/', () => {
|
context('/src/Examples/CollaborativeEditing/React/', () => {
|
||||||
before(() => {
|
beforeEach(() => {
|
||||||
cy.visit('/src/Examples/CollaborativeEditing/React/')
|
cy.visit('/src/Examples/CollaborativeEditing/React/')
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: Write tests
|
/* it('should show the current room with participants', () => {
|
||||||
|
cy.wait(6000)
|
||||||
|
cy.get('.editor__status')
|
||||||
|
.should('contain', 'rooms.')
|
||||||
|
.should('contain', 'users online')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should allow user to change name', () => {
|
||||||
|
cy.window().then(win => {
|
||||||
|
cy.stub(win, 'prompt').returns('John Doe')
|
||||||
|
cy.get('.editor__name > button').click()
|
||||||
|
cy.wait(6000)
|
||||||
|
cy.get('.editor__name').should('contain', 'John Doe')
|
||||||
|
})
|
||||||
|
}) */
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,21 @@
|
|||||||
context('/src/Examples/CollaborativeEditing/Vue/', () => {
|
context('/src/Examples/CollaborativeEditing/Vue/', () => {
|
||||||
before(() => {
|
beforeEach(() => {
|
||||||
cy.visit('/src/Examples/CollaborativeEditing/Vue/')
|
cy.visit('/src/Examples/CollaborativeEditing/Vue/')
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: Write tests
|
/* it('should show the current room with participants', () => {
|
||||||
|
cy.wait(6000)
|
||||||
|
cy.get('.editor__status')
|
||||||
|
.should('contain', 'rooms.')
|
||||||
|
.should('contain', 'users online')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should allow user to change name', () => {
|
||||||
|
cy.window().then(win => {
|
||||||
|
cy.stub(win, 'prompt').returns('John Doe')
|
||||||
|
cy.get('.editor__name > button').click()
|
||||||
|
cy.wait(6000)
|
||||||
|
cy.get('.editor__name').should('contain', 'John Doe')
|
||||||
|
})
|
||||||
|
}) */
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -21,16 +21,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
import { HocuspocusProvider } from '@hocuspocus/provider'
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
import CharacterCount from '@tiptap/extension-character-count'
|
||||||
import Collaboration from '@tiptap/extension-collaboration'
|
import Collaboration from '@tiptap/extension-collaboration'
|
||||||
import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
|
import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
|
||||||
import TaskList from '@tiptap/extension-task-list'
|
|
||||||
import TaskItem from '@tiptap/extension-task-item'
|
|
||||||
import Highlight from '@tiptap/extension-highlight'
|
import Highlight from '@tiptap/extension-highlight'
|
||||||
import CharacterCount from '@tiptap/extension-character-count'
|
import TaskItem from '@tiptap/extension-task-item'
|
||||||
|
import TaskList from '@tiptap/extension-task-list'
|
||||||
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
import * as Y from 'yjs'
|
import * as Y from 'yjs'
|
||||||
import { HocuspocusProvider } from '@hocuspocus/provider'
|
|
||||||
import MenuBar from './MenuBar.vue'
|
import MenuBar from './MenuBar.vue'
|
||||||
|
|
||||||
const getRandomElement = list => {
|
const getRandomElement = list => {
|
||||||
@@ -70,7 +71,7 @@ export default {
|
|||||||
this.provider = new HocuspocusProvider({
|
this.provider = new HocuspocusProvider({
|
||||||
url: 'wss://connect.hocuspocus.cloud',
|
url: 'wss://connect.hocuspocus.cloud',
|
||||||
parameters: {
|
parameters: {
|
||||||
key: 'write_B0sHbuV5xwYl6WzGjoqL',
|
key: 'write_bqgvQ3Zwl34V4Nxt43zR',
|
||||||
},
|
},
|
||||||
name: this.room,
|
name: this.room,
|
||||||
document: ydoc,
|
document: ydoc,
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import React, {
|
|
||||||
useState,
|
|
||||||
useEffect,
|
|
||||||
forwardRef,
|
|
||||||
useImperativeHandle,
|
|
||||||
} from 'react'
|
|
||||||
import './MentionList.scss'
|
import './MentionList.scss'
|
||||||
|
|
||||||
|
import React, {
|
||||||
|
forwardRef,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
|
|
||||||
export const MentionList = forwardRef((props, ref) => {
|
export const MentionList = forwardRef((props, ref) => {
|
||||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import React from 'react'
|
import './styles.scss'
|
||||||
import { useEditor, EditorContent } from '@tiptap/react'
|
|
||||||
|
import CharacterCount from '@tiptap/extension-character-count'
|
||||||
import Document from '@tiptap/extension-document'
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Mention from '@tiptap/extension-mention'
|
||||||
import Paragraph from '@tiptap/extension-paragraph'
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
import Text from '@tiptap/extension-text'
|
import Text from '@tiptap/extension-text'
|
||||||
import CharacterCount from '@tiptap/extension-character-count'
|
import { EditorContent, useEditor } from '@tiptap/react'
|
||||||
import Mention from '@tiptap/extension-mention'
|
import React from 'react'
|
||||||
|
|
||||||
import suggestion from './suggestion'
|
import suggestion from './suggestion'
|
||||||
import './styles.scss'
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const limit = 280
|
const limit = 280
|
||||||
|
|||||||
37
demos/src/Examples/Community/React/index.spec.js
Normal file
37
demos/src/Examples/Community/React/index.spec.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
context('/src/Examples/Community/React/', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/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')
|
||||||
|
|
||||||
|
// type in .ProseMirror
|
||||||
|
cy.get('.ProseMirror').type(' Hello World')
|
||||||
|
cy.get('.character-count__text').should('have.text', '56/280 characters')
|
||||||
|
|
||||||
|
// remove content from .ProseMirror and enter text
|
||||||
|
cy.get('.ProseMirror').type('{selectall}{backspace}Hello World')
|
||||||
|
cy.get('.character-count__text').should('have.text', '11/280 characters')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should mention a user', () => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}{backspace}@')
|
||||||
|
|
||||||
|
// check if the mention autocomplete is visible
|
||||||
|
cy.get('.tippy-content .items').should('be.visible')
|
||||||
|
|
||||||
|
// select the first user
|
||||||
|
cy.get('.tippy-content .items .item').first().then($el => {
|
||||||
|
const name = $el.text()
|
||||||
|
|
||||||
|
$el.click()
|
||||||
|
|
||||||
|
// check if the user is mentioned
|
||||||
|
cy.get('.ProseMirror').should('have.text', `@${name} `)
|
||||||
|
cy.get('.character-count__text').should('have.text', '2/280 characters')
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ReactRenderer } from '@tiptap/react'
|
import { ReactRenderer } from '@tiptap/react'
|
||||||
import tippy from 'tippy.js'
|
import tippy from 'tippy.js'
|
||||||
|
|
||||||
import { MentionList } from './MentionList'
|
import { MentionList } from './MentionList'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -20,6 +21,10 @@ export default {
|
|||||||
editor: props.editor,
|
editor: props.editor,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!props.clientRect) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
popup = tippy('body', {
|
popup = tippy('body', {
|
||||||
getReferenceClientRect: props.clientRect,
|
getReferenceClientRect: props.clientRect,
|
||||||
appendTo: () => document.body,
|
appendTo: () => document.body,
|
||||||
@@ -34,6 +39,10 @@ export default {
|
|||||||
onUpdate(props) {
|
onUpdate(props) {
|
||||||
reactRenderer.updateProps(props)
|
reactRenderer.updateProps(props)
|
||||||
|
|
||||||
|
if (!props.clientRect) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
popup[0].setProps({
|
popup[0].setProps({
|
||||||
getReferenceClientRect: props.clientRect,
|
getReferenceClientRect: props.clientRect,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,37 @@
|
|||||||
context('/src/Examples/Community/Vue/', () => {
|
context('/src/Examples/Community/Vue/', () => {
|
||||||
before(() => {
|
beforeEach(() => {
|
||||||
cy.visit('/src/Examples/Community/Vue/')
|
cy.visit('/src/Examples/Community/Vue/')
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: Write tests
|
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')
|
||||||
|
|
||||||
|
// type in .ProseMirror
|
||||||
|
cy.get('.ProseMirror').type(' Hello World')
|
||||||
|
cy.get('.character-count__text').should('have.text', '56/280 characters')
|
||||||
|
|
||||||
|
// remove content from .ProseMirror and enter text
|
||||||
|
cy.get('.ProseMirror').type('{selectall}{backspace}Hello World')
|
||||||
|
cy.get('.character-count__text').should('have.text', '11/280 characters')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should mention a user', () => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}{backspace}@')
|
||||||
|
|
||||||
|
// check if the mention autocomplete is visible
|
||||||
|
cy.get('.tippy-content .items').should('be.visible')
|
||||||
|
|
||||||
|
// select the first user
|
||||||
|
cy.get('.tippy-content .items .item').first().then($el => {
|
||||||
|
const name = $el.text()
|
||||||
|
|
||||||
|
$el.click()
|
||||||
|
|
||||||
|
// check if the user is mentioned
|
||||||
|
cy.get('.ProseMirror').should('have.text', `@${name} `)
|
||||||
|
cy.get('.character-count__text').should('have.text', '2/280 characters')
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -32,19 +32,18 @@
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<div class="character-count__text">
|
<div class="character-count__text">{{ editor.storage.characterCount.characters() }}/{{ limit }} characters</div>
|
||||||
{{ editor.storage.characterCount.characters() }}/{{ limit }} characters
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
import CharacterCount from '@tiptap/extension-character-count'
|
||||||
import Document from '@tiptap/extension-document'
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Mention from '@tiptap/extension-mention'
|
||||||
import Paragraph from '@tiptap/extension-paragraph'
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
import Text from '@tiptap/extension-text'
|
import Text from '@tiptap/extension-text'
|
||||||
import CharacterCount from '@tiptap/extension-character-count'
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
import Mention from '@tiptap/extension-mention'
|
|
||||||
import suggestion from './suggestion'
|
import suggestion from './suggestion'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { VueRenderer } from '@tiptap/vue-3'
|
import { VueRenderer } from '@tiptap/vue-3'
|
||||||
import tippy from 'tippy.js'
|
import tippy from 'tippy.js'
|
||||||
|
|
||||||
import MentionList from './MentionList.vue'
|
import MentionList from './MentionList.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -23,6 +24,10 @@ export default {
|
|||||||
editor: props.editor,
|
editor: props.editor,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!props.clientRect) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
popup = tippy('body', {
|
popup = tippy('body', {
|
||||||
getReferenceClientRect: props.clientRect,
|
getReferenceClientRect: props.clientRect,
|
||||||
appendTo: () => document.body,
|
appendTo: () => document.body,
|
||||||
@@ -37,6 +42,10 @@ export default {
|
|||||||
onUpdate(props) {
|
onUpdate(props) {
|
||||||
component.updateProps(props)
|
component.updateProps(props)
|
||||||
|
|
||||||
|
if (!props.clientRect) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
popup[0].setProps({
|
popup[0].setProps({
|
||||||
getReferenceClientRect: props.clientRect,
|
getReferenceClientRect: props.clientRect,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import React from 'react'
|
import './styles.scss'
|
||||||
import { useEditor, EditorContent } from '@tiptap/react'
|
|
||||||
import Document from '@tiptap/extension-document'
|
import Document from '@tiptap/extension-document'
|
||||||
import Placeholder from '@tiptap/extension-placeholder'
|
import Placeholder from '@tiptap/extension-placeholder'
|
||||||
|
import { EditorContent, useEditor } from '@tiptap/react'
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
import './styles.scss'
|
import React from 'react'
|
||||||
|
|
||||||
const CustomDocument = Document.extend({
|
const CustomDocument = Document.extend({
|
||||||
content: 'heading block*',
|
content: 'heading block*',
|
||||||
|
|||||||
46
demos/src/Examples/CustomDocument/React/index.spec.js
Normal file
46
demos/src/Examples/CustomDocument/React/index.spec.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
context('/src/Examples/CustomDocument/React/', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/src/Examples/CustomDocument/React/')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have a working tiptap instance', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
expect(editor).to.not.be.null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have a headline and a paragraph', () => {
|
||||||
|
cy.get('.ProseMirror h1').should('exist').should('have.text', 'It’ll always have a heading …')
|
||||||
|
cy.get('.ProseMirror p').should('exist').should('have.text', '… if you pass a custom document. That’s the beauty of having full control over the schema.')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have a tooltip for a paragraph on a new line', () => {
|
||||||
|
cy.get('.ProseMirror').type('{enter}')
|
||||||
|
cy.get('.ProseMirror p[data-placeholder]').should('exist').should('have.attr', 'data-placeholder', 'Can you add some further context?')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have a headline after clearing the document', () => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}{backspace}')
|
||||||
|
cy.get('.ProseMirror').focus()
|
||||||
|
cy.get('.ProseMirror h1[data-placeholder]')
|
||||||
|
.should('exist')
|
||||||
|
.should('have.attr', 'class', 'is-empty is-editor-empty')
|
||||||
|
.should('have.attr', 'data-placeholder', 'What’s the title?')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have a headline after clearing the document & enter paragraph automatically after adding a headline', () => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}{backspace}Hello world{enter}')
|
||||||
|
cy.get('.ProseMirror h1')
|
||||||
|
.should('exist')
|
||||||
|
.should('have.text', 'Hello world')
|
||||||
|
cy.get('.ProseMirror p[data-placeholder]')
|
||||||
|
.should('exist')
|
||||||
|
.should('have.attr', 'data-placeholder', 'Can you add some further context?')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror').type('This is a paragraph for this test document')
|
||||||
|
cy.get('.ProseMirror p')
|
||||||
|
.should('exist')
|
||||||
|
.should('have.text', 'This is a paragraph for this test document')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,7 +1,48 @@
|
|||||||
context('/src/Examples/CustomDocument/Vue/', () => {
|
context('/src/Examples/CustomDocument/Vue/', () => {
|
||||||
before(() => {
|
beforeEach(() => {
|
||||||
cy.visit('/src/Examples/CustomDocument/Vue/')
|
cy.visit('/src/Examples/CustomDocument/Vue/')
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: Write tests
|
it('should have a working tiptap instance', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
expect(editor).to.not.be.null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have a headline and a paragraph', () => {
|
||||||
|
cy.get('.ProseMirror h1').should('exist').should('have.text', 'It’ll always have a heading …')
|
||||||
|
cy.get('.ProseMirror p').should('exist').should('have.text', '… if you pass a custom document. That’s the beauty of having full control over the schema.')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have a tooltip for a paragraph on a new line', () => {
|
||||||
|
cy.get('.ProseMirror').type('{enter}')
|
||||||
|
cy.get('.ProseMirror p[data-placeholder]').should('exist').should('have.attr', 'data-placeholder', 'Can you add some further context?')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have a headline after clearing the document', () => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}{backspace}')
|
||||||
|
cy.wait(100)
|
||||||
|
cy.get('.ProseMirror').focus()
|
||||||
|
cy.get('.ProseMirror h1[data-placeholder]')
|
||||||
|
.should('exist')
|
||||||
|
.should('have.attr', 'class', 'is-empty is-editor-empty')
|
||||||
|
.should('have.attr', 'data-placeholder', 'What’s the title?')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have a headline after clearing the document & enter paragraph automatically after adding a headline', () => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}{backspace}Hello world{enter}')
|
||||||
|
cy.wait(100)
|
||||||
|
cy.get('.ProseMirror h1')
|
||||||
|
.should('exist')
|
||||||
|
.should('have.text', 'Hello world')
|
||||||
|
cy.get('.ProseMirror p[data-placeholder]')
|
||||||
|
.should('exist')
|
||||||
|
.should('have.attr', 'data-placeholder', 'Can you add some further context?')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror').type('This is a paragraph for this test document')
|
||||||
|
cy.get('.ProseMirror p')
|
||||||
|
.should('exist')
|
||||||
|
.should('have.text', 'This is a paragraph for this test document')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
|
||||||
import Document from '@tiptap/extension-document'
|
import Document from '@tiptap/extension-document'
|
||||||
import Placeholder from '@tiptap/extension-placeholder'
|
import Placeholder from '@tiptap/extension-placeholder'
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
|
||||||
const CustomDocument = Document.extend({
|
const CustomDocument = Document.extend({
|
||||||
content: 'heading block*',
|
content: 'heading block*',
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import React from 'react'
|
|
||||||
import { useEditor, EditorContent } from '@tiptap/react'
|
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
|
||||||
import './styles.scss'
|
import './styles.scss'
|
||||||
|
|
||||||
|
import { EditorContent, useEditor } from '@tiptap/react'
|
||||||
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
const MenuBar = ({ editor }) => {
|
const MenuBar = ({ editor }) => {
|
||||||
if (!editor) {
|
if (!editor) {
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -19,4 +19,99 @@ context('/src/Examples/Default/React/', () => {
|
|||||||
.find('p')
|
.find('p')
|
||||||
.should('contain', 'Example Text')
|
.should('contain', 'Example Text')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const buttonMarks = [
|
||||||
|
{ 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('.ProseMirror').type('{selectall}Hello world')
|
||||||
|
cy.get('button').contains('paragraph').click()
|
||||||
|
cy.get('.ProseMirror').type('{selectall}')
|
||||||
|
cy.get('button').contains(m.label).click()
|
||||||
|
cy.get(`.ProseMirror ${m.tag}`).should('exist').should('have.text', 'Hello world')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should clear marks when the button is pressed', () => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}Hello world')
|
||||||
|
cy.get('button').contains('paragraph').click()
|
||||||
|
cy.get('.ProseMirror').type('{selectall}')
|
||||||
|
cy.get('button').contains('bold').click()
|
||||||
|
cy.get('.ProseMirror strong').should('exist').should('have.text', 'Hello world')
|
||||||
|
cy.get('button').contains('clear marks').click()
|
||||||
|
cy.get('.ProseMirror strong').should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should clear nodes when the button is pressed', () => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}Hello world')
|
||||||
|
cy.get('button').contains('bullet list').click()
|
||||||
|
cy.get('.ProseMirror ul').should('exist').should('have.text', 'Hello world')
|
||||||
|
cy.get('.ProseMirror').type('{enter}A second item{enter}A third item{selectall}')
|
||||||
|
cy.get('button').contains('clear nodes').click()
|
||||||
|
cy.get('.ProseMirror ul').should('not.exist')
|
||||||
|
cy.get('.ProseMirror 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' },
|
||||||
|
]
|
||||||
|
|
||||||
|
buttonNodes.forEach(n => {
|
||||||
|
it(`should set ${n.label} when the button is pressed`, () => {
|
||||||
|
cy.get('button').contains('paragraph').click()
|
||||||
|
cy.get('.ProseMirror').type('{selectall}Hello world{selectall}')
|
||||||
|
|
||||||
|
cy.get('button').contains(n.label).click()
|
||||||
|
cy.get(`.ProseMirror ${n.tag}`).should('exist').should('have.text', 'Hello world')
|
||||||
|
cy.get('button').contains(n.label).click()
|
||||||
|
cy.get(`.ProseMirror ${n.tag}`).should('not.exist')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should add a hr when on the same line as a node', () => {
|
||||||
|
cy.get('.ProseMirror').type('{rightArrow}')
|
||||||
|
cy.get('button').contains('horizontal rule').click()
|
||||||
|
cy.get('.ProseMirror hr').should('exist')
|
||||||
|
cy.get('.ProseMirror h1').should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should add a hr when on a new line', () => {
|
||||||
|
cy.get('.ProseMirror').type('{rightArrow}{enter}')
|
||||||
|
cy.get('button').contains('horizontal rule').click()
|
||||||
|
cy.get('.ProseMirror hr').should('exist')
|
||||||
|
cy.get('.ProseMirror h1').should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should add a br', () => {
|
||||||
|
cy.get('.ProseMirror').type('{rightArrow}')
|
||||||
|
cy.get('button').contains('hard break').click()
|
||||||
|
cy.get('.ProseMirror h1 br').should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should undo', () => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}{backspace}')
|
||||||
|
cy.get('button').contains('undo').click()
|
||||||
|
cy.get('.ProseMirror').should('contain', 'Hello world')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should redo', () => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}{backspace}')
|
||||||
|
cy.get('button').contains('undo').click()
|
||||||
|
cy.get('.ProseMirror').should('contain', 'Hello world')
|
||||||
|
cy.get('button').contains('redo').click()
|
||||||
|
cy.get('.ProseMirror').should('not.contain', 'Hello world')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
0
demos/src/Examples/Default/Svelte/index.html
Normal file
0
demos/src/Examples/Default/Svelte/index.html
Normal file
117
demos/src/Examples/Default/Svelte/index.spec.js
Normal file
117
demos/src/Examples/Default/Svelte/index.spec.js
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
context('/src/Examples/Default/React/', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/src/Examples/Default/React/')
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<h1>Example Text</h1>')
|
||||||
|
cy.get('.ProseMirror').type('{selectall}')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should apply the paragraph style when the keyboard shortcut is pressed', () => {
|
||||||
|
cy.get('.ProseMirror h1').should('exist')
|
||||||
|
cy.get('.ProseMirror p').should('not.exist')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.trigger('keydown', { modKey: true, altKey: true, key: '0' })
|
||||||
|
.find('p')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
})
|
||||||
|
|
||||||
|
const buttonMarks = [
|
||||||
|
{ 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('.ProseMirror').type('{selectall}Hello world')
|
||||||
|
cy.get('button').contains('paragraph').click()
|
||||||
|
cy.get('.ProseMirror').type('{selectall}')
|
||||||
|
cy.get('button').contains(m.label).click()
|
||||||
|
cy.get(`.ProseMirror ${m.tag}`).should('exist').should('have.text', 'Hello world')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should clear marks when the button is pressed', () => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}Hello world')
|
||||||
|
cy.get('button').contains('paragraph').click()
|
||||||
|
cy.get('.ProseMirror').type('{selectall}')
|
||||||
|
cy.get('button').contains('bold').click()
|
||||||
|
cy.get('.ProseMirror strong').should('exist').should('have.text', 'Hello world')
|
||||||
|
cy.get('button').contains('clear marks').click()
|
||||||
|
cy.get('.ProseMirror strong').should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should clear nodes when the button is pressed', () => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}Hello world')
|
||||||
|
cy.get('button').contains('bullet list').click()
|
||||||
|
cy.get('.ProseMirror ul').should('exist').should('have.text', 'Hello world')
|
||||||
|
cy.get('.ProseMirror').type('{enter}A second item{enter}A third item{selectall}')
|
||||||
|
cy.get('button').contains('clear nodes').click()
|
||||||
|
cy.get('.ProseMirror ul').should('not.exist')
|
||||||
|
cy.get('.ProseMirror 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' },
|
||||||
|
]
|
||||||
|
|
||||||
|
buttonNodes.forEach(n => {
|
||||||
|
it(`should set ${n.label} when the button is pressed`, () => {
|
||||||
|
cy.get('button').contains('paragraph').click()
|
||||||
|
cy.get('.ProseMirror').type('{selectall}Hello world{selectall}')
|
||||||
|
|
||||||
|
cy.get('button').contains(n.label).click()
|
||||||
|
cy.get(`.ProseMirror ${n.tag}`).should('exist').should('have.text', 'Hello world')
|
||||||
|
cy.get('button').contains(n.label).click()
|
||||||
|
cy.get(`.ProseMirror ${n.tag}`).should('not.exist')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should add a hr when on the same line as a node', () => {
|
||||||
|
cy.get('.ProseMirror').type('{rightArrow}')
|
||||||
|
cy.get('button').contains('horizontal rule').click()
|
||||||
|
cy.get('.ProseMirror hr').should('exist')
|
||||||
|
cy.get('.ProseMirror h1').should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should add a hr when on a new line', () => {
|
||||||
|
cy.get('.ProseMirror').type('{rightArrow}{enter}')
|
||||||
|
cy.get('button').contains('horizontal rule').click()
|
||||||
|
cy.get('.ProseMirror hr').should('exist')
|
||||||
|
cy.get('.ProseMirror h1').should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should add a br', () => {
|
||||||
|
cy.get('.ProseMirror').type('{rightArrow}')
|
||||||
|
cy.get('button').contains('hard break').click()
|
||||||
|
cy.get('.ProseMirror h1 br').should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should undo', () => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}{backspace}')
|
||||||
|
cy.get('button').contains('undo').click()
|
||||||
|
cy.get('.ProseMirror').should('contain', 'Hello world')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should redo', () => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}{backspace}')
|
||||||
|
cy.get('button').contains('undo').click()
|
||||||
|
cy.get('.ProseMirror').should('contain', 'Hello world')
|
||||||
|
cy.get('button').contains('redo').click()
|
||||||
|
cy.get('.ProseMirror').should('not.contain', 'Hello world')
|
||||||
|
})
|
||||||
|
})
|
||||||
157
demos/src/Examples/Default/Svelte/index.svelte
Normal file
157
demos/src/Examples/Default/Svelte/index.svelte
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
<script>
|
||||||
|
import "./styles.scss";
|
||||||
|
|
||||||
|
import StarterKit from "@tiptap/starter-kit";
|
||||||
|
import { Editor } from "@tiptap/core";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
let element;
|
||||||
|
let editor;
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
editor = new Editor({
|
||||||
|
element: element,
|
||||||
|
extensions: [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 you’d probably expect from a text editor. But wait until you see the lists:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
That’s a bullet list with one …
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
… or two list items.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Isn’t that great? And all of that is editable. But wait, there’s more. Let’s try a code block:
|
||||||
|
</p>
|
||||||
|
<pre><code class="language-css">body {
|
||||||
|
display: none;
|
||||||
|
}</code></pre>
|
||||||
|
<p>
|
||||||
|
I know, I know, this is impressive. It’s only the tip of the iceberg though. Give it a try and click a little bit around. Don’t forget to check the other examples too.
|
||||||
|
</p>
|
||||||
|
<blockquote>
|
||||||
|
Wow, that’s amazing. Good work, boy! 👏
|
||||||
|
<br />
|
||||||
|
— Mom
|
||||||
|
</blockquote>
|
||||||
|
`,
|
||||||
|
onTransaction: () => {
|
||||||
|
// force re-render so `editor.isActive` works as expected
|
||||||
|
editor = editor;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if editor}
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
on:click={() => console.log && editor.chain().focus().toggleBold().run()}
|
||||||
|
class={editor.isActive("bold") ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
bold
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={() => editor.chain().focus().toggleItalic().run()}
|
||||||
|
class={editor.isActive("italic") ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
italic
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={() => editor.chain().focus().toggleStrike().run()}
|
||||||
|
class={editor.isActive("strike") ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
strike
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={() => editor.chain().focus().toggleCode().run()}
|
||||||
|
class={editor.isActive("code") ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
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().setParagraph().run()}
|
||||||
|
class={editor.isActive("paragraph") ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
paragraph
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
|
||||||
|
class={editor.isActive("heading", { level: 1 }) ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
h1
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
|
||||||
|
class={editor.isActive("heading", { level: 2 }) ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
h2
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
|
||||||
|
class={editor.isActive("heading", { level: 3 }) ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
h3
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={() => editor.chain().focus().toggleHeading({ level: 4 }).run()}
|
||||||
|
class={editor.isActive("heading", { level: 4 }) ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
h4
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={() => editor.chain().focus().toggleHeading({ level: 5 }).run()}
|
||||||
|
class={editor.isActive("heading", { level: 5 }) ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
h5
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={() => editor.chain().focus().toggleHeading({ level: 6 }).run()}
|
||||||
|
class={editor.isActive("heading", { level: 6 }) ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
h6
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={() => editor.chain().focus().toggleBulletList().run()}
|
||||||
|
class={editor.isActive("bulletList") ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
bullet list
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={() => editor.chain().focus().toggleOrderedList().run()}
|
||||||
|
class={editor.isActive("orderedList") ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
ordered list
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={() => editor.chain().focus().toggleCodeBlock().run()}
|
||||||
|
class={editor.isActive("codeBlock") ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
code block
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={() => editor.chain().focus().toggleBlockquote().run()}
|
||||||
|
class={editor.isActive("blockquote") ? "is-active" : ""}
|
||||||
|
>
|
||||||
|
blockquote
|
||||||
|
</button>
|
||||||
|
<button on:click={() => editor.chain().focus().setHorizontalRule().run()}>
|
||||||
|
horizontal rule
|
||||||
|
</button>
|
||||||
|
<button on:click={() => editor.chain().focus().setHardBreak().run()}> hard break </button>
|
||||||
|
<button on:click={() => editor.chain().focus().undo().run()}> undo </button>
|
||||||
|
<button on:click={() => editor.chain().focus().redo().run()}> redo </button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div bind:this={element} />
|
||||||
56
demos/src/Examples/Default/Svelte/styles.scss
Normal file
56
demos/src/Examples/Default/Svelte/styles.scss
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/* Basic editor styles */
|
||||||
|
.ProseMirror {
|
||||||
|
> * + * {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,4 +19,99 @@ context('/src/Examples/Default/Vue/', () => {
|
|||||||
.find('p')
|
.find('p')
|
||||||
.should('contain', 'Example Text')
|
.should('contain', 'Example Text')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const buttonMarks = [
|
||||||
|
{ 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('.ProseMirror').type('{selectall}Hello world')
|
||||||
|
cy.get('button').contains('paragraph').click()
|
||||||
|
cy.get('.ProseMirror').type('{selectall}')
|
||||||
|
cy.get('button').contains(m.label).click()
|
||||||
|
cy.get(`.ProseMirror ${m.tag}`).should('exist').should('have.text', 'Hello world')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should clear marks when the button is pressed', () => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}Hello world')
|
||||||
|
cy.get('button').contains('paragraph').click()
|
||||||
|
cy.get('.ProseMirror').type('{selectall}')
|
||||||
|
cy.get('button').contains('bold').click()
|
||||||
|
cy.get('.ProseMirror strong').should('exist').should('have.text', 'Hello world')
|
||||||
|
cy.get('button').contains('clear marks').click()
|
||||||
|
cy.get('.ProseMirror strong').should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should clear nodes when the button is pressed', () => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}Hello world')
|
||||||
|
cy.get('button').contains('bullet list').click()
|
||||||
|
cy.get('.ProseMirror ul').should('exist').should('have.text', 'Hello world')
|
||||||
|
cy.get('.ProseMirror').type('{enter}A second item{enter}A third item{selectall}')
|
||||||
|
cy.get('button').contains('clear nodes').click()
|
||||||
|
cy.get('.ProseMirror ul').should('not.exist')
|
||||||
|
cy.get('.ProseMirror 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' },
|
||||||
|
]
|
||||||
|
|
||||||
|
buttonNodes.forEach(n => {
|
||||||
|
it(`should set ${n.label} when the button is pressed`, () => {
|
||||||
|
cy.get('button').contains('paragraph').click()
|
||||||
|
cy.get('.ProseMirror').type('{selectall}Hello world{selectall}')
|
||||||
|
|
||||||
|
cy.get('button').contains(n.label).click()
|
||||||
|
cy.get(`.ProseMirror ${n.tag}`).should('exist').should('have.text', 'Hello world')
|
||||||
|
cy.get('button').contains(n.label).click()
|
||||||
|
cy.get(`.ProseMirror ${n.tag}`).should('not.exist')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should add a hr when on the same line as a node', () => {
|
||||||
|
cy.get('.ProseMirror').type('{rightArrow}')
|
||||||
|
cy.get('button').contains('horizontal rule').click()
|
||||||
|
cy.get('.ProseMirror hr').should('exist')
|
||||||
|
cy.get('.ProseMirror h1').should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should add a hr when on a new line', () => {
|
||||||
|
cy.get('.ProseMirror').type('{rightArrow}{enter}')
|
||||||
|
cy.get('button').contains('horizontal rule').click()
|
||||||
|
cy.get('.ProseMirror hr').should('exist')
|
||||||
|
cy.get('.ProseMirror h1').should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should add a br', () => {
|
||||||
|
cy.get('.ProseMirror').type('{rightArrow}')
|
||||||
|
cy.get('button').contains('hard break').click()
|
||||||
|
cy.get('.ProseMirror h1 br').should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should undo', () => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}{backspace}')
|
||||||
|
cy.get('button').contains('undo').click()
|
||||||
|
cy.get('.ProseMirror').should('contain', 'Hello world')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should redo', () => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}{backspace}')
|
||||||
|
cy.get('button').contains('undo').click()
|
||||||
|
cy.get('.ProseMirror').should('contain', 'Hello world')
|
||||||
|
cy.get('button').contains('redo').click()
|
||||||
|
cy.get('.ProseMirror').should('not.contain', 'Hello world')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -68,8 +68,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -26,9 +26,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { NodeViewWrapper, nodeViewProps } from '@tiptap/vue-3'
|
import { nodeViewProps, NodeViewWrapper } from '@tiptap/vue-3'
|
||||||
import { v4 as uuid } from 'uuid'
|
|
||||||
import * as d3 from 'd3'
|
import * as d3 from 'd3'
|
||||||
|
import { v4 as uuid } from 'uuid'
|
||||||
|
|
||||||
const getRandomElement = list => {
|
const getRandomElement = list => {
|
||||||
return list[Math.floor(Math.random() * list.length)]
|
return list[Math.floor(Math.random() * list.length)]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { VueNodeViewRenderer, Node, mergeAttributes } from '@tiptap/vue-3'
|
import { mergeAttributes, Node, VueNodeViewRenderer } from '@tiptap/vue-3'
|
||||||
|
|
||||||
import Component from './Component.vue'
|
import Component from './Component.vue'
|
||||||
|
|
||||||
export default Node.create({
|
export default Node.create({
|
||||||
|
|||||||
38
demos/src/Examples/Drawing/Vue/index.spec.js
Normal file
38
demos/src/Examples/Drawing/Vue/index.spec.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
context('/src/Examples/Drawing/Vue/', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/src/Examples/Drawing/Vue/')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have a working tiptap instance', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
expect(editor).to.not.be.null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have a svg canvas', () => {
|
||||||
|
cy.get('.ProseMirror svg').should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should draw on the svg canvas', () => {
|
||||||
|
cy.get('.ProseMirror svg').should('exist')
|
||||||
|
|
||||||
|
cy.wait(500)
|
||||||
|
|
||||||
|
cy.get('input').then(inputs => {
|
||||||
|
const color = inputs[0].value
|
||||||
|
const size = inputs[1].value
|
||||||
|
|
||||||
|
cy.get('.ProseMirror svg')
|
||||||
|
.click()
|
||||||
|
.trigger('mousedown', { pageX: 100, pageY: 100, which: 1 })
|
||||||
|
.trigger('mousemove', { pageX: 200, pageY: 200, which: 1 })
|
||||||
|
.trigger('mouseup')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror svg path')
|
||||||
|
.should('exist')
|
||||||
|
.should('have.attr', 'stroke-width', size)
|
||||||
|
.should('have.attr', 'stroke', color.toUpperCase())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -3,9 +3,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
|
||||||
import Document from '@tiptap/extension-document'
|
import Document from '@tiptap/extension-document'
|
||||||
import Text from '@tiptap/extension-text'
|
import Text from '@tiptap/extension-text'
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
|
||||||
import Paper from './Paper'
|
import Paper from './Paper'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import React from 'react'
|
|
||||||
import { useEditor, EditorContent } from '@tiptap/react'
|
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
|
||||||
import TextAlign from '@tiptap/extension-text-align'
|
|
||||||
import Highlight from '@tiptap/extension-highlight'
|
|
||||||
import './styles.scss'
|
import './styles.scss'
|
||||||
|
|
||||||
|
import Highlight from '@tiptap/extension-highlight'
|
||||||
|
import TextAlign from '@tiptap/extension-text-align'
|
||||||
|
import { EditorContent, useEditor } from '@tiptap/react'
|
||||||
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
const MenuBar = ({ editor }) => {
|
const MenuBar = ({ editor }) => {
|
||||||
if (!editor) {
|
if (!editor) {
|
||||||
return null
|
return null
|
||||||
|
|||||||
38
demos/src/Examples/Formatting/React/index.spec.js
Normal file
38
demos/src/Examples/Formatting/React/index.spec.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
context('/src/Examples/Formatting/React/', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/src/Examples/Formatting/React/')
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}{backspace}')
|
||||||
|
})
|
||||||
|
|
||||||
|
const marks = [
|
||||||
|
{ label: 'highlight', mark: 'mark' },
|
||||||
|
]
|
||||||
|
|
||||||
|
marks.forEach(m => {
|
||||||
|
it(`sets ${m.label}`, () => {
|
||||||
|
cy.get('.ProseMirror').type('Hello world.{selectall}')
|
||||||
|
cy.get('button').contains(m.label).click()
|
||||||
|
cy.get('.ProseMirror mark').should('exist')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const alignments = [
|
||||||
|
{ label: 'left', alignment: 'left' },
|
||||||
|
{ label: 'center', alignment: 'center' },
|
||||||
|
{ label: 'right', alignment: 'right' },
|
||||||
|
{ label: 'justify', alignment: 'justify' },
|
||||||
|
]
|
||||||
|
|
||||||
|
alignments.forEach(a => {
|
||||||
|
it(`sets ${a.label}`, () => {
|
||||||
|
cy.get('.ProseMirror').type('Hello world.{selectall}')
|
||||||
|
cy.get('button').contains(a.label).click()
|
||||||
|
if (a.alignment !== 'left') {
|
||||||
|
cy.get('.ProseMirror p').should('have.css', 'text-align', a.alignment)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -3,5 +3,36 @@ context('/src/Examples/Formatting/Vue/', () => {
|
|||||||
cy.visit('/src/Examples/Formatting/Vue/')
|
cy.visit('/src/Examples/Formatting/Vue/')
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: Write tests
|
beforeEach(() => {
|
||||||
|
cy.get('.ProseMirror').type('{selectall}{backspace}')
|
||||||
|
})
|
||||||
|
|
||||||
|
const marks = [
|
||||||
|
{ label: 'highlight', mark: 'mark' },
|
||||||
|
]
|
||||||
|
|
||||||
|
marks.forEach(m => {
|
||||||
|
it(`sets ${m.label}`, () => {
|
||||||
|
cy.get('.ProseMirror').type('Hello world.{selectall}')
|
||||||
|
cy.get('button').contains(m.label).click()
|
||||||
|
cy.get('.ProseMirror mark').should('exist')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const alignments = [
|
||||||
|
{ label: 'left', alignment: 'left' },
|
||||||
|
{ label: 'center', alignment: 'center' },
|
||||||
|
{ label: 'right', alignment: 'right' },
|
||||||
|
{ label: 'justify', alignment: 'justify' },
|
||||||
|
]
|
||||||
|
|
||||||
|
alignments.forEach(a => {
|
||||||
|
it(`sets ${a.label}`, () => {
|
||||||
|
cy.get('.ProseMirror').type('Hello world.{selectall}')
|
||||||
|
cy.get('button').contains(a.label).click()
|
||||||
|
if (a.alignment !== 'left') {
|
||||||
|
cy.get('.ProseMirror p').should('have.css', 'text-align', a.alignment)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -41,10 +41,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
|
||||||
import TextAlign from '@tiptap/extension-text-align'
|
|
||||||
import Highlight from '@tiptap/extension-highlight'
|
import Highlight from '@tiptap/extension-highlight'
|
||||||
|
import TextAlign from '@tiptap/extension-text-align'
|
||||||
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import React from 'react'
|
import './styles.scss'
|
||||||
import { useEditor, EditorContent } from '@tiptap/react'
|
|
||||||
import Document from '@tiptap/extension-document'
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Dropcursor from '@tiptap/extension-dropcursor'
|
||||||
|
import Image from '@tiptap/extension-image'
|
||||||
import Paragraph from '@tiptap/extension-paragraph'
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
import Text from '@tiptap/extension-text'
|
import Text from '@tiptap/extension-text'
|
||||||
import Image from '@tiptap/extension-image'
|
import { EditorContent, useEditor } from '@tiptap/react'
|
||||||
import Dropcursor from '@tiptap/extension-dropcursor'
|
import React from 'react'
|
||||||
import './styles.scss'
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
@@ -33,9 +34,7 @@ export default () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<button onClick={addImage}>
|
<button onClick={addImage}>add image from URL</button>
|
||||||
add image from URL
|
|
||||||
</button>
|
|
||||||
<EditorContent editor={editor} />
|
<EditorContent editor={editor} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
24
demos/src/Examples/Images/React/index.spec.js
Normal file
24
demos/src/Examples/Images/React/index.spec.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
context('/src/Examples/Images/React/', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/src/Examples/Images/React/')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('finds image elements inside editor', () => {
|
||||||
|
cy.get('.ProseMirror img').should('have.length', 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('allows removing images', () => {
|
||||||
|
cy.get('.ProseMirror img').should('have.length', 2)
|
||||||
|
cy.get('.ProseMirror img').first().trigger('mousedown', { which: 1 })
|
||||||
|
cy.get('.ProseMirror').type('{backspace}')
|
||||||
|
cy.get('.ProseMirror img').should('have.length', 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('allows images to be added via URL', () => {
|
||||||
|
cy.window().then(win => {
|
||||||
|
cy.stub(win, 'prompt').returns('https://unsplash.it/250/250')
|
||||||
|
cy.get('button').contains('add image from URL').click({ force: false })
|
||||||
|
cy.get('.ProseMirror img').should('have.length', 3)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,7 +1,28 @@
|
|||||||
context('/src/Examples/Images/Vue/', () => {
|
context('/src/Examples/Images/Vue/', () => {
|
||||||
before(() => {
|
beforeEach(() => {
|
||||||
cy.visit('/src/Examples/Images/Vue/')
|
cy.visit('/src/Examples/Images/Vue/')
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: Write tests
|
// TODO: Write tests
|
||||||
|
it('finds image elements inside editor', () => {
|
||||||
|
cy.get('.ProseMirror img').should('have.length', 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('allows removing images', () => {
|
||||||
|
cy.get('.ProseMirror img').should('have.length', 2)
|
||||||
|
cy.get('.ProseMirror img').first().trigger('mousedown', { which: 1 })
|
||||||
|
cy.get('.ProseMirror').type('{backspace}')
|
||||||
|
cy.get('.ProseMirror img').should('have.length', 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('allows images to be added via URL', () => {
|
||||||
|
cy.window().then(win => {
|
||||||
|
cy.stub(win, 'prompt').returns('https://unsplash.it/250/250')
|
||||||
|
|
||||||
|
cy.wait(1000)
|
||||||
|
cy.get('button').contains('add image from URL').click({ force: false })
|
||||||
|
cy.wait(1000)
|
||||||
|
cy.get('.ProseMirror img').should('have.length', 3)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="editor">
|
<div v-if="editor">
|
||||||
<button @click="addImage">
|
<button @click="addImage">add image from URL</button>
|
||||||
add image from URL
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<editor-content :editor="editor" />
|
<editor-content :editor="editor" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
|
||||||
import Document from '@tiptap/extension-document'
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Dropcursor from '@tiptap/extension-dropcursor'
|
||||||
|
import Image from '@tiptap/extension-image'
|
||||||
import Paragraph from '@tiptap/extension-paragraph'
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
import Text from '@tiptap/extension-text'
|
import Text from '@tiptap/extension-text'
|
||||||
import Image from '@tiptap/extension-image'
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
import Dropcursor from '@tiptap/extension-dropcursor'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
|
||||||
import { NodeViewWrapper } from '@tiptap/react'
|
import { NodeViewWrapper } from '@tiptap/react'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
export default props => {
|
export default props => {
|
||||||
const increase = () => {
|
const increase = () => {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Node, mergeAttributes } from '@tiptap/core'
|
import { mergeAttributes, Node } from '@tiptap/core'
|
||||||
import { ReactNodeViewRenderer } from '@tiptap/react'
|
import { ReactNodeViewRenderer } from '@tiptap/react'
|
||||||
|
|
||||||
import Component from './Component.jsx'
|
import Component from './Component.jsx'
|
||||||
|
|
||||||
export default Node.create({
|
export default Node.create({
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import React from 'react'
|
|
||||||
import { useEditor, EditorContent } from '@tiptap/react'
|
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
|
||||||
import ReactComponent from './Extension.js'
|
|
||||||
import './styles.scss'
|
import './styles.scss'
|
||||||
|
|
||||||
|
import { EditorContent, useEditor } from '@tiptap/react'
|
||||||
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import ReactComponent from './Extension.js'
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
extensions: [
|
extensions: [
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
context('/src/Examples/InteractivityComponent/React/', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/src/Examples/InteractivityComponent/React/')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have a working tiptap instance', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
expect(editor).to.not.be.null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should render a custom node', () => {
|
||||||
|
cy.get('.ProseMirror .react-component').should('have.length', 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle count click inside custom node', () => {
|
||||||
|
cy.get('.ProseMirror .react-component button')
|
||||||
|
.should('have.text', 'This button has been clicked 0 times.')
|
||||||
|
.click()
|
||||||
|
.should('have.text', 'This button has been clicked 1 times.')
|
||||||
|
.click()
|
||||||
|
.should('have.text', 'This button has been clicked 2 times.')
|
||||||
|
.click()
|
||||||
|
.should('have.text', 'This button has been clicked 3 times.')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -3,15 +3,13 @@
|
|||||||
<span class="label">Vue Component</span>
|
<span class="label">Vue Component</span>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<button @click="increase">
|
<button @click="increase">This button has been clicked {{ node.attrs.count }} times.</button>
|
||||||
This button has been clicked {{ node.attrs.count }} times.
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</node-view-wrapper>
|
</node-view-wrapper>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { NodeViewWrapper, nodeViewProps } from '@tiptap/vue-3'
|
import { nodeViewProps, NodeViewWrapper } from '@tiptap/vue-3'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Node, mergeAttributes } from '@tiptap/core'
|
import { mergeAttributes, Node } from '@tiptap/core'
|
||||||
import { VueNodeViewRenderer } from '@tiptap/vue-3'
|
import { VueNodeViewRenderer } from '@tiptap/vue-3'
|
||||||
|
|
||||||
import Component from './Component.vue'
|
import Component from './Component.vue'
|
||||||
|
|
||||||
export default Node.create({
|
export default Node.create({
|
||||||
|
|||||||
27
demos/src/Examples/InteractivityComponent/Vue/index.spec.js
Normal file
27
demos/src/Examples/InteractivityComponent/Vue/index.spec.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
context('/src/Examples/InteractivityComponent/Vue/', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/src/Examples/InteractivityComponent/Vue/')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have a working tiptap instance', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
expect(editor).to.not.be.null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should render a custom node', () => {
|
||||||
|
cy.get('.ProseMirror .vue-component').should('have.length', 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle count click inside custom node', () => {
|
||||||
|
cy.get('.ProseMirror .vue-component button')
|
||||||
|
.should('have.text', 'This button has been clicked 0 times.')
|
||||||
|
.click()
|
||||||
|
.should('have.text', 'This button has been clicked 1 times.')
|
||||||
|
.click()
|
||||||
|
.should('have.text', 'This button has been clicked 2 times.')
|
||||||
|
.click()
|
||||||
|
.should('have.text', 'This button has been clicked 3 times.')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -3,8 +3,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
|
||||||
import VueComponent from './Extension.js'
|
import VueComponent from './Extension.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { NodeViewContent, NodeViewWrapper } from '@tiptap/react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react'
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Node, mergeAttributes } from '@tiptap/core'
|
import { mergeAttributes, Node } from '@tiptap/core'
|
||||||
import { ReactNodeViewRenderer } from '@tiptap/react'
|
import { ReactNodeViewRenderer } from '@tiptap/react'
|
||||||
|
|
||||||
import Component from './Component.jsx'
|
import Component from './Component.jsx'
|
||||||
|
|
||||||
export default Node.create({
|
export default Node.create({
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import React from 'react'
|
|
||||||
import { useEditor, EditorContent } from '@tiptap/react'
|
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
|
||||||
import ReactComponent from './Extension.js'
|
|
||||||
import './styles.scss'
|
import './styles.scss'
|
||||||
|
|
||||||
|
import { EditorContent, useEditor } from '@tiptap/react'
|
||||||
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import ReactComponent from './Extension.js'
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
extensions: [
|
extensions: [
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
context('/src/Examples/InteractivityComponentContent/React/', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/src/Examples/InteractivityComponentContent/React/')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have a working tiptap instance', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
expect(editor).to.not.be.null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should render a custom node', () => {
|
||||||
|
cy.get('.ProseMirror .react-component-with-content')
|
||||||
|
.should('have.length', 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should allow text editing inside component', () => {
|
||||||
|
cy.get('.ProseMirror .react-component-with-content .content div')
|
||||||
|
.invoke('attr', 'contentEditable', true)
|
||||||
|
.invoke('text', '')
|
||||||
|
.type('Hello World!')
|
||||||
|
.should('have.text', 'Hello World!')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should allow text editing inside component with markdown text', () => {
|
||||||
|
cy.get('.ProseMirror .react-component-with-content .content div')
|
||||||
|
.invoke('attr', 'contentEditable', true)
|
||||||
|
.invoke('text', '')
|
||||||
|
.type('Hello World! This is **bold**.')
|
||||||
|
.should('have.text', 'Hello World! This is bold.')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror .react-component-with-content .content strong')
|
||||||
|
.should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should remove node via selectall', () => {
|
||||||
|
cy.get('.ProseMirror .react-component-with-content')
|
||||||
|
.should('have.length', 1)
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('{selectall}{backspace}')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror .react-component-with-content')
|
||||||
|
.should('have.length', 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { NodeViewWrapper, NodeViewContent, nodeViewProps } from '@tiptap/vue-3'
|
import { NodeViewContent, nodeViewProps, NodeViewWrapper } from '@tiptap/vue-3'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Node, mergeAttributes } from '@tiptap/core'
|
import { mergeAttributes, Node } from '@tiptap/core'
|
||||||
import { VueNodeViewRenderer } from '@tiptap/vue-3'
|
import { VueNodeViewRenderer } from '@tiptap/vue-3'
|
||||||
|
|
||||||
import Component from './Component.vue'
|
import Component from './Component.vue'
|
||||||
|
|
||||||
export default Node.create({
|
export default Node.create({
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user