@cyberyozh/text-editor 0.4.0 → 0.5.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/dist/editor.d.mts +15 -1
- package/dist/editor.d.ts +15 -1
- package/dist/editor.js +46 -13
- package/dist/editor.js.map +1 -1
- package/dist/editor.mjs +51 -14
- 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/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,
|
|
@@ -7758,7 +7789,8 @@ function TiptapContent({
|
|
|
7758
7789
|
isEditable = true,
|
|
7759
7790
|
isDisabled = false,
|
|
7760
7791
|
lang,
|
|
7761
|
-
error
|
|
7792
|
+
error,
|
|
7793
|
+
trustedDomains
|
|
7762
7794
|
}) {
|
|
7763
7795
|
const [linkDialogContext, setLinkDialogContext] = (0, import_react38.useState)(null);
|
|
7764
7796
|
const [katexDialogContext, setKatexDialogContext] = (0, import_react38.useState)(
|
|
@@ -7793,6 +7825,7 @@ function TiptapContent({
|
|
|
7793
7825
|
suggestion: createSuggestions(translations2, !!imageHandlers),
|
|
7794
7826
|
isEditable: isEditable && !disabled,
|
|
7795
7827
|
getImageById: imageHandlers?.getImage,
|
|
7828
|
+
trustedDomains,
|
|
7796
7829
|
onKatexClick: (latex, pos) => {
|
|
7797
7830
|
setKatexDialogContext({ latex, pos });
|
|
7798
7831
|
openDialogCommand("katex");
|
|
@@ -7982,7 +8015,7 @@ var import_extension_table2 = require("@tiptap/extension-table");
|
|
|
7982
8015
|
function isJSONContent(value) {
|
|
7983
8016
|
return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
|
|
7984
8017
|
}
|
|
7985
|
-
function htmlToTiptapContent(content) {
|
|
8018
|
+
function htmlToTiptapContent(content, options = {}) {
|
|
7986
8019
|
if (isJSONContent(content)) {
|
|
7987
8020
|
return content;
|
|
7988
8021
|
}
|
|
@@ -7998,7 +8031,7 @@ function htmlToTiptapContent(content) {
|
|
|
7998
8031
|
if (!content || content.trim() === "") {
|
|
7999
8032
|
return { type: "doc", content: [{ type: "paragraph" }] };
|
|
8000
8033
|
}
|
|
8001
|
-
const sharedExtensions = getSharedExtensions();
|
|
8034
|
+
const sharedExtensions = getSharedExtensions(options);
|
|
8002
8035
|
const extensions = [...sharedExtensions, BaseFigure, import_extension_table2.TableKit, BaseCodeBlock];
|
|
8003
8036
|
try {
|
|
8004
8037
|
return (0, import_core20.generateJSON)(content, extensions);
|