@cyberyozh/text-editor 0.1.14 → 0.1.16
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/dist/editor.js +1335 -124
- package/dist/editor.js.map +1 -1
- package/dist/editor.mjs +1283 -72
- package/dist/editor.mjs.map +1 -1
- package/dist/render.js +1303 -93
- package/dist/render.js.map +1 -1
- package/dist/render.mjs +1258 -48
- package/dist/render.mjs.map +1 -1
- package/package.json +38 -38
- package/src/editor/controls/bubble-menu-content.tsx +1 -1
- package/src/editor/dialogs/add-link-dialog/add-link-dialog.tsx +2 -6
- package/src/editor/handlers/dialog-handlers.ts +1 -0
- package/src/extensions/base/figure.ts +37 -0
- package/src/extensions/base/link.ts +63 -6
- package/src/types/link.ts +1 -0
- package/src/utils/html-to-tiptap-content.ts +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyberyozh/text-editor",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "Rich text editor library based on Tiptap for CyberYozh",
|
|
5
5
|
"author": "CyberYozh",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,52 +47,52 @@
|
|
|
47
47
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@floating-ui/dom": "^1.7.
|
|
51
|
-
"@tiptap/core": "^3.
|
|
52
|
-
"@tiptap/extension-blockquote": "^3.
|
|
53
|
-
"@tiptap/extension-bubble-menu": "^3.
|
|
54
|
-
"@tiptap/extension-code": "^3.
|
|
55
|
-
"@tiptap/extension-code-block-lowlight": "^3.
|
|
56
|
-
"@tiptap/extension-collaboration": "^3.
|
|
57
|
-
"@tiptap/extension-details": "^3.
|
|
58
|
-
"@tiptap/extension-drag-handle": "^3.
|
|
59
|
-
"@tiptap/extension-drag-handle-react": "^3.
|
|
60
|
-
"@tiptap/extension-floating-menu": "^3.
|
|
61
|
-
"@tiptap/extension-hard-break": "^3.
|
|
62
|
-
"@tiptap/extension-highlight": "^3.
|
|
63
|
-
"@tiptap/extension-horizontal-rule": "^3.
|
|
64
|
-
"@tiptap/extension-image": "^3.
|
|
65
|
-
"@tiptap/extension-link": "^3.
|
|
66
|
-
"@tiptap/extension-list": "^3.
|
|
67
|
-
"@tiptap/extension-mathematics": "^3.
|
|
68
|
-
"@tiptap/extension-node-range": "^3.
|
|
69
|
-
"@tiptap/extension-subscript": "^3.
|
|
70
|
-
"@tiptap/extension-superscript": "^3.
|
|
71
|
-
"@tiptap/extension-table": "^3.
|
|
72
|
-
"@tiptap/extension-text-align": "^3.
|
|
73
|
-
"@tiptap/extensions": "^3.
|
|
74
|
-
"@tiptap/html": "^3.
|
|
75
|
-
"@tiptap/pm": "^3.
|
|
50
|
+
"@floating-ui/dom": "^1.7.6",
|
|
51
|
+
"@tiptap/core": "^3.20.0",
|
|
52
|
+
"@tiptap/extension-blockquote": "^3.20.0",
|
|
53
|
+
"@tiptap/extension-bubble-menu": "^3.20.0",
|
|
54
|
+
"@tiptap/extension-code": "^3.20.0",
|
|
55
|
+
"@tiptap/extension-code-block-lowlight": "^3.20.0",
|
|
56
|
+
"@tiptap/extension-collaboration": "^3.20.0",
|
|
57
|
+
"@tiptap/extension-details": "^3.20.0",
|
|
58
|
+
"@tiptap/extension-drag-handle": "^3.20.0",
|
|
59
|
+
"@tiptap/extension-drag-handle-react": "^3.20.0",
|
|
60
|
+
"@tiptap/extension-floating-menu": "^3.20.0",
|
|
61
|
+
"@tiptap/extension-hard-break": "^3.20.0",
|
|
62
|
+
"@tiptap/extension-highlight": "^3.20.0",
|
|
63
|
+
"@tiptap/extension-horizontal-rule": "^3.20.0",
|
|
64
|
+
"@tiptap/extension-image": "^3.20.0",
|
|
65
|
+
"@tiptap/extension-link": "^3.20.0",
|
|
66
|
+
"@tiptap/extension-list": "^3.20.0",
|
|
67
|
+
"@tiptap/extension-mathematics": "^3.20.0",
|
|
68
|
+
"@tiptap/extension-node-range": "^3.20.0",
|
|
69
|
+
"@tiptap/extension-subscript": "^3.20.0",
|
|
70
|
+
"@tiptap/extension-superscript": "^3.20.0",
|
|
71
|
+
"@tiptap/extension-table": "^3.20.0",
|
|
72
|
+
"@tiptap/extension-text-align": "^3.20.0",
|
|
73
|
+
"@tiptap/extensions": "^3.20.0",
|
|
74
|
+
"@tiptap/html": "^3.20.0",
|
|
75
|
+
"@tiptap/pm": "^3.20.0",
|
|
76
76
|
"@tiptap/react": "^3.17.1",
|
|
77
|
-
"@tiptap/starter-kit": "^3.
|
|
78
|
-
"@tiptap/static-renderer": "^3.
|
|
79
|
-
"@tiptap/suggestion": "^3.
|
|
77
|
+
"@tiptap/starter-kit": "^3.20.0",
|
|
78
|
+
"@tiptap/static-renderer": "^3.20.0",
|
|
79
|
+
"@tiptap/suggestion": "^3.20.0",
|
|
80
80
|
"@tiptap/y-tiptap": "^3.0.2",
|
|
81
|
-
"emoji-picker-react": "^4.
|
|
81
|
+
"emoji-picker-react": "^4.18.0",
|
|
82
82
|
"highlight.js": "^11.11.1",
|
|
83
|
-
"katex": "^0.16.
|
|
83
|
+
"katex": "^0.16.35",
|
|
84
84
|
"lowlight": "^3.3.0",
|
|
85
85
|
"y-protocols": "^1.0.7",
|
|
86
86
|
"yjs": "^13.6.29"
|
|
87
87
|
},
|
|
88
88
|
"devDependencies": {
|
|
89
|
-
"@changesets/cli": "^2.
|
|
90
|
-
"@types/node": "^
|
|
91
|
-
"eslint": "^
|
|
92
|
-
"tsup": "^8.
|
|
93
|
-
"typescript": "^5"
|
|
89
|
+
"@changesets/cli": "^2.30.0",
|
|
90
|
+
"@types/node": "^25.3.3",
|
|
91
|
+
"eslint": "^10.0.2",
|
|
92
|
+
"tsup": "^8.5.1",
|
|
93
|
+
"typescript": "^5.9.3"
|
|
94
94
|
},
|
|
95
95
|
"overrides": {
|
|
96
|
-
"glob": "^
|
|
96
|
+
"glob": "^13.0.6"
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -103,7 +103,7 @@ export const BubbleMenuContent = ({ editor, onLinkClick }: Props) => {
|
|
|
103
103
|
);
|
|
104
104
|
|
|
105
105
|
const linkAttrs = editor.getAttributes("link");
|
|
106
|
-
onLinkClick?.({
|
|
106
|
+
onLinkClick?.({ ...(linkAttrs as LinkAttrsType), title: selectedText });
|
|
107
107
|
openDialogCommand("link");
|
|
108
108
|
};
|
|
109
109
|
|
|
@@ -21,13 +21,9 @@ export const AddLinkDialog = ({
|
|
|
21
21
|
}: AddLinkDialogProps) => {
|
|
22
22
|
const translations = useTiptapTranslations();
|
|
23
23
|
|
|
24
|
-
const initialLinkTarget = linkDialogContext
|
|
25
|
-
? linkDialogContext.target === "_blank"
|
|
26
|
-
: true;
|
|
24
|
+
const initialLinkTarget = linkDialogContext ? linkDialogContext.target === "_blank" : true;
|
|
27
25
|
|
|
28
|
-
const initialLinkRel = linkDialogContext?.
|
|
29
|
-
? linkDialogContext.rel?.includes("noopener")
|
|
30
|
-
: true;
|
|
26
|
+
const initialLinkRel = linkDialogContext ? !!linkDialogContext.rel?.includes("noopener") : true;
|
|
31
27
|
|
|
32
28
|
const [title, setTitle] = useState(linkDialogContext?.title);
|
|
33
29
|
const [url, setUrl] = useState(linkDialogContext?.href);
|
|
@@ -101,6 +101,7 @@ export const createHandleLinkAdd = (
|
|
|
101
101
|
.command(({ tr, state }) => {
|
|
102
102
|
const { from, to } = state.selection;
|
|
103
103
|
tr.insertText(linkParams.title ?? "", from, to);
|
|
104
|
+
tr.removeMark(from, from + titleLength);
|
|
104
105
|
tr.addMark(from, from + titleLength, editor.schema.marks.link.create({ ...linkParams }));
|
|
105
106
|
return true;
|
|
106
107
|
})
|
|
@@ -37,6 +37,43 @@ export const BaseFigure = Image.extend<FigureOptions>({
|
|
|
37
37
|
addAttributes() {
|
|
38
38
|
return {
|
|
39
39
|
...this.parent?.(),
|
|
40
|
+
src: {
|
|
41
|
+
default: null,
|
|
42
|
+
parseHTML: (element) => {
|
|
43
|
+
const img = element.tagName === "FIGURE" ? element.querySelector("img") : element;
|
|
44
|
+
return img?.getAttribute("src") ?? null;
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
alt: {
|
|
48
|
+
default: null,
|
|
49
|
+
parseHTML: (element) => {
|
|
50
|
+
const img = element.tagName === "FIGURE" ? element.querySelector("img") : element;
|
|
51
|
+
return img?.getAttribute("alt") ?? null;
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
title: {
|
|
55
|
+
default: null,
|
|
56
|
+
parseHTML: (element) => {
|
|
57
|
+
const img = element.tagName === "FIGURE" ? element.querySelector("img") : element;
|
|
58
|
+
return img?.getAttribute("title") ?? null;
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
width: {
|
|
62
|
+
default: null,
|
|
63
|
+
parseHTML: (element) => {
|
|
64
|
+
const img = element.tagName === "FIGURE" ? element.querySelector("img") : element;
|
|
65
|
+
const val = img?.getAttribute("width");
|
|
66
|
+
return val ? parseInt(val) : null;
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
height: {
|
|
70
|
+
default: null,
|
|
71
|
+
parseHTML: (element) => {
|
|
72
|
+
const img = element.tagName === "FIGURE" ? element.querySelector("img") : element;
|
|
73
|
+
const val = img?.getAttribute("height");
|
|
74
|
+
return val ? parseInt(val) : null;
|
|
75
|
+
},
|
|
76
|
+
},
|
|
40
77
|
fileId: {
|
|
41
78
|
default: null,
|
|
42
79
|
parseHTML: (element) => {
|
|
@@ -1,15 +1,72 @@
|
|
|
1
|
-
import Link from "@tiptap/extension-link";
|
|
1
|
+
import Link, { isAllowedUri } from "@tiptap/extension-link";
|
|
2
|
+
import { markPasteRule } from "@tiptap/core";
|
|
3
|
+
import { find } from "linkifyjs";
|
|
4
|
+
|
|
5
|
+
const isSameDomain = (url: string): boolean => {
|
|
6
|
+
if (typeof window === "undefined") return false;
|
|
7
|
+
try {
|
|
8
|
+
return new URL(url).hostname === window.location.hostname;
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
2
13
|
|
|
3
14
|
export const LinkExtension = Link.extend({
|
|
4
|
-
|
|
15
|
+
addAttributes() {
|
|
5
16
|
return {
|
|
6
17
|
...this.parent?.(),
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
18
|
+
target: {
|
|
19
|
+
default: "_blank",
|
|
20
|
+
parseHTML: (element) => {
|
|
21
|
+
const href = element.getAttribute("href") ?? "";
|
|
22
|
+
return isSameDomain(href) ? "_self" : "_blank";
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
rel: {
|
|
26
|
+
default: null,
|
|
27
|
+
parseHTML: (element) => {
|
|
28
|
+
const href = element.getAttribute("href") ?? "";
|
|
29
|
+
return isSameDomain(href) ? null : "noopener noreferrer nofollow";
|
|
30
|
+
},
|
|
31
|
+
},
|
|
11
32
|
};
|
|
12
33
|
},
|
|
34
|
+
|
|
35
|
+
addPasteRules() {
|
|
36
|
+
if (!this.options.linkOnPaste) {
|
|
37
|
+
return this.parent?.() ?? [];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const { protocols, defaultProtocol } = this.options;
|
|
41
|
+
|
|
42
|
+
return [
|
|
43
|
+
markPasteRule({
|
|
44
|
+
find: (text) =>
|
|
45
|
+
find(text)
|
|
46
|
+
.filter(
|
|
47
|
+
(link) =>
|
|
48
|
+
link.isLink &&
|
|
49
|
+
this.options.isAllowedUri(link.value, {
|
|
50
|
+
defaultValidate: (href) => !!isAllowedUri(href, protocols),
|
|
51
|
+
protocols,
|
|
52
|
+
defaultProtocol,
|
|
53
|
+
}),
|
|
54
|
+
)
|
|
55
|
+
.filter((link) => this.options.shouldAutoLink?.(link.value) !== false)
|
|
56
|
+
.map((link) => ({ text: link.value, data: { href: link.href }, index: link.start })),
|
|
57
|
+
type: this.type,
|
|
58
|
+
getAttributes: (match) => {
|
|
59
|
+
const href = match.data?.href ?? "";
|
|
60
|
+
const sameDomain = isSameDomain(href);
|
|
61
|
+
return {
|
|
62
|
+
href,
|
|
63
|
+
target: sameDomain ? "_self" : "_blank",
|
|
64
|
+
rel: sameDomain ? null : "noopener noreferrer nofollow",
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
];
|
|
69
|
+
},
|
|
13
70
|
}).configure({
|
|
14
71
|
openOnClick: false,
|
|
15
72
|
linkOnPaste: true,
|
package/src/types/link.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { generateJSON, type JSONContent } from "@tiptap/core";
|
|
2
2
|
import { getSharedExtensions } from "./get-shared-extensions";
|
|
3
3
|
import { BaseFigure } from "@/extensions/base/figure";
|
|
4
|
+
import { TableKit } from "@tiptap/extension-table";
|
|
5
|
+
import { BaseCodeBlock } from "@/extensions/base";
|
|
4
6
|
|
|
5
7
|
function isJSONContent(value: unknown): value is JSONContent {
|
|
6
8
|
return (
|
|
@@ -30,7 +32,7 @@ export function htmlToTiptapContent(content: string | JSONContent): JSONContent
|
|
|
30
32
|
|
|
31
33
|
const sharedExtensions = getSharedExtensions();
|
|
32
34
|
|
|
33
|
-
const extensions = [...sharedExtensions, BaseFigure];
|
|
35
|
+
const extensions = [...sharedExtensions, BaseFigure, TableKit, BaseCodeBlock];
|
|
34
36
|
|
|
35
37
|
try {
|
|
36
38
|
return generateJSON(content, extensions);
|