@cyberyozh/text-editor 0.5.2 → 0.5.4

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) of the site the content belongs to:
55
+ * such links open in the same tab and get no rel on paste / HTML parsing.
56
+ * Read once when the editor instance is created.
57
+ */
58
+ ownDomains?: string[];
53
59
  /**
54
60
  * Domains (with all their subdomains) whose links are treated as internal:
55
61
  * rel="noopener noreferrer nofollow" is not added on paste / HTML parsing.
@@ -64,6 +70,11 @@ interface KatexDialogContextProps {
64
70
  declare const Tiptap: (props: TiptapProps) => react.JSX.Element;
65
71
 
66
72
  interface SharedExtensionsOptions {
73
+ /**
74
+ * Domains (with all their subdomains) of the site the content belongs to —
75
+ * see the ownDomains option of LinkExtension.
76
+ */
77
+ ownDomains?: string[];
67
78
  /**
68
79
  * Domains (with all their subdomains) whose links are treated as internal —
69
80
  * see the trustedDomains option of LinkExtension.
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) of the site the content belongs to:
55
+ * such links open in the same tab and get no rel on paste / HTML parsing.
56
+ * Read once when the editor instance is created.
57
+ */
58
+ ownDomains?: string[];
53
59
  /**
54
60
  * Domains (with all their subdomains) whose links are treated as internal:
55
61
  * rel="noopener noreferrer nofollow" is not added on paste / HTML parsing.
@@ -64,6 +70,11 @@ interface KatexDialogContextProps {
64
70
  declare const Tiptap: (props: TiptapProps) => react.JSX.Element;
65
71
 
66
72
  interface SharedExtensionsOptions {
73
+ /**
74
+ * Domains (with all their subdomains) of the site the content belongs to —
75
+ * see the ownDomains option of LinkExtension.
76
+ */
77
+ ownDomains?: string[];
67
78
  /**
68
79
  * Domains (with all their subdomains) whose links are treated as internal —
69
80
  * see the trustedDomains option of LinkExtension.
package/dist/editor.js CHANGED
@@ -2997,17 +2997,27 @@ var import_xml = __toESM(require("highlight.js/lib/languages/xml"));
2997
2997
  var import_lowlight = require("lowlight");
2998
2998
  var lowlight = (0, import_lowlight.createLowlight)(import_lowlight.common);
2999
2999
  lowlight.register("html", import_xml.default);
3000
+ function languageFromClass(code, prefix) {
3001
+ const languageClass = Array.from(code?.classList ?? []).find(
3002
+ (className) => className.startsWith(prefix)
3003
+ );
3004
+ return languageClass ? languageClass.slice(prefix.length) : null;
3005
+ }
3000
3006
  var BaseCodeBlock = import_extension_code_block_lowlight.CodeBlockLowlight.configure({
3001
3007
  lowlight
3002
3008
  }).extend({
3003
3009
  selectable: false,
3004
3010
  draggable: true,
3005
3011
  addAttributes() {
3012
+ const { languageClassPrefix } = this.options;
3006
3013
  return {
3007
3014
  language: {
3008
3015
  default: null,
3016
+ rendered: false,
3009
3017
  parseHTML: (element) => {
3010
- return element.querySelector("code")?.getAttribute("data-language") || "bash";
3018
+ const code = element.querySelector("code");
3019
+ const fromClass = languageClassPrefix ? languageFromClass(code, languageClassPrefix) : null;
3020
+ return fromClass || code?.getAttribute("data-language") || "bash";
3011
3021
  }
3012
3022
  }
3013
3023
  };
@@ -4326,52 +4336,53 @@ function find(str, type = null, opts = null) {
4326
4336
  }
4327
4337
 
4328
4338
  // src/extensions/base/link.ts
4329
- var isSameDomain = (url) => {
4330
- if (typeof window === "undefined") return false;
4339
+ var hostnameOf = (url) => {
4331
4340
  try {
4332
- return new URL(url).hostname === window.location.hostname;
4341
+ return new URL(url).hostname.toLowerCase();
4333
4342
  } catch {
4334
- return false;
4343
+ return null;
4335
4344
  }
4336
4345
  };
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
- }
4346
+ var isSameDomain = (url) => {
4347
+ if (typeof window === "undefined") return false;
4348
+ return hostnameOf(url) === window.location.hostname;
4349
+ };
4350
+ var matchesDomains = (url, domains) => {
4351
+ const hostname = hostnameOf(url);
4352
+ if (hostname === null) return false;
4353
+ return domains.some((candidate) => {
4354
+ const domain2 = candidate.toLowerCase();
4355
+ return hostname === domain2 || hostname.endsWith(`.${domain2}`);
4356
+ });
4357
+ };
4358
+ var isOwnDomain = (url, ownDomains) => isSameDomain(url) || matchesDomains(url, ownDomains);
4359
+ var isTrustedDomain = (url, trustedDomains) => isSameDomain(url) || matchesDomains(url, trustedDomains);
4360
+ var linkAttributes = (href, { ownDomains, trustedDomains }) => {
4361
+ const own = isOwnDomain(href, ownDomains);
4362
+ return {
4363
+ target: own ? "_self" : "_blank",
4364
+ rel: own || isTrustedDomain(href, trustedDomains) ? null : "noopener noreferrer nofollow"
4365
+ };
4350
4366
  };
4351
4367
  var LinkExtension = import_extension_link.default.extend({
4352
4368
  addOptions() {
4353
4369
  return {
4354
4370
  ...this.parent?.() ?? {},
4371
+ ownDomains: [],
4355
4372
  trustedDomains: []
4356
4373
  };
4357
4374
  },
4358
4375
  addAttributes() {
4359
- const { trustedDomains } = this.options;
4376
+ const domains = this.options;
4360
4377
  return {
4361
4378
  ...this.parent?.(),
4362
4379
  target: {
4363
4380
  default: "_blank",
4364
- parseHTML: (element) => {
4365
- const href = element.getAttribute("href") ?? "";
4366
- return isSameDomain(href) ? "_self" : "_blank";
4367
- }
4381
+ parseHTML: (element) => linkAttributes(element.getAttribute("href") ?? "", domains).target
4368
4382
  },
4369
4383
  rel: {
4370
4384
  default: null,
4371
- parseHTML: (element) => {
4372
- const href = element.getAttribute("href") ?? "";
4373
- return isTrustedDomain(href, trustedDomains) ? null : "noopener noreferrer nofollow";
4374
- }
4385
+ parseHTML: (element) => linkAttributes(element.getAttribute("href") ?? "", domains).rel
4375
4386
  }
4376
4387
  };
4377
4388
  },
@@ -4398,11 +4409,7 @@ var LinkExtension = import_extension_link.default.extend({
4398
4409
  type: this.type,
4399
4410
  getAttributes: (match) => {
4400
4411
  const href = match.data?.href ?? "";
4401
- return {
4402
- href,
4403
- target: isSameDomain(href) ? "_self" : "_blank",
4404
- rel: isTrustedDomain(href, this.options.trustedDomains) ? null : "noopener noreferrer nofollow"
4405
- };
4412
+ return { href, ...linkAttributes(href, this.options) };
4406
4413
  }
4407
4414
  })
4408
4415
  ];
@@ -6394,7 +6401,10 @@ function getSharedExtensions(options = {}) {
6394
6401
  link: false
6395
6402
  }),
6396
6403
  import_extension_highlight.default.configure({ multicolor: true }),
6397
- LinkExtension.configure({ trustedDomains: options.trustedDomains ?? [] }),
6404
+ LinkExtension.configure({
6405
+ ownDomains: options.ownDomains ?? [],
6406
+ trustedDomains: options.trustedDomains ?? []
6407
+ }),
6398
6408
  import_extension_superscript.default,
6399
6409
  import_extension_subscript.default,
6400
6410
  import_extension_text_align.default.configure({
@@ -6636,9 +6646,10 @@ function getEditorExtensions(options) {
6636
6646
  getImageById,
6637
6647
  onKatexClick,
6638
6648
  onFigureClick,
6649
+ ownDomains,
6639
6650
  trustedDomains
6640
6651
  } = options;
6641
- const sharedExtensions = getSharedExtensions({ trustedDomains });
6652
+ const sharedExtensions = getSharedExtensions({ ownDomains, trustedDomains });
6642
6653
  return [
6643
6654
  ...sharedExtensions,
6644
6655
  Callout2,
@@ -7704,6 +7715,7 @@ function TiptapContent({
7704
7715
  isDisabled = false,
7705
7716
  lang,
7706
7717
  error,
7718
+ ownDomains,
7707
7719
  trustedDomains
7708
7720
  }) {
7709
7721
  const [linkDialogContext, setLinkDialogContext] = (0, import_react38.useState)(null);
@@ -7739,6 +7751,7 @@ function TiptapContent({
7739
7751
  suggestion: createSuggestions(translations2, !!imageHandlers),
7740
7752
  isEditable: isEditable && !disabled,
7741
7753
  getImageById: imageHandlers?.getImage,
7754
+ ownDomains,
7742
7755
  trustedDomains,
7743
7756
  onKatexClick: (latex, pos) => {
7744
7757
  setKatexDialogContext({ latex, pos });
@@ -7924,8 +7937,52 @@ var Tiptap = (props) => {
7924
7937
  };
7925
7938
 
7926
7939
  // src/utils/html-to-tiptap-content.ts
7927
- var import_core14 = require("@tiptap/core");
7940
+ var import_html = require("@tiptap/html");
7941
+
7942
+ // src/extensions/render/get-render-extensions.ts
7943
+ var import_extension_mathematics2 = require("@tiptap/extension-mathematics");
7928
7944
  var import_extension_table2 = require("@tiptap/extension-table");
7945
+ function getRenderExtensions(options = {}) {
7946
+ const sharedExtensions = getSharedExtensions(options);
7947
+ return [
7948
+ ...sharedExtensions,
7949
+ CopyableText,
7950
+ BaseCallout,
7951
+ BaseCodeBlock,
7952
+ BaseCustomQuote,
7953
+ BaseCustomQuoteText,
7954
+ BaseCustomQuoteAuthor,
7955
+ BaseCustomQuoteSign,
7956
+ BasePrompt,
7957
+ BasePromptHeader,
7958
+ BasePromptContent,
7959
+ BaseVideo,
7960
+ BaseFigure,
7961
+ import_extension_table2.TableKit.configure({
7962
+ table: { resizable: false, renderWrapper: true }
7963
+ }),
7964
+ import_extension_mathematics2.Mathematics.configure({
7965
+ katexOptions: {
7966
+ throwOnError: false
7967
+ }
7968
+ }),
7969
+ // ========================================================================
7970
+ // Legacy Extensions (Deprecated - for backward compatibility only)
7971
+ // ========================================================================
7972
+ BaseLegacyBookAlert,
7973
+ BaseLegacyBookTip,
7974
+ BaseLegacySEQuote,
7975
+ BaseLegacyPromptSE,
7976
+ BaseLegacyPromptSEHeader,
7977
+ BaseLegacyPromptSEContent,
7978
+ BaseLegacyCustomBlockquote,
7979
+ BaseLegacyYouTube,
7980
+ BaseLegacyFigure
7981
+ // Update
7982
+ ];
7983
+ }
7984
+
7985
+ // src/utils/html-to-tiptap-content.ts
7929
7986
  function isJSONContent(value) {
7930
7987
  return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
7931
7988
  }
@@ -7945,10 +8002,8 @@ function htmlToTiptapContent(content, options = {}) {
7945
8002
  if (!content || content.trim() === "") {
7946
8003
  return { type: "doc", content: [{ type: "paragraph" }] };
7947
8004
  }
7948
- const sharedExtensions = getSharedExtensions(options);
7949
- const extensions = [...sharedExtensions, BaseFigure, import_extension_table2.TableKit, BaseCodeBlock];
7950
8005
  try {
7951
- return (0, import_core14.generateJSON)(content, extensions);
8006
+ return (0, import_html.generateJSON)(content, getRenderExtensions(options));
7952
8007
  } catch (error) {
7953
8008
  console.error("Failed to convert HTML to Tiptap content:", error);
7954
8009
  return { type: "doc", content: [{ type: "paragraph" }] };