@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 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
- declare function htmlToTiptapContent(content: string | JSONContent$1): JSONContent$1;
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
- declare function htmlToTiptapContent(content: string | JSONContent$1): JSONContent$1;
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 isSameDomain(href) ? null : "noopener noreferrer nofollow";
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((link) => this.options.shouldAutoLink?.(link.value) !== false).map((link) => ({ text: link.value, data: { href: link.href }, index: link.start })),
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: sameDomain ? "_self" : "_blank",
4378
- rel: sameDomain ? null : "noopener noreferrer nofollow"
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(view.state.doc, range.from, range.to);
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.preventDefault();
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(view.state.tr.replaceSelection(removeImagesFromSlice(slice)));
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);