@tiptap/extension-invisible-characters 2.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/index.cjs +310 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +300 -0
- package/dist/index.js.map +1 -0
- package/dist/index.umd.js +312 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/invisible-characters.d.ts +27 -0
- package/dist/invisible-characters.d.ts.map +1 -0
- package/dist/plugin/InvisibleCharacter.d.ts +19 -0
- package/dist/plugin/InvisibleCharacter.d.ts.map +1 -0
- package/dist/plugin/InvisibleCharactersPlugin.d.ts +10 -0
- package/dist/plugin/InvisibleCharactersPlugin.d.ts.map +1 -0
- package/dist/plugin/InvisibleNode.d.ts +21 -0
- package/dist/plugin/InvisibleNode.d.ts.map +1 -0
- package/dist/plugin/index.d.ts +2 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/invisible-characters/hardBreak.d.ts +6 -0
- package/dist/plugin/invisible-characters/hardBreak.d.ts.map +1 -0
- package/dist/plugin/invisible-characters/paragraph.d.ts +6 -0
- package/dist/plugin/invisible-characters/paragraph.d.ts.map +1 -0
- package/dist/plugin/invisible-characters/space.d.ts +6 -0
- package/dist/plugin/invisible-characters/space.d.ts.map +1 -0
- package/dist/plugin/reducers.d.ts +4 -0
- package/dist/plugin/reducers.d.ts.map +1 -0
- package/dist/plugin/style.d.ts +3 -0
- package/dist/plugin/style.d.ts.map +1 -0
- package/dist/plugin/utils/create-decoration-widget.d.ts +4 -0
- package/dist/plugin/utils/create-decoration-widget.d.ts.map +1 -0
- package/dist/plugin/utils/create-style-tag.d.ts +3 -0
- package/dist/plugin/utils/create-style-tag.d.ts.map +1 -0
- package/dist/plugin/utils/get-updated-ranges.d.ts +5 -0
- package/dist/plugin/utils/get-updated-ranges.d.ts.map +1 -0
- package/dist/plugin/utils/text-between.d.ts +5 -0
- package/dist/plugin/utils/text-between.d.ts.map +1 -0
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +48 -0
- package/src/index.ts +11 -0
- package/src/invisible-characters.ts +99 -0
- package/src/plugin/InvisibleCharacter.ts +47 -0
- package/src/plugin/InvisibleCharactersPlugin.ts +79 -0
- package/src/plugin/InvisibleNode.ts +61 -0
- package/src/plugin/index.ts +1 -0
- package/src/plugin/invisible-characters/hardBreak.ts +12 -0
- package/src/plugin/invisible-characters/paragraph.ts +12 -0
- package/src/plugin/invisible-characters/space.ts +12 -0
- package/src/plugin/reducers.ts +14 -0
- package/src/plugin/style.ts +43 -0
- package/src/plugin/utils/create-decoration-widget.ts +28 -0
- package/src/plugin/utils/create-style-tag.ts +21 -0
- package/src/plugin/utils/get-updated-ranges.ts +20 -0
- package/src/plugin/utils/text-between.ts +22 -0
- package/src/types.ts +21 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAE/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAEzD,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,KAAK,CAAC,kBAAkB,GAAG,aAAa,CAAC,CAAC;IACpD,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAED,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,aAAa,CAAC;CAC5B"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tiptap/extension-invisible-characters",
|
|
3
|
+
"description": "invisible characters extension for tiptap",
|
|
4
|
+
"version": "2.22.0",
|
|
5
|
+
"homepage": "https://tiptap.dev/docs/editor/extensions/functionality/invisiblecharacters",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"tiptap",
|
|
8
|
+
"tiptap extension"
|
|
9
|
+
],
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"funding": {
|
|
12
|
+
"type": "github",
|
|
13
|
+
"url": "https://github.com/sponsors/ueberdosis"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js",
|
|
20
|
+
"require": "./dist/index.cjs"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"main": "dist/index.cjs",
|
|
24
|
+
"module": "dist/index.js",
|
|
25
|
+
"umd": "dist/index.umd.js",
|
|
26
|
+
"types": "dist/index.d.ts",
|
|
27
|
+
"files": [
|
|
28
|
+
"src",
|
|
29
|
+
"dist"
|
|
30
|
+
],
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@tiptap/core": "^2.22.0",
|
|
33
|
+
"@tiptap/extension-text-style": "^2.22.0"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@tiptap/core": "^2.7.0",
|
|
37
|
+
"@tiptap/extension-text-style": "^2.7.0"
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/ueberdosis/tiptap",
|
|
42
|
+
"directory": "packages/extension-invisible-characters"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"clean": "rm -rf dist",
|
|
46
|
+
"build": "npm run clean && rollup -c"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { InvisibleCharacters } from './invisible-characters.js'
|
|
2
|
+
|
|
3
|
+
export * from './invisible-characters.js'
|
|
4
|
+
export * from './plugin/invisible-characters/hardBreak.js'
|
|
5
|
+
export * from './plugin/invisible-characters/paragraph.js'
|
|
6
|
+
export * from './plugin/invisible-characters/space.js'
|
|
7
|
+
export * from './plugin/InvisibleCharacter.js'
|
|
8
|
+
export * from './plugin/InvisibleNode.js'
|
|
9
|
+
export * from './types.js'
|
|
10
|
+
|
|
11
|
+
export default InvisibleCharacters
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Extension } from '@tiptap/core'
|
|
2
|
+
|
|
3
|
+
import { InvisibleCharactersPlugin, InvisibleCharactersPluginKey } from './plugin/index.js'
|
|
4
|
+
import { HardBreakNode } from './plugin/invisible-characters/hardBreak.js'
|
|
5
|
+
import { ParagraphNode } from './plugin/invisible-characters/paragraph.js'
|
|
6
|
+
import { SpaceCharacter } from './plugin/invisible-characters/space.js'
|
|
7
|
+
import { InvisibleCharactersOptions } from './types.js'
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
HardBreakNode,
|
|
11
|
+
ParagraphNode,
|
|
12
|
+
SpaceCharacter,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
declare module '@tiptap/core' {
|
|
16
|
+
interface Commands<ReturnType> {
|
|
17
|
+
invisibleCharacters: {
|
|
18
|
+
/**
|
|
19
|
+
* Show invisible characters
|
|
20
|
+
*/
|
|
21
|
+
showInvisibleCharacters: (show?: boolean) => ReturnType
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Hide invisible characters
|
|
25
|
+
*/
|
|
26
|
+
hideInvisibleCharacters: () => ReturnType
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Toggle invisible characters
|
|
30
|
+
*/
|
|
31
|
+
toggleInvisibleCharacters: () => ReturnType
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const InvisibleCharacters = Extension.create<InvisibleCharactersOptions>({
|
|
37
|
+
name: 'invisibleCharacters',
|
|
38
|
+
|
|
39
|
+
addOptions() {
|
|
40
|
+
return {
|
|
41
|
+
visible: true,
|
|
42
|
+
builders: [
|
|
43
|
+
new SpaceCharacter(),
|
|
44
|
+
new ParagraphNode(),
|
|
45
|
+
new HardBreakNode(),
|
|
46
|
+
],
|
|
47
|
+
injectCSS: true,
|
|
48
|
+
injectNonce: undefined,
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
addProseMirrorPlugins() {
|
|
53
|
+
return [
|
|
54
|
+
InvisibleCharactersPlugin(this.editor.state, this.options),
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
addStorage() {
|
|
59
|
+
return {
|
|
60
|
+
visibility: () => this.options.visible,
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
onBeforeCreate() {
|
|
65
|
+
this.storage.visibility = () => {
|
|
66
|
+
return InvisibleCharactersPluginKey.getState(this.editor.state)?.visible
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
addCommands() {
|
|
71
|
+
return {
|
|
72
|
+
showInvisibleCharacters: (visibility = true) => ({ dispatch, tr }) => {
|
|
73
|
+
if (dispatch) {
|
|
74
|
+
tr.setMeta('setInvisibleCharactersVisible', visibility)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return true
|
|
78
|
+
},
|
|
79
|
+
hideInvisibleCharacters: () => ({ dispatch, tr }) => {
|
|
80
|
+
if (dispatch) {
|
|
81
|
+
tr.setMeta('setInvisibleCharactersVisible', false)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return true
|
|
85
|
+
},
|
|
86
|
+
toggleInvisibleCharacters: () => ({ dispatch, tr, state }) => {
|
|
87
|
+
const visibility = !InvisibleCharactersPluginKey.getState(state)?.visible
|
|
88
|
+
|
|
89
|
+
if (dispatch) {
|
|
90
|
+
tr.setMeta('setInvisibleCharactersVisible', visibility)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return true
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
export default InvisibleCharacters
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Node } from '@tiptap/pm/model'
|
|
2
|
+
import { DecorationSet } from '@tiptap/pm/view'
|
|
3
|
+
|
|
4
|
+
import { createDecorationWidget } from './utils/create-decoration-widget.js'
|
|
5
|
+
import { textBetween } from './utils/text-between.js'
|
|
6
|
+
|
|
7
|
+
export interface InvisibleCharacterOptions {
|
|
8
|
+
type: string;
|
|
9
|
+
predicate: (value: string) => boolean
|
|
10
|
+
priority?: number
|
|
11
|
+
content?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class InvisibleCharacter {
|
|
15
|
+
predicate: (value: string) => boolean
|
|
16
|
+
|
|
17
|
+
content?: string
|
|
18
|
+
|
|
19
|
+
type: string
|
|
20
|
+
|
|
21
|
+
priority: number
|
|
22
|
+
|
|
23
|
+
constructor(options: InvisibleCharacterOptions) {
|
|
24
|
+
this.predicate = options.predicate
|
|
25
|
+
this.type = options.type
|
|
26
|
+
this.content = options.content
|
|
27
|
+
this.priority = options.priority || 100
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
createDecoration(from: number, to: number, doc: Node, decorations: DecorationSet) {
|
|
31
|
+
const textContent = textBetween(from, to, doc)
|
|
32
|
+
|
|
33
|
+
return textContent.reduce((oldDecorations, currentPosition) => {
|
|
34
|
+
return currentPosition.text.split('').reduce((innerDecorations, char, i) => {
|
|
35
|
+
return this.test(char)
|
|
36
|
+
? innerDecorations.add(doc, [createDecorationWidget(currentPosition.pos + i, this.type, this.content)])
|
|
37
|
+
: innerDecorations
|
|
38
|
+
}, oldDecorations)
|
|
39
|
+
}, decorations)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
test(value: string) {
|
|
43
|
+
return this.predicate(value)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default InvisibleCharacter
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AllSelection, EditorState, Plugin, PluginKey,
|
|
3
|
+
} from '@tiptap/pm/state'
|
|
4
|
+
import { DecorationSet } from '@tiptap/pm/view'
|
|
5
|
+
|
|
6
|
+
import { InvisibleCharactersOptions, PluginState } from '../types.js'
|
|
7
|
+
import { stateReducer } from './reducers.js'
|
|
8
|
+
import { style } from './style.js'
|
|
9
|
+
import { createStyleTag } from './utils/create-style-tag.js'
|
|
10
|
+
import { getUpdatedRanges } from './utils/get-updated-ranges.js'
|
|
11
|
+
|
|
12
|
+
export const InvisibleCharactersPluginKey = new PluginKey<PluginState>('invisibleCharacters')
|
|
13
|
+
|
|
14
|
+
export const InvisibleCharactersPlugin = (state: EditorState, options: InvisibleCharactersOptions) => {
|
|
15
|
+
const key = InvisibleCharactersPluginKey
|
|
16
|
+
const emptyDecorationSet = DecorationSet.create(state.doc, [])
|
|
17
|
+
|
|
18
|
+
const addDecorationsBetweenPositions = (
|
|
19
|
+
from: number,
|
|
20
|
+
to: number,
|
|
21
|
+
editorState: EditorState,
|
|
22
|
+
decorations: DecorationSet,
|
|
23
|
+
) => {
|
|
24
|
+
return options.builders.sort((builderA, builderB) => {
|
|
25
|
+
if (builderA.priority > builderB.priority) {
|
|
26
|
+
return 1
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return -1
|
|
30
|
+
}).reduce((newDecos, builder) => {
|
|
31
|
+
return builder.createDecoration(from, to, editorState.doc, newDecos)
|
|
32
|
+
}, decorations)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return new Plugin({
|
|
36
|
+
key,
|
|
37
|
+
|
|
38
|
+
state: {
|
|
39
|
+
init: () => {
|
|
40
|
+
const { $from, $to } = new AllSelection(state.doc)
|
|
41
|
+
|
|
42
|
+
if (options.injectCSS && document) {
|
|
43
|
+
createStyleTag(style, options.injectNonce)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
visible: options.visible,
|
|
48
|
+
decorations: addDecorationsBetweenPositions($from.pos, $to.pos, state, DecorationSet.empty),
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
apply: (tr, pluginState, _, currentState) => {
|
|
52
|
+
const newPluginState = stateReducer(pluginState, tr.getMeta('setInvisibleCharactersVisible'))
|
|
53
|
+
|
|
54
|
+
const decorations = getUpdatedRanges(tr).reduce((nextDecorations, [from, to]) => {
|
|
55
|
+
return addDecorationsBetweenPositions(from, to, currentState, nextDecorations)
|
|
56
|
+
}, newPluginState.decorations.map(tr.mapping, tr.doc))
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
...newPluginState,
|
|
60
|
+
decorations,
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
props: {
|
|
66
|
+
decorations(editorState) {
|
|
67
|
+
const pluginState = this.getState(editorState)
|
|
68
|
+
|
|
69
|
+
const visible = pluginState?.visible
|
|
70
|
+
const decorations = pluginState?.decorations
|
|
71
|
+
|
|
72
|
+
return visible ? decorations : emptyDecorationSet
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export default InvisibleCharactersPlugin
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Node } from '@tiptap/pm/model'
|
|
2
|
+
import { DecorationSet } from '@tiptap/pm/view'
|
|
3
|
+
|
|
4
|
+
import { createDecorationWidget } from './utils/create-decoration-widget.js'
|
|
5
|
+
|
|
6
|
+
export interface InvisibleNodeOptions {
|
|
7
|
+
content?: string
|
|
8
|
+
position?: (node: Node, pos: number) => number;
|
|
9
|
+
predicate: (value: Node) => boolean
|
|
10
|
+
priority?: number
|
|
11
|
+
type: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Default position for nodes is always at the end of the node */
|
|
15
|
+
const defaultPosition = (node: Node, pos: number) => pos + node.nodeSize - 1
|
|
16
|
+
|
|
17
|
+
export class InvisibleNode {
|
|
18
|
+
predicate: (value: Node) => boolean
|
|
19
|
+
|
|
20
|
+
position: (node: Node, pos: number) => number
|
|
21
|
+
|
|
22
|
+
content ?: string
|
|
23
|
+
|
|
24
|
+
type: string
|
|
25
|
+
|
|
26
|
+
priority: number
|
|
27
|
+
|
|
28
|
+
constructor(options: InvisibleNodeOptions) {
|
|
29
|
+
this.predicate = options.predicate
|
|
30
|
+
this.type = options.type
|
|
31
|
+
this.position = options.position || defaultPosition
|
|
32
|
+
this.content = options.content
|
|
33
|
+
this.priority = options.priority || 100
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
createDecoration(from: number, to: number, doc: Node, decorations: DecorationSet) {
|
|
37
|
+
let newDecorations = decorations
|
|
38
|
+
|
|
39
|
+
doc.nodesBetween(from, to, (node, pos) => {
|
|
40
|
+
if (this.test(node)) {
|
|
41
|
+
const decorationPosition = this.position(node, pos)
|
|
42
|
+
const oldDecorations = newDecorations.find(
|
|
43
|
+
decorationPosition,
|
|
44
|
+
decorationPosition,
|
|
45
|
+
spec => spec.key === this.type,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
newDecorations = newDecorations
|
|
49
|
+
.remove(oldDecorations)
|
|
50
|
+
.add(doc, [createDecorationWidget(decorationPosition, this.type, this.content)])
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
return newDecorations
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
test(value: Node) {
|
|
57
|
+
return this.predicate(value)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default InvisibleNode
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './InvisibleCharactersPlugin.js'
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { InvisibleNode } from '../InvisibleNode.js'
|
|
2
|
+
|
|
3
|
+
export class HardBreakNode extends InvisibleNode {
|
|
4
|
+
constructor() {
|
|
5
|
+
super({
|
|
6
|
+
type: 'break',
|
|
7
|
+
predicate: node => node.type === node.type.schema.nodes.hardBreak,
|
|
8
|
+
})
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default HardBreakNode
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { InvisibleNode } from '../InvisibleNode.js'
|
|
2
|
+
|
|
3
|
+
export class ParagraphNode extends InvisibleNode {
|
|
4
|
+
constructor() {
|
|
5
|
+
super({
|
|
6
|
+
type: 'paragraph',
|
|
7
|
+
predicate: node => node.type === node.type.schema.nodes.paragraph,
|
|
8
|
+
})
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default ParagraphNode
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { InvisibleCharacter } from '../InvisibleCharacter.js'
|
|
2
|
+
|
|
3
|
+
export class SpaceCharacter extends InvisibleCharacter {
|
|
4
|
+
constructor() {
|
|
5
|
+
super({
|
|
6
|
+
type: 'space',
|
|
7
|
+
predicate: (char: string) => char === ' ',
|
|
8
|
+
})
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default SpaceCharacter
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PluginState } from '../types.js'
|
|
2
|
+
|
|
3
|
+
export const stateReducer = (
|
|
4
|
+
state: PluginState,
|
|
5
|
+
visible: boolean,
|
|
6
|
+
): PluginState => {
|
|
7
|
+
if (visible === undefined) {
|
|
8
|
+
return state
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return { ...state, visible }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default stateReducer
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export const style = `.Tiptap-invisible-character {
|
|
2
|
+
height: 0;
|
|
3
|
+
padding: 0;
|
|
4
|
+
pointer-events: none;
|
|
5
|
+
user-select: none;
|
|
6
|
+
width: 0;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.Tiptap-invisible-character::before {
|
|
10
|
+
caret-color: inherit;
|
|
11
|
+
color: #aaa;
|
|
12
|
+
display: inline-block;
|
|
13
|
+
font-style: normal;
|
|
14
|
+
font-weight: 400;
|
|
15
|
+
line-height: 1em;
|
|
16
|
+
width: 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.Tiptap-invisible-character--space::before {
|
|
20
|
+
content: '·'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.Tiptap-invisible-character--break::before {
|
|
24
|
+
content: '¬'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.Tiptap-invisible-character--paragraph::before {
|
|
28
|
+
content: '¶'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.Tiptap-invisible-character + img.ProseMirror-separator {
|
|
32
|
+
height: 0 !important;
|
|
33
|
+
pointer-events: none;
|
|
34
|
+
user-select: none;
|
|
35
|
+
width: 0 !important;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.is-empty[data-placeholder].has-focus > .Tiptap-invisible-character {
|
|
39
|
+
display: none;
|
|
40
|
+
}
|
|
41
|
+
`
|
|
42
|
+
|
|
43
|
+
export default style
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Decoration } from '@tiptap/pm/view'
|
|
2
|
+
|
|
3
|
+
export const createDecorationWidget = (pos: number, type: string, content?: string) => {
|
|
4
|
+
const createElement = () => {
|
|
5
|
+
const newElement = document.createElement('span')
|
|
6
|
+
|
|
7
|
+
newElement.classList.add('Tiptap-invisible-character')
|
|
8
|
+
newElement.classList.add(`Tiptap-invisible-character--${type}`)
|
|
9
|
+
|
|
10
|
+
if (content) {
|
|
11
|
+
newElement.textContent = content
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return newElement
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// return Decoration.inline(pos - 1, pos, {
|
|
18
|
+
// class: `Tiptap-invisible-character Tiptap-invisible-character--${type}`,
|
|
19
|
+
// })
|
|
20
|
+
|
|
21
|
+
return Decoration.widget(pos, createElement, {
|
|
22
|
+
key: type,
|
|
23
|
+
marks: [],
|
|
24
|
+
side: 1000,
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default createDecorationWidget
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const createStyleTag = (style: string, nonce?: string): HTMLStyleElement => {
|
|
2
|
+
const tiptapStyleTag = (<HTMLStyleElement>document.querySelector('style[data-tiptap-extension-invisible-characters-style]'))
|
|
3
|
+
|
|
4
|
+
if (tiptapStyleTag !== null) {
|
|
5
|
+
return tiptapStyleTag
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const styleNode = document.createElement('style')
|
|
9
|
+
|
|
10
|
+
if (nonce) {
|
|
11
|
+
styleNode.setAttribute('nonce', nonce)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
styleNode.setAttribute('data-tiptap-extension-invisible-characters-style', '')
|
|
15
|
+
styleNode.innerHTML = style
|
|
16
|
+
document.getElementsByTagName('head')[0].appendChild(styleNode)
|
|
17
|
+
|
|
18
|
+
return styleNode
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default createStyleTag
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Transaction } from '@tiptap/pm/state'
|
|
2
|
+
|
|
3
|
+
type StepRange = [from: number, to: number];
|
|
4
|
+
|
|
5
|
+
export const getUpdatedRanges = ({ mapping }: Transaction): StepRange[] => {
|
|
6
|
+
const ranges: StepRange[] = []
|
|
7
|
+
|
|
8
|
+
mapping.maps.forEach((stepMap, i) => {
|
|
9
|
+
stepMap.forEach((_oldStart, _oldEnd, newStart, newEnd) => {
|
|
10
|
+
ranges.push([
|
|
11
|
+
mapping.slice(i + 1).map(newStart),
|
|
12
|
+
mapping.slice(i + 1).map(newEnd),
|
|
13
|
+
])
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
return ranges
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default getUpdatedRanges
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Node } from '@tiptap/pm/model'
|
|
2
|
+
|
|
3
|
+
import { Position } from '../../types.js'
|
|
4
|
+
|
|
5
|
+
export const textBetween = (from: number, to: number, doc: Node) => {
|
|
6
|
+
const positions: Position[] = []
|
|
7
|
+
|
|
8
|
+
doc.nodesBetween(from, to, (node, pos) => {
|
|
9
|
+
if (node.isText) {
|
|
10
|
+
const offset = Math.max(from, pos) - pos
|
|
11
|
+
|
|
12
|
+
positions.push({
|
|
13
|
+
pos: pos + offset,
|
|
14
|
+
text: node.text?.slice(offset, to - pos) || '',
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
return positions
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default textBetween
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { DecorationSet } from '@tiptap/pm/view'
|
|
2
|
+
|
|
3
|
+
import { InvisibleCharacter } from './plugin/InvisibleCharacter.js'
|
|
4
|
+
import { InvisibleNode } from './plugin/InvisibleNode.js'
|
|
5
|
+
|
|
6
|
+
export interface InvisibleCharactersOptions {
|
|
7
|
+
visible: boolean,
|
|
8
|
+
builders: Array<InvisibleCharacter | InvisibleNode>,
|
|
9
|
+
injectCSS: boolean,
|
|
10
|
+
injectNonce: string | undefined,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface Position {
|
|
14
|
+
pos: number,
|
|
15
|
+
text: string,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PluginState {
|
|
19
|
+
visible: boolean,
|
|
20
|
+
decorations: DecorationSet,
|
|
21
|
+
}
|