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',
|
||||
'cypress',
|
||||
'@typescript-eslint',
|
||||
'simple-import-sort',
|
||||
],
|
||||
env: {
|
||||
'cypress/globals': true,
|
||||
@@ -90,6 +91,8 @@ module.exports = {
|
||||
'@typescript-eslint/ban-types': 'off',
|
||||
'@typescript-eslint/comma-dangle': ['error', 'always-multiline'],
|
||||
'@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 …"
|
||||
validations:
|
||||
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
|
||||
id: reproduction
|
||||
attributes:
|
||||
@@ -46,7 +53,7 @@ body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Did you update your dependencies?
|
||||
description: "Use `yarn upgrade-interactive` to update your dependencies."
|
||||
description: "Use `npm update` to update your dependencies."
|
||||
options:
|
||||
- label: Yes, I’ve updated my dependencies to use the latest version of all packages.
|
||||
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:
|
||||
- documentation
|
||||
assignees:
|
||||
- hanspagel
|
||||
- bdbch
|
||||
body:
|
||||
- type: input
|
||||
id: url
|
||||
|
||||
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -11,5 +11,5 @@ updates:
|
||||
interval: 'weekly'
|
||||
day: 'monday'
|
||||
reviewers:
|
||||
- 'hanspagel'
|
||||
- 'bdbch'
|
||||
|
||||
|
||||
38
.github/workflows/build.yml
vendored
38
.github/workflows/build.yml
vendored
@@ -24,30 +24,30 @@ jobs:
|
||||
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: actions/checkout@v3.0.2
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2.5.1
|
||||
uses: actions/setup-node@v3.4.1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Load cached dependencies
|
||||
uses: actions/cache@v2.1.7
|
||||
uses: actions/cache@v3.0.8
|
||||
id: cache
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
/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
|
||||
id: install-dependencies
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: yarn install
|
||||
run: npm install
|
||||
|
||||
# - name: Fix code style linting errors
|
||||
# id: lint-fix
|
||||
# run: yarn lint:fix
|
||||
# run: npm run lint:fix
|
||||
# continue-on-error: true
|
||||
#
|
||||
# - name: Commit fixed linting errors
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
- name: Lint code
|
||||
id: lint
|
||||
run: yarn lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Send Slack notifications
|
||||
uses: act10ns/slack@v1
|
||||
@@ -80,10 +80,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: actions/checkout@v3.0.2
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2.5.1
|
||||
uses: actions/setup-node@v3.4.1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
@@ -91,15 +91,15 @@ jobs:
|
||||
id: cypress
|
||||
uses: cypress-io/github-action@v2
|
||||
with:
|
||||
cache-key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
|
||||
start: yarn start
|
||||
cache-key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
|
||||
start: npm run start
|
||||
wait-on: 'http://localhost:3000'
|
||||
project: ./tests
|
||||
browser: chrome
|
||||
quiet: true
|
||||
|
||||
- name: Export screenshots (on failure only)
|
||||
uses: actions/upload-artifact@v2.3.1
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
if: failure()
|
||||
with:
|
||||
name: cypress-screenshots
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
retention-days: 7
|
||||
|
||||
- name: Export screen recordings (on failure only)
|
||||
uses: actions/upload-artifact@v2.3.1
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
if: failure()
|
||||
with:
|
||||
name: cypress-videos
|
||||
@@ -136,30 +136,30 @@ jobs:
|
||||
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: actions/checkout@v3.0.2
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2.5.1
|
||||
uses: actions/setup-node@v3.4.1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Load cached dependencies
|
||||
uses: actions/cache@v2.1.7
|
||||
uses: actions/cache@v3.0.8
|
||||
id: cache
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
/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
|
||||
id: install-dependencies
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: yarn install
|
||||
run: npm install
|
||||
|
||||
- name: Try to build the packages
|
||||
id: build-packages
|
||||
run: yarn build:ci
|
||||
run: npm run build:ci
|
||||
|
||||
- name: Send Slack notifications
|
||||
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-schema-list
|
||||
prosemirror-state
|
||||
prosemirror-tables
|
||||
@_ueberdosis/prosemirror-tables
|
||||
prosemirror-transform
|
||||
prosemirror-view
|
||||
react
|
||||
react-dom
|
||||
react-dom/client
|
||||
shiki
|
||||
simplify-js
|
||||
tippy.js
|
||||
uuid
|
||||
y-prosemirror
|
||||
y-webrtc
|
||||
yjs
|
||||
|
||||
2112
demos/package-lock.json
generated
2112
demos/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,33 +4,38 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hocuspocus/provider": "^1.0.0-alpha.29",
|
||||
"d3": "^7.3.0",
|
||||
"fast-glob": "^3.2.11",
|
||||
"highlight.js": "^11.6.0",
|
||||
"lowlight": "^2.7.0",
|
||||
"remixicon": "^2.5.0",
|
||||
"shiki": "^0.10.0",
|
||||
"simplify-js": "^1.2.4",
|
||||
"y-webrtc": "^10.2.2",
|
||||
"yjs": "^13.5.26"
|
||||
"y-prosemirror": "1.0.20",
|
||||
"y-webrtc": "^10.2.3",
|
||||
"yjs": "^13.5.39"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.49",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@vitejs/plugin-react-refresh": "^1.3.6",
|
||||
"@vitejs/plugin-react": "^1.3.1",
|
||||
"@vitejs/plugin-vue": "^1.10.2",
|
||||
"autoprefixer": "^10.4.2",
|
||||
"iframe-resizer": "^4.3.2",
|
||||
"postcss": "^8.4.6",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"sass": "^1.49.7",
|
||||
"svelte": "^3.49.0",
|
||||
"tailwindcss": "^2.2.19",
|
||||
"typescript": "^4.5.5",
|
||||
"uuid": "^8.3.2",
|
||||
"vite": "^2.7.13",
|
||||
"vite": "^2.9.13",
|
||||
"vite-plugin-checker": "^0.3.4",
|
||||
"vue": "^3.0.5",
|
||||
"vue-router": "^4.0.11"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
v-for="(language, index) in sortedTabs"
|
||||
:key="index"
|
||||
@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
|
||||
? 'bg-black text-white'
|
||||
: 'text-black'
|
||||
@@ -21,7 +21,7 @@
|
||||
</div>
|
||||
<div class="overflow-hidden rounded-b-xl">
|
||||
<div
|
||||
class="bg-white border-3 border-black last:rounded-b-xl"
|
||||
class="bg-white border-black border-3 last:rounded-b-xl"
|
||||
:class="[
|
||||
showTabs && firstTabSelected
|
||||
? 'rounded-tr-xl'
|
||||
@@ -34,7 +34,7 @@
|
||||
/>
|
||||
</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 flex-auto px-4 border-b-2 border-gray-800">
|
||||
<button
|
||||
@@ -66,17 +66,17 @@
|
||||
|
||||
<div class="overflow-dark overflow-auto max-h-[500px] relative text-white">
|
||||
<shiki
|
||||
class="overflow-visible p-4"
|
||||
class="p-4 overflow-visible"
|
||||
:language="debugJSON && showDebug ? 'js' : getFileExtension(currentFile.name)"
|
||||
:code="debugJSON && showDebug ? debugJSON : currentFile.content"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between px-4 py-2 text-md text-gray-400 border-t border-gray-800">
|
||||
<a class="flex-shrink min-w-0 overflow-ellipsis overflow-hidden whitespace-nowrap" :href="currentIframeUrl">
|
||||
<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-hidden overflow-ellipsis whitespace-nowrap" :href="currentIframeUrl">
|
||||
{{ name }}/{{ currentTab }}
|
||||
</a>
|
||||
<a class="whitespace-nowrap pl-4" :href="githubUrl" target="_blank">
|
||||
<a class="pl-4 whitespace-nowrap" :href="githubUrl" target="_blank">
|
||||
Edit on GitHub →
|
||||
</a>
|
||||
</div>
|
||||
@@ -87,6 +87,7 @@
|
||||
|
||||
<script>
|
||||
import { getDebugJSON } from '@tiptap/core'
|
||||
|
||||
import DemoFrame from './DemoFrame.vue'
|
||||
import Shiki from './Shiki.vue'
|
||||
|
||||
@@ -114,7 +115,7 @@ export default {
|
||||
sources: {},
|
||||
currentTab: null,
|
||||
currentFile: null,
|
||||
tabOrder: ['React', 'Vue', 'JS'],
|
||||
tabOrder: ['React', 'Vue', 'Svelte', 'JS'],
|
||||
debugJSON: null,
|
||||
showDebug: false,
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Worker from './shiki.worker?worker'
|
||||
// this import is a bugfix
|
||||
// otherwise the `onig.wasm` file is missing in the dist folder
|
||||
import 'shiki/dist/onig.wasm?url'
|
||||
|
||||
import Worker from './shiki.worker?worker'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
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 { createRouter, createWebHistory } from 'vue-router'
|
||||
import App from './index.vue'
|
||||
|
||||
import Demo from './Demo.vue'
|
||||
import { demos } from '@demos'
|
||||
import 'iframe-resizer/js/iframeResizer.contentWindow'
|
||||
import iframeResize from 'iframe-resizer/js/iframeResizer'
|
||||
import './style.css'
|
||||
import App from './index.vue'
|
||||
|
||||
const routes = demos
|
||||
.map(({ name, tabs }) => {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import * as shiki from 'shiki'
|
||||
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 langJS from 'shiki/languages/javascript.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 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
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'iframe-resizer/js/iframeResizer.contentWindow'
|
||||
import { debug } from './helper'
|
||||
import './style.scss'
|
||||
|
||||
import { debug } from './helper'
|
||||
|
||||
export default function init(name: string, source: any) {
|
||||
// @ts-ignore
|
||||
window.source = source
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import 'iframe-resizer/js/iframeResizer.contentWindow'
|
||||
import { debug, splitName } from './helper'
|
||||
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) {
|
||||
// @ts-ignore
|
||||
window.source = source
|
||||
@@ -13,7 +15,11 @@ export default function init(name: string, source: any) {
|
||||
|
||||
import(`../src/${demoCategory}/${demoName}/React/index.jsx`)
|
||||
.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()
|
||||
})
|
||||
}
|
||||
|
||||
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 { debug, splitName } from './helper'
|
||||
import './style.scss'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
|
||||
import { debug, splitName } from './helper'
|
||||
|
||||
export default function init(name: string, source: any) {
|
||||
// @ts-ignore
|
||||
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 { EditorContent, useEditor } from '@tiptap/react'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import React from 'react'
|
||||
|
||||
import { content } from '../content.js'
|
||||
|
||||
const MenuBar = ({ editor }) => {
|
||||
if (!editor) {
|
||||
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/')
|
||||
})
|
||||
|
||||
// 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>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||
|
||||
import { content } from '../content.js'
|
||||
|
||||
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 { NodeViewContent, NodeViewWrapper } from '@tiptap/react'
|
||||
import React from 'react'
|
||||
|
||||
export default ({ node: { attrs: { language: defaultLanguage } }, updateAttributes, extension }) => (
|
||||
<NodeViewWrapper className="code-block">
|
||||
<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
|
||||
// import lowlight from 'lowlight/lib/core'
|
||||
// import { lowlight } from 'lowlight/lib/core'
|
||||
// import javascript from 'highlight.js/lib/languages/javascript'
|
||||
// lowlight.registerLanguage('javascript', javascript)
|
||||
import './styles.scss'
|
||||
|
||||
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
|
||||
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 }) => {
|
||||
if (!editor) {
|
||||
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>
|
||||
|
||||
<script>
|
||||
import { NodeViewWrapper, NodeViewContent, nodeViewProps } from '@tiptap/vue-3'
|
||||
import { NodeViewContent, nodeViewProps, NodeViewWrapper } from '@tiptap/vue-3'
|
||||
|
||||
export default {
|
||||
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>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent, VueNodeViewRenderer } from '@tiptap/vue-3'
|
||||
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 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'
|
||||
|
||||
// load all highlight.js languages
|
||||
import lowlight from 'lowlight'
|
||||
lowlight.registerLanguage('html', html)
|
||||
lowlight.registerLanguage('css', css)
|
||||
lowlight.registerLanguage('js', js)
|
||||
lowlight.registerLanguage('ts', ts)
|
||||
|
||||
// load specific languages only
|
||||
// import lowlight from 'lowlight/lib/core'
|
||||
// import { lowlight } from 'lowlight/lib/core'
|
||||
// import javascript from 'highlight.js/lib/languages/javascript'
|
||||
// lowlight.registerLanguage('javascript', javascript)
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import React, { Fragment } from 'react'
|
||||
import MenuItem from './MenuItem'
|
||||
import './MenuBar.scss'
|
||||
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
import MenuItem from './MenuItem'
|
||||
|
||||
export default ({ editor }) => {
|
||||
const items = [
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react'
|
||||
import './MenuItem.scss'
|
||||
|
||||
import React from 'react'
|
||||
import remixiconUrl from 'remixicon/fonts/remixicon.symbol.svg'
|
||||
|
||||
export default ({
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import React, {
|
||||
useState, useCallback, useEffect,
|
||||
} from 'react'
|
||||
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 './styles.scss'
|
||||
|
||||
import { HocuspocusProvider } from '@hocuspocus/provider'
|
||||
import CharacterCount from '@tiptap/extension-character-count'
|
||||
import Collaboration from '@tiptap/extension-collaboration'
|
||||
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 './styles.scss'
|
||||
|
||||
const colors = ['#958DF1', '#F98181', '#FBBC88', '#FAF594', '#70CFF8', '#94FADB', '#B9F18D']
|
||||
const rooms = ['rooms.10', 'rooms.11', 'rooms.12']
|
||||
@@ -56,7 +59,7 @@ const ydoc = new Y.Doc()
|
||||
const websocketProvider = new HocuspocusProvider({
|
||||
url: 'wss://connect.hocuspocus.cloud',
|
||||
parameters: {
|
||||
key: 'write_B0sHbuV5xwYl6WzGjoqL',
|
||||
key: 'write_bqgvQ3Zwl34V4Nxt43zR',
|
||||
},
|
||||
name: room,
|
||||
document: ydoc,
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
context('/src/Examples/CollaborativeEditing/React/', () => {
|
||||
before(() => {
|
||||
beforeEach(() => {
|
||||
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/', () => {
|
||||
before(() => {
|
||||
beforeEach(() => {
|
||||
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>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import { HocuspocusProvider } from '@hocuspocus/provider'
|
||||
import CharacterCount from '@tiptap/extension-character-count'
|
||||
import Collaboration from '@tiptap/extension-collaboration'
|
||||
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 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 { HocuspocusProvider } from '@hocuspocus/provider'
|
||||
|
||||
import MenuBar from './MenuBar.vue'
|
||||
|
||||
const getRandomElement = list => {
|
||||
@@ -70,7 +71,7 @@ export default {
|
||||
this.provider = new HocuspocusProvider({
|
||||
url: 'wss://connect.hocuspocus.cloud',
|
||||
parameters: {
|
||||
key: 'write_B0sHbuV5xwYl6WzGjoqL',
|
||||
key: 'write_bqgvQ3Zwl34V4Nxt43zR',
|
||||
},
|
||||
name: this.room,
|
||||
document: ydoc,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
} from 'react'
|
||||
import './MentionList.scss'
|
||||
|
||||
import React, {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useState,
|
||||
} from 'react'
|
||||
|
||||
export const MentionList = forwardRef((props, ref) => {
|
||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import React from 'react'
|
||||
import { useEditor, EditorContent } from '@tiptap/react'
|
||||
import './styles.scss'
|
||||
|
||||
import CharacterCount from '@tiptap/extension-character-count'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Mention from '@tiptap/extension-mention'
|
||||
import Paragraph from '@tiptap/extension-paragraph'
|
||||
import Text from '@tiptap/extension-text'
|
||||
import CharacterCount from '@tiptap/extension-character-count'
|
||||
import Mention from '@tiptap/extension-mention'
|
||||
import { EditorContent, useEditor } from '@tiptap/react'
|
||||
import React from 'react'
|
||||
|
||||
import suggestion from './suggestion'
|
||||
import './styles.scss'
|
||||
|
||||
export default () => {
|
||||
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 tippy from 'tippy.js'
|
||||
|
||||
import { MentionList } from './MentionList'
|
||||
|
||||
export default {
|
||||
@@ -20,6 +21,10 @@ export default {
|
||||
editor: props.editor,
|
||||
})
|
||||
|
||||
if (!props.clientRect) {
|
||||
return
|
||||
}
|
||||
|
||||
popup = tippy('body', {
|
||||
getReferenceClientRect: props.clientRect,
|
||||
appendTo: () => document.body,
|
||||
@@ -34,6 +39,10 @@ export default {
|
||||
onUpdate(props) {
|
||||
reactRenderer.updateProps(props)
|
||||
|
||||
if (!props.clientRect) {
|
||||
return
|
||||
}
|
||||
|
||||
popup[0].setProps({
|
||||
getReferenceClientRect: props.clientRect,
|
||||
})
|
||||
|
||||
@@ -1,7 +1,37 @@
|
||||
context('/src/Examples/Community/Vue/', () => {
|
||||
before(() => {
|
||||
beforeEach(() => {
|
||||
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>
|
||||
|
||||
<div class="character-count__text">
|
||||
{{ editor.storage.characterCount.characters() }}/{{ limit }} characters
|
||||
</div>
|
||||
<div class="character-count__text">{{ editor.storage.characterCount.characters() }}/{{ limit }} characters</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||
import CharacterCount from '@tiptap/extension-character-count'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Mention from '@tiptap/extension-mention'
|
||||
import Paragraph from '@tiptap/extension-paragraph'
|
||||
import Text from '@tiptap/extension-text'
|
||||
import CharacterCount from '@tiptap/extension-character-count'
|
||||
import Mention from '@tiptap/extension-mention'
|
||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||
|
||||
import suggestion from './suggestion'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { VueRenderer } from '@tiptap/vue-3'
|
||||
import tippy from 'tippy.js'
|
||||
|
||||
import MentionList from './MentionList.vue'
|
||||
|
||||
export default {
|
||||
@@ -23,6 +24,10 @@ export default {
|
||||
editor: props.editor,
|
||||
})
|
||||
|
||||
if (!props.clientRect) {
|
||||
return
|
||||
}
|
||||
|
||||
popup = tippy('body', {
|
||||
getReferenceClientRect: props.clientRect,
|
||||
appendTo: () => document.body,
|
||||
@@ -37,6 +42,10 @@ export default {
|
||||
onUpdate(props) {
|
||||
component.updateProps(props)
|
||||
|
||||
if (!props.clientRect) {
|
||||
return
|
||||
}
|
||||
|
||||
popup[0].setProps({
|
||||
getReferenceClientRect: props.clientRect,
|
||||
})
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from 'react'
|
||||
import { useEditor, EditorContent } from '@tiptap/react'
|
||||
import './styles.scss'
|
||||
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Placeholder from '@tiptap/extension-placeholder'
|
||||
import { EditorContent, useEditor } from '@tiptap/react'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import './styles.scss'
|
||||
import React from 'react'
|
||||
|
||||
const CustomDocument = Document.extend({
|
||||
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/', () => {
|
||||
before(() => {
|
||||
beforeEach(() => {
|
||||
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>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Placeholder from '@tiptap/extension-placeholder'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||
|
||||
const CustomDocument = Document.extend({
|
||||
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 { EditorContent, useEditor } from '@tiptap/react'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import React from 'react'
|
||||
|
||||
const MenuBar = ({ editor }) => {
|
||||
if (!editor) {
|
||||
return null
|
||||
|
||||
@@ -19,4 +19,99 @@ context('/src/Examples/Default/React/', () => {
|
||||
.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')
|
||||
})
|
||||
})
|
||||
|
||||
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')
|
||||
.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>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { NodeViewWrapper, nodeViewProps } from '@tiptap/vue-3'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { nodeViewProps, NodeViewWrapper } from '@tiptap/vue-3'
|
||||
import * as d3 from 'd3'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
const getRandomElement = list => {
|
||||
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'
|
||||
|
||||
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>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Text from '@tiptap/extension-text'
|
||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||
|
||||
import Paper from './Paper'
|
||||
|
||||
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 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 }) => {
|
||||
if (!editor) {
|
||||
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/')
|
||||
})
|
||||
|
||||
// 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>
|
||||
|
||||
<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 TextAlign from '@tiptap/extension-text-align'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import React from 'react'
|
||||
import { useEditor, EditorContent } from '@tiptap/react'
|
||||
import './styles.scss'
|
||||
|
||||
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 Text from '@tiptap/extension-text'
|
||||
import Image from '@tiptap/extension-image'
|
||||
import Dropcursor from '@tiptap/extension-dropcursor'
|
||||
import './styles.scss'
|
||||
import { EditorContent, useEditor } from '@tiptap/react'
|
||||
import React from 'react'
|
||||
|
||||
export default () => {
|
||||
const editor = useEditor({
|
||||
@@ -33,9 +34,7 @@ export default () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={addImage}>
|
||||
add image from URL
|
||||
</button>
|
||||
<button onClick={addImage}>add image from URL</button>
|
||||
<EditorContent editor={editor} />
|
||||
</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/', () => {
|
||||
before(() => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/src/Examples/Images/Vue/')
|
||||
})
|
||||
|
||||
// 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>
|
||||
<div v-if="editor">
|
||||
<button @click="addImage">
|
||||
add image from URL
|
||||
</button>
|
||||
<button @click="addImage">add image from URL</button>
|
||||
</div>
|
||||
<editor-content :editor="editor" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||
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 Text from '@tiptap/extension-text'
|
||||
import Image from '@tiptap/extension-image'
|
||||
import Dropcursor from '@tiptap/extension-dropcursor'
|
||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { NodeViewWrapper } from '@tiptap/react'
|
||||
import React from 'react'
|
||||
|
||||
export default props => {
|
||||
const increase = () => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Node, mergeAttributes } from '@tiptap/core'
|
||||
import { mergeAttributes, Node } from '@tiptap/core'
|
||||
import { ReactNodeViewRenderer } from '@tiptap/react'
|
||||
|
||||
import Component from './Component.jsx'
|
||||
|
||||
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 { EditorContent, useEditor } from '@tiptap/react'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import React from 'react'
|
||||
|
||||
import ReactComponent from './Extension.js'
|
||||
|
||||
export default () => {
|
||||
const editor = useEditor({
|
||||
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>
|
||||
|
||||
<div class="content">
|
||||
<button @click="increase">
|
||||
This button has been clicked {{ node.attrs.count }} times.
|
||||
</button>
|
||||
<button @click="increase">This button has been clicked {{ node.attrs.count }} times.</button>
|
||||
</div>
|
||||
</node-view-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { NodeViewWrapper, nodeViewProps } from '@tiptap/vue-3'
|
||||
import { nodeViewProps, NodeViewWrapper } from '@tiptap/vue-3'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Node, mergeAttributes } from '@tiptap/core'
|
||||
import { mergeAttributes, Node } from '@tiptap/core'
|
||||
import { VueNodeViewRenderer } from '@tiptap/vue-3'
|
||||
|
||||
import Component from './Component.vue'
|
||||
|
||||
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>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||
|
||||
import VueComponent from './Extension.js'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NodeViewContent, NodeViewWrapper } from '@tiptap/react'
|
||||
import React from 'react'
|
||||
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react'
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Node, mergeAttributes } from '@tiptap/core'
|
||||
import { mergeAttributes, Node } from '@tiptap/core'
|
||||
import { ReactNodeViewRenderer } from '@tiptap/react'
|
||||
|
||||
import Component from './Component.jsx'
|
||||
|
||||
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 { EditorContent, useEditor } from '@tiptap/react'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import React from 'react'
|
||||
|
||||
import ReactComponent from './Extension.js'
|
||||
|
||||
export default () => {
|
||||
const editor = useEditor({
|
||||
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>
|
||||
|
||||
<script>
|
||||
import { NodeViewWrapper, NodeViewContent, nodeViewProps } from '@tiptap/vue-3'
|
||||
import { NodeViewContent, nodeViewProps, NodeViewWrapper } from '@tiptap/vue-3'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Node, mergeAttributes } from '@tiptap/core'
|
||||
import { mergeAttributes, Node } from '@tiptap/core'
|
||||
import { VueNodeViewRenderer } from '@tiptap/vue-3'
|
||||
|
||||
import Component from './Component.vue'
|
||||
|
||||
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