@automattic/social-previews 3.2.4 → 3.2.5

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/index.mjs CHANGED
@@ -50,6 +50,43 @@ var formatMastodonDate = new Intl.DateTimeFormat("en-US", {
50
50
  day: "numeric",
51
51
  year: "numeric"
52
52
  }).format;
53
+ var collapseWhitespace = (text) => text.replace(/\s+/g, " ").trim();
54
+ var countOccurrences = (haystack, needle) => {
55
+ let count = 0;
56
+ for (let pos = haystack.indexOf(needle); pos !== -1; pos = haystack.indexOf(needle, pos + 1)) {
57
+ count++;
58
+ }
59
+ return count;
60
+ };
61
+ var nthIndexOf = (haystack, needle, n) => {
62
+ let pos = haystack.indexOf(needle);
63
+ while (pos !== -1 && n > 0) {
64
+ n--;
65
+ pos = haystack.indexOf(needle, pos + 1);
66
+ }
67
+ return pos;
68
+ };
69
+ function parseHyperlinks(html) {
70
+ if (!html) {
71
+ return [];
72
+ }
73
+ const doc = document.implementation.createHTMLDocument("");
74
+ doc.body.innerHTML = html;
75
+ const links = [];
76
+ for (const anchor of Array.from(doc.body.querySelectorAll("a[href]"))) {
77
+ const href = anchor.getAttribute("href") ?? "";
78
+ const text = collapseWhitespace(anchor.textContent ?? "");
79
+ if (!/^https?:\/\//i.test(href) || "" === text || text === href) {
80
+ continue;
81
+ }
82
+ const range = doc.createRange();
83
+ range.selectNodeContents(doc.body);
84
+ range.setEndBefore(anchor);
85
+ const occurrence = countOccurrences(collapseWhitespace(range.toString()), text);
86
+ links.push({ text, href, occurrence });
87
+ }
88
+ return links;
89
+ }
53
90
  var hashtagUrlMap = {
54
91
  twitter: "https://twitter.com/hashtag/%1$s",
55
92
  facebook: "https://www.facebook.com/hashtag/%1$s",
@@ -68,7 +105,8 @@ function preparePreviewText(text, options) {
68
105
  maxLines,
69
106
  hyperlinkHashtags = true,
70
107
  // Instagram doesn't support hyperlink URLs at the moment.
71
- hyperlinkUrls = "instagram" !== platform
108
+ hyperlinkUrls = "instagram" !== platform,
109
+ hyperlinks
72
110
  } = options;
73
111
  let result = stripHtmlTags(text);
74
112
  result = result.replaceAll(/(?:\s*[\n\r]){2,}/g, "\n\n");
@@ -98,6 +136,31 @@ function preparePreviewText(text, options) {
98
136
  result = result.replace(fullMatch, `${whitespace}<Hashtag${index} />`);
99
137
  });
100
138
  }
139
+ if (hyperlinks?.length) {
140
+ const matches = [];
141
+ hyperlinks.forEach(({ text: anchorText, href, occurrence = 0 }, index) => {
142
+ if (!anchorText) {
143
+ return;
144
+ }
145
+ const pos = nthIndexOf(result, anchorText, occurrence);
146
+ if (pos === -1) {
147
+ return;
148
+ }
149
+ const overlaps = matches.some(
150
+ (match) => pos < match.pos + match.text.length && match.pos < pos + anchorText.length
151
+ );
152
+ if (!overlaps) {
153
+ matches.push({ pos, text: anchorText, href, index });
154
+ }
155
+ });
156
+ matches.sort((a, b) => b.pos - a.pos);
157
+ for (const { pos, text: anchorText, href, index } of matches) {
158
+ const token = `Hyperlink${index}`;
159
+ componentMap[token] = /* @__PURE__ */ jsx("a", { href, rel: "noopener noreferrer", target: "_blank" });
160
+ const wrapped = `<${token}>${anchorText}</${token}>`;
161
+ result = result.slice(0, pos) + wrapped + result.slice(pos + anchorText.length);
162
+ }
163
+ }
101
164
  result = result.replace(/\n/g, "<br />");
102
165
  componentMap.br = /* @__PURE__ */ jsx("br", {});
103
166
  return createInterpolateElement(result, componentMap);
@@ -836,7 +899,8 @@ var TumblrPostPreview = ({
836
899
  image,
837
900
  user,
838
901
  url,
839
- media
902
+ media,
903
+ hyperlinks
840
904
  }) => {
841
905
  const avatarUrl = user?.avatarUrl;
842
906
  const mediaItem = media?.[0];
@@ -847,7 +911,8 @@ var TumblrPostPreview = ({
847
911
  /* @__PURE__ */ jsxs16("div", { className: "tumblr-preview__body", children: [
848
912
  title ? /* @__PURE__ */ jsx25("div", { className: "tumblr-preview__title", children: tumblrTitle(title) }) : null,
849
913
  description && /* @__PURE__ */ jsx25("div", { className: "tumblr-preview__description", children: /* @__PURE__ */ jsx25(ExpandableText, { text: description, children: (visibleText) => preparePreviewText(tumblrDescription(visibleText), {
850
- platform: "tumblr"
914
+ platform: "tumblr",
915
+ hyperlinks
851
916
  }) }) }),
852
917
  mediaItem ? /* @__PURE__ */ jsx25("div", { className: "tumblr-preview__media-item", children: mediaItem.type.startsWith("video/") ? /* @__PURE__ */ jsx25("video", { controls: true, className: "tumblr-preview__media--video", children: /* @__PURE__ */ jsx25("source", { src: mediaItem.url, type: mediaItem.type }) }) : /* @__PURE__ */ jsx25("img", { className: "tumblr-preview__image", src: mediaItem.url, alt: "" }) }) : image && /* @__PURE__ */ jsx25(
853
918
  "img",
@@ -1820,21 +1885,28 @@ var blueskyTitle = (text) => firstValid(
1820
1885
  hardTruncation(TITLE_LENGTH5)
1821
1886
  )(stripHtmlTags(text)) || "";
1822
1887
  var blueskyBody = (text, options = {}) => {
1823
- const { offset = 0, reserveUrlSpace = true } = options;
1888
+ const { offset = 0, reserveUrlSpace = true, hyperlinks } = options;
1824
1889
  return preparePreviewText(text, {
1825
1890
  platform: "bluesky",
1826
- maxChars: BODY_LENGTH2 - (reserveUrlSpace ? URL_LENGTH2 : 0) - offset
1891
+ maxChars: BODY_LENGTH2 - (reserveUrlSpace ? URL_LENGTH2 : 0) - offset,
1892
+ hyperlinks
1827
1893
  });
1828
1894
  };
1829
1895
  var blueskyUrl = (text) => firstValid(shortEnough(URL_LENGTH2), hardTruncation(URL_LENGTH2))(stripHtmlTags(text)) || "";
1830
1896
 
1831
1897
  // src/bluesky-preview/post/body/index.tsx
1832
1898
  import { Fragment as Fragment6, jsx as jsx54, jsxs as jsxs37 } from "react/jsx-runtime";
1833
- var BlueskyPostBody = ({ customText, url, children, appendUrl }) => {
1899
+ var BlueskyPostBody = ({
1900
+ customText,
1901
+ url,
1902
+ children,
1903
+ appendUrl,
1904
+ hyperlinks
1905
+ }) => {
1834
1906
  const showUrl = appendUrl && !!url && !customText?.includes(url);
1835
1907
  return /* @__PURE__ */ jsxs37("div", { className: "bluesky-preview__body", children: [
1836
1908
  customText ? /* @__PURE__ */ jsxs37(Fragment6, { children: [
1837
- /* @__PURE__ */ jsx54("div", { children: blueskyBody(customText, { reserveUrlSpace: showUrl }) }),
1909
+ /* @__PURE__ */ jsx54("div", { children: blueskyBody(customText, { reserveUrlSpace: showUrl, hyperlinks }) }),
1838
1910
  showUrl ? /* @__PURE__ */ jsxs37(Fragment6, { children: [
1839
1911
  /* @__PURE__ */ jsx54("br", {}),
1840
1912
  /* @__PURE__ */ jsx54("a", { href: url, target: "_blank", rel: "noreferrer noopener", children: blueskyUrl(url.replace(/^https?:\/\//, "")) })
@@ -2441,6 +2513,7 @@ export {
2441
2513
  TumblrPreviews,
2442
2514
  TwitterLinkPreview,
2443
2515
  TwitterPostPreview,
2444
- TwitterPreviews
2516
+ TwitterPreviews,
2517
+ parseHyperlinks
2445
2518
  };
2446
2519
  //# sourceMappingURL=index.mjs.map