Files
_hublib-web/packages/content-suggestions/dist/react/index.cjs

396 lines
12 KiB
JavaScript

'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