'use strict'; var React = require('react'); var react = require('@hublib-web/tach-typography/react'); var jsxRuntime = require('react/jsx-runtime'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var React__default = /*#__PURE__*/_interopDefault(React); // src/react/components/content-text.tsx // src/core/parser.ts var mentionLinkRegexp = /@\[[^\]]+]\([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\)/g; var parseMention = (mention) => { const regex = /@\[([^\]]+)\]\(([\w-]{36})\)/; const match = mention.match(regex); if (!match) { return null; } const mentionText = match[1]; const mentionId = match[2]; if (!mentionText || !mentionId) { return null; } return { mention: `@${mentionText}`, id: mentionId }; }; var findMentions = (text) => { let match; const matches = []; while ((match = mentionLinkRegexp.exec(text)) !== null) { const parsed = parseMention(match[0]); const baseMention = { start: match.index, end: mentionLinkRegexp.lastIndex, text: match[0], type: "mention", displayText: parsed?.mention ?? match[0] }; matches.push(parsed?.id ? { ...baseMention, userId: parsed.id } : baseMention); } return matches; }; var findTags = (content) => { const regex = /#[^\s]{1,201}/g; const results = []; let match; while ((match = regex.exec(content)) !== null) { const value = match[0]; results.push({ start: match.index, end: match.index + value.length, text: value, type: "tag", tag: value.replace("#", "") }); } return results; }; var findLinks = (content) => { const regex = /\b((https?:\/\/)?(?:[\w-]+\.)+[a-z]{2,}(\/[\w\-._~:/?#[\]@!$&'()*+,;=]*)?)/gi; const results = []; let match; while ((match = regex.exec(content)) !== null) { const rawUrl = match[0]; const hasProtocol = /^https?:\/\//i.test(rawUrl); const fullUrl = hasProtocol ? rawUrl : `https://${rawUrl}`; results.push({ start: match.index, end: match.index + rawUrl.length, text: rawUrl, url: fullUrl, type: "link" }); } return results; }; var findAllEntities = (content) => { const mentions = findMentions(content); const tags = findTags(content); const links = findLinks(content); return [...mentions, ...tags, ...links].sort((a, b) => a.start - b.start); }; var processContent = (content) => { const processedText = content.replace(mentionLinkRegexp, (match) => { const parsed = parseMention(match); return parsed ? parsed.mention : match; }); const tags = findTags(content).map((tag) => tag.tag); return { processedText, tags }; }; var joinClassName = (...values) => values.filter(Boolean).join(" "); var baseSelectableStyle = { whiteSpace: "pre-wrap", WebkitTouchCallout: "default", WebkitUserSelect: "text", KhtmlUserSelect: "text", MozUserSelect: "text", msUserSelect: "text", userSelect: "text" }; var blurStyle = { filter: "blur(3px)", WebkitUserSelect: "none", KhtmlUserSelect: "none", MozUserSelect: "none", msUserSelect: "none", userSelect: "none", pointerEvents: "none" }; var ReadMoreButton = ({ onClick, symbol = "\u0427\u0438\u0442\u0430\u0442\u044C \u043F\u043E\u043B\u043D\u043E\u0441\u0442\u044C\u044E", expanded = false, TitleComponent }) => /* @__PURE__ */ jsxRuntime.jsx( TitleComponent, { onClick: (event) => { event.preventDefault(); event.stopPropagation(); onClick(event); }, weight: "bold", level: 5, children: typeof symbol === "function" ? symbol(expanded) : symbol } ); var ContentText = React__default.default.memo( ({ text, className, ellipsis = false, blur = false, weight = "normal", style, onView, renderMention, renderTag, renderLink, ParagraphComponent = react.TachTypography.Paragraph.Body, TextComponent = react.TachTypography.Text.Body, TitleComponent = react.TachTypography.Title.Body, ...props }) => { const containerRef = React.useRef(null); const [expanded, setExpanded] = React.useState(false); const [containerInsideView, setContainerInsideView] = React.useState(false); const [viewed, setViewed] = React.useState(false); const content = text || ""; const entities = React.useMemo(() => findAllEntities(content), [content]); const wrapWithKey = (node, key) => { if (React__default.default.isValidElement(node)) { return React__default.default.cloneElement(node, { key }); } return /* @__PURE__ */ jsxRuntime.jsx(React__default.default.Fragment, { children: node }, key); }; const buildMentionNode = (entity, index) => { const defaultNode = /* @__PURE__ */ jsxRuntime.jsx(TextComponent, { color: "link", weight, children: entity.displayText }); const customNode = renderMention?.(entity, index) ?? defaultNode; return wrapWithKey(customNode, `mention-${entity.start}-${index}`); }; const buildTagNode = (entity, index) => { const defaultNode = /* @__PURE__ */ jsxRuntime.jsx(TextComponent, { color: "link", weight, children: entity.text }); const customNode = renderTag?.(entity, index) ?? defaultNode; return wrapWithKey(customNode, `tag-${entity.start}-${index}`); }; const buildLinkNode = (entity, index) => { const defaultNode = /* @__PURE__ */ jsxRuntime.jsx( react.TachTypography.Link.Body, { target: "_blank", referrerPolicy: "no-referrer", color: "link", weight, href: entity.url, children: entity.text } ); const customNode = renderLink?.(entity, index) ?? defaultNode; return wrapWithKey(customNode, `link-${entity.start}-${index}`); }; const buildParts = (upto = null) => { let lastIndex = 0; const nodes = []; for (const [i, entity] of entities.entries()) { if (upto !== null && entity.start >= upto) { break; } const textEnd = upto !== null ? Math.min(entity.start, upto) : entity.start; if (entity.start > lastIndex && lastIndex < textEnd) { nodes.push(content.slice(lastIndex, textEnd)); } if (upto === null || entity.end <= upto) { if (entity.type === "mention") { nodes.push(buildMentionNode(entity, i)); } else if (entity.type === "tag") { nodes.push(buildTagNode(entity, i)); } else if (entity.type === "link") { nodes.push(buildLinkNode(entity, i)); } } lastIndex = entity.end; } if (upto === null) { if (lastIndex < content.length) { nodes.push(content.slice(lastIndex)); } } else if (lastIndex < upto) { nodes.push(content.slice(lastIndex, upto)); } return nodes; }; const ellipsisConfig = ellipsis && typeof ellipsis === "object" ? ellipsis : null; const expandedFromProps = ellipsisConfig && typeof ellipsisConfig.expanded === "boolean" ? ellipsisConfig.expanded : void 0; const isExpandedControlled = expandedFromProps !== void 0; const mergedExpanded = isExpandedControlled ? expandedFromProps : expanded; const handleExpand = React.useCallback( (event) => { if (!isExpandedControlled) { setExpanded(true); } ellipsisConfig?.onExpand?.(event, { expanded: true }); }, [ellipsisConfig, isExpandedControlled] ); React.useEffect(() => { if (isExpandedControlled) { return; } if (ellipsisConfig && "count" in ellipsisConfig) { const count = ellipsisConfig.count ?? 0; if (content.length <= count && !expanded) { setExpanded(true); } } }, [content.length, ellipsisConfig, expanded, isExpandedControlled]); React.useEffect(() => { if (mergedExpanded && !viewed && containerInsideView) { setViewed(true); onView?.(); } }, [mergedExpanded, viewed, containerInsideView, onView]); React.useEffect(() => { const ref = containerRef.current; if (!ref) { return; } const observer = new IntersectionObserver((entries) => { const entry = entries[0]; if (!entry) { return; } if (entry.isIntersecting && !containerInsideView) { setContainerInsideView(true); } }, { threshold: 0.5 }); observer.observe(ref); return () => { observer.unobserve(ref); }; }, [containerInsideView]); const mergedStyle = { ...baseSelectableStyle, ...blur ? blurStyle : void 0, ...style }; if (ellipsisConfig && "count" in ellipsisConfig) { const { count, expandable } = ellipsisConfig; if (!mergedExpanded && count && content.length > count) { let cutoff = count; let extended = true; while (extended) { extended = false; for (const entity of entities) { if (entity.start < cutoff && entity.end > cutoff) { cutoff = entity.end; extended = true; } } } const truncatedNodes = buildParts(cutoff); return /* @__PURE__ */ jsxRuntime.jsxs( ParagraphComponent, { ref: containerRef, weight, className: joinClassName(className), style: mergedStyle, ...props, children: [ truncatedNodes, "\u2026", expandable && /* @__PURE__ */ jsxRuntime.jsx( ReadMoreButton, { symbol: ellipsisConfig.symbol, onClick: handleExpand, expanded: mergedExpanded, TitleComponent } ) ] } ); } } if (ellipsisConfig && "rows" in ellipsisConfig) { const paragraphEllipsis = mergedExpanded ? false : { ...ellipsisConfig, symbol: ellipsisConfig.expandable ? /* @__PURE__ */ jsxRuntime.jsx( ReadMoreButton, { symbol: ellipsisConfig.symbol, onClick: handleExpand, expanded: mergedExpanded, TitleComponent } ) : ellipsisConfig.symbol }; return /* @__PURE__ */ jsxRuntime.jsx( ParagraphComponent, { ref: containerRef, weight, className: joinClassName(className), style: mergedStyle, ellipsis: paragraphEllipsis, ...props, children: buildParts() } ); } return /* @__PURE__ */ jsxRuntime.jsx( ParagraphComponent, { ref: containerRef, weight, className: joinClassName(className), style: mergedStyle, ...props, children: buildParts() } ); } ); ContentText.displayName = "ContentText"; var DefaultMention = ({ entity }) => /* @__PURE__ */ jsxRuntime.jsx(react.TachTypography.Text.Body, { color: "link", children: entity.displayText }); var DefaultTag = ({ entity }) => /* @__PURE__ */ jsxRuntime.jsx(react.TachTypography.Text.Body, { color: "link", children: entity.text }); var ContentTextWithSuggestions = ({ renderMention, renderTag, ...props }) => { return /* @__PURE__ */ jsxRuntime.jsx( ContentText, { ...props, renderMention: (entity, index) => renderMention ? renderMention(entity, index) : /* @__PURE__ */ jsxRuntime.jsx(DefaultMention, { entity }), renderTag: (entity, index) => renderTag ? renderTag(entity, index) : /* @__PURE__ */ jsxRuntime.jsx(DefaultTag, { entity }) } ); }; var ContentTitleWithSuggestions = ({ text, ellipsis, blur = false, ...rest }) => { const normalizedEllipsis = ellipsis === void 0 ? { rows: 2 } : ellipsis; const textProps = text === void 0 ? {} : { text }; return /* @__PURE__ */ jsxRuntime.jsx( ContentTextWithSuggestions, { weight: "bold", blur, ellipsis: normalizedEllipsis, ...textProps, ...rest } ); }; exports.ContentText = ContentText; exports.ContentTextWithSuggestions = ContentTextWithSuggestions; exports.ContentTitleWithSuggestions = ContentTitleWithSuggestions; exports.findAllEntities = findAllEntities; exports.findLinks = findLinks; exports.findMentions = findMentions; exports.findTags = findTags; exports.mentionLinkRegexp = mentionLinkRegexp; exports.parseMention = parseMention; exports.processContent = processContent; //# sourceMappingURL=index.cjs.map //# sourceMappingURL=index.cjs.map