@cyberyozh/text-editor 0.1.15 → 0.1.17

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.
@@ -642,6 +642,7 @@
642
642
  display: inline-block;
643
643
  position: relative;
644
644
  margin: 0 auto;
645
+ min-width: 0;
645
646
  }
646
647
  [data-resize-handle] {
647
648
  z-index: 10;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyberyozh/text-editor",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
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.4",
51
- "@tiptap/core": "^3.17.1",
52
- "@tiptap/extension-blockquote": "^3.17.1",
53
- "@tiptap/extension-bubble-menu": "^3.17.1",
54
- "@tiptap/extension-code": "^3.17.1",
55
- "@tiptap/extension-code-block-lowlight": "^3.17.1",
56
- "@tiptap/extension-collaboration": "^3.17.1",
57
- "@tiptap/extension-details": "^3.17.1",
58
- "@tiptap/extension-drag-handle": "^3.17.1",
59
- "@tiptap/extension-drag-handle-react": "^3.17.1",
60
- "@tiptap/extension-floating-menu": "^3.17.1",
61
- "@tiptap/extension-hard-break": "^3.17.1",
62
- "@tiptap/extension-highlight": "^3.10.6",
63
- "@tiptap/extension-horizontal-rule": "^3.17.1",
64
- "@tiptap/extension-image": "^3.17.1",
65
- "@tiptap/extension-link": "^3.17.1",
66
- "@tiptap/extension-list": "^3.17.1",
67
- "@tiptap/extension-mathematics": "^3.17.1",
68
- "@tiptap/extension-node-range": "^3.17.1",
69
- "@tiptap/extension-subscript": "^3.17.1",
70
- "@tiptap/extension-superscript": "^3.17.1",
71
- "@tiptap/extension-table": "^3.17.1",
72
- "@tiptap/extension-text-align": "^3.17.1",
73
- "@tiptap/extensions": "^3.17.1",
74
- "@tiptap/html": "^3.17.1",
75
- "@tiptap/pm": "^3.17.1",
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.17.1",
78
- "@tiptap/static-renderer": "^3.17.1",
79
- "@tiptap/suggestion": "^3.17.1",
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.17.1",
81
+ "emoji-picker-react": "^4.18.0",
82
82
  "highlight.js": "^11.11.1",
83
- "katex": "^0.16.27",
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.29.7",
90
- "@types/node": "^20",
91
- "eslint": "^8",
92
- "tsup": "^8.0.0",
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": "^11.0.4"
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?.({ title: selectedText, ...(linkAttrs as LinkAttrsType) });
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?.target
25
- ? linkDialogContext.target === "_blank"
26
- : true;
24
+ const initialLinkTarget = linkDialogContext ? linkDialogContext.target === "_blank" : true;
27
25
 
28
- const initialLinkRel = linkDialogContext?.rel
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
- addOptions() {
15
+ addAttributes() {
5
16
  return {
6
17
  ...this.parent?.(),
7
- openOnClick: false,
8
- linkOnPaste: true,
9
- autolink: true,
10
- protocols: ["http", "https", "mailto", "tel"],
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,
@@ -54,7 +54,8 @@ export const Figure = BaseFigure.extend({
54
54
 
55
55
  const updateImageDimensions = (width: number, height: number) => {
56
56
  imageContainer.style.width = `${width}px`;
57
- imageContainer.style.height = `${height}px`;
57
+ imageContainer.style.aspectRatio = `${width} / ${height}`;
58
+ imageContainer.style.height = '';
58
59
  };
59
60
 
60
61
  const figure = document.createElement("figure");
@@ -70,7 +71,7 @@ export const Figure = BaseFigure.extend({
70
71
 
71
72
  if (attrs.width && attrs.height) {
72
73
  imageContainer.style.width = `${attrs.width}px`;
73
- imageContainer.style.height = `${attrs.height}px`;
74
+ imageContainer.style.aspectRatio = `${attrs.width} / ${attrs.height}`;
74
75
  }
75
76
 
76
77
  img.onload = () => {
@@ -81,19 +82,13 @@ export const Figure = BaseFigure.extend({
81
82
  }
82
83
 
83
84
  if (attrs.width && attrs.height && maxWidth && attrs.width > maxWidth) {
84
- const width = maxWidth;
85
- const height = Math.round((attrs.height / attrs.width) * maxWidth);
86
-
87
- imageContainer.style.width = `${width}px`;
88
- imageContainer.style.height = `${height}px`;
89
-
90
85
  const pos = getPos();
91
86
  if (typeof pos === "number") {
92
87
  editor
93
88
  .chain()
94
89
  .updateAttributes("image", {
95
- width: Math.round(width),
96
- height: Math.round(height),
90
+ width: maxWidth,
91
+ height: Math.round((attrs.height / attrs.width) * maxWidth),
97
92
  })
98
93
  .run();
99
94
  }
@@ -149,11 +144,13 @@ export const Figure = BaseFigure.extend({
149
144
  }
150
145
  },
151
146
  onUpdate: (updatedNode) => {
152
- if (updatedNode.attrs.width) {
153
- imageContainer.style.width = `${updatedNode.attrs.width}px`;
154
- }
155
- if (updatedNode.attrs.height) {
156
- imageContainer.style.height = `${updatedNode.attrs.height}px`;
147
+ const newWidth = updatedNode.attrs.width;
148
+ const newHeight = updatedNode.attrs.height;
149
+
150
+ if (newWidth) imageContainer.style.width = `${newWidth}px`;
151
+ if (newWidth && newHeight) {
152
+ imageContainer.style.aspectRatio = `${newWidth} / ${newHeight}`;
153
+ imageContainer.style.height = '';
157
154
  }
158
155
 
159
156
  const existingCaption = figure.querySelector("figcaption");
@@ -194,6 +191,8 @@ export const Figure = BaseFigure.extend({
194
191
  },
195
192
  });
196
193
 
194
+ imageContainer.style.height = '';
195
+
197
196
  figure.appendChild(resizableView.dom);
198
197
 
199
198
  if (attrs.caption) {
@@ -85,6 +85,7 @@
85
85
  display: inline-block;
86
86
  position: relative;
87
87
  margin: 0 auto;
88
+ min-width: 0;
88
89
  }
89
90
 
90
91
  [data-resize-handle] {
package/src/types/link.ts CHANGED
@@ -7,3 +7,4 @@ export interface LinkAttrsType {
7
7
  target: string;
8
8
  rel: string | null;
9
9
  }
10
+