@cyberyozh/text-editor 0.4.0 → 0.5.1
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.d.mts +15 -1
- package/dist/editor.d.ts +15 -1
- package/dist/editor.js +56 -17
- package/dist/editor.js.map +1 -1
- package/dist/editor.mjs +61 -18
- package/dist/editor.mjs.map +1 -1
- package/dist/render.d.mts +9 -1
- package/dist/render.d.ts +9 -1
- package/dist/render.js +40 -10
- package/dist/render.js.map +1 -1
- package/dist/render.mjs +45 -11
- package/dist/render.mjs.map +1 -1
- package/package.json +1 -1
- package/src/editor/editor.tsx +8 -0
- package/src/editor/handlers/insertion-handlers.test.ts +37 -0
- package/src/editor/handlers/insertion-handlers.ts +14 -9
- package/src/editor/handlers/markup-insertion.ts +9 -0
- package/src/extensions/base/link.test.ts +60 -0
- package/src/extensions/base/link.ts +61 -9
- package/src/extensions/editor/get-editor-extensions.ts +3 -1
- package/src/utils/get-shared-extensions.ts +20 -4
- package/src/utils/html-to-tiptap-content.ts +13 -4
package/dist/editor.d.mts
CHANGED
|
@@ -50,6 +50,12 @@ interface TiptapProps {
|
|
|
50
50
|
*/
|
|
51
51
|
lang?: SupportedLanguage;
|
|
52
52
|
error?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Domains (with all their subdomains) whose links are treated as internal:
|
|
55
|
+
* rel="noopener noreferrer nofollow" is not added on paste / HTML parsing.
|
|
56
|
+
* Read once when the editor instance is created.
|
|
57
|
+
*/
|
|
58
|
+
trustedDomains?: string[];
|
|
53
59
|
}
|
|
54
60
|
interface KatexDialogContextProps {
|
|
55
61
|
latex?: string;
|
|
@@ -57,7 +63,15 @@ interface KatexDialogContextProps {
|
|
|
57
63
|
}
|
|
58
64
|
declare const Tiptap: (props: TiptapProps) => react_jsx_runtime.JSX.Element;
|
|
59
65
|
|
|
60
|
-
|
|
66
|
+
interface SharedExtensionsOptions {
|
|
67
|
+
/**
|
|
68
|
+
* Domains (with all their subdomains) whose links are treated as internal —
|
|
69
|
+
* see the trustedDomains option of LinkExtension.
|
|
70
|
+
*/
|
|
71
|
+
trustedDomains?: string[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
declare function htmlToTiptapContent(content: string | JSONContent$1, options?: SharedExtensionsOptions): JSONContent$1;
|
|
61
75
|
|
|
62
76
|
interface CombinedErrors extends Error {
|
|
63
77
|
cause: Array<Error>;
|
package/dist/editor.d.ts
CHANGED
|
@@ -50,6 +50,12 @@ interface TiptapProps {
|
|
|
50
50
|
*/
|
|
51
51
|
lang?: SupportedLanguage;
|
|
52
52
|
error?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Domains (with all their subdomains) whose links are treated as internal:
|
|
55
|
+
* rel="noopener noreferrer nofollow" is not added on paste / HTML parsing.
|
|
56
|
+
* Read once when the editor instance is created.
|
|
57
|
+
*/
|
|
58
|
+
trustedDomains?: string[];
|
|
53
59
|
}
|
|
54
60
|
interface KatexDialogContextProps {
|
|
55
61
|
latex?: string;
|
|
@@ -57,7 +63,15 @@ interface KatexDialogContextProps {
|
|
|
57
63
|
}
|
|
58
64
|
declare const Tiptap: (props: TiptapProps) => react_jsx_runtime.JSX.Element;
|
|
59
65
|
|
|
60
|
-
|
|
66
|
+
interface SharedExtensionsOptions {
|
|
67
|
+
/**
|
|
68
|
+
* Domains (with all their subdomains) whose links are treated as internal —
|
|
69
|
+
* see the trustedDomains option of LinkExtension.
|
|
70
|
+
*/
|
|
71
|
+
trustedDomains?: string[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
declare function htmlToTiptapContent(content: string | JSONContent$1, options?: SharedExtensionsOptions): JSONContent$1;
|
|
61
75
|
|
|
62
76
|
interface CombinedErrors extends Error {
|
|
63
77
|
cause: Array<Error>;
|
package/dist/editor.js
CHANGED
|
@@ -4334,8 +4334,29 @@ var isSameDomain = (url) => {
|
|
|
4334
4334
|
return false;
|
|
4335
4335
|
}
|
|
4336
4336
|
};
|
|
4337
|
+
var isTrustedDomain = (url, trustedDomains) => {
|
|
4338
|
+
try {
|
|
4339
|
+
const { hostname } = new URL(url);
|
|
4340
|
+
if (typeof window !== "undefined" && hostname === window.location.hostname) {
|
|
4341
|
+
return true;
|
|
4342
|
+
}
|
|
4343
|
+
return trustedDomains.some((trusted) => {
|
|
4344
|
+
const domain2 = trusted.toLowerCase();
|
|
4345
|
+
return hostname === domain2 || hostname.endsWith(`.${domain2}`);
|
|
4346
|
+
});
|
|
4347
|
+
} catch {
|
|
4348
|
+
return false;
|
|
4349
|
+
}
|
|
4350
|
+
};
|
|
4337
4351
|
var LinkExtension = import_extension_link.default.extend({
|
|
4352
|
+
addOptions() {
|
|
4353
|
+
return {
|
|
4354
|
+
...this.parent?.() ?? {},
|
|
4355
|
+
trustedDomains: []
|
|
4356
|
+
};
|
|
4357
|
+
},
|
|
4338
4358
|
addAttributes() {
|
|
4359
|
+
const { trustedDomains } = this.options;
|
|
4339
4360
|
return {
|
|
4340
4361
|
...this.parent?.(),
|
|
4341
4362
|
target: {
|
|
@@ -4349,7 +4370,7 @@ var LinkExtension = import_extension_link.default.extend({
|
|
|
4349
4370
|
default: null,
|
|
4350
4371
|
parseHTML: (element) => {
|
|
4351
4372
|
const href = element.getAttribute("href") ?? "";
|
|
4352
|
-
return
|
|
4373
|
+
return isTrustedDomain(href, trustedDomains) ? null : "noopener noreferrer nofollow";
|
|
4353
4374
|
}
|
|
4354
4375
|
}
|
|
4355
4376
|
};
|
|
@@ -4367,15 +4388,20 @@ var LinkExtension = import_extension_link.default.extend({
|
|
|
4367
4388
|
protocols,
|
|
4368
4389
|
defaultProtocol
|
|
4369
4390
|
})
|
|
4370
|
-
).filter(
|
|
4391
|
+
).filter(
|
|
4392
|
+
(link) => this.options.shouldAutoLink?.(link.value) !== false
|
|
4393
|
+
).map((link) => ({
|
|
4394
|
+
text: link.value,
|
|
4395
|
+
data: { href: link.href },
|
|
4396
|
+
index: link.start
|
|
4397
|
+
})),
|
|
4371
4398
|
type: this.type,
|
|
4372
4399
|
getAttributes: (match) => {
|
|
4373
4400
|
const href = match.data?.href ?? "";
|
|
4374
|
-
const sameDomain = isSameDomain(href);
|
|
4375
4401
|
return {
|
|
4376
4402
|
href,
|
|
4377
|
-
target:
|
|
4378
|
-
rel:
|
|
4403
|
+
target: isSameDomain(href) ? "_self" : "_blank",
|
|
4404
|
+
rel: isTrustedDomain(href, this.options.trustedDomains) ? null : "noopener noreferrer nofollow"
|
|
4379
4405
|
};
|
|
4380
4406
|
}
|
|
4381
4407
|
})
|
|
@@ -4390,7 +4416,11 @@ var LinkExtension = import_extension_link.default.extend({
|
|
|
4390
4416
|
if (!view.editable) return false;
|
|
4391
4417
|
const range = (0, import_core4.getMarkRange)(view.state.doc.resolve(pos), this.type);
|
|
4392
4418
|
if (!range) return false;
|
|
4393
|
-
const selection = import_state3.TextSelection.create(
|
|
4419
|
+
const selection = import_state3.TextSelection.create(
|
|
4420
|
+
view.state.doc,
|
|
4421
|
+
range.from,
|
|
4422
|
+
range.to
|
|
4423
|
+
);
|
|
4394
4424
|
view.dispatch(view.state.tr.setSelection(selection));
|
|
4395
4425
|
return true;
|
|
4396
4426
|
}
|
|
@@ -6449,14 +6479,14 @@ var TextStyleKit = import_core17.Extension.create({
|
|
|
6449
6479
|
});
|
|
6450
6480
|
|
|
6451
6481
|
// src/utils/get-shared-extensions.ts
|
|
6452
|
-
function getSharedExtensions() {
|
|
6482
|
+
function getSharedExtensions(options = {}) {
|
|
6453
6483
|
return [
|
|
6454
6484
|
import_starter_kit.default.configure({
|
|
6455
6485
|
codeBlock: false,
|
|
6456
6486
|
link: false
|
|
6457
6487
|
}),
|
|
6458
6488
|
import_extension_highlight.default.configure({ multicolor: true }),
|
|
6459
|
-
LinkExtension,
|
|
6489
|
+
LinkExtension.configure({ trustedDomains: options.trustedDomains ?? [] }),
|
|
6460
6490
|
import_extension_superscript.default,
|
|
6461
6491
|
import_extension_subscript.default,
|
|
6462
6492
|
import_extension_text_align.default.configure({
|
|
@@ -6697,9 +6727,10 @@ function getEditorExtensions(options) {
|
|
|
6697
6727
|
isEditable = false,
|
|
6698
6728
|
getImageById,
|
|
6699
6729
|
onKatexClick,
|
|
6700
|
-
onFigureClick
|
|
6730
|
+
onFigureClick,
|
|
6731
|
+
trustedDomains
|
|
6701
6732
|
} = options;
|
|
6702
|
-
const sharedExtensions = getSharedExtensions();
|
|
6733
|
+
const sharedExtensions = getSharedExtensions({ trustedDomains });
|
|
6703
6734
|
return [
|
|
6704
6735
|
...sharedExtensions,
|
|
6705
6736
|
Callout2,
|
|
@@ -6933,6 +6964,12 @@ function applyUploadResultsToNodes(imagesToNodes, results) {
|
|
|
6933
6964
|
node.attrs.storageKey = result.storageKey;
|
|
6934
6965
|
});
|
|
6935
6966
|
}
|
|
6967
|
+
function shouldHandleInsertedFile(file, event) {
|
|
6968
|
+
if (!file) return false;
|
|
6969
|
+
event.preventDefault();
|
|
6970
|
+
event.stopPropagation();
|
|
6971
|
+
return file.type.startsWith("image/");
|
|
6972
|
+
}
|
|
6936
6973
|
|
|
6937
6974
|
// src/editor/handlers/insertion-handlers.ts
|
|
6938
6975
|
function useInsertionHandlers(imageHandlers) {
|
|
@@ -6944,9 +6981,7 @@ function useInsertionHandlers(imageHandlers) {
|
|
|
6944
6981
|
}
|
|
6945
6982
|
}, [insertedImage, activeDialog]);
|
|
6946
6983
|
function genericInsertionHandler(file, event) {
|
|
6947
|
-
event
|
|
6948
|
-
event.stopPropagation();
|
|
6949
|
-
if (!file || !file.type.startsWith("image/")) return;
|
|
6984
|
+
if (!file || !shouldHandleInsertedFile(file, event)) return;
|
|
6950
6985
|
if (!imageHandlers?.upload) return;
|
|
6951
6986
|
setInsertedImage(file);
|
|
6952
6987
|
openDialogCommand("image");
|
|
@@ -6982,7 +7017,9 @@ function useInsertionHandlers(imageHandlers) {
|
|
|
6982
7017
|
setDisabled(false);
|
|
6983
7018
|
});
|
|
6984
7019
|
} else {
|
|
6985
|
-
view.dispatch(
|
|
7020
|
+
view.dispatch(
|
|
7021
|
+
view.state.tr.replaceSelection(removeImagesFromSlice(slice))
|
|
7022
|
+
);
|
|
6986
7023
|
}
|
|
6987
7024
|
return true;
|
|
6988
7025
|
}
|
|
@@ -7758,7 +7795,8 @@ function TiptapContent({
|
|
|
7758
7795
|
isEditable = true,
|
|
7759
7796
|
isDisabled = false,
|
|
7760
7797
|
lang,
|
|
7761
|
-
error
|
|
7798
|
+
error,
|
|
7799
|
+
trustedDomains
|
|
7762
7800
|
}) {
|
|
7763
7801
|
const [linkDialogContext, setLinkDialogContext] = (0, import_react38.useState)(null);
|
|
7764
7802
|
const [katexDialogContext, setKatexDialogContext] = (0, import_react38.useState)(
|
|
@@ -7793,6 +7831,7 @@ function TiptapContent({
|
|
|
7793
7831
|
suggestion: createSuggestions(translations2, !!imageHandlers),
|
|
7794
7832
|
isEditable: isEditable && !disabled,
|
|
7795
7833
|
getImageById: imageHandlers?.getImage,
|
|
7834
|
+
trustedDomains,
|
|
7796
7835
|
onKatexClick: (latex, pos) => {
|
|
7797
7836
|
setKatexDialogContext({ latex, pos });
|
|
7798
7837
|
openDialogCommand("katex");
|
|
@@ -7982,7 +8021,7 @@ var import_extension_table2 = require("@tiptap/extension-table");
|
|
|
7982
8021
|
function isJSONContent(value) {
|
|
7983
8022
|
return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
|
|
7984
8023
|
}
|
|
7985
|
-
function htmlToTiptapContent(content) {
|
|
8024
|
+
function htmlToTiptapContent(content, options = {}) {
|
|
7986
8025
|
if (isJSONContent(content)) {
|
|
7987
8026
|
return content;
|
|
7988
8027
|
}
|
|
@@ -7998,7 +8037,7 @@ function htmlToTiptapContent(content) {
|
|
|
7998
8037
|
if (!content || content.trim() === "") {
|
|
7999
8038
|
return { type: "doc", content: [{ type: "paragraph" }] };
|
|
8000
8039
|
}
|
|
8001
|
-
const sharedExtensions = getSharedExtensions();
|
|
8040
|
+
const sharedExtensions = getSharedExtensions(options);
|
|
8002
8041
|
const extensions = [...sharedExtensions, BaseFigure, import_extension_table2.TableKit, BaseCodeBlock];
|
|
8003
8042
|
try {
|
|
8004
8043
|
return (0, import_core20.generateJSON)(content, extensions);
|