snapshot of https://github.com/ueberdosis/tiptap.git (main, 2026-09-15; blobs > 131072B omitted)
Some checks failed
Verify / Install node dependencies (push) Failing after 2s
Verify / Build packages (push) Has been skipped
Verify / Check linting & formatting (push) Has been skipped
Verify / Run unit tests (push) Has been skipped
Verify / Run e2e tests (shard 1/4) (push) Has been skipped
Verify / Run e2e tests (shard 2/4) (push) Has been skipped
Verify / Run e2e tests (shard 3/4) (push) Has been skipped
Verify / Run e2e tests (shard 4/4) (push) Has been skipped
Publish / Resolve publish config (push) Failing after 2s
Publish / Send notification to Slack (push) Has been skipped
Publish / Send failure notification to Slack (push) Failing after 8s
Verify / Merge e2e reports (push) Has been skipped
Publish / Build (push) Has been skipped
Publish / Bump Versions OR Release to Registry (push) Has been skipped
Some checks failed
Verify / Install node dependencies (push) Failing after 2s
Verify / Build packages (push) Has been skipped
Verify / Check linting & formatting (push) Has been skipped
Verify / Run unit tests (push) Has been skipped
Verify / Run e2e tests (shard 1/4) (push) Has been skipped
Verify / Run e2e tests (shard 2/4) (push) Has been skipped
Verify / Run e2e tests (shard 3/4) (push) Has been skipped
Verify / Run e2e tests (shard 4/4) (push) Has been skipped
Publish / Resolve publish config (push) Failing after 2s
Publish / Send notification to Slack (push) Has been skipped
Publish / Send failure notification to Slack (push) Failing after 8s
Verify / Merge e2e reports (push) Has been skipped
Publish / Build (push) Has been skipped
Publish / Bump Versions OR Release to Registry (push) Has been skipped
This commit is contained in:
1185
packages/extension-invisible-characters/CHANGELOG.md
Normal file
1185
packages/extension-invisible-characters/CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
18
packages/extension-invisible-characters/README.md
Normal file
18
packages/extension-invisible-characters/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# @tiptap/extension-invisible-characters
|
||||
|
||||
[](https://www.npmjs.com/package/@tiptap/extension-invisible-characters)
|
||||
[](https://npmcharts.com/compare/tiptap?minimal=true)
|
||||
[](https://www.npmjs.com/package/@tiptap/extension-invisible-characters)
|
||||
[](https://github.com/sponsors/ueberdosis)
|
||||
|
||||
## Introduction
|
||||
|
||||
Tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as _New York Times_, _The Guardian_ or _Atlassian_.
|
||||
|
||||
## Official Documentation
|
||||
|
||||
Documentation can be found on the [Tiptap website](https://tiptap.dev).
|
||||
|
||||
## License
|
||||
|
||||
Tiptap is open sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap/blob/main/LICENSE.md).
|
||||
54
packages/extension-invisible-characters/package.json
Normal file
54
packages/extension-invisible-characters/package.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "@tiptap/extension-invisible-characters",
|
||||
"version": "3.30.3",
|
||||
"description": "invisible characters extension for tiptap",
|
||||
"keywords": [
|
||||
"tiptap",
|
||||
"tiptap extension"
|
||||
],
|
||||
"homepage": "https://tiptap.dev/docs/editor/extensions/functionality/invisiblecharacters",
|
||||
"bugs": {
|
||||
"url": "https://github.com/ueberdosis/tiptap/issues"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ueberdosis/tiptap",
|
||||
"directory": "packages/extension-invisible-characters"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
"dist"
|
||||
],
|
||||
"type": "module",
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": {
|
||||
"import": "./dist/index.d.ts",
|
||||
"require": "./dist/index.d.cts"
|
||||
},
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vp pack"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tiptap/core": "workspace:^",
|
||||
"@tiptap/extension-text-style": "workspace:^",
|
||||
"@tiptap/pm": "workspace:^"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "workspace:*",
|
||||
"@tiptap/extension-text-style": "workspace:*",
|
||||
"@tiptap/pm": "workspace:*"
|
||||
}
|
||||
}
|
||||
11
packages/extension-invisible-characters/src/index.ts
Normal file
11
packages/extension-invisible-characters/src/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { InvisibleCharacters } from './invisible-characters.js'
|
||||
|
||||
export * from './invisible-characters.js'
|
||||
export * from './plugin/invisible-characters/hardBreak.js'
|
||||
export * from './plugin/invisible-characters/paragraph.js'
|
||||
export * from './plugin/invisible-characters/space.js'
|
||||
export * from './plugin/InvisibleCharacter.js'
|
||||
export * from './plugin/InvisibleNode.js'
|
||||
export * from './types.js'
|
||||
|
||||
export default InvisibleCharacters
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Extension } from '@tiptap/core'
|
||||
|
||||
import { InvisibleCharactersPlugin, InvisibleCharactersPluginKey } from './plugin/index.js'
|
||||
import { HardBreakNode } from './plugin/invisible-characters/hardBreak.js'
|
||||
import { ParagraphNode } from './plugin/invisible-characters/paragraph.js'
|
||||
import { SpaceCharacter } from './plugin/invisible-characters/space.js'
|
||||
import type { InvisibleCharactersOptions } from './types.js'
|
||||
|
||||
export { HardBreakNode, ParagraphNode, SpaceCharacter }
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
invisibleCharacters: {
|
||||
/**
|
||||
* Show invisible characters
|
||||
*/
|
||||
showInvisibleCharacters: (show?: boolean) => ReturnType
|
||||
|
||||
/**
|
||||
* Hide invisible characters
|
||||
*/
|
||||
hideInvisibleCharacters: () => ReturnType
|
||||
|
||||
/**
|
||||
* Toggle invisible characters
|
||||
*/
|
||||
toggleInvisibleCharacters: () => ReturnType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const InvisibleCharacters = Extension.create<InvisibleCharactersOptions>({
|
||||
name: 'invisibleCharacters',
|
||||
|
||||
addOptions() {
|
||||
return {
|
||||
visible: true,
|
||||
builders: [new SpaceCharacter(), new ParagraphNode(), new HardBreakNode()],
|
||||
injectCSS: true,
|
||||
injectNonce: undefined,
|
||||
}
|
||||
},
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
return [InvisibleCharactersPlugin(this.editor.state, this.options)]
|
||||
},
|
||||
|
||||
addStorage() {
|
||||
return {
|
||||
visibility: () => this.options.visible,
|
||||
}
|
||||
},
|
||||
|
||||
onBeforeCreate() {
|
||||
this.storage.visibility = () => {
|
||||
return InvisibleCharactersPluginKey.getState(this.editor.state)?.visible
|
||||
}
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
return {
|
||||
showInvisibleCharacters:
|
||||
(visibility = true) =>
|
||||
({ dispatch, tr }) => {
|
||||
if (dispatch) {
|
||||
tr.setMeta('setInvisibleCharactersVisible', visibility)
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
hideInvisibleCharacters:
|
||||
() =>
|
||||
({ dispatch, tr }) => {
|
||||
if (dispatch) {
|
||||
tr.setMeta('setInvisibleCharactersVisible', false)
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
toggleInvisibleCharacters:
|
||||
() =>
|
||||
({ dispatch, tr, state }) => {
|
||||
const visibility = !InvisibleCharactersPluginKey.getState(state)?.visible
|
||||
|
||||
if (dispatch) {
|
||||
tr.setMeta('setInvisibleCharactersVisible', visibility)
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export default InvisibleCharacters
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { Node as PMNode } from '@tiptap/pm/model'
|
||||
import type { DecorationSet } from '@tiptap/pm/view'
|
||||
|
||||
import { createDecorationWidget } from './utils/create-decoration-widget.js'
|
||||
import { textBetween } from './utils/text-between.js'
|
||||
|
||||
export interface InvisibleCharacterOptions {
|
||||
type: string
|
||||
predicate: (value: string) => boolean
|
||||
priority?: number
|
||||
content?: string
|
||||
}
|
||||
|
||||
export class InvisibleCharacter {
|
||||
predicate: (value: string) => boolean
|
||||
|
||||
content?: string
|
||||
|
||||
type: string
|
||||
|
||||
priority: number
|
||||
|
||||
constructor(options: InvisibleCharacterOptions) {
|
||||
this.predicate = options.predicate
|
||||
this.type = options.type
|
||||
this.content = options.content
|
||||
this.priority = options.priority || 100
|
||||
}
|
||||
|
||||
createDecoration(from: number, to: number, doc: PMNode, decorations: DecorationSet) {
|
||||
const textContent = textBetween(from, to, doc)
|
||||
|
||||
return textContent.reduce((oldDecorations, currentPosition) => {
|
||||
return currentPosition.text.split('').reduce((innerDecorations, char, i) => {
|
||||
return this.test(char)
|
||||
? innerDecorations.add(doc, [
|
||||
createDecorationWidget(currentPosition.pos + i, this.type, this.content),
|
||||
])
|
||||
: innerDecorations
|
||||
}, oldDecorations)
|
||||
}, decorations)
|
||||
}
|
||||
|
||||
test(value: string) {
|
||||
return this.predicate(value)
|
||||
}
|
||||
}
|
||||
|
||||
export default InvisibleCharacter
|
||||
@@ -0,0 +1,93 @@
|
||||
import type { EditorState } from '@tiptap/pm/state'
|
||||
import { AllSelection, Plugin, PluginKey } from '@tiptap/pm/state'
|
||||
import { DecorationSet } from '@tiptap/pm/view'
|
||||
|
||||
import type { InvisibleCharactersOptions, PluginState } from '../types.js'
|
||||
import { stateReducer } from './reducers.js'
|
||||
import { style } from './style.js'
|
||||
import { createStyleTag } from './utils/create-style-tag.js'
|
||||
import { getUpdatedRanges } from './utils/get-updated-ranges.js'
|
||||
|
||||
export const InvisibleCharactersPluginKey = new PluginKey<PluginState>('invisibleCharacters')
|
||||
|
||||
export const InvisibleCharactersPlugin = (
|
||||
state: EditorState,
|
||||
options: InvisibleCharactersOptions,
|
||||
) => {
|
||||
const key = InvisibleCharactersPluginKey
|
||||
const emptyDecorationSet = DecorationSet.create(state.doc, [])
|
||||
|
||||
const addDecorationsBetweenPositions = (
|
||||
from: number,
|
||||
to: number,
|
||||
editorState: EditorState,
|
||||
decorations: DecorationSet,
|
||||
) => {
|
||||
return options.builders
|
||||
.sort((builderA, builderB) => {
|
||||
if (builderA.priority > builderB.priority) {
|
||||
return 1
|
||||
}
|
||||
|
||||
return -1
|
||||
})
|
||||
.reduce((newDecos, builder) => {
|
||||
return builder.createDecoration(from, to, editorState.doc, newDecos)
|
||||
}, decorations)
|
||||
}
|
||||
|
||||
return new Plugin({
|
||||
key,
|
||||
|
||||
state: {
|
||||
init: () => {
|
||||
const { $from, $to } = new AllSelection(state.doc)
|
||||
|
||||
if (options.injectCSS && document) {
|
||||
createStyleTag(style, options.injectNonce)
|
||||
}
|
||||
|
||||
return {
|
||||
visible: options.visible,
|
||||
decorations: addDecorationsBetweenPositions(
|
||||
$from.pos,
|
||||
$to.pos,
|
||||
state,
|
||||
DecorationSet.empty,
|
||||
),
|
||||
}
|
||||
},
|
||||
apply: (tr, pluginState, _, currentState) => {
|
||||
const newPluginState = stateReducer(
|
||||
pluginState,
|
||||
tr.getMeta('setInvisibleCharactersVisible'),
|
||||
)
|
||||
|
||||
const decorations = getUpdatedRanges(tr).reduce(
|
||||
(nextDecorations, [from, to]) => {
|
||||
return addDecorationsBetweenPositions(from, to, currentState, nextDecorations)
|
||||
},
|
||||
newPluginState.decorations.map(tr.mapping, tr.doc),
|
||||
)
|
||||
|
||||
return {
|
||||
...newPluginState,
|
||||
decorations,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
props: {
|
||||
decorations(editorState) {
|
||||
const pluginState = this.getState(editorState)
|
||||
|
||||
const visible = pluginState?.visible
|
||||
const decorations = pluginState?.decorations
|
||||
|
||||
return visible ? decorations : emptyDecorationSet
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export default InvisibleCharactersPlugin
|
||||
@@ -0,0 +1,61 @@
|
||||
import type { Node as PMNode } from '@tiptap/pm/model'
|
||||
import type { DecorationSet } from '@tiptap/pm/view'
|
||||
|
||||
import { createDecorationWidget } from './utils/create-decoration-widget.js'
|
||||
|
||||
export interface InvisibleNodeOptions {
|
||||
content?: string
|
||||
position?: (node: PMNode, pos: number) => number
|
||||
predicate: (value: PMNode) => boolean
|
||||
priority?: number
|
||||
type: string
|
||||
}
|
||||
|
||||
/** Default position for nodes is always at the end of the node */
|
||||
const defaultPosition = (node: PMNode, pos: number) => pos + node.nodeSize - 1
|
||||
|
||||
export class InvisibleNode {
|
||||
predicate: (value: PMNode) => boolean
|
||||
|
||||
position: (node: PMNode, pos: number) => number
|
||||
|
||||
content?: string
|
||||
|
||||
type: string
|
||||
|
||||
priority: number
|
||||
|
||||
constructor(options: InvisibleNodeOptions) {
|
||||
this.predicate = options.predicate
|
||||
this.type = options.type
|
||||
this.position = options.position || defaultPosition
|
||||
this.content = options.content
|
||||
this.priority = options.priority || 100
|
||||
}
|
||||
|
||||
createDecoration(from: number, to: number, doc: PMNode, decorations: DecorationSet) {
|
||||
let newDecorations = decorations
|
||||
|
||||
doc.nodesBetween(from, to, (node, pos) => {
|
||||
if (this.test(node)) {
|
||||
const decorationPosition = this.position(node, pos)
|
||||
const oldDecorations = newDecorations.find(
|
||||
decorationPosition,
|
||||
decorationPosition,
|
||||
spec => spec.key === this.type,
|
||||
)
|
||||
|
||||
newDecorations = newDecorations
|
||||
.remove(oldDecorations)
|
||||
.add(doc, [createDecorationWidget(decorationPosition, this.type, this.content)])
|
||||
}
|
||||
})
|
||||
return newDecorations
|
||||
}
|
||||
|
||||
test(value: PMNode) {
|
||||
return this.predicate(value)
|
||||
}
|
||||
}
|
||||
|
||||
export default InvisibleNode
|
||||
@@ -0,0 +1 @@
|
||||
export * from './InvisibleCharactersPlugin.js'
|
||||
@@ -0,0 +1,12 @@
|
||||
import { InvisibleNode } from '../InvisibleNode.js'
|
||||
|
||||
export class HardBreakNode extends InvisibleNode {
|
||||
constructor() {
|
||||
super({
|
||||
type: 'break',
|
||||
predicate: node => node.type === node.type.schema.nodes.hardBreak,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default HardBreakNode
|
||||
@@ -0,0 +1,12 @@
|
||||
import { InvisibleNode } from '../InvisibleNode.js'
|
||||
|
||||
export class ParagraphNode extends InvisibleNode {
|
||||
constructor() {
|
||||
super({
|
||||
type: 'paragraph',
|
||||
predicate: node => node.type === node.type.schema.nodes.paragraph,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default ParagraphNode
|
||||
@@ -0,0 +1,12 @@
|
||||
import { InvisibleCharacter } from '../InvisibleCharacter.js'
|
||||
|
||||
export class SpaceCharacter extends InvisibleCharacter {
|
||||
constructor() {
|
||||
super({
|
||||
type: 'space',
|
||||
predicate: (char: string) => char === ' ',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default SpaceCharacter
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { PluginState } from '../types.js'
|
||||
|
||||
export const stateReducer = (state: PluginState, visible: boolean): PluginState => {
|
||||
if (visible === undefined) {
|
||||
return state
|
||||
}
|
||||
|
||||
return { ...state, visible }
|
||||
}
|
||||
|
||||
export default stateReducer
|
||||
43
packages/extension-invisible-characters/src/plugin/style.ts
Normal file
43
packages/extension-invisible-characters/src/plugin/style.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
export const style = `.tiptap-invisible-character {
|
||||
height: 0;
|
||||
padding: 0;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.tiptap-invisible-character::before {
|
||||
caret-color: inherit;
|
||||
color: #aaa;
|
||||
display: inline-block;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1em;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.tiptap-invisible-character--space::before {
|
||||
content: '·'
|
||||
}
|
||||
|
||||
.tiptap-invisible-character--break::before {
|
||||
content: '¬'
|
||||
}
|
||||
|
||||
.tiptap-invisible-character--paragraph::before {
|
||||
content: '¶'
|
||||
}
|
||||
|
||||
.tiptap-invisible-character + img.ProseMirror-separator {
|
||||
height: 0 !important;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
width: 0 !important;
|
||||
}
|
||||
|
||||
.is-empty[data-placeholder].has-focus > .tiptap-invisible-character {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
|
||||
export default style
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Decoration } from '@tiptap/pm/view'
|
||||
|
||||
export const createDecorationWidget = (pos: number, type: string, content?: string) => {
|
||||
const createElement = () => {
|
||||
const newElement = document.createElement('span')
|
||||
|
||||
newElement.classList.add('tiptap-invisible-character')
|
||||
newElement.classList.add(`tiptap-invisible-character--${type}`)
|
||||
|
||||
if (content) {
|
||||
newElement.textContent = content
|
||||
}
|
||||
|
||||
return newElement
|
||||
}
|
||||
|
||||
return Decoration.widget(pos, createElement, {
|
||||
key: type,
|
||||
marks: [],
|
||||
side: 1000,
|
||||
})
|
||||
}
|
||||
|
||||
export default createDecorationWidget
|
||||
@@ -0,0 +1,23 @@
|
||||
export const createStyleTag = (style: string, nonce?: string): HTMLStyleElement => {
|
||||
const tiptapStyleTag = <HTMLStyleElement>(
|
||||
document.querySelector('style[data-tiptap-extension-invisible-characters-style]')
|
||||
)
|
||||
|
||||
if (tiptapStyleTag !== null) {
|
||||
return tiptapStyleTag
|
||||
}
|
||||
|
||||
const styleNode = document.createElement('style')
|
||||
|
||||
if (nonce) {
|
||||
styleNode.setAttribute('nonce', nonce)
|
||||
}
|
||||
|
||||
styleNode.setAttribute('data-tiptap-extension-invisible-characters-style', '')
|
||||
styleNode.innerHTML = style
|
||||
document.getElementsByTagName('head')[0].appendChild(styleNode)
|
||||
|
||||
return styleNode
|
||||
}
|
||||
|
||||
export default createStyleTag
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { Transaction } from '@tiptap/pm/state'
|
||||
|
||||
type StepRange = [from: number, to: number]
|
||||
|
||||
export const getUpdatedRanges = ({ mapping }: Transaction): StepRange[] => {
|
||||
const ranges: StepRange[] = []
|
||||
|
||||
mapping.maps.forEach((stepMap, i) => {
|
||||
stepMap.forEach((_oldStart, _oldEnd, newStart, newEnd) => {
|
||||
ranges.push([mapping.slice(i + 1).map(newStart), mapping.slice(i + 1).map(newEnd)])
|
||||
})
|
||||
})
|
||||
|
||||
return ranges
|
||||
}
|
||||
|
||||
export default getUpdatedRanges
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { Node as PMNode } from '@tiptap/pm/model'
|
||||
|
||||
import type { Position } from '../../types.js'
|
||||
|
||||
export const textBetween = (from: number, to: number, doc: PMNode) => {
|
||||
const positions: Position[] = []
|
||||
|
||||
doc.nodesBetween(from, to, (node, pos) => {
|
||||
if (node.isText) {
|
||||
const offset = Math.max(from, pos) - pos
|
||||
|
||||
positions.push({
|
||||
pos: pos + offset,
|
||||
text: node.text?.slice(offset, to - pos) || '',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return positions
|
||||
}
|
||||
|
||||
export default textBetween
|
||||
34
packages/extension-invisible-characters/src/types.ts
Normal file
34
packages/extension-invisible-characters/src/types.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { DecorationSet } from '@tiptap/pm/view'
|
||||
|
||||
import type { InvisibleCharacter } from './plugin/InvisibleCharacter.js'
|
||||
import type { InvisibleNode } from './plugin/InvisibleNode.js'
|
||||
|
||||
export interface InvisibleCharactersOptions {
|
||||
visible: boolean
|
||||
builders: Array<InvisibleCharacter | InvisibleNode>
|
||||
injectCSS: boolean
|
||||
injectNonce: string | undefined
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
pos: number
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface PluginState {
|
||||
visible: boolean
|
||||
decorations: DecorationSet
|
||||
}
|
||||
|
||||
export interface InvisibleCharactersStorage {
|
||||
/**
|
||||
* Get whether the invisible characters are shown or not
|
||||
*/
|
||||
visibility: () => boolean
|
||||
}
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Storage {
|
||||
invisibleCharacters: InvisibleCharactersStorage
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Editor } from '@tiptap/core'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import HardBreak from '@tiptap/extension-hard-break'
|
||||
import Heading from '@tiptap/extension-heading'
|
||||
import InvisibleCharacters from '@tiptap/extension-invisible-characters'
|
||||
import Paragraph from '@tiptap/extension-paragraph'
|
||||
import Text from '@tiptap/extension-text'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vite-plus/test'
|
||||
|
||||
describe('InvisibleCharacters', () => {
|
||||
let editor: Editor
|
||||
|
||||
beforeEach(() => {
|
||||
const element = document.createElement('div')
|
||||
document.body.appendChild(element)
|
||||
editor = new Editor({
|
||||
element,
|
||||
extensions: [Document, Paragraph, Heading, Text, InvisibleCharacters, HardBreak],
|
||||
content:
|
||||
'<h1>This is a heading.</h1><p>This<br>is<br>a<br>paragraph.</p><p>Another paragraph.</p>',
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
editor.destroy()
|
||||
})
|
||||
|
||||
it('renders invisible character decorations when shown', () => {
|
||||
editor.commands.showInvisibleCharacters()
|
||||
const decorations = editor.view.dom.querySelectorAll('[class*="tiptap-invisible-character"]')
|
||||
expect(decorations.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
9
packages/extension-invisible-characters/vite.config.ts
Normal file
9
packages/extension-invisible-characters/vite.config.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { defineConfig } from 'vite-plus'
|
||||
import { basePackConfig } from '../../pack.config.mjs'
|
||||
|
||||
export default defineConfig({
|
||||
pack: {
|
||||
entry: ['src/index.ts'],
|
||||
...basePackConfig(),
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user