diff --git a/packages/content-suggestions/README.md b/packages/content-suggestions/README.md index 752d746..cb503c0 100644 --- a/packages/content-suggestions/README.md +++ b/packages/content-suggestions/README.md @@ -77,12 +77,40 @@ By default, tags are rendered as plain styled text (not links). ## Angular usage +Tokenization helper: + ```ts import { createAngularContentTokens } from "@hublib-web/content-suggestions/angular"; const tokens = createAngularContentTokens(text); ``` +Ready-to-use UI renderer (React-like props API): + +```ts +import { AngularContentTextWithSuggestionsRenderer } from "@hublib-web/content-suggestions/angular"; + +const renderer = new AngularContentTextWithSuggestionsRenderer(); +renderer.attach(containerElement, { + text, + weight: "normal", + ellipsis: { count: 180, expandable: true }, + blur: false, + onView: () => { + // analytics + }, +}); +``` + +Title variant with defaults (`weight: "bold"`, `ellipsis: { rows: 2 }`): + +```ts +import { AngularContentTitleWithSuggestionsRenderer } from "@hublib-web/content-suggestions/angular"; + +const titleRenderer = new AngularContentTitleWithSuggestionsRenderer(); +titleRenderer.attach(titleContainer, { text: title }); +``` + ## Storybook (dev/design system) Run from repository root: diff --git a/packages/content-suggestions/dist/angular/index.cjs b/packages/content-suggestions/dist/angular/index.cjs index 979739f..1aa2211 100644 --- a/packages/content-suggestions/dist/angular/index.cjs +++ b/packages/content-suggestions/dist/angular/index.cjs @@ -76,6 +76,41 @@ var findAllEntities = (content) => { }; // src/angular/index.ts +var LINK_COLOR = "#1677ff"; +var READ_MORE_TEXT = "\u0427\u0438\u0442\u0430\u0442\u044C \u043F\u043E\u043B\u043D\u043E\u0441\u0442\u044C\u044E"; +var toKebabCase = (value) => value.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`); +var applyStyleObject = (element, styles) => { + if (!styles) { + return; + } + for (const [key, rawValue] of Object.entries(styles)) { + if (rawValue === null || rawValue === void 0) { + continue; + } + const styleValue = typeof rawValue === "number" ? `${rawValue}px` : String(rawValue); + if (key.startsWith("--")) { + element.style.setProperty(key, styleValue); + continue; + } + const cssKey = key.includes("-") ? key : toKebabCase(key); + element.style.setProperty(cssKey, styleValue); + } +}; +var toNode = (value) => { + if (value === null || value === void 0) { + return null; + } + if (value instanceof Node) { + return value; + } + return document.createTextNode(String(value)); +}; +var resolveEllipsisSymbol = (symbol, expanded) => { + if (typeof symbol === "function") { + return symbol(expanded); + } + return symbol ?? READ_MORE_TEXT; +}; var buildAngularTagHref = (entity) => { return `/search/?query=${encodeURIComponent(entity.tag.toLowerCase())}`; }; @@ -133,9 +168,325 @@ var AngularContentSuggestionsAdapter = class { }; } }; +var AngularContentTextRenderer = class { + host = null; + props = {}; + expanded = false; + viewed = false; + hostInsideView = false; + observer = null; + attach(host, props = {}) { + this.destroy(); + this.host = host; + this.props = { ...props }; + this.expanded = false; + this.viewed = false; + this.hostInsideView = false; + this.initObserver(); + this.render(); + return this.getState(); + } + update(nextProps) { + this.props = { + ...this.props, + ...nextProps + }; + this.render(); + return this.getState(); + } + destroy() { + if (this.observer && this.host) { + this.observer.unobserve(this.host); + this.observer.disconnect(); + } + this.observer = null; + if (this.host) { + this.host.innerHTML = ""; + } + this.host = null; + this.props = {}; + this.expanded = false; + this.viewed = false; + this.hostInsideView = false; + } + getState() { + return { + props: { ...this.props }, + snapshot: this.createSnapshot(), + expanded: this.getMergedExpanded(this.resolveEllipsisConfig(), this.getText()) + }; + } + getText() { + return this.props.text ?? ""; + } + createSnapshot() { + const text = this.getText(); + const entities = findAllEntities(text); + const tokens = createAngularContentTokens(text); + return { + text, + entities, + tokens + }; + } + resolveEllipsisConfig() { + const ellipsis = this.props.ellipsis; + if (!ellipsis || typeof ellipsis !== "object") { + return null; + } + return ellipsis; + } + getMergedExpanded(ellipsisConfig, text) { + const controlledExpanded = ellipsisConfig && typeof ellipsisConfig.expanded === "boolean" ? ellipsisConfig.expanded : void 0; + if (controlledExpanded !== void 0) { + return controlledExpanded; + } + if (ellipsisConfig && "count" in ellipsisConfig) { + const count = ellipsisConfig.count ?? 0; + if (count <= 0 || text.length <= count) { + return true; + } + } + return this.expanded; + } + createDefaultMentionNode(entity) { + const span = document.createElement("span"); + span.style.color = LINK_COLOR; + span.style.fontWeight = this.props.weight === "bold" ? "700" : "400"; + span.textContent = entity.displayText; + return span; + } + createDefaultTagNode(entity) { + const span = document.createElement("span"); + span.style.color = LINK_COLOR; + span.style.fontWeight = this.props.weight === "bold" ? "700" : "400"; + span.textContent = entity.text; + return span; + } + createDefaultLinkNode(entity) { + const anchor = document.createElement("a"); + anchor.href = entity.url; + anchor.target = "_blank"; + anchor.referrerPolicy = "no-referrer"; + anchor.style.color = LINK_COLOR; + anchor.style.fontWeight = this.props.weight === "bold" ? "700" : "400"; + anchor.textContent = entity.text; + return anchor; + } + renderEntity(entity, index) { + if (entity.type === "mention") { + const customNode2 = this.props.renderMention?.(entity, index); + return toNode(customNode2) ?? this.createDefaultMentionNode(entity); + } + if (entity.type === "tag") { + const customNode2 = this.props.renderTag?.(entity, index); + return toNode(customNode2) ?? this.createDefaultTagNode(entity); + } + const customNode = this.props.renderLink?.(entity, index); + return toNode(customNode) ?? this.createDefaultLinkNode(entity); + } + buildTextNodes(text, entities, upto = null) { + let lastIndex = 0; + const nodes = []; + for (const [index, 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(document.createTextNode(text.slice(lastIndex, textEnd))); + } + if (upto === null || entity.end <= upto) { + const entityNode = this.renderEntity(entity, index); + if (entityNode) { + nodes.push(entityNode); + } + } + lastIndex = entity.end; + } + if (upto === null) { + if (lastIndex < text.length) { + nodes.push(document.createTextNode(text.slice(lastIndex))); + } + } else if (lastIndex < upto) { + nodes.push(document.createTextNode(text.slice(lastIndex, upto))); + } + return nodes; + } + applyBaseParagraphStyle(element) { + element.style.whiteSpace = "pre-wrap"; + element.style.fontWeight = this.props.weight === "bold" ? "700" : "400"; + element.style.setProperty("-webkit-touch-callout", "default"); + element.style.setProperty("-webkit-user-select", "text"); + element.style.setProperty("-khtml-user-select", "text"); + element.style.setProperty("-moz-user-select", "text"); + element.style.setProperty("-ms-user-select", "text"); + element.style.setProperty("user-select", "text"); + if (this.props.blur) { + element.style.filter = "blur(3px)"; + element.style.setProperty("-webkit-user-select", "none"); + element.style.setProperty("-khtml-user-select", "none"); + element.style.setProperty("-moz-user-select", "none"); + element.style.setProperty("-ms-user-select", "none"); + element.style.setProperty("user-select", "none"); + element.style.pointerEvents = "none"; + } + applyStyleObject(element, this.props.style); + } + buildReadMoreButton(symbol) { + const button = document.createElement("button"); + button.type = "button"; + button.textContent = resolveEllipsisSymbol(symbol, this.expanded); + button.style.border = "0"; + button.style.background = "none"; + button.style.padding = "0"; + button.style.marginLeft = "6px"; + button.style.cursor = "pointer"; + button.style.color = LINK_COLOR; + button.style.fontWeight = "700"; + button.onclick = (event) => { + event.preventDefault(); + event.stopPropagation(); + this.handleExpand(); + }; + return button; + } + handleExpand() { + const ellipsisConfig = this.resolveEllipsisConfig(); + if (!ellipsisConfig) { + return; + } + this.expanded = true; + ellipsisConfig.onExpand?.(true); + this.render(); + } + computeEntitySafeCutoff(count, entities) { + 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; + } + } + } + return cutoff; + } + render() { + if (!this.host) { + return; + } + const text = this.getText(); + const entities = findAllEntities(text); + const ellipsisConfig = this.resolveEllipsisConfig(); + const mergedExpanded = this.getMergedExpanded(ellipsisConfig, text); + const wrapper = document.createElement("div"); + const paragraph = document.createElement("div"); + if (this.props.className) { + paragraph.className = this.props.className; + } + this.applyBaseParagraphStyle(paragraph); + if (ellipsisConfig && "count" in ellipsisConfig) { + const count = ellipsisConfig.count ?? 0; + const shouldTruncate = !mergedExpanded && count > 0 && text.length > count; + if (shouldTruncate) { + const cutoff = this.computeEntitySafeCutoff(count, entities); + const nodes = this.buildTextNodes(text, entities, cutoff); + paragraph.append(...nodes); + paragraph.append(document.createTextNode("\u2026")); + if (ellipsisConfig.expandable) { + paragraph.append(this.buildReadMoreButton(ellipsisConfig.symbol)); + } + } else { + paragraph.append(...this.buildTextNodes(text, entities)); + } + } else { + paragraph.append(...this.buildTextNodes(text, entities)); + if (ellipsisConfig && "rows" in ellipsisConfig && !mergedExpanded) { + paragraph.style.display = "-webkit-box"; + paragraph.style.setProperty("-webkit-box-orient", "vertical"); + paragraph.style.setProperty("-webkit-line-clamp", String(ellipsisConfig.rows)); + paragraph.style.overflow = "hidden"; + if (ellipsisConfig.expandable) { + wrapper.append(paragraph); + wrapper.append(this.buildReadMoreButton(ellipsisConfig.symbol)); + this.host.replaceChildren(wrapper); + this.emitOnViewIfNeeded(mergedExpanded); + return; + } + } + } + wrapper.append(paragraph); + this.host.replaceChildren(wrapper); + this.emitOnViewIfNeeded(mergedExpanded); + } + initObserver() { + if (!this.host) { + return; + } + if (typeof IntersectionObserver === "undefined") { + this.hostInsideView = true; + return; + } + this.observer = new IntersectionObserver( + (entries) => { + const entry = entries[0]; + if (!entry) { + return; + } + if (entry.isIntersecting && !this.hostInsideView) { + this.hostInsideView = true; + const ellipsisConfig = this.resolveEllipsisConfig(); + const mergedExpanded = this.getMergedExpanded(ellipsisConfig, this.getText()); + this.emitOnViewIfNeeded(mergedExpanded); + } + }, + { threshold: 0.5 } + ); + this.observer.observe(this.host); + } + emitOnViewIfNeeded(mergedExpanded) { + if (!mergedExpanded || this.viewed || !this.hostInsideView) { + return; + } + this.viewed = true; + this.props.onView?.(); + } +}; +var AngularContentTextWithSuggestionsRenderer = class extends AngularContentTextRenderer { +}; +var AngularContentTitleWithSuggestionsRenderer = class { + renderer = new AngularContentTextWithSuggestionsRenderer(); + attach(host, props = {}) { + return this.renderer.attach(host, this.normalizeProps(props)); + } + update(props) { + return this.renderer.update(this.normalizeProps(props)); + } + destroy() { + this.renderer.destroy(); + } + getState() { + return this.renderer.getState(); + } + normalizeProps(props) { + return { + ...props, + weight: "bold", + blur: props.blur ?? false, + ellipsis: props.ellipsis === void 0 ? { rows: 2 } : props.ellipsis + }; + } +}; exports.AngularContentSuggestionsAdapter = AngularContentSuggestionsAdapter; +exports.AngularContentTextRenderer = AngularContentTextRenderer; +exports.AngularContentTextWithSuggestionsRenderer = AngularContentTextWithSuggestionsRenderer; +exports.AngularContentTitleWithSuggestionsRenderer = AngularContentTitleWithSuggestionsRenderer; exports.buildAngularTagHref = buildAngularTagHref; exports.createAngularContentTokens = createAngularContentTokens; +exports.toKebabCase = toKebabCase; //# sourceMappingURL=index.cjs.map //# sourceMappingURL=index.cjs.map \ No newline at end of file diff --git a/packages/content-suggestions/dist/angular/index.cjs.map b/packages/content-suggestions/dist/angular/index.cjs.map index da09da4..eb3cca5 100644 --- a/packages/content-suggestions/dist/angular/index.cjs.map +++ b/packages/content-suggestions/dist/angular/index.cjs.map @@ -1 +1 @@ -{"version":3,"sources":["../../src/core/parser.ts","../../src/angular/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type {\n ContentEntity,\n LinkEntity,\n MentionEntity,\n ParsedMention,\n ProcessedContent,\n TagEntity,\n} from \"./types\";\n\nexport const mentionLinkRegexp =\n /@\\[[^\\]]+]\\([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;\n\nexport const parseMention = (mention: string): ParsedMention | null => {\n const regex = /@\\[([^\\]]+)\\]\\(([\\w-]{36})\\)/;\n const match = mention.match(regex);\n\n if (!match) {\n return null;\n }\n\n const mentionText = match[1];\n const mentionId = match[2];\n if (!mentionText || !mentionId) {\n return null;\n }\n\n return {\n mention: `@${mentionText}`,\n id: mentionId,\n };\n};\n\nexport const findMentions = (text: string): MentionEntity[] => {\n let match: RegExpExecArray | null;\n const matches: MentionEntity[] = [];\n\n while ((match = mentionLinkRegexp.exec(text)) !== null) {\n const parsed = parseMention(match[0]);\n const baseMention: Omit = {\n start: match.index,\n end: mentionLinkRegexp.lastIndex,\n text: match[0],\n type: \"mention\",\n displayText: parsed?.mention ?? match[0],\n };\n\n matches.push(parsed?.id ? { ...baseMention, userId: parsed.id } : baseMention);\n }\n\n return matches;\n};\n\nexport const findTags = (content: string): TagEntity[] => {\n const regex = /#[^\\s]{1,201}/g;\n const results: TagEntity[] = [];\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(content)) !== null) {\n const value = match[0];\n results.push({\n start: match.index,\n end: match.index + value.length,\n text: value,\n type: \"tag\",\n tag: value.replace(\"#\", \"\"),\n });\n }\n\n return results;\n};\n\nexport const findLinks = (content: string): LinkEntity[] => {\n const regex =\n /\\b((https?:\\/\\/)?(?:[\\w-]+\\.)+[a-z]{2,}(\\/[\\w\\-._~:/?#[\\]@!$&'()*+,;=]*)?)/gi;\n const results: LinkEntity[] = [];\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(content)) !== null) {\n const rawUrl = match[0];\n const hasProtocol = /^https?:\\/\\//i.test(rawUrl);\n const fullUrl = hasProtocol ? rawUrl : `https://${rawUrl}`;\n\n results.push({\n start: match.index,\n end: match.index + rawUrl.length,\n text: rawUrl,\n url: fullUrl,\n type: \"link\",\n });\n }\n\n return results;\n};\n\nexport const findAllEntities = (content: string): ContentEntity[] => {\n const mentions = findMentions(content);\n const tags = findTags(content);\n const links = findLinks(content);\n\n return [...mentions, ...tags, ...links].sort((a, b) => a.start - b.start);\n};\n\nexport const processContent = (content: string): ProcessedContent => {\n const processedText = content.replace(mentionLinkRegexp, match => {\n const parsed = parseMention(match);\n return parsed ? parsed.mention : match;\n });\n\n const tags = findTags(content).map(tag => tag.tag);\n\n return {\n processedText,\n tags,\n };\n};\n","import type { ContentEntity, LinkEntity, MentionEntity, TagEntity } from \"../core\";\n\nimport { findAllEntities } from \"../core\";\n\nexport interface AngularTextToken {\n kind: \"text\";\n text: string;\n start: number;\n end: number;\n}\n\nexport interface AngularMentionToken {\n kind: \"mention\";\n entity: MentionEntity;\n}\n\nexport interface AngularTagToken {\n kind: \"tag\";\n entity: TagEntity;\n}\n\nexport interface AngularLinkToken {\n kind: \"link\";\n entity: LinkEntity;\n}\n\nexport type AngularContentToken =\n | AngularTextToken\n | AngularMentionToken\n | AngularTagToken\n | AngularLinkToken;\n\nexport interface AngularContentSnapshot {\n text: string;\n entities: ContentEntity[];\n tokens: AngularContentToken[];\n}\n\nexport const buildAngularTagHref = (entity: TagEntity): string => {\n return `/search/?query=${encodeURIComponent(entity.tag.toLowerCase())}`;\n};\n\nexport const createAngularContentTokens = (\n inputText: string | null | undefined,\n): AngularContentToken[] => {\n const text = inputText ?? \"\";\n const entities = findAllEntities(text);\n\n let cursor = 0;\n const tokens: AngularContentToken[] = [];\n\n for (const entity of entities) {\n if (entity.start > cursor) {\n tokens.push({\n kind: \"text\",\n text: text.slice(cursor, entity.start),\n start: cursor,\n end: entity.start,\n });\n }\n\n if (entity.type === \"mention\") {\n tokens.push({\n kind: \"mention\",\n entity,\n });\n } else if (entity.type === \"tag\") {\n tokens.push({\n kind: \"tag\",\n entity,\n });\n } else {\n tokens.push({\n kind: \"link\",\n entity,\n });\n }\n\n cursor = entity.end;\n }\n\n if (cursor < text.length) {\n tokens.push({\n kind: \"text\",\n text: text.slice(cursor),\n start: cursor,\n end: text.length,\n });\n }\n\n return tokens;\n};\n\nexport class AngularContentSuggestionsAdapter {\n snapshot(inputText: string | null | undefined): AngularContentSnapshot {\n const text = inputText ?? \"\";\n const entities = findAllEntities(text);\n const tokens = createAngularContentTokens(text);\n\n return {\n text,\n entities,\n tokens,\n };\n }\n}\n"]} \ No newline at end of file +{"version":3,"sources":["../../src/core/parser.ts","../../src/angular/index.ts"],"names":["customNode"],"mappings":"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","file":"index.cjs","sourcesContent":["import type {\n ContentEntity,\n LinkEntity,\n MentionEntity,\n ParsedMention,\n ProcessedContent,\n TagEntity,\n} from \"./types\";\n\nexport const mentionLinkRegexp =\n /@\\[[^\\]]+]\\([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;\n\nexport const parseMention = (mention: string): ParsedMention | null => {\n const regex = /@\\[([^\\]]+)\\]\\(([\\w-]{36})\\)/;\n const match = mention.match(regex);\n\n if (!match) {\n return null;\n }\n\n const mentionText = match[1];\n const mentionId = match[2];\n if (!mentionText || !mentionId) {\n return null;\n }\n\n return {\n mention: `@${mentionText}`,\n id: mentionId,\n };\n};\n\nexport const findMentions = (text: string): MentionEntity[] => {\n let match: RegExpExecArray | null;\n const matches: MentionEntity[] = [];\n\n while ((match = mentionLinkRegexp.exec(text)) !== null) {\n const parsed = parseMention(match[0]);\n const baseMention: Omit = {\n start: match.index,\n end: mentionLinkRegexp.lastIndex,\n text: match[0],\n type: \"mention\",\n displayText: parsed?.mention ?? match[0],\n };\n\n matches.push(parsed?.id ? { ...baseMention, userId: parsed.id } : baseMention);\n }\n\n return matches;\n};\n\nexport const findTags = (content: string): TagEntity[] => {\n const regex = /#[^\\s]{1,201}/g;\n const results: TagEntity[] = [];\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(content)) !== null) {\n const value = match[0];\n results.push({\n start: match.index,\n end: match.index + value.length,\n text: value,\n type: \"tag\",\n tag: value.replace(\"#\", \"\"),\n });\n }\n\n return results;\n};\n\nexport const findLinks = (content: string): LinkEntity[] => {\n const regex =\n /\\b((https?:\\/\\/)?(?:[\\w-]+\\.)+[a-z]{2,}(\\/[\\w\\-._~:/?#[\\]@!$&'()*+,;=]*)?)/gi;\n const results: LinkEntity[] = [];\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(content)) !== null) {\n const rawUrl = match[0];\n const hasProtocol = /^https?:\\/\\//i.test(rawUrl);\n const fullUrl = hasProtocol ? rawUrl : `https://${rawUrl}`;\n\n results.push({\n start: match.index,\n end: match.index + rawUrl.length,\n text: rawUrl,\n url: fullUrl,\n type: \"link\",\n });\n }\n\n return results;\n};\n\nexport const findAllEntities = (content: string): ContentEntity[] => {\n const mentions = findMentions(content);\n const tags = findTags(content);\n const links = findLinks(content);\n\n return [...mentions, ...tags, ...links].sort((a, b) => a.start - b.start);\n};\n\nexport const processContent = (content: string): ProcessedContent => {\n const processedText = content.replace(mentionLinkRegexp, match => {\n const parsed = parseMention(match);\n return parsed ? parsed.mention : match;\n });\n\n const tags = findTags(content).map(tag => tag.tag);\n\n return {\n processedText,\n tags,\n };\n};\n","import type { ContentEntity, LinkEntity, MentionEntity, TagEntity } from \"../core\";\n\nimport { findAllEntities } from \"../core\";\n\nexport interface AngularTextToken {\n kind: \"text\";\n text: string;\n start: number;\n end: number;\n}\n\nexport interface AngularMentionToken {\n kind: \"mention\";\n entity: MentionEntity;\n}\n\nexport interface AngularTagToken {\n kind: \"tag\";\n entity: TagEntity;\n}\n\nexport interface AngularLinkToken {\n kind: \"link\";\n entity: LinkEntity;\n}\n\nexport type AngularContentToken =\n | AngularTextToken\n | AngularMentionToken\n | AngularTagToken\n | AngularLinkToken;\n\nexport interface AngularContentSnapshot {\n text: string;\n entities: ContentEntity[];\n tokens: AngularContentToken[];\n}\n\nexport type AngularEllipsisSymbol = string | ((expanded: boolean) => string);\n\nexport type AngularCountEllipsisConfig = {\n count: number;\n rows?: never;\n expandable?: boolean;\n expanded?: boolean;\n symbol?: AngularEllipsisSymbol;\n onExpand?: (expanded: boolean) => void;\n};\n\nexport type AngularRowsEllipsisConfig = {\n rows: number;\n count?: never;\n expandable?: boolean;\n expanded?: boolean;\n symbol?: AngularEllipsisSymbol;\n onExpand?: (expanded: boolean) => void;\n};\n\nexport type AngularContentEllipsisConfig =\n | AngularCountEllipsisConfig\n | AngularRowsEllipsisConfig\n | false;\n\nexport type AngularRenderResult = Node | string | number | null | undefined;\n\nexport type AngularMentionRenderer = (\n entity: MentionEntity,\n index: number,\n) => AngularRenderResult;\n\nexport type AngularTagRenderer = (\n entity: TagEntity,\n index: number,\n) => AngularRenderResult;\n\nexport type AngularLinkRenderer = (\n entity: LinkEntity,\n index: number,\n) => AngularRenderResult;\n\nexport interface AngularContentTextProps {\n className?: string;\n weight?: \"normal\" | \"bold\";\n text?: string | null;\n ellipsis?: AngularContentEllipsisConfig;\n blur?: boolean;\n style?: Record | null;\n onView?: () => void;\n renderMention?: AngularMentionRenderer;\n renderTag?: AngularTagRenderer;\n renderLink?: AngularLinkRenderer;\n}\n\nexport type AngularContentTextWithSuggestionsProps = Omit<\n AngularContentTextProps,\n \"renderMention\" | \"renderTag\"\n> & {\n renderMention?: AngularMentionRenderer;\n renderTag?: AngularTagRenderer;\n};\n\nexport type AngularContentTitleWithSuggestionsProps = Omit<\n AngularContentTextWithSuggestionsProps,\n \"weight\"\n>;\n\nexport interface AngularContentTextRendererState {\n props: AngularContentTextProps;\n snapshot: AngularContentSnapshot;\n expanded: boolean;\n}\n\nconst LINK_COLOR = \"#1677ff\";\nconst READ_MORE_TEXT = \"Читать полностью\";\n\nconst toKebabCase = (value: string) => value.replace(/[A-Z]/g, char => `-${char.toLowerCase()}`);\n\nconst applyStyleObject = (\n element: HTMLElement,\n styles?: Record | null,\n) => {\n if (!styles) {\n return;\n }\n\n for (const [key, rawValue] of Object.entries(styles)) {\n if (rawValue === null || rawValue === undefined) {\n continue;\n }\n\n const styleValue =\n typeof rawValue === \"number\" ? `${rawValue}px` : String(rawValue);\n\n if (key.startsWith(\"--\")) {\n element.style.setProperty(key, styleValue);\n continue;\n }\n\n const cssKey = key.includes(\"-\") ? key : toKebabCase(key);\n element.style.setProperty(cssKey, styleValue);\n }\n};\n\nconst toNode = (value: AngularRenderResult): Node | null => {\n if (value === null || value === undefined) {\n return null;\n }\n\n if (value instanceof Node) {\n return value;\n }\n\n return document.createTextNode(String(value));\n};\n\nconst resolveEllipsisSymbol = (\n symbol: AngularEllipsisSymbol | undefined,\n expanded: boolean,\n): string => {\n if (typeof symbol === \"function\") {\n return symbol(expanded);\n }\n\n return symbol ?? READ_MORE_TEXT;\n};\n\nconst buildAngularTagHref = (entity: TagEntity): string => {\n return `/search/?query=${encodeURIComponent(entity.tag.toLowerCase())}`;\n};\n\nexport { buildAngularTagHref };\n\nexport const createAngularContentTokens = (\n inputText: string | null | undefined,\n): AngularContentToken[] => {\n const text = inputText ?? \"\";\n const entities = findAllEntities(text);\n\n let cursor = 0;\n const tokens: AngularContentToken[] = [];\n\n for (const entity of entities) {\n if (entity.start > cursor) {\n tokens.push({\n kind: \"text\",\n text: text.slice(cursor, entity.start),\n start: cursor,\n end: entity.start,\n });\n }\n\n if (entity.type === \"mention\") {\n tokens.push({\n kind: \"mention\",\n entity,\n });\n } else if (entity.type === \"tag\") {\n tokens.push({\n kind: \"tag\",\n entity,\n });\n } else {\n tokens.push({\n kind: \"link\",\n entity,\n });\n }\n\n cursor = entity.end;\n }\n\n if (cursor < text.length) {\n tokens.push({\n kind: \"text\",\n text: text.slice(cursor),\n start: cursor,\n end: text.length,\n });\n }\n\n return tokens;\n};\n\nexport class AngularContentSuggestionsAdapter {\n snapshot(inputText: string | null | undefined): AngularContentSnapshot {\n const text = inputText ?? \"\";\n const entities = findAllEntities(text);\n const tokens = createAngularContentTokens(text);\n\n return {\n text,\n entities,\n tokens,\n };\n }\n}\n\nexport class AngularContentTextRenderer {\n private host: HTMLElement | null = null;\n private props: AngularContentTextProps = {};\n private expanded = false;\n private viewed = false;\n private hostInsideView = false;\n private observer: IntersectionObserver | null = null;\n\n attach(host: HTMLElement, props: AngularContentTextProps = {}): AngularContentTextRendererState {\n this.destroy();\n\n this.host = host;\n this.props = { ...props };\n this.expanded = false;\n this.viewed = false;\n this.hostInsideView = false;\n\n this.initObserver();\n this.render();\n\n return this.getState();\n }\n\n update(nextProps: AngularContentTextProps): AngularContentTextRendererState {\n this.props = {\n ...this.props,\n ...nextProps,\n };\n\n this.render();\n\n return this.getState();\n }\n\n destroy(): void {\n if (this.observer && this.host) {\n this.observer.unobserve(this.host);\n this.observer.disconnect();\n }\n\n this.observer = null;\n\n if (this.host) {\n this.host.innerHTML = \"\";\n }\n\n this.host = null;\n this.props = {};\n this.expanded = false;\n this.viewed = false;\n this.hostInsideView = false;\n }\n\n getState(): AngularContentTextRendererState {\n return {\n props: { ...this.props },\n snapshot: this.createSnapshot(),\n expanded: this.getMergedExpanded(this.resolveEllipsisConfig(), this.getText()),\n };\n }\n\n private getText(): string {\n return this.props.text ?? \"\";\n }\n\n private createSnapshot(): AngularContentSnapshot {\n const text = this.getText();\n const entities = findAllEntities(text);\n const tokens = createAngularContentTokens(text);\n\n return {\n text,\n entities,\n tokens,\n };\n }\n\n private resolveEllipsisConfig():\n | AngularCountEllipsisConfig\n | AngularRowsEllipsisConfig\n | null {\n const ellipsis = this.props.ellipsis;\n\n if (!ellipsis || typeof ellipsis !== \"object\") {\n return null;\n }\n\n return ellipsis;\n }\n\n private getMergedExpanded(\n ellipsisConfig: AngularCountEllipsisConfig | AngularRowsEllipsisConfig | null,\n text: string,\n ): boolean {\n const controlledExpanded =\n ellipsisConfig && typeof ellipsisConfig.expanded === \"boolean\"\n ? ellipsisConfig.expanded\n : undefined;\n\n if (controlledExpanded !== undefined) {\n return controlledExpanded;\n }\n\n if (ellipsisConfig && \"count\" in ellipsisConfig) {\n const count = ellipsisConfig.count ?? 0;\n if (count <= 0 || text.length <= count) {\n return true;\n }\n }\n\n return this.expanded;\n }\n\n private createDefaultMentionNode(entity: MentionEntity): Node {\n const span = document.createElement(\"span\");\n span.style.color = LINK_COLOR;\n span.style.fontWeight = this.props.weight === \"bold\" ? \"700\" : \"400\";\n span.textContent = entity.displayText;\n return span;\n }\n\n private createDefaultTagNode(entity: TagEntity): Node {\n const span = document.createElement(\"span\");\n span.style.color = LINK_COLOR;\n span.style.fontWeight = this.props.weight === \"bold\" ? \"700\" : \"400\";\n span.textContent = entity.text;\n return span;\n }\n\n private createDefaultLinkNode(entity: LinkEntity): Node {\n const anchor = document.createElement(\"a\");\n anchor.href = entity.url;\n anchor.target = \"_blank\";\n anchor.referrerPolicy = \"no-referrer\";\n anchor.style.color = LINK_COLOR;\n anchor.style.fontWeight = this.props.weight === \"bold\" ? \"700\" : \"400\";\n anchor.textContent = entity.text;\n return anchor;\n }\n\n private renderEntity(entity: ContentEntity, index: number): Node | null {\n if (entity.type === \"mention\") {\n const customNode = this.props.renderMention?.(entity, index);\n return toNode(customNode) ?? this.createDefaultMentionNode(entity);\n }\n\n if (entity.type === \"tag\") {\n const customNode = this.props.renderTag?.(entity, index);\n return toNode(customNode) ?? this.createDefaultTagNode(entity);\n }\n\n const customNode = this.props.renderLink?.(entity, index);\n return toNode(customNode) ?? this.createDefaultLinkNode(entity);\n }\n\n private buildTextNodes(\n text: string,\n entities: ContentEntity[],\n upto: number | null = null,\n ): Node[] {\n let lastIndex = 0;\n const nodes: Node[] = [];\n\n for (const [index, entity] of entities.entries()) {\n if (upto !== null && entity.start >= upto) {\n break;\n }\n\n const textEnd = upto !== null ? Math.min(entity.start, upto) : entity.start;\n if (entity.start > lastIndex && lastIndex < textEnd) {\n nodes.push(document.createTextNode(text.slice(lastIndex, textEnd)));\n }\n\n if (upto === null || entity.end <= upto) {\n const entityNode = this.renderEntity(entity, index);\n if (entityNode) {\n nodes.push(entityNode);\n }\n }\n\n lastIndex = entity.end;\n }\n\n if (upto === null) {\n if (lastIndex < text.length) {\n nodes.push(document.createTextNode(text.slice(lastIndex)));\n }\n } else if (lastIndex < upto) {\n nodes.push(document.createTextNode(text.slice(lastIndex, upto)));\n }\n\n return nodes;\n }\n\n private applyBaseParagraphStyle(element: HTMLElement): void {\n element.style.whiteSpace = \"pre-wrap\";\n element.style.fontWeight = this.props.weight === \"bold\" ? \"700\" : \"400\";\n element.style.setProperty(\"-webkit-touch-callout\", \"default\");\n element.style.setProperty(\"-webkit-user-select\", \"text\");\n element.style.setProperty(\"-khtml-user-select\", \"text\");\n element.style.setProperty(\"-moz-user-select\", \"text\");\n element.style.setProperty(\"-ms-user-select\", \"text\");\n element.style.setProperty(\"user-select\", \"text\");\n\n if (this.props.blur) {\n element.style.filter = \"blur(3px)\";\n element.style.setProperty(\"-webkit-user-select\", \"none\");\n element.style.setProperty(\"-khtml-user-select\", \"none\");\n element.style.setProperty(\"-moz-user-select\", \"none\");\n element.style.setProperty(\"-ms-user-select\", \"none\");\n element.style.setProperty(\"user-select\", \"none\");\n element.style.pointerEvents = \"none\";\n }\n\n applyStyleObject(element, this.props.style);\n }\n\n private buildReadMoreButton(symbol: AngularEllipsisSymbol | undefined): HTMLButtonElement {\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.textContent = resolveEllipsisSymbol(symbol, this.expanded);\n button.style.border = \"0\";\n button.style.background = \"none\";\n button.style.padding = \"0\";\n button.style.marginLeft = \"6px\";\n button.style.cursor = \"pointer\";\n button.style.color = LINK_COLOR;\n button.style.fontWeight = \"700\";\n button.onclick = event => {\n event.preventDefault();\n event.stopPropagation();\n this.handleExpand();\n };\n return button;\n }\n\n private handleExpand(): void {\n const ellipsisConfig = this.resolveEllipsisConfig();\n\n if (!ellipsisConfig) {\n return;\n }\n\n this.expanded = true;\n ellipsisConfig.onExpand?.(true);\n this.render();\n }\n\n private computeEntitySafeCutoff(\n count: number,\n entities: ContentEntity[],\n ): number {\n let cutoff = count;\n let extended = true;\n\n while (extended) {\n extended = false;\n\n for (const entity of entities) {\n if (entity.start < cutoff && entity.end > cutoff) {\n cutoff = entity.end;\n extended = true;\n }\n }\n }\n\n return cutoff;\n }\n\n private render(): void {\n if (!this.host) {\n return;\n }\n\n const text = this.getText();\n const entities = findAllEntities(text);\n const ellipsisConfig = this.resolveEllipsisConfig();\n const mergedExpanded = this.getMergedExpanded(ellipsisConfig, text);\n\n const wrapper = document.createElement(\"div\");\n const paragraph = document.createElement(\"div\");\n\n if (this.props.className) {\n paragraph.className = this.props.className;\n }\n\n this.applyBaseParagraphStyle(paragraph);\n\n if (ellipsisConfig && \"count\" in ellipsisConfig) {\n const count = ellipsisConfig.count ?? 0;\n const shouldTruncate = !mergedExpanded && count > 0 && text.length > count;\n\n if (shouldTruncate) {\n const cutoff = this.computeEntitySafeCutoff(count, entities);\n const nodes = this.buildTextNodes(text, entities, cutoff);\n\n paragraph.append(...nodes);\n paragraph.append(document.createTextNode(\"…\"));\n\n if (ellipsisConfig.expandable) {\n paragraph.append(this.buildReadMoreButton(ellipsisConfig.symbol));\n }\n } else {\n paragraph.append(...this.buildTextNodes(text, entities));\n }\n } else {\n paragraph.append(...this.buildTextNodes(text, entities));\n\n if (ellipsisConfig && \"rows\" in ellipsisConfig && !mergedExpanded) {\n paragraph.style.display = \"-webkit-box\";\n paragraph.style.setProperty(\"-webkit-box-orient\", \"vertical\");\n paragraph.style.setProperty(\"-webkit-line-clamp\", String(ellipsisConfig.rows));\n paragraph.style.overflow = \"hidden\";\n\n if (ellipsisConfig.expandable) {\n wrapper.append(paragraph);\n wrapper.append(this.buildReadMoreButton(ellipsisConfig.symbol));\n this.host.replaceChildren(wrapper);\n this.emitOnViewIfNeeded(mergedExpanded);\n return;\n }\n }\n }\n\n wrapper.append(paragraph);\n this.host.replaceChildren(wrapper);\n this.emitOnViewIfNeeded(mergedExpanded);\n }\n\n private initObserver(): void {\n if (!this.host) {\n return;\n }\n\n if (typeof IntersectionObserver === \"undefined\") {\n this.hostInsideView = true;\n return;\n }\n\n this.observer = new IntersectionObserver(\n entries => {\n const entry = entries[0];\n if (!entry) {\n return;\n }\n\n if (entry.isIntersecting && !this.hostInsideView) {\n this.hostInsideView = true;\n const ellipsisConfig = this.resolveEllipsisConfig();\n const mergedExpanded = this.getMergedExpanded(ellipsisConfig, this.getText());\n this.emitOnViewIfNeeded(mergedExpanded);\n }\n },\n { threshold: 0.5 },\n );\n\n this.observer.observe(this.host);\n }\n\n private emitOnViewIfNeeded(mergedExpanded: boolean): void {\n if (!mergedExpanded || this.viewed || !this.hostInsideView) {\n return;\n }\n\n this.viewed = true;\n this.props.onView?.();\n }\n}\n\nexport class AngularContentTextWithSuggestionsRenderer extends AngularContentTextRenderer {}\n\nexport class AngularContentTitleWithSuggestionsRenderer {\n private readonly renderer = new AngularContentTextWithSuggestionsRenderer();\n\n attach(\n host: HTMLElement,\n props: AngularContentTitleWithSuggestionsProps = {},\n ): AngularContentTextRendererState {\n return this.renderer.attach(host, this.normalizeProps(props));\n }\n\n update(props: AngularContentTitleWithSuggestionsProps): AngularContentTextRendererState {\n return this.renderer.update(this.normalizeProps(props));\n }\n\n destroy(): void {\n this.renderer.destroy();\n }\n\n getState(): AngularContentTextRendererState {\n return this.renderer.getState();\n }\n\n private normalizeProps(\n props: AngularContentTitleWithSuggestionsProps,\n ): AngularContentTextWithSuggestionsProps {\n return {\n ...props,\n weight: \"bold\",\n blur: props.blur ?? false,\n ellipsis: props.ellipsis === undefined ? { rows: 2 } : props.ellipsis,\n };\n }\n}\n\nexport { toKebabCase };\n"]} \ No newline at end of file diff --git a/packages/content-suggestions/dist/angular/index.d.cts b/packages/content-suggestions/dist/angular/index.d.cts index 9c7131a..3f0a2b8 100644 --- a/packages/content-suggestions/dist/angular/index.d.cts +++ b/packages/content-suggestions/dist/angular/index.d.cts @@ -24,10 +24,94 @@ interface AngularContentSnapshot { entities: ContentEntity[]; tokens: AngularContentToken[]; } +type AngularEllipsisSymbol = string | ((expanded: boolean) => string); +type AngularCountEllipsisConfig = { + count: number; + rows?: never; + expandable?: boolean; + expanded?: boolean; + symbol?: AngularEllipsisSymbol; + onExpand?: (expanded: boolean) => void; +}; +type AngularRowsEllipsisConfig = { + rows: number; + count?: never; + expandable?: boolean; + expanded?: boolean; + symbol?: AngularEllipsisSymbol; + onExpand?: (expanded: boolean) => void; +}; +type AngularContentEllipsisConfig = AngularCountEllipsisConfig | AngularRowsEllipsisConfig | false; +type AngularRenderResult = Node | string | number | null | undefined; +type AngularMentionRenderer = (entity: MentionEntity, index: number) => AngularRenderResult; +type AngularTagRenderer = (entity: TagEntity, index: number) => AngularRenderResult; +type AngularLinkRenderer = (entity: LinkEntity, index: number) => AngularRenderResult; +interface AngularContentTextProps { + className?: string; + weight?: "normal" | "bold"; + text?: string | null; + ellipsis?: AngularContentEllipsisConfig; + blur?: boolean; + style?: Record | null; + onView?: () => void; + renderMention?: AngularMentionRenderer; + renderTag?: AngularTagRenderer; + renderLink?: AngularLinkRenderer; +} +type AngularContentTextWithSuggestionsProps = Omit & { + renderMention?: AngularMentionRenderer; + renderTag?: AngularTagRenderer; +}; +type AngularContentTitleWithSuggestionsProps = Omit; +interface AngularContentTextRendererState { + props: AngularContentTextProps; + snapshot: AngularContentSnapshot; + expanded: boolean; +} +declare const toKebabCase: (value: string) => string; declare const buildAngularTagHref: (entity: TagEntity) => string; + declare const createAngularContentTokens: (inputText: string | null | undefined) => AngularContentToken[]; declare class AngularContentSuggestionsAdapter { snapshot(inputText: string | null | undefined): AngularContentSnapshot; } +declare class AngularContentTextRenderer { + private host; + private props; + private expanded; + private viewed; + private hostInsideView; + private observer; + attach(host: HTMLElement, props?: AngularContentTextProps): AngularContentTextRendererState; + update(nextProps: AngularContentTextProps): AngularContentTextRendererState; + destroy(): void; + getState(): AngularContentTextRendererState; + private getText; + private createSnapshot; + private resolveEllipsisConfig; + private getMergedExpanded; + private createDefaultMentionNode; + private createDefaultTagNode; + private createDefaultLinkNode; + private renderEntity; + private buildTextNodes; + private applyBaseParagraphStyle; + private buildReadMoreButton; + private handleExpand; + private computeEntitySafeCutoff; + private render; + private initObserver; + private emitOnViewIfNeeded; +} +declare class AngularContentTextWithSuggestionsRenderer extends AngularContentTextRenderer { +} +declare class AngularContentTitleWithSuggestionsRenderer { + private readonly renderer; + attach(host: HTMLElement, props?: AngularContentTitleWithSuggestionsProps): AngularContentTextRendererState; + update(props: AngularContentTitleWithSuggestionsProps): AngularContentTextRendererState; + destroy(): void; + getState(): AngularContentTextRendererState; + private normalizeProps; +} -export { type AngularContentSnapshot, AngularContentSuggestionsAdapter, type AngularContentToken, type AngularLinkToken, type AngularMentionToken, type AngularTagToken, type AngularTextToken, buildAngularTagHref, createAngularContentTokens }; +export { type AngularContentEllipsisConfig, type AngularContentSnapshot, AngularContentSuggestionsAdapter, type AngularContentTextProps, AngularContentTextRenderer, type AngularContentTextRendererState, type AngularContentTextWithSuggestionsProps, AngularContentTextWithSuggestionsRenderer, type AngularContentTitleWithSuggestionsProps, AngularContentTitleWithSuggestionsRenderer, type AngularContentToken, type AngularCountEllipsisConfig, type AngularEllipsisSymbol, type AngularLinkRenderer, type AngularLinkToken, type AngularMentionRenderer, type AngularMentionToken, type AngularRenderResult, type AngularRowsEllipsisConfig, type AngularTagRenderer, type AngularTagToken, type AngularTextToken, buildAngularTagHref, createAngularContentTokens, toKebabCase }; diff --git a/packages/content-suggestions/dist/angular/index.d.ts b/packages/content-suggestions/dist/angular/index.d.ts index 3652c24..d47018f 100644 --- a/packages/content-suggestions/dist/angular/index.d.ts +++ b/packages/content-suggestions/dist/angular/index.d.ts @@ -24,10 +24,94 @@ interface AngularContentSnapshot { entities: ContentEntity[]; tokens: AngularContentToken[]; } +type AngularEllipsisSymbol = string | ((expanded: boolean) => string); +type AngularCountEllipsisConfig = { + count: number; + rows?: never; + expandable?: boolean; + expanded?: boolean; + symbol?: AngularEllipsisSymbol; + onExpand?: (expanded: boolean) => void; +}; +type AngularRowsEllipsisConfig = { + rows: number; + count?: never; + expandable?: boolean; + expanded?: boolean; + symbol?: AngularEllipsisSymbol; + onExpand?: (expanded: boolean) => void; +}; +type AngularContentEllipsisConfig = AngularCountEllipsisConfig | AngularRowsEllipsisConfig | false; +type AngularRenderResult = Node | string | number | null | undefined; +type AngularMentionRenderer = (entity: MentionEntity, index: number) => AngularRenderResult; +type AngularTagRenderer = (entity: TagEntity, index: number) => AngularRenderResult; +type AngularLinkRenderer = (entity: LinkEntity, index: number) => AngularRenderResult; +interface AngularContentTextProps { + className?: string; + weight?: "normal" | "bold"; + text?: string | null; + ellipsis?: AngularContentEllipsisConfig; + blur?: boolean; + style?: Record | null; + onView?: () => void; + renderMention?: AngularMentionRenderer; + renderTag?: AngularTagRenderer; + renderLink?: AngularLinkRenderer; +} +type AngularContentTextWithSuggestionsProps = Omit & { + renderMention?: AngularMentionRenderer; + renderTag?: AngularTagRenderer; +}; +type AngularContentTitleWithSuggestionsProps = Omit; +interface AngularContentTextRendererState { + props: AngularContentTextProps; + snapshot: AngularContentSnapshot; + expanded: boolean; +} +declare const toKebabCase: (value: string) => string; declare const buildAngularTagHref: (entity: TagEntity) => string; + declare const createAngularContentTokens: (inputText: string | null | undefined) => AngularContentToken[]; declare class AngularContentSuggestionsAdapter { snapshot(inputText: string | null | undefined): AngularContentSnapshot; } +declare class AngularContentTextRenderer { + private host; + private props; + private expanded; + private viewed; + private hostInsideView; + private observer; + attach(host: HTMLElement, props?: AngularContentTextProps): AngularContentTextRendererState; + update(nextProps: AngularContentTextProps): AngularContentTextRendererState; + destroy(): void; + getState(): AngularContentTextRendererState; + private getText; + private createSnapshot; + private resolveEllipsisConfig; + private getMergedExpanded; + private createDefaultMentionNode; + private createDefaultTagNode; + private createDefaultLinkNode; + private renderEntity; + private buildTextNodes; + private applyBaseParagraphStyle; + private buildReadMoreButton; + private handleExpand; + private computeEntitySafeCutoff; + private render; + private initObserver; + private emitOnViewIfNeeded; +} +declare class AngularContentTextWithSuggestionsRenderer extends AngularContentTextRenderer { +} +declare class AngularContentTitleWithSuggestionsRenderer { + private readonly renderer; + attach(host: HTMLElement, props?: AngularContentTitleWithSuggestionsProps): AngularContentTextRendererState; + update(props: AngularContentTitleWithSuggestionsProps): AngularContentTextRendererState; + destroy(): void; + getState(): AngularContentTextRendererState; + private normalizeProps; +} -export { type AngularContentSnapshot, AngularContentSuggestionsAdapter, type AngularContentToken, type AngularLinkToken, type AngularMentionToken, type AngularTagToken, type AngularTextToken, buildAngularTagHref, createAngularContentTokens }; +export { type AngularContentEllipsisConfig, type AngularContentSnapshot, AngularContentSuggestionsAdapter, type AngularContentTextProps, AngularContentTextRenderer, type AngularContentTextRendererState, type AngularContentTextWithSuggestionsProps, AngularContentTextWithSuggestionsRenderer, type AngularContentTitleWithSuggestionsProps, AngularContentTitleWithSuggestionsRenderer, type AngularContentToken, type AngularCountEllipsisConfig, type AngularEllipsisSymbol, type AngularLinkRenderer, type AngularLinkToken, type AngularMentionRenderer, type AngularMentionToken, type AngularRenderResult, type AngularRowsEllipsisConfig, type AngularTagRenderer, type AngularTagToken, type AngularTextToken, buildAngularTagHref, createAngularContentTokens, toKebabCase }; diff --git a/packages/content-suggestions/dist/angular/index.js b/packages/content-suggestions/dist/angular/index.js index a87f35b..bc64515 100644 --- a/packages/content-suggestions/dist/angular/index.js +++ b/packages/content-suggestions/dist/angular/index.js @@ -74,6 +74,41 @@ var findAllEntities = (content) => { }; // src/angular/index.ts +var LINK_COLOR = "#1677ff"; +var READ_MORE_TEXT = "\u0427\u0438\u0442\u0430\u0442\u044C \u043F\u043E\u043B\u043D\u043E\u0441\u0442\u044C\u044E"; +var toKebabCase = (value) => value.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`); +var applyStyleObject = (element, styles) => { + if (!styles) { + return; + } + for (const [key, rawValue] of Object.entries(styles)) { + if (rawValue === null || rawValue === void 0) { + continue; + } + const styleValue = typeof rawValue === "number" ? `${rawValue}px` : String(rawValue); + if (key.startsWith("--")) { + element.style.setProperty(key, styleValue); + continue; + } + const cssKey = key.includes("-") ? key : toKebabCase(key); + element.style.setProperty(cssKey, styleValue); + } +}; +var toNode = (value) => { + if (value === null || value === void 0) { + return null; + } + if (value instanceof Node) { + return value; + } + return document.createTextNode(String(value)); +}; +var resolveEllipsisSymbol = (symbol, expanded) => { + if (typeof symbol === "function") { + return symbol(expanded); + } + return symbol ?? READ_MORE_TEXT; +}; var buildAngularTagHref = (entity) => { return `/search/?query=${encodeURIComponent(entity.tag.toLowerCase())}`; }; @@ -131,7 +166,319 @@ var AngularContentSuggestionsAdapter = class { }; } }; +var AngularContentTextRenderer = class { + host = null; + props = {}; + expanded = false; + viewed = false; + hostInsideView = false; + observer = null; + attach(host, props = {}) { + this.destroy(); + this.host = host; + this.props = { ...props }; + this.expanded = false; + this.viewed = false; + this.hostInsideView = false; + this.initObserver(); + this.render(); + return this.getState(); + } + update(nextProps) { + this.props = { + ...this.props, + ...nextProps + }; + this.render(); + return this.getState(); + } + destroy() { + if (this.observer && this.host) { + this.observer.unobserve(this.host); + this.observer.disconnect(); + } + this.observer = null; + if (this.host) { + this.host.innerHTML = ""; + } + this.host = null; + this.props = {}; + this.expanded = false; + this.viewed = false; + this.hostInsideView = false; + } + getState() { + return { + props: { ...this.props }, + snapshot: this.createSnapshot(), + expanded: this.getMergedExpanded(this.resolveEllipsisConfig(), this.getText()) + }; + } + getText() { + return this.props.text ?? ""; + } + createSnapshot() { + const text = this.getText(); + const entities = findAllEntities(text); + const tokens = createAngularContentTokens(text); + return { + text, + entities, + tokens + }; + } + resolveEllipsisConfig() { + const ellipsis = this.props.ellipsis; + if (!ellipsis || typeof ellipsis !== "object") { + return null; + } + return ellipsis; + } + getMergedExpanded(ellipsisConfig, text) { + const controlledExpanded = ellipsisConfig && typeof ellipsisConfig.expanded === "boolean" ? ellipsisConfig.expanded : void 0; + if (controlledExpanded !== void 0) { + return controlledExpanded; + } + if (ellipsisConfig && "count" in ellipsisConfig) { + const count = ellipsisConfig.count ?? 0; + if (count <= 0 || text.length <= count) { + return true; + } + } + return this.expanded; + } + createDefaultMentionNode(entity) { + const span = document.createElement("span"); + span.style.color = LINK_COLOR; + span.style.fontWeight = this.props.weight === "bold" ? "700" : "400"; + span.textContent = entity.displayText; + return span; + } + createDefaultTagNode(entity) { + const span = document.createElement("span"); + span.style.color = LINK_COLOR; + span.style.fontWeight = this.props.weight === "bold" ? "700" : "400"; + span.textContent = entity.text; + return span; + } + createDefaultLinkNode(entity) { + const anchor = document.createElement("a"); + anchor.href = entity.url; + anchor.target = "_blank"; + anchor.referrerPolicy = "no-referrer"; + anchor.style.color = LINK_COLOR; + anchor.style.fontWeight = this.props.weight === "bold" ? "700" : "400"; + anchor.textContent = entity.text; + return anchor; + } + renderEntity(entity, index) { + if (entity.type === "mention") { + const customNode2 = this.props.renderMention?.(entity, index); + return toNode(customNode2) ?? this.createDefaultMentionNode(entity); + } + if (entity.type === "tag") { + const customNode2 = this.props.renderTag?.(entity, index); + return toNode(customNode2) ?? this.createDefaultTagNode(entity); + } + const customNode = this.props.renderLink?.(entity, index); + return toNode(customNode) ?? this.createDefaultLinkNode(entity); + } + buildTextNodes(text, entities, upto = null) { + let lastIndex = 0; + const nodes = []; + for (const [index, 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(document.createTextNode(text.slice(lastIndex, textEnd))); + } + if (upto === null || entity.end <= upto) { + const entityNode = this.renderEntity(entity, index); + if (entityNode) { + nodes.push(entityNode); + } + } + lastIndex = entity.end; + } + if (upto === null) { + if (lastIndex < text.length) { + nodes.push(document.createTextNode(text.slice(lastIndex))); + } + } else if (lastIndex < upto) { + nodes.push(document.createTextNode(text.slice(lastIndex, upto))); + } + return nodes; + } + applyBaseParagraphStyle(element) { + element.style.whiteSpace = "pre-wrap"; + element.style.fontWeight = this.props.weight === "bold" ? "700" : "400"; + element.style.setProperty("-webkit-touch-callout", "default"); + element.style.setProperty("-webkit-user-select", "text"); + element.style.setProperty("-khtml-user-select", "text"); + element.style.setProperty("-moz-user-select", "text"); + element.style.setProperty("-ms-user-select", "text"); + element.style.setProperty("user-select", "text"); + if (this.props.blur) { + element.style.filter = "blur(3px)"; + element.style.setProperty("-webkit-user-select", "none"); + element.style.setProperty("-khtml-user-select", "none"); + element.style.setProperty("-moz-user-select", "none"); + element.style.setProperty("-ms-user-select", "none"); + element.style.setProperty("user-select", "none"); + element.style.pointerEvents = "none"; + } + applyStyleObject(element, this.props.style); + } + buildReadMoreButton(symbol) { + const button = document.createElement("button"); + button.type = "button"; + button.textContent = resolveEllipsisSymbol(symbol, this.expanded); + button.style.border = "0"; + button.style.background = "none"; + button.style.padding = "0"; + button.style.marginLeft = "6px"; + button.style.cursor = "pointer"; + button.style.color = LINK_COLOR; + button.style.fontWeight = "700"; + button.onclick = (event) => { + event.preventDefault(); + event.stopPropagation(); + this.handleExpand(); + }; + return button; + } + handleExpand() { + const ellipsisConfig = this.resolveEllipsisConfig(); + if (!ellipsisConfig) { + return; + } + this.expanded = true; + ellipsisConfig.onExpand?.(true); + this.render(); + } + computeEntitySafeCutoff(count, entities) { + 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; + } + } + } + return cutoff; + } + render() { + if (!this.host) { + return; + } + const text = this.getText(); + const entities = findAllEntities(text); + const ellipsisConfig = this.resolveEllipsisConfig(); + const mergedExpanded = this.getMergedExpanded(ellipsisConfig, text); + const wrapper = document.createElement("div"); + const paragraph = document.createElement("div"); + if (this.props.className) { + paragraph.className = this.props.className; + } + this.applyBaseParagraphStyle(paragraph); + if (ellipsisConfig && "count" in ellipsisConfig) { + const count = ellipsisConfig.count ?? 0; + const shouldTruncate = !mergedExpanded && count > 0 && text.length > count; + if (shouldTruncate) { + const cutoff = this.computeEntitySafeCutoff(count, entities); + const nodes = this.buildTextNodes(text, entities, cutoff); + paragraph.append(...nodes); + paragraph.append(document.createTextNode("\u2026")); + if (ellipsisConfig.expandable) { + paragraph.append(this.buildReadMoreButton(ellipsisConfig.symbol)); + } + } else { + paragraph.append(...this.buildTextNodes(text, entities)); + } + } else { + paragraph.append(...this.buildTextNodes(text, entities)); + if (ellipsisConfig && "rows" in ellipsisConfig && !mergedExpanded) { + paragraph.style.display = "-webkit-box"; + paragraph.style.setProperty("-webkit-box-orient", "vertical"); + paragraph.style.setProperty("-webkit-line-clamp", String(ellipsisConfig.rows)); + paragraph.style.overflow = "hidden"; + if (ellipsisConfig.expandable) { + wrapper.append(paragraph); + wrapper.append(this.buildReadMoreButton(ellipsisConfig.symbol)); + this.host.replaceChildren(wrapper); + this.emitOnViewIfNeeded(mergedExpanded); + return; + } + } + } + wrapper.append(paragraph); + this.host.replaceChildren(wrapper); + this.emitOnViewIfNeeded(mergedExpanded); + } + initObserver() { + if (!this.host) { + return; + } + if (typeof IntersectionObserver === "undefined") { + this.hostInsideView = true; + return; + } + this.observer = new IntersectionObserver( + (entries) => { + const entry = entries[0]; + if (!entry) { + return; + } + if (entry.isIntersecting && !this.hostInsideView) { + this.hostInsideView = true; + const ellipsisConfig = this.resolveEllipsisConfig(); + const mergedExpanded = this.getMergedExpanded(ellipsisConfig, this.getText()); + this.emitOnViewIfNeeded(mergedExpanded); + } + }, + { threshold: 0.5 } + ); + this.observer.observe(this.host); + } + emitOnViewIfNeeded(mergedExpanded) { + if (!mergedExpanded || this.viewed || !this.hostInsideView) { + return; + } + this.viewed = true; + this.props.onView?.(); + } +}; +var AngularContentTextWithSuggestionsRenderer = class extends AngularContentTextRenderer { +}; +var AngularContentTitleWithSuggestionsRenderer = class { + renderer = new AngularContentTextWithSuggestionsRenderer(); + attach(host, props = {}) { + return this.renderer.attach(host, this.normalizeProps(props)); + } + update(props) { + return this.renderer.update(this.normalizeProps(props)); + } + destroy() { + this.renderer.destroy(); + } + getState() { + return this.renderer.getState(); + } + normalizeProps(props) { + return { + ...props, + weight: "bold", + blur: props.blur ?? false, + ellipsis: props.ellipsis === void 0 ? { rows: 2 } : props.ellipsis + }; + } +}; -export { AngularContentSuggestionsAdapter, buildAngularTagHref, createAngularContentTokens }; +export { AngularContentSuggestionsAdapter, AngularContentTextRenderer, AngularContentTextWithSuggestionsRenderer, AngularContentTitleWithSuggestionsRenderer, buildAngularTagHref, createAngularContentTokens, toKebabCase }; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/content-suggestions/dist/angular/index.js.map b/packages/content-suggestions/dist/angular/index.js.map index 755fe98..f5e841c 100644 --- a/packages/content-suggestions/dist/angular/index.js.map +++ b/packages/content-suggestions/dist/angular/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../src/core/parser.ts","../../src/angular/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type {\n ContentEntity,\n LinkEntity,\n MentionEntity,\n ParsedMention,\n ProcessedContent,\n TagEntity,\n} from \"./types\";\n\nexport const mentionLinkRegexp =\n /@\\[[^\\]]+]\\([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;\n\nexport const parseMention = (mention: string): ParsedMention | null => {\n const regex = /@\\[([^\\]]+)\\]\\(([\\w-]{36})\\)/;\n const match = mention.match(regex);\n\n if (!match) {\n return null;\n }\n\n const mentionText = match[1];\n const mentionId = match[2];\n if (!mentionText || !mentionId) {\n return null;\n }\n\n return {\n mention: `@${mentionText}`,\n id: mentionId,\n };\n};\n\nexport const findMentions = (text: string): MentionEntity[] => {\n let match: RegExpExecArray | null;\n const matches: MentionEntity[] = [];\n\n while ((match = mentionLinkRegexp.exec(text)) !== null) {\n const parsed = parseMention(match[0]);\n const baseMention: Omit = {\n start: match.index,\n end: mentionLinkRegexp.lastIndex,\n text: match[0],\n type: \"mention\",\n displayText: parsed?.mention ?? match[0],\n };\n\n matches.push(parsed?.id ? { ...baseMention, userId: parsed.id } : baseMention);\n }\n\n return matches;\n};\n\nexport const findTags = (content: string): TagEntity[] => {\n const regex = /#[^\\s]{1,201}/g;\n const results: TagEntity[] = [];\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(content)) !== null) {\n const value = match[0];\n results.push({\n start: match.index,\n end: match.index + value.length,\n text: value,\n type: \"tag\",\n tag: value.replace(\"#\", \"\"),\n });\n }\n\n return results;\n};\n\nexport const findLinks = (content: string): LinkEntity[] => {\n const regex =\n /\\b((https?:\\/\\/)?(?:[\\w-]+\\.)+[a-z]{2,}(\\/[\\w\\-._~:/?#[\\]@!$&'()*+,;=]*)?)/gi;\n const results: LinkEntity[] = [];\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(content)) !== null) {\n const rawUrl = match[0];\n const hasProtocol = /^https?:\\/\\//i.test(rawUrl);\n const fullUrl = hasProtocol ? rawUrl : `https://${rawUrl}`;\n\n results.push({\n start: match.index,\n end: match.index + rawUrl.length,\n text: rawUrl,\n url: fullUrl,\n type: \"link\",\n });\n }\n\n return results;\n};\n\nexport const findAllEntities = (content: string): ContentEntity[] => {\n const mentions = findMentions(content);\n const tags = findTags(content);\n const links = findLinks(content);\n\n return [...mentions, ...tags, ...links].sort((a, b) => a.start - b.start);\n};\n\nexport const processContent = (content: string): ProcessedContent => {\n const processedText = content.replace(mentionLinkRegexp, match => {\n const parsed = parseMention(match);\n return parsed ? parsed.mention : match;\n });\n\n const tags = findTags(content).map(tag => tag.tag);\n\n return {\n processedText,\n tags,\n };\n};\n","import type { ContentEntity, LinkEntity, MentionEntity, TagEntity } from \"../core\";\n\nimport { findAllEntities } from \"../core\";\n\nexport interface AngularTextToken {\n kind: \"text\";\n text: string;\n start: number;\n end: number;\n}\n\nexport interface AngularMentionToken {\n kind: \"mention\";\n entity: MentionEntity;\n}\n\nexport interface AngularTagToken {\n kind: \"tag\";\n entity: TagEntity;\n}\n\nexport interface AngularLinkToken {\n kind: \"link\";\n entity: LinkEntity;\n}\n\nexport type AngularContentToken =\n | AngularTextToken\n | AngularMentionToken\n | AngularTagToken\n | AngularLinkToken;\n\nexport interface AngularContentSnapshot {\n text: string;\n entities: ContentEntity[];\n tokens: AngularContentToken[];\n}\n\nexport const buildAngularTagHref = (entity: TagEntity): string => {\n return `/search/?query=${encodeURIComponent(entity.tag.toLowerCase())}`;\n};\n\nexport const createAngularContentTokens = (\n inputText: string | null | undefined,\n): AngularContentToken[] => {\n const text = inputText ?? \"\";\n const entities = findAllEntities(text);\n\n let cursor = 0;\n const tokens: AngularContentToken[] = [];\n\n for (const entity of entities) {\n if (entity.start > cursor) {\n tokens.push({\n kind: \"text\",\n text: text.slice(cursor, entity.start),\n start: cursor,\n end: entity.start,\n });\n }\n\n if (entity.type === \"mention\") {\n tokens.push({\n kind: \"mention\",\n entity,\n });\n } else if (entity.type === \"tag\") {\n tokens.push({\n kind: \"tag\",\n entity,\n });\n } else {\n tokens.push({\n kind: \"link\",\n entity,\n });\n }\n\n cursor = entity.end;\n }\n\n if (cursor < text.length) {\n tokens.push({\n kind: \"text\",\n text: text.slice(cursor),\n start: cursor,\n end: text.length,\n });\n }\n\n return tokens;\n};\n\nexport class AngularContentSuggestionsAdapter {\n snapshot(inputText: string | null | undefined): AngularContentSnapshot {\n const text = inputText ?? \"\";\n const entities = findAllEntities(text);\n const tokens = createAngularContentTokens(text);\n\n return {\n text,\n entities,\n tokens,\n };\n }\n}\n"]} \ No newline at end of file +{"version":3,"sources":["../../src/core/parser.ts","../../src/angular/index.ts"],"names":["customNode"],"mappings":";AASO,IAAM,iBAAA,GACX,4FAAA;AAEK,IAAM,YAAA,GAAe,CAAC,OAAA,KAA0C;AACrE,EAAA,MAAM,KAAA,GAAQ,8BAAA;AACd,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,CAAM,KAAK,CAAA;AAEjC,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,WAAA,GAAc,MAAM,CAAC,CAAA;AAC3B,EAAA,MAAM,SAAA,GAAY,MAAM,CAAC,CAAA;AACzB,EAAA,IAAI,CAAC,WAAA,IAAe,CAAC,SAAA,EAAW;AAC9B,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,IAAI,WAAW,CAAA,CAAA;AAAA,IACxB,EAAA,EAAI;AAAA,GACN;AACF,CAAA;AAEO,IAAM,YAAA,GAAe,CAAC,IAAA,KAAkC;AAC7D,EAAA,IAAI,KAAA;AACJ,EAAA,MAAM,UAA2B,EAAC;AAElC,EAAA,OAAA,CAAQ,KAAA,GAAQ,iBAAA,CAAkB,IAAA,CAAK,IAAI,OAAO,IAAA,EAAM;AACtD,IAAA,MAAM,MAAA,GAAS,YAAA,CAAa,KAAA,CAAM,CAAC,CAAC,CAAA;AACpC,IAAA,MAAM,WAAA,GAA6C;AAAA,MACjD,OAAO,KAAA,CAAM,KAAA;AAAA,MACb,KAAK,iBAAA,CAAkB,SAAA;AAAA,MACvB,IAAA,EAAM,MAAM,CAAC,CAAA;AAAA,MACb,IAAA,EAAM,SAAA;AAAA,MACN,WAAA,EAAa,MAAA,EAAQ,OAAA,IAAW,KAAA,CAAM,CAAC;AAAA,KACzC;AAEA,IAAA,OAAA,CAAQ,IAAA,CAAK,MAAA,EAAQ,EAAA,GAAK,EAAE,GAAG,aAAa,MAAA,EAAQ,MAAA,CAAO,EAAA,EAAG,GAAI,WAAW,CAAA;AAAA,EAC/E;AAEA,EAAA,OAAO,OAAA;AACT,CAAA;AAEO,IAAM,QAAA,GAAW,CAAC,OAAA,KAAiC;AACxD,EAAA,MAAM,KAAA,GAAQ,gBAAA;AACd,EAAA,MAAM,UAAuB,EAAC;AAC9B,EAAA,IAAI,KAAA;AAEJ,EAAA,OAAA,CAAQ,KAAA,GAAQ,KAAA,CAAM,IAAA,CAAK,OAAO,OAAO,IAAA,EAAM;AAC7C,IAAA,MAAM,KAAA,GAAQ,MAAM,CAAC,CAAA;AACrB,IAAA,OAAA,CAAQ,IAAA,CAAK;AAAA,MACX,OAAO,KAAA,CAAM,KAAA;AAAA,MACb,GAAA,EAAK,KAAA,CAAM,KAAA,GAAQ,KAAA,CAAM,MAAA;AAAA,MACzB,IAAA,EAAM,KAAA;AAAA,MACN,IAAA,EAAM,KAAA;AAAA,MACN,GAAA,EAAK,KAAA,CAAM,OAAA,CAAQ,GAAA,EAAK,EAAE;AAAA,KAC3B,CAAA;AAAA,EACH;AAEA,EAAA,OAAO,OAAA;AACT,CAAA;AAEO,IAAM,SAAA,GAAY,CAAC,OAAA,KAAkC;AAC1D,EAAA,MAAM,KAAA,GACJ,8EAAA;AACF,EAAA,MAAM,UAAwB,EAAC;AAC/B,EAAA,IAAI,KAAA;AAEJ,EAAA,OAAA,CAAQ,KAAA,GAAQ,KAAA,CAAM,IAAA,CAAK,OAAO,OAAO,IAAA,EAAM;AAC7C,IAAA,MAAM,MAAA,GAAS,MAAM,CAAC,CAAA;AACtB,IAAA,MAAM,WAAA,GAAc,eAAA,CAAgB,IAAA,CAAK,MAAM,CAAA;AAC/C,IAAA,MAAM,OAAA,GAAU,WAAA,GAAc,MAAA,GAAS,CAAA,QAAA,EAAW,MAAM,CAAA,CAAA;AAExD,IAAA,OAAA,CAAQ,IAAA,CAAK;AAAA,MACX,OAAO,KAAA,CAAM,KAAA;AAAA,MACb,GAAA,EAAK,KAAA,CAAM,KAAA,GAAQ,MAAA,CAAO,MAAA;AAAA,MAC1B,IAAA,EAAM,MAAA;AAAA,MACN,GAAA,EAAK,OAAA;AAAA,MACL,IAAA,EAAM;AAAA,KACP,CAAA;AAAA,EACH;AAEA,EAAA,OAAO,OAAA;AACT,CAAA;AAEO,IAAM,eAAA,GAAkB,CAAC,OAAA,KAAqC;AACnE,EAAA,MAAM,QAAA,GAAW,aAAa,OAAO,CAAA;AACrC,EAAA,MAAM,IAAA,GAAO,SAAS,OAAO,CAAA;AAC7B,EAAA,MAAM,KAAA,GAAQ,UAAU,OAAO,CAAA;AAE/B,EAAA,OAAO,CAAC,GAAG,QAAA,EAAU,GAAG,MAAM,GAAG,KAAK,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,CAAE,KAAA,GAAQ,EAAE,KAAK,CAAA;AAC1E,CAAA;;;ACYA,IAAM,UAAA,GAAa,SAAA;AACnB,IAAM,cAAA,GAAiB,6FAAA;AAEvB,IAAM,WAAA,GAAc,CAAC,KAAA,KAAkB,KAAA,CAAM,OAAA,CAAQ,QAAA,EAAU,CAAA,IAAA,KAAQ,CAAA,CAAA,EAAI,IAAA,CAAK,WAAA,EAAa,CAAA,CAAE;AAE/F,IAAM,gBAAA,GAAmB,CACvB,OAAA,EACA,MAAA,KACG;AACH,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA;AAAA,EACF;AAEA,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,QAAQ,KAAK,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,EAAG;AACpD,IAAA,IAAI,QAAA,KAAa,IAAA,IAAQ,QAAA,KAAa,MAAA,EAAW;AAC/C,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,UAAA,GACJ,OAAO,QAAA,KAAa,QAAA,GAAW,GAAG,QAAQ,CAAA,EAAA,CAAA,GAAO,OAAO,QAAQ,CAAA;AAElE,IAAA,IAAI,GAAA,CAAI,UAAA,CAAW,IAAI,CAAA,EAAG;AACxB,MAAA,OAAA,CAAQ,KAAA,CAAM,WAAA,CAAY,GAAA,EAAK,UAAU,CAAA;AACzC,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,SAAS,GAAA,CAAI,QAAA,CAAS,GAAG,CAAA,GAAI,GAAA,GAAM,YAAY,GAAG,CAAA;AACxD,IAAA,OAAA,CAAQ,KAAA,CAAM,WAAA,CAAY,MAAA,EAAQ,UAAU,CAAA;AAAA,EAC9C;AACF,CAAA;AAEA,IAAM,MAAA,GAAS,CAAC,KAAA,KAA4C;AAC1D,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,KAAA,KAAU,MAAA,EAAW;AACzC,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,IAAI,iBAAiB,IAAA,EAAM;AACzB,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAO,QAAA,CAAS,cAAA,CAAe,MAAA,CAAO,KAAK,CAAC,CAAA;AAC9C,CAAA;AAEA,IAAM,qBAAA,GAAwB,CAC5B,MAAA,EACA,QAAA,KACW;AACX,EAAA,IAAI,OAAO,WAAW,UAAA,EAAY;AAChC,IAAA,OAAO,OAAO,QAAQ,CAAA;AAAA,EACxB;AAEA,EAAA,OAAO,MAAA,IAAU,cAAA;AACnB,CAAA;AAEA,IAAM,mBAAA,GAAsB,CAAC,MAAA,KAA8B;AACzD,EAAA,OAAO,kBAAkB,kBAAA,CAAmB,MAAA,CAAO,GAAA,CAAI,WAAA,EAAa,CAAC,CAAA,CAAA;AACvE;AAIO,IAAM,0BAAA,GAA6B,CACxC,SAAA,KAC0B;AAC1B,EAAA,MAAM,OAAO,SAAA,IAAa,EAAA;AAC1B,EAAA,MAAM,QAAA,GAAW,gBAAgB,IAAI,CAAA;AAErC,EAAA,IAAI,MAAA,GAAS,CAAA;AACb,EAAA,MAAM,SAAgC,EAAC;AAEvC,EAAA,KAAA,MAAW,UAAU,QAAA,EAAU;AAC7B,IAAA,IAAI,MAAA,CAAO,QAAQ,MAAA,EAAQ;AACzB,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,IAAA,EAAM,MAAA;AAAA,QACN,IAAA,EAAM,IAAA,CAAK,KAAA,CAAM,MAAA,EAAQ,OAAO,KAAK,CAAA;AAAA,QACrC,KAAA,EAAO,MAAA;AAAA,QACP,KAAK,MAAA,CAAO;AAAA,OACb,CAAA;AAAA,IACH;AAEA,IAAA,IAAI,MAAA,CAAO,SAAS,SAAA,EAAW;AAC7B,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,IAAA,EAAM,SAAA;AAAA,QACN;AAAA,OACD,CAAA;AAAA,IACH,CAAA,MAAA,IAAW,MAAA,CAAO,IAAA,KAAS,KAAA,EAAO;AAChC,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,IAAA,EAAM,KAAA;AAAA,QACN;AAAA,OACD,CAAA;AAAA,IACH,CAAA,MAAO;AACL,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,IAAA,EAAM,MAAA;AAAA,QACN;AAAA,OACD,CAAA;AAAA,IACH;AAEA,IAAA,MAAA,GAAS,MAAA,CAAO,GAAA;AAAA,EAClB;AAEA,EAAA,IAAI,MAAA,GAAS,KAAK,MAAA,EAAQ;AACxB,IAAA,MAAA,CAAO,IAAA,CAAK;AAAA,MACV,IAAA,EAAM,MAAA;AAAA,MACN,IAAA,EAAM,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA;AAAA,MACvB,KAAA,EAAO,MAAA;AAAA,MACP,KAAK,IAAA,CAAK;AAAA,KACX,CAAA;AAAA,EACH;AAEA,EAAA,OAAO,MAAA;AACT;AAEO,IAAM,mCAAN,MAAuC;AAAA,EAC5C,SAAS,SAAA,EAA8D;AACrE,IAAA,MAAM,OAAO,SAAA,IAAa,EAAA;AAC1B,IAAA,MAAM,QAAA,GAAW,gBAAgB,IAAI,CAAA;AACrC,IAAA,MAAM,MAAA,GAAS,2BAA2B,IAAI,CAAA;AAE9C,IAAA,OAAO;AAAA,MACL,IAAA;AAAA,MACA,QAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACF;AAEO,IAAM,6BAAN,MAAiC;AAAA,EAC9B,IAAA,GAA2B,IAAA;AAAA,EAC3B,QAAiC,EAAC;AAAA,EAClC,QAAA,GAAW,KAAA;AAAA,EACX,MAAA,GAAS,KAAA;AAAA,EACT,cAAA,GAAiB,KAAA;AAAA,EACjB,QAAA,GAAwC,IAAA;AAAA,EAEhD,MAAA,CAAO,IAAA,EAAmB,KAAA,GAAiC,EAAC,EAAoC;AAC9F,IAAA,IAAA,CAAK,OAAA,EAAQ;AAEb,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,KAAA,GAAQ,EAAE,GAAG,KAAA,EAAM;AACxB,IAAA,IAAA,CAAK,QAAA,GAAW,KAAA;AAChB,IAAA,IAAA,CAAK,MAAA,GAAS,KAAA;AACd,IAAA,IAAA,CAAK,cAAA,GAAiB,KAAA;AAEtB,IAAA,IAAA,CAAK,YAAA,EAAa;AAClB,IAAA,IAAA,CAAK,MAAA,EAAO;AAEZ,IAAA,OAAO,KAAK,QAAA,EAAS;AAAA,EACvB;AAAA,EAEA,OAAO,SAAA,EAAqE;AAC1E,IAAA,IAAA,CAAK,KAAA,GAAQ;AAAA,MACX,GAAG,IAAA,CAAK,KAAA;AAAA,MACR,GAAG;AAAA,KACL;AAEA,IAAA,IAAA,CAAK,MAAA,EAAO;AAEZ,IAAA,OAAO,KAAK,QAAA,EAAS;AAAA,EACvB;AAAA,EAEA,OAAA,GAAgB;AACd,IAAA,IAAI,IAAA,CAAK,QAAA,IAAY,IAAA,CAAK,IAAA,EAAM;AAC9B,MAAA,IAAA,CAAK,QAAA,CAAS,SAAA,CAAU,IAAA,CAAK,IAAI,CAAA;AACjC,MAAA,IAAA,CAAK,SAAS,UAAA,EAAW;AAAA,IAC3B;AAEA,IAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAEhB,IAAA,IAAI,KAAK,IAAA,EAAM;AACb,MAAA,IAAA,CAAK,KAAK,SAAA,GAAY,EAAA;AAAA,IACxB;AAEA,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,QAAQ,EAAC;AACd,IAAA,IAAA,CAAK,QAAA,GAAW,KAAA;AAChB,IAAA,IAAA,CAAK,MAAA,GAAS,KAAA;AACd,IAAA,IAAA,CAAK,cAAA,GAAiB,KAAA;AAAA,EACxB;AAAA,EAEA,QAAA,GAA4C;AAC1C,IAAA,OAAO;AAAA,MACL,KAAA,EAAO,EAAE,GAAG,IAAA,CAAK,KAAA,EAAM;AAAA,MACvB,QAAA,EAAU,KAAK,cAAA,EAAe;AAAA,MAC9B,QAAA,EAAU,KAAK,iBAAA,CAAkB,IAAA,CAAK,uBAAsB,EAAG,IAAA,CAAK,SAAS;AAAA,KAC/E;AAAA,EACF;AAAA,EAEQ,OAAA,GAAkB;AACxB,IAAA,OAAO,IAAA,CAAK,MAAM,IAAA,IAAQ,EAAA;AAAA,EAC5B;AAAA,EAEQ,cAAA,GAAyC;AAC/C,IAAA,MAAM,IAAA,GAAO,KAAK,OAAA,EAAQ;AAC1B,IAAA,MAAM,QAAA,GAAW,gBAAgB,IAAI,CAAA;AACrC,IAAA,MAAM,MAAA,GAAS,2BAA2B,IAAI,CAAA;AAE9C,IAAA,OAAO;AAAA,MACL,IAAA;AAAA,MACA,QAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AAAA,EAEQ,qBAAA,GAGC;AACP,IAAA,MAAM,QAAA,GAAW,KAAK,KAAA,CAAM,QAAA;AAE5B,IAAA,IAAI,CAAC,QAAA,IAAY,OAAO,QAAA,KAAa,QAAA,EAAU;AAC7C,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,OAAO,QAAA;AAAA,EACT;AAAA,EAEQ,iBAAA,CACN,gBACA,IAAA,EACS;AACT,IAAA,MAAM,qBACJ,cAAA,IAAkB,OAAO,eAAe,QAAA,KAAa,SAAA,GACjD,eAAe,QAAA,GACf,MAAA;AAEN,IAAA,IAAI,uBAAuB,MAAA,EAAW;AACpC,MAAA,OAAO,kBAAA;AAAA,IACT;AAEA,IAAA,IAAI,cAAA,IAAkB,WAAW,cAAA,EAAgB;AAC/C,MAAA,MAAM,KAAA,GAAQ,eAAe,KAAA,IAAS,CAAA;AACtC,MAAA,IAAI,KAAA,IAAS,CAAA,IAAK,IAAA,CAAK,MAAA,IAAU,KAAA,EAAO;AACtC,QAAA,OAAO,IAAA;AAAA,MACT;AAAA,IACF;AAEA,IAAA,OAAO,IAAA,CAAK,QAAA;AAAA,EACd;AAAA,EAEQ,yBAAyB,MAAA,EAA6B;AAC5D,IAAA,MAAM,IAAA,GAAO,QAAA,CAAS,aAAA,CAAc,MAAM,CAAA;AAC1C,IAAA,IAAA,CAAK,MAAM,KAAA,GAAQ,UAAA;AACnB,IAAA,IAAA,CAAK,MAAM,UAAA,GAAa,IAAA,CAAK,KAAA,CAAM,MAAA,KAAW,SAAS,KAAA,GAAQ,KAAA;AAC/D,IAAA,IAAA,CAAK,cAAc,MAAA,CAAO,WAAA;AAC1B,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEQ,qBAAqB,MAAA,EAAyB;AACpD,IAAA,MAAM,IAAA,GAAO,QAAA,CAAS,aAAA,CAAc,MAAM,CAAA;AAC1C,IAAA,IAAA,CAAK,MAAM,KAAA,GAAQ,UAAA;AACnB,IAAA,IAAA,CAAK,MAAM,UAAA,GAAa,IAAA,CAAK,KAAA,CAAM,MAAA,KAAW,SAAS,KAAA,GAAQ,KAAA;AAC/D,IAAA,IAAA,CAAK,cAAc,MAAA,CAAO,IAAA;AAC1B,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEQ,sBAAsB,MAAA,EAA0B;AACtD,IAAA,MAAM,MAAA,GAAS,QAAA,CAAS,aAAA,CAAc,GAAG,CAAA;AACzC,IAAA,MAAA,CAAO,OAAO,MAAA,CAAO,GAAA;AACrB,IAAA,MAAA,CAAO,MAAA,GAAS,QAAA;AAChB,IAAA,MAAA,CAAO,cAAA,GAAiB,aAAA;AACxB,IAAA,MAAA,CAAO,MAAM,KAAA,GAAQ,UAAA;AACrB,IAAA,MAAA,CAAO,MAAM,UAAA,GAAa,IAAA,CAAK,KAAA,CAAM,MAAA,KAAW,SAAS,KAAA,GAAQ,KAAA;AACjE,IAAA,MAAA,CAAO,cAAc,MAAA,CAAO,IAAA;AAC5B,IAAA,OAAO,MAAA;AAAA,EACT;AAAA,EAEQ,YAAA,CAAa,QAAuB,KAAA,EAA4B;AACtE,IAAA,IAAI,MAAA,CAAO,SAAS,SAAA,EAAW;AAC7B,MAAA,MAAMA,WAAAA,GAAa,IAAA,CAAK,KAAA,CAAM,aAAA,GAAgB,QAAQ,KAAK,CAAA;AAC3D,MAAA,OAAO,MAAA,CAAOA,WAAU,CAAA,IAAK,IAAA,CAAK,yBAAyB,MAAM,CAAA;AAAA,IACnE;AAEA,IAAA,IAAI,MAAA,CAAO,SAAS,KAAA,EAAO;AACzB,MAAA,MAAMA,WAAAA,GAAa,IAAA,CAAK,KAAA,CAAM,SAAA,GAAY,QAAQ,KAAK,CAAA;AACvD,MAAA,OAAO,MAAA,CAAOA,WAAU,CAAA,IAAK,IAAA,CAAK,qBAAqB,MAAM,CAAA;AAAA,IAC/D;AAEA,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,KAAA,CAAM,UAAA,GAAa,QAAQ,KAAK,CAAA;AACxD,IAAA,OAAO,MAAA,CAAO,UAAU,CAAA,IAAK,IAAA,CAAK,sBAAsB,MAAM,CAAA;AAAA,EAChE;AAAA,EAEQ,cAAA,CACN,IAAA,EACA,QAAA,EACA,IAAA,GAAsB,IAAA,EACd;AACR,IAAA,IAAI,SAAA,GAAY,CAAA;AAChB,IAAA,MAAM,QAAgB,EAAC;AAEvB,IAAA,KAAA,MAAW,CAAC,KAAA,EAAO,MAAM,CAAA,IAAK,QAAA,CAAS,SAAQ,EAAG;AAChD,MAAA,IAAI,IAAA,KAAS,IAAA,IAAQ,MAAA,CAAO,KAAA,IAAS,IAAA,EAAM;AACzC,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,OAAA,GAAU,SAAS,IAAA,GAAO,IAAA,CAAK,IAAI,MAAA,CAAO,KAAA,EAAO,IAAI,CAAA,GAAI,MAAA,CAAO,KAAA;AACtE,MAAA,IAAI,MAAA,CAAO,KAAA,GAAQ,SAAA,IAAa,SAAA,GAAY,OAAA,EAAS;AACnD,QAAA,KAAA,CAAM,IAAA,CAAK,SAAS,cAAA,CAAe,IAAA,CAAK,MAAM,SAAA,EAAW,OAAO,CAAC,CAAC,CAAA;AAAA,MACpE;AAEA,MAAA,IAAI,IAAA,KAAS,IAAA,IAAQ,MAAA,CAAO,GAAA,IAAO,IAAA,EAAM;AACvC,QAAA,MAAM,UAAA,GAAa,IAAA,CAAK,YAAA,CAAa,MAAA,EAAQ,KAAK,CAAA;AAClD,QAAA,IAAI,UAAA,EAAY;AACd,UAAA,KAAA,CAAM,KAAK,UAAU,CAAA;AAAA,QACvB;AAAA,MACF;AAEA,MAAA,SAAA,GAAY,MAAA,CAAO,GAAA;AAAA,IACrB;AAEA,IAAA,IAAI,SAAS,IAAA,EAAM;AACjB,MAAA,IAAI,SAAA,GAAY,KAAK,MAAA,EAAQ;AAC3B,QAAA,KAAA,CAAM,KAAK,QAAA,CAAS,cAAA,CAAe,KAAK,KAAA,CAAM,SAAS,CAAC,CAAC,CAAA;AAAA,MAC3D;AAAA,IACF,CAAA,MAAA,IAAW,YAAY,IAAA,EAAM;AAC3B,MAAA,KAAA,CAAM,IAAA,CAAK,SAAS,cAAA,CAAe,IAAA,CAAK,MAAM,SAAA,EAAW,IAAI,CAAC,CAAC,CAAA;AAAA,IACjE;AAEA,IAAA,OAAO,KAAA;AAAA,EACT;AAAA,EAEQ,wBAAwB,OAAA,EAA4B;AAC1D,IAAA,OAAA,CAAQ,MAAM,UAAA,GAAa,UAAA;AAC3B,IAAA,OAAA,CAAQ,MAAM,UAAA,GAAa,IAAA,CAAK,KAAA,CAAM,MAAA,KAAW,SAAS,KAAA,GAAQ,KAAA;AAClE,IAAA,OAAA,CAAQ,KAAA,CAAM,WAAA,CAAY,uBAAA,EAAyB,SAAS,CAAA;AAC5D,IAAA,OAAA,CAAQ,KAAA,CAAM,WAAA,CAAY,qBAAA,EAAuB,MAAM,CAAA;AACvD,IAAA,OAAA,CAAQ,KAAA,CAAM,WAAA,CAAY,oBAAA,EAAsB,MAAM,CAAA;AACtD,IAAA,OAAA,CAAQ,KAAA,CAAM,WAAA,CAAY,kBAAA,EAAoB,MAAM,CAAA;AACpD,IAAA,OAAA,CAAQ,KAAA,CAAM,WAAA,CAAY,iBAAA,EAAmB,MAAM,CAAA;AACnD,IAAA,OAAA,CAAQ,KAAA,CAAM,WAAA,CAAY,aAAA,EAAe,MAAM,CAAA;AAE/C,IAAA,IAAI,IAAA,CAAK,MAAM,IAAA,EAAM;AACnB,MAAA,OAAA,CAAQ,MAAM,MAAA,GAAS,WAAA;AACvB,MAAA,OAAA,CAAQ,KAAA,CAAM,WAAA,CAAY,qBAAA,EAAuB,MAAM,CAAA;AACvD,MAAA,OAAA,CAAQ,KAAA,CAAM,WAAA,CAAY,oBAAA,EAAsB,MAAM,CAAA;AACtD,MAAA,OAAA,CAAQ,KAAA,CAAM,WAAA,CAAY,kBAAA,EAAoB,MAAM,CAAA;AACpD,MAAA,OAAA,CAAQ,KAAA,CAAM,WAAA,CAAY,iBAAA,EAAmB,MAAM,CAAA;AACnD,MAAA,OAAA,CAAQ,KAAA,CAAM,WAAA,CAAY,aAAA,EAAe,MAAM,CAAA;AAC/C,MAAA,OAAA,CAAQ,MAAM,aAAA,GAAgB,MAAA;AAAA,IAChC;AAEA,IAAA,gBAAA,CAAiB,OAAA,EAAS,IAAA,CAAK,KAAA,CAAM,KAAK,CAAA;AAAA,EAC5C;AAAA,EAEQ,oBAAoB,MAAA,EAA8D;AACxF,IAAA,MAAM,MAAA,GAAS,QAAA,CAAS,aAAA,CAAc,QAAQ,CAAA;AAC9C,IAAA,MAAA,CAAO,IAAA,GAAO,QAAA;AACd,IAAA,MAAA,CAAO,WAAA,GAAc,qBAAA,CAAsB,MAAA,EAAQ,IAAA,CAAK,QAAQ,CAAA;AAChE,IAAA,MAAA,CAAO,MAAM,MAAA,GAAS,GAAA;AACtB,IAAA,MAAA,CAAO,MAAM,UAAA,GAAa,MAAA;AAC1B,IAAA,MAAA,CAAO,MAAM,OAAA,GAAU,GAAA;AACvB,IAAA,MAAA,CAAO,MAAM,UAAA,GAAa,KAAA;AAC1B,IAAA,MAAA,CAAO,MAAM,MAAA,GAAS,SAAA;AACtB,IAAA,MAAA,CAAO,MAAM,KAAA,GAAQ,UAAA;AACrB,IAAA,MAAA,CAAO,MAAM,UAAA,GAAa,KAAA;AAC1B,IAAA,MAAA,CAAO,UAAU,CAAA,KAAA,KAAS;AACxB,MAAA,KAAA,CAAM,cAAA,EAAe;AACrB,MAAA,KAAA,CAAM,eAAA,EAAgB;AACtB,MAAA,IAAA,CAAK,YAAA,EAAa;AAAA,IACpB,CAAA;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAAA,EAEQ,YAAA,GAAqB;AAC3B,IAAA,MAAM,cAAA,GAAiB,KAAK,qBAAA,EAAsB;AAElD,IAAA,IAAI,CAAC,cAAA,EAAgB;AACnB,MAAA;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAChB,IAAA,cAAA,CAAe,WAAW,IAAI,CAAA;AAC9B,IAAA,IAAA,CAAK,MAAA,EAAO;AAAA,EACd;AAAA,EAEQ,uBAAA,CACN,OACA,QAAA,EACQ;AACR,IAAA,IAAI,MAAA,GAAS,KAAA;AACb,IAAA,IAAI,QAAA,GAAW,IAAA;AAEf,IAAA,OAAO,QAAA,EAAU;AACf,MAAA,QAAA,GAAW,KAAA;AAEX,MAAA,KAAA,MAAW,UAAU,QAAA,EAAU;AAC7B,QAAA,IAAI,MAAA,CAAO,KAAA,GAAQ,MAAA,IAAU,MAAA,CAAO,MAAM,MAAA,EAAQ;AAChD,UAAA,MAAA,GAAS,MAAA,CAAO,GAAA;AAChB,UAAA,QAAA,GAAW,IAAA;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAEA,IAAA,OAAO,MAAA;AAAA,EACT;AAAA,EAEQ,MAAA,GAAe;AACrB,IAAA,IAAI,CAAC,KAAK,IAAA,EAAM;AACd,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,IAAA,GAAO,KAAK,OAAA,EAAQ;AAC1B,IAAA,MAAM,QAAA,GAAW,gBAAgB,IAAI,CAAA;AACrC,IAAA,MAAM,cAAA,GAAiB,KAAK,qBAAA,EAAsB;AAClD,IAAA,MAAM,cAAA,GAAiB,IAAA,CAAK,iBAAA,CAAkB,cAAA,EAAgB,IAAI,CAAA;AAElE,IAAA,MAAM,OAAA,GAAU,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;AAC5C,IAAA,MAAM,SAAA,GAAY,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;AAE9C,IAAA,IAAI,IAAA,CAAK,MAAM,SAAA,EAAW;AACxB,MAAA,SAAA,CAAU,SAAA,GAAY,KAAK,KAAA,CAAM,SAAA;AAAA,IACnC;AAEA,IAAA,IAAA,CAAK,wBAAwB,SAAS,CAAA;AAEtC,IAAA,IAAI,cAAA,IAAkB,WAAW,cAAA,EAAgB;AAC/C,MAAA,MAAM,KAAA,GAAQ,eAAe,KAAA,IAAS,CAAA;AACtC,MAAA,MAAM,iBAAiB,CAAC,cAAA,IAAkB,KAAA,GAAQ,CAAA,IAAK,KAAK,MAAA,GAAS,KAAA;AAErE,MAAA,IAAI,cAAA,EAAgB;AAClB,QAAA,MAAM,MAAA,GAAS,IAAA,CAAK,uBAAA,CAAwB,KAAA,EAAO,QAAQ,CAAA;AAC3D,QAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,cAAA,CAAe,IAAA,EAAM,UAAU,MAAM,CAAA;AAExD,QAAA,SAAA,CAAU,MAAA,CAAO,GAAG,KAAK,CAAA;AACzB,QAAA,SAAA,CAAU,MAAA,CAAO,QAAA,CAAS,cAAA,CAAe,QAAG,CAAC,CAAA;AAE7C,QAAA,IAAI,eAAe,UAAA,EAAY;AAC7B,UAAA,SAAA,CAAU,MAAA,CAAO,IAAA,CAAK,mBAAA,CAAoB,cAAA,CAAe,MAAM,CAAC,CAAA;AAAA,QAClE;AAAA,MACF,CAAA,MAAO;AACL,QAAA,SAAA,CAAU,OAAO,GAAG,IAAA,CAAK,cAAA,CAAe,IAAA,EAAM,QAAQ,CAAC,CAAA;AAAA,MACzD;AAAA,IACF,CAAA,MAAO;AACL,MAAA,SAAA,CAAU,OAAO,GAAG,IAAA,CAAK,cAAA,CAAe,IAAA,EAAM,QAAQ,CAAC,CAAA;AAEvD,MAAA,IAAI,cAAA,IAAkB,MAAA,IAAU,cAAA,IAAkB,CAAC,cAAA,EAAgB;AACjE,QAAA,SAAA,CAAU,MAAM,OAAA,GAAU,aAAA;AAC1B,QAAA,SAAA,CAAU,KAAA,CAAM,WAAA,CAAY,oBAAA,EAAsB,UAAU,CAAA;AAC5D,QAAA,SAAA,CAAU,MAAM,WAAA,CAAY,oBAAA,EAAsB,MAAA,CAAO,cAAA,CAAe,IAAI,CAAC,CAAA;AAC7E,QAAA,SAAA,CAAU,MAAM,QAAA,GAAW,QAAA;AAE3B,QAAA,IAAI,eAAe,UAAA,EAAY;AAC7B,UAAA,OAAA,CAAQ,OAAO,SAAS,CAAA;AACxB,UAAA,OAAA,CAAQ,MAAA,CAAO,IAAA,CAAK,mBAAA,CAAoB,cAAA,CAAe,MAAM,CAAC,CAAA;AAC9D,UAAA,IAAA,CAAK,IAAA,CAAK,gBAAgB,OAAO,CAAA;AACjC,UAAA,IAAA,CAAK,mBAAmB,cAAc,CAAA;AACtC,UAAA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,IAAA,OAAA,CAAQ,OAAO,SAAS,CAAA;AACxB,IAAA,IAAA,CAAK,IAAA,CAAK,gBAAgB,OAAO,CAAA;AACjC,IAAA,IAAA,CAAK,mBAAmB,cAAc,CAAA;AAAA,EACxC;AAAA,EAEQ,YAAA,GAAqB;AAC3B,IAAA,IAAI,CAAC,KAAK,IAAA,EAAM;AACd,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,OAAO,yBAAyB,WAAA,EAAa;AAC/C,MAAA,IAAA,CAAK,cAAA,GAAiB,IAAA;AACtB,MAAA;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,WAAW,IAAI,oBAAA;AAAA,MAClB,CAAA,OAAA,KAAW;AACT,QAAA,MAAM,KAAA,GAAQ,QAAQ,CAAC,CAAA;AACvB,QAAA,IAAI,CAAC,KAAA,EAAO;AACV,UAAA;AAAA,QACF;AAEA,QAAA,IAAI,KAAA,CAAM,cAAA,IAAkB,CAAC,IAAA,CAAK,cAAA,EAAgB;AAChD,UAAA,IAAA,CAAK,cAAA,GAAiB,IAAA;AACtB,UAAA,MAAM,cAAA,GAAiB,KAAK,qBAAA,EAAsB;AAClD,UAAA,MAAM,iBAAiB,IAAA,CAAK,iBAAA,CAAkB,cAAA,EAAgB,IAAA,CAAK,SAAS,CAAA;AAC5E,UAAA,IAAA,CAAK,mBAAmB,cAAc,CAAA;AAAA,QACxC;AAAA,MACF,CAAA;AAAA,MACA,EAAE,WAAW,GAAA;AAAI,KACnB;AAEA,IAAA,IAAA,CAAK,QAAA,CAAS,OAAA,CAAQ,IAAA,CAAK,IAAI,CAAA;AAAA,EACjC;AAAA,EAEQ,mBAAmB,cAAA,EAA+B;AACxD,IAAA,IAAI,CAAC,cAAA,IAAkB,IAAA,CAAK,MAAA,IAAU,CAAC,KAAK,cAAA,EAAgB;AAC1D,MAAA;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,MAAA,GAAS,IAAA;AACd,IAAA,IAAA,CAAK,MAAM,MAAA,IAAS;AAAA,EACtB;AACF;AAEO,IAAM,yCAAA,GAAN,cAAwD,0BAAA,CAA2B;AAAC;AAEpF,IAAM,6CAAN,MAAiD;AAAA,EACrC,QAAA,GAAW,IAAI,yCAAA,EAA0C;AAAA,EAE1E,MAAA,CACE,IAAA,EACA,KAAA,GAAiD,EAAC,EACjB;AACjC,IAAA,OAAO,KAAK,QAAA,CAAS,MAAA,CAAO,MAAM,IAAA,CAAK,cAAA,CAAe,KAAK,CAAC,CAAA;AAAA,EAC9D;AAAA,EAEA,OAAO,KAAA,EAAiF;AACtF,IAAA,OAAO,KAAK,QAAA,CAAS,MAAA,CAAO,IAAA,CAAK,cAAA,CAAe,KAAK,CAAC,CAAA;AAAA,EACxD;AAAA,EAEA,OAAA,GAAgB;AACd,IAAA,IAAA,CAAK,SAAS,OAAA,EAAQ;AAAA,EACxB;AAAA,EAEA,QAAA,GAA4C;AAC1C,IAAA,OAAO,IAAA,CAAK,SAAS,QAAA,EAAS;AAAA,EAChC;AAAA,EAEQ,eACN,KAAA,EACwC;AACxC,IAAA,OAAO;AAAA,MACL,GAAG,KAAA;AAAA,MACH,MAAA,EAAQ,MAAA;AAAA,MACR,IAAA,EAAM,MAAM,IAAA,IAAQ,KAAA;AAAA,MACpB,QAAA,EAAU,MAAM,QAAA,KAAa,MAAA,GAAY,EAAE,IAAA,EAAM,CAAA,KAAM,KAAA,CAAM;AAAA,KAC/D;AAAA,EACF;AACF","file":"index.js","sourcesContent":["import type {\n ContentEntity,\n LinkEntity,\n MentionEntity,\n ParsedMention,\n ProcessedContent,\n TagEntity,\n} from \"./types\";\n\nexport const mentionLinkRegexp =\n /@\\[[^\\]]+]\\([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;\n\nexport const parseMention = (mention: string): ParsedMention | null => {\n const regex = /@\\[([^\\]]+)\\]\\(([\\w-]{36})\\)/;\n const match = mention.match(regex);\n\n if (!match) {\n return null;\n }\n\n const mentionText = match[1];\n const mentionId = match[2];\n if (!mentionText || !mentionId) {\n return null;\n }\n\n return {\n mention: `@${mentionText}`,\n id: mentionId,\n };\n};\n\nexport const findMentions = (text: string): MentionEntity[] => {\n let match: RegExpExecArray | null;\n const matches: MentionEntity[] = [];\n\n while ((match = mentionLinkRegexp.exec(text)) !== null) {\n const parsed = parseMention(match[0]);\n const baseMention: Omit = {\n start: match.index,\n end: mentionLinkRegexp.lastIndex,\n text: match[0],\n type: \"mention\",\n displayText: parsed?.mention ?? match[0],\n };\n\n matches.push(parsed?.id ? { ...baseMention, userId: parsed.id } : baseMention);\n }\n\n return matches;\n};\n\nexport const findTags = (content: string): TagEntity[] => {\n const regex = /#[^\\s]{1,201}/g;\n const results: TagEntity[] = [];\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(content)) !== null) {\n const value = match[0];\n results.push({\n start: match.index,\n end: match.index + value.length,\n text: value,\n type: \"tag\",\n tag: value.replace(\"#\", \"\"),\n });\n }\n\n return results;\n};\n\nexport const findLinks = (content: string): LinkEntity[] => {\n const regex =\n /\\b((https?:\\/\\/)?(?:[\\w-]+\\.)+[a-z]{2,}(\\/[\\w\\-._~:/?#[\\]@!$&'()*+,;=]*)?)/gi;\n const results: LinkEntity[] = [];\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(content)) !== null) {\n const rawUrl = match[0];\n const hasProtocol = /^https?:\\/\\//i.test(rawUrl);\n const fullUrl = hasProtocol ? rawUrl : `https://${rawUrl}`;\n\n results.push({\n start: match.index,\n end: match.index + rawUrl.length,\n text: rawUrl,\n url: fullUrl,\n type: \"link\",\n });\n }\n\n return results;\n};\n\nexport const findAllEntities = (content: string): ContentEntity[] => {\n const mentions = findMentions(content);\n const tags = findTags(content);\n const links = findLinks(content);\n\n return [...mentions, ...tags, ...links].sort((a, b) => a.start - b.start);\n};\n\nexport const processContent = (content: string): ProcessedContent => {\n const processedText = content.replace(mentionLinkRegexp, match => {\n const parsed = parseMention(match);\n return parsed ? parsed.mention : match;\n });\n\n const tags = findTags(content).map(tag => tag.tag);\n\n return {\n processedText,\n tags,\n };\n};\n","import type { ContentEntity, LinkEntity, MentionEntity, TagEntity } from \"../core\";\n\nimport { findAllEntities } from \"../core\";\n\nexport interface AngularTextToken {\n kind: \"text\";\n text: string;\n start: number;\n end: number;\n}\n\nexport interface AngularMentionToken {\n kind: \"mention\";\n entity: MentionEntity;\n}\n\nexport interface AngularTagToken {\n kind: \"tag\";\n entity: TagEntity;\n}\n\nexport interface AngularLinkToken {\n kind: \"link\";\n entity: LinkEntity;\n}\n\nexport type AngularContentToken =\n | AngularTextToken\n | AngularMentionToken\n | AngularTagToken\n | AngularLinkToken;\n\nexport interface AngularContentSnapshot {\n text: string;\n entities: ContentEntity[];\n tokens: AngularContentToken[];\n}\n\nexport type AngularEllipsisSymbol = string | ((expanded: boolean) => string);\n\nexport type AngularCountEllipsisConfig = {\n count: number;\n rows?: never;\n expandable?: boolean;\n expanded?: boolean;\n symbol?: AngularEllipsisSymbol;\n onExpand?: (expanded: boolean) => void;\n};\n\nexport type AngularRowsEllipsisConfig = {\n rows: number;\n count?: never;\n expandable?: boolean;\n expanded?: boolean;\n symbol?: AngularEllipsisSymbol;\n onExpand?: (expanded: boolean) => void;\n};\n\nexport type AngularContentEllipsisConfig =\n | AngularCountEllipsisConfig\n | AngularRowsEllipsisConfig\n | false;\n\nexport type AngularRenderResult = Node | string | number | null | undefined;\n\nexport type AngularMentionRenderer = (\n entity: MentionEntity,\n index: number,\n) => AngularRenderResult;\n\nexport type AngularTagRenderer = (\n entity: TagEntity,\n index: number,\n) => AngularRenderResult;\n\nexport type AngularLinkRenderer = (\n entity: LinkEntity,\n index: number,\n) => AngularRenderResult;\n\nexport interface AngularContentTextProps {\n className?: string;\n weight?: \"normal\" | \"bold\";\n text?: string | null;\n ellipsis?: AngularContentEllipsisConfig;\n blur?: boolean;\n style?: Record | null;\n onView?: () => void;\n renderMention?: AngularMentionRenderer;\n renderTag?: AngularTagRenderer;\n renderLink?: AngularLinkRenderer;\n}\n\nexport type AngularContentTextWithSuggestionsProps = Omit<\n AngularContentTextProps,\n \"renderMention\" | \"renderTag\"\n> & {\n renderMention?: AngularMentionRenderer;\n renderTag?: AngularTagRenderer;\n};\n\nexport type AngularContentTitleWithSuggestionsProps = Omit<\n AngularContentTextWithSuggestionsProps,\n \"weight\"\n>;\n\nexport interface AngularContentTextRendererState {\n props: AngularContentTextProps;\n snapshot: AngularContentSnapshot;\n expanded: boolean;\n}\n\nconst LINK_COLOR = \"#1677ff\";\nconst READ_MORE_TEXT = \"Читать полностью\";\n\nconst toKebabCase = (value: string) => value.replace(/[A-Z]/g, char => `-${char.toLowerCase()}`);\n\nconst applyStyleObject = (\n element: HTMLElement,\n styles?: Record | null,\n) => {\n if (!styles) {\n return;\n }\n\n for (const [key, rawValue] of Object.entries(styles)) {\n if (rawValue === null || rawValue === undefined) {\n continue;\n }\n\n const styleValue =\n typeof rawValue === \"number\" ? `${rawValue}px` : String(rawValue);\n\n if (key.startsWith(\"--\")) {\n element.style.setProperty(key, styleValue);\n continue;\n }\n\n const cssKey = key.includes(\"-\") ? key : toKebabCase(key);\n element.style.setProperty(cssKey, styleValue);\n }\n};\n\nconst toNode = (value: AngularRenderResult): Node | null => {\n if (value === null || value === undefined) {\n return null;\n }\n\n if (value instanceof Node) {\n return value;\n }\n\n return document.createTextNode(String(value));\n};\n\nconst resolveEllipsisSymbol = (\n symbol: AngularEllipsisSymbol | undefined,\n expanded: boolean,\n): string => {\n if (typeof symbol === \"function\") {\n return symbol(expanded);\n }\n\n return symbol ?? READ_MORE_TEXT;\n};\n\nconst buildAngularTagHref = (entity: TagEntity): string => {\n return `/search/?query=${encodeURIComponent(entity.tag.toLowerCase())}`;\n};\n\nexport { buildAngularTagHref };\n\nexport const createAngularContentTokens = (\n inputText: string | null | undefined,\n): AngularContentToken[] => {\n const text = inputText ?? \"\";\n const entities = findAllEntities(text);\n\n let cursor = 0;\n const tokens: AngularContentToken[] = [];\n\n for (const entity of entities) {\n if (entity.start > cursor) {\n tokens.push({\n kind: \"text\",\n text: text.slice(cursor, entity.start),\n start: cursor,\n end: entity.start,\n });\n }\n\n if (entity.type === \"mention\") {\n tokens.push({\n kind: \"mention\",\n entity,\n });\n } else if (entity.type === \"tag\") {\n tokens.push({\n kind: \"tag\",\n entity,\n });\n } else {\n tokens.push({\n kind: \"link\",\n entity,\n });\n }\n\n cursor = entity.end;\n }\n\n if (cursor < text.length) {\n tokens.push({\n kind: \"text\",\n text: text.slice(cursor),\n start: cursor,\n end: text.length,\n });\n }\n\n return tokens;\n};\n\nexport class AngularContentSuggestionsAdapter {\n snapshot(inputText: string | null | undefined): AngularContentSnapshot {\n const text = inputText ?? \"\";\n const entities = findAllEntities(text);\n const tokens = createAngularContentTokens(text);\n\n return {\n text,\n entities,\n tokens,\n };\n }\n}\n\nexport class AngularContentTextRenderer {\n private host: HTMLElement | null = null;\n private props: AngularContentTextProps = {};\n private expanded = false;\n private viewed = false;\n private hostInsideView = false;\n private observer: IntersectionObserver | null = null;\n\n attach(host: HTMLElement, props: AngularContentTextProps = {}): AngularContentTextRendererState {\n this.destroy();\n\n this.host = host;\n this.props = { ...props };\n this.expanded = false;\n this.viewed = false;\n this.hostInsideView = false;\n\n this.initObserver();\n this.render();\n\n return this.getState();\n }\n\n update(nextProps: AngularContentTextProps): AngularContentTextRendererState {\n this.props = {\n ...this.props,\n ...nextProps,\n };\n\n this.render();\n\n return this.getState();\n }\n\n destroy(): void {\n if (this.observer && this.host) {\n this.observer.unobserve(this.host);\n this.observer.disconnect();\n }\n\n this.observer = null;\n\n if (this.host) {\n this.host.innerHTML = \"\";\n }\n\n this.host = null;\n this.props = {};\n this.expanded = false;\n this.viewed = false;\n this.hostInsideView = false;\n }\n\n getState(): AngularContentTextRendererState {\n return {\n props: { ...this.props },\n snapshot: this.createSnapshot(),\n expanded: this.getMergedExpanded(this.resolveEllipsisConfig(), this.getText()),\n };\n }\n\n private getText(): string {\n return this.props.text ?? \"\";\n }\n\n private createSnapshot(): AngularContentSnapshot {\n const text = this.getText();\n const entities = findAllEntities(text);\n const tokens = createAngularContentTokens(text);\n\n return {\n text,\n entities,\n tokens,\n };\n }\n\n private resolveEllipsisConfig():\n | AngularCountEllipsisConfig\n | AngularRowsEllipsisConfig\n | null {\n const ellipsis = this.props.ellipsis;\n\n if (!ellipsis || typeof ellipsis !== \"object\") {\n return null;\n }\n\n return ellipsis;\n }\n\n private getMergedExpanded(\n ellipsisConfig: AngularCountEllipsisConfig | AngularRowsEllipsisConfig | null,\n text: string,\n ): boolean {\n const controlledExpanded =\n ellipsisConfig && typeof ellipsisConfig.expanded === \"boolean\"\n ? ellipsisConfig.expanded\n : undefined;\n\n if (controlledExpanded !== undefined) {\n return controlledExpanded;\n }\n\n if (ellipsisConfig && \"count\" in ellipsisConfig) {\n const count = ellipsisConfig.count ?? 0;\n if (count <= 0 || text.length <= count) {\n return true;\n }\n }\n\n return this.expanded;\n }\n\n private createDefaultMentionNode(entity: MentionEntity): Node {\n const span = document.createElement(\"span\");\n span.style.color = LINK_COLOR;\n span.style.fontWeight = this.props.weight === \"bold\" ? \"700\" : \"400\";\n span.textContent = entity.displayText;\n return span;\n }\n\n private createDefaultTagNode(entity: TagEntity): Node {\n const span = document.createElement(\"span\");\n span.style.color = LINK_COLOR;\n span.style.fontWeight = this.props.weight === \"bold\" ? \"700\" : \"400\";\n span.textContent = entity.text;\n return span;\n }\n\n private createDefaultLinkNode(entity: LinkEntity): Node {\n const anchor = document.createElement(\"a\");\n anchor.href = entity.url;\n anchor.target = \"_blank\";\n anchor.referrerPolicy = \"no-referrer\";\n anchor.style.color = LINK_COLOR;\n anchor.style.fontWeight = this.props.weight === \"bold\" ? \"700\" : \"400\";\n anchor.textContent = entity.text;\n return anchor;\n }\n\n private renderEntity(entity: ContentEntity, index: number): Node | null {\n if (entity.type === \"mention\") {\n const customNode = this.props.renderMention?.(entity, index);\n return toNode(customNode) ?? this.createDefaultMentionNode(entity);\n }\n\n if (entity.type === \"tag\") {\n const customNode = this.props.renderTag?.(entity, index);\n return toNode(customNode) ?? this.createDefaultTagNode(entity);\n }\n\n const customNode = this.props.renderLink?.(entity, index);\n return toNode(customNode) ?? this.createDefaultLinkNode(entity);\n }\n\n private buildTextNodes(\n text: string,\n entities: ContentEntity[],\n upto: number | null = null,\n ): Node[] {\n let lastIndex = 0;\n const nodes: Node[] = [];\n\n for (const [index, entity] of entities.entries()) {\n if (upto !== null && entity.start >= upto) {\n break;\n }\n\n const textEnd = upto !== null ? Math.min(entity.start, upto) : entity.start;\n if (entity.start > lastIndex && lastIndex < textEnd) {\n nodes.push(document.createTextNode(text.slice(lastIndex, textEnd)));\n }\n\n if (upto === null || entity.end <= upto) {\n const entityNode = this.renderEntity(entity, index);\n if (entityNode) {\n nodes.push(entityNode);\n }\n }\n\n lastIndex = entity.end;\n }\n\n if (upto === null) {\n if (lastIndex < text.length) {\n nodes.push(document.createTextNode(text.slice(lastIndex)));\n }\n } else if (lastIndex < upto) {\n nodes.push(document.createTextNode(text.slice(lastIndex, upto)));\n }\n\n return nodes;\n }\n\n private applyBaseParagraphStyle(element: HTMLElement): void {\n element.style.whiteSpace = \"pre-wrap\";\n element.style.fontWeight = this.props.weight === \"bold\" ? \"700\" : \"400\";\n element.style.setProperty(\"-webkit-touch-callout\", \"default\");\n element.style.setProperty(\"-webkit-user-select\", \"text\");\n element.style.setProperty(\"-khtml-user-select\", \"text\");\n element.style.setProperty(\"-moz-user-select\", \"text\");\n element.style.setProperty(\"-ms-user-select\", \"text\");\n element.style.setProperty(\"user-select\", \"text\");\n\n if (this.props.blur) {\n element.style.filter = \"blur(3px)\";\n element.style.setProperty(\"-webkit-user-select\", \"none\");\n element.style.setProperty(\"-khtml-user-select\", \"none\");\n element.style.setProperty(\"-moz-user-select\", \"none\");\n element.style.setProperty(\"-ms-user-select\", \"none\");\n element.style.setProperty(\"user-select\", \"none\");\n element.style.pointerEvents = \"none\";\n }\n\n applyStyleObject(element, this.props.style);\n }\n\n private buildReadMoreButton(symbol: AngularEllipsisSymbol | undefined): HTMLButtonElement {\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.textContent = resolveEllipsisSymbol(symbol, this.expanded);\n button.style.border = \"0\";\n button.style.background = \"none\";\n button.style.padding = \"0\";\n button.style.marginLeft = \"6px\";\n button.style.cursor = \"pointer\";\n button.style.color = LINK_COLOR;\n button.style.fontWeight = \"700\";\n button.onclick = event => {\n event.preventDefault();\n event.stopPropagation();\n this.handleExpand();\n };\n return button;\n }\n\n private handleExpand(): void {\n const ellipsisConfig = this.resolveEllipsisConfig();\n\n if (!ellipsisConfig) {\n return;\n }\n\n this.expanded = true;\n ellipsisConfig.onExpand?.(true);\n this.render();\n }\n\n private computeEntitySafeCutoff(\n count: number,\n entities: ContentEntity[],\n ): number {\n let cutoff = count;\n let extended = true;\n\n while (extended) {\n extended = false;\n\n for (const entity of entities) {\n if (entity.start < cutoff && entity.end > cutoff) {\n cutoff = entity.end;\n extended = true;\n }\n }\n }\n\n return cutoff;\n }\n\n private render(): void {\n if (!this.host) {\n return;\n }\n\n const text = this.getText();\n const entities = findAllEntities(text);\n const ellipsisConfig = this.resolveEllipsisConfig();\n const mergedExpanded = this.getMergedExpanded(ellipsisConfig, text);\n\n const wrapper = document.createElement(\"div\");\n const paragraph = document.createElement(\"div\");\n\n if (this.props.className) {\n paragraph.className = this.props.className;\n }\n\n this.applyBaseParagraphStyle(paragraph);\n\n if (ellipsisConfig && \"count\" in ellipsisConfig) {\n const count = ellipsisConfig.count ?? 0;\n const shouldTruncate = !mergedExpanded && count > 0 && text.length > count;\n\n if (shouldTruncate) {\n const cutoff = this.computeEntitySafeCutoff(count, entities);\n const nodes = this.buildTextNodes(text, entities, cutoff);\n\n paragraph.append(...nodes);\n paragraph.append(document.createTextNode(\"…\"));\n\n if (ellipsisConfig.expandable) {\n paragraph.append(this.buildReadMoreButton(ellipsisConfig.symbol));\n }\n } else {\n paragraph.append(...this.buildTextNodes(text, entities));\n }\n } else {\n paragraph.append(...this.buildTextNodes(text, entities));\n\n if (ellipsisConfig && \"rows\" in ellipsisConfig && !mergedExpanded) {\n paragraph.style.display = \"-webkit-box\";\n paragraph.style.setProperty(\"-webkit-box-orient\", \"vertical\");\n paragraph.style.setProperty(\"-webkit-line-clamp\", String(ellipsisConfig.rows));\n paragraph.style.overflow = \"hidden\";\n\n if (ellipsisConfig.expandable) {\n wrapper.append(paragraph);\n wrapper.append(this.buildReadMoreButton(ellipsisConfig.symbol));\n this.host.replaceChildren(wrapper);\n this.emitOnViewIfNeeded(mergedExpanded);\n return;\n }\n }\n }\n\n wrapper.append(paragraph);\n this.host.replaceChildren(wrapper);\n this.emitOnViewIfNeeded(mergedExpanded);\n }\n\n private initObserver(): void {\n if (!this.host) {\n return;\n }\n\n if (typeof IntersectionObserver === \"undefined\") {\n this.hostInsideView = true;\n return;\n }\n\n this.observer = new IntersectionObserver(\n entries => {\n const entry = entries[0];\n if (!entry) {\n return;\n }\n\n if (entry.isIntersecting && !this.hostInsideView) {\n this.hostInsideView = true;\n const ellipsisConfig = this.resolveEllipsisConfig();\n const mergedExpanded = this.getMergedExpanded(ellipsisConfig, this.getText());\n this.emitOnViewIfNeeded(mergedExpanded);\n }\n },\n { threshold: 0.5 },\n );\n\n this.observer.observe(this.host);\n }\n\n private emitOnViewIfNeeded(mergedExpanded: boolean): void {\n if (!mergedExpanded || this.viewed || !this.hostInsideView) {\n return;\n }\n\n this.viewed = true;\n this.props.onView?.();\n }\n}\n\nexport class AngularContentTextWithSuggestionsRenderer extends AngularContentTextRenderer {}\n\nexport class AngularContentTitleWithSuggestionsRenderer {\n private readonly renderer = new AngularContentTextWithSuggestionsRenderer();\n\n attach(\n host: HTMLElement,\n props: AngularContentTitleWithSuggestionsProps = {},\n ): AngularContentTextRendererState {\n return this.renderer.attach(host, this.normalizeProps(props));\n }\n\n update(props: AngularContentTitleWithSuggestionsProps): AngularContentTextRendererState {\n return this.renderer.update(this.normalizeProps(props));\n }\n\n destroy(): void {\n this.renderer.destroy();\n }\n\n getState(): AngularContentTextRendererState {\n return this.renderer.getState();\n }\n\n private normalizeProps(\n props: AngularContentTitleWithSuggestionsProps,\n ): AngularContentTextWithSuggestionsProps {\n return {\n ...props,\n weight: \"bold\",\n blur: props.blur ?? false,\n ellipsis: props.ellipsis === undefined ? { rows: 2 } : props.ellipsis,\n };\n }\n}\n\nexport { toKebabCase };\n"]} \ No newline at end of file diff --git a/packages/content-suggestions/package.json b/packages/content-suggestions/package.json index ced7e24..8e2c7b4 100644 --- a/packages/content-suggestions/package.json +++ b/packages/content-suggestions/package.json @@ -1,6 +1,6 @@ { "name": "@hublib-web/content-suggestions", - "version": "0.1.0", + "version": "0.1.1", "description": "Content text/title with mentions, tags and links for React and Angular", "license": "MIT", "type": "module", diff --git a/packages/content-suggestions/src/angular/index.ts b/packages/content-suggestions/src/angular/index.ts index 78dc06c..dd11331 100644 --- a/packages/content-suggestions/src/angular/index.ts +++ b/packages/content-suggestions/src/angular/index.ts @@ -36,10 +36,140 @@ export interface AngularContentSnapshot { tokens: AngularContentToken[]; } -export const buildAngularTagHref = (entity: TagEntity): string => { +export type AngularEllipsisSymbol = string | ((expanded: boolean) => string); + +export type AngularCountEllipsisConfig = { + count: number; + rows?: never; + expandable?: boolean; + expanded?: boolean; + symbol?: AngularEllipsisSymbol; + onExpand?: (expanded: boolean) => void; +}; + +export type AngularRowsEllipsisConfig = { + rows: number; + count?: never; + expandable?: boolean; + expanded?: boolean; + symbol?: AngularEllipsisSymbol; + onExpand?: (expanded: boolean) => void; +}; + +export type AngularContentEllipsisConfig = + | AngularCountEllipsisConfig + | AngularRowsEllipsisConfig + | false; + +export type AngularRenderResult = Node | string | number | null | undefined; + +export type AngularMentionRenderer = ( + entity: MentionEntity, + index: number, +) => AngularRenderResult; + +export type AngularTagRenderer = ( + entity: TagEntity, + index: number, +) => AngularRenderResult; + +export type AngularLinkRenderer = ( + entity: LinkEntity, + index: number, +) => AngularRenderResult; + +export interface AngularContentTextProps { + className?: string; + weight?: "normal" | "bold"; + text?: string | null; + ellipsis?: AngularContentEllipsisConfig; + blur?: boolean; + style?: Record | null; + onView?: () => void; + renderMention?: AngularMentionRenderer; + renderTag?: AngularTagRenderer; + renderLink?: AngularLinkRenderer; +} + +export type AngularContentTextWithSuggestionsProps = Omit< + AngularContentTextProps, + "renderMention" | "renderTag" +> & { + renderMention?: AngularMentionRenderer; + renderTag?: AngularTagRenderer; +}; + +export type AngularContentTitleWithSuggestionsProps = Omit< + AngularContentTextWithSuggestionsProps, + "weight" +>; + +export interface AngularContentTextRendererState { + props: AngularContentTextProps; + snapshot: AngularContentSnapshot; + expanded: boolean; +} + +const LINK_COLOR = "#1677ff"; +const READ_MORE_TEXT = "Читать полностью"; + +const toKebabCase = (value: string) => value.replace(/[A-Z]/g, char => `-${char.toLowerCase()}`); + +const applyStyleObject = ( + element: HTMLElement, + styles?: Record | null, +) => { + if (!styles) { + return; + } + + for (const [key, rawValue] of Object.entries(styles)) { + if (rawValue === null || rawValue === undefined) { + continue; + } + + const styleValue = + typeof rawValue === "number" ? `${rawValue}px` : String(rawValue); + + if (key.startsWith("--")) { + element.style.setProperty(key, styleValue); + continue; + } + + const cssKey = key.includes("-") ? key : toKebabCase(key); + element.style.setProperty(cssKey, styleValue); + } +}; + +const toNode = (value: AngularRenderResult): Node | null => { + if (value === null || value === undefined) { + return null; + } + + if (value instanceof Node) { + return value; + } + + return document.createTextNode(String(value)); +}; + +const resolveEllipsisSymbol = ( + symbol: AngularEllipsisSymbol | undefined, + expanded: boolean, +): string => { + if (typeof symbol === "function") { + return symbol(expanded); + } + + return symbol ?? READ_MORE_TEXT; +}; + +const buildAngularTagHref = (entity: TagEntity): string => { return `/search/?query=${encodeURIComponent(entity.tag.toLowerCase())}`; }; +export { buildAngularTagHref }; + export const createAngularContentTokens = ( inputText: string | null | undefined, ): AngularContentToken[] => { @@ -104,3 +234,410 @@ export class AngularContentSuggestionsAdapter { }; } } + +export class AngularContentTextRenderer { + private host: HTMLElement | null = null; + private props: AngularContentTextProps = {}; + private expanded = false; + private viewed = false; + private hostInsideView = false; + private observer: IntersectionObserver | null = null; + + attach(host: HTMLElement, props: AngularContentTextProps = {}): AngularContentTextRendererState { + this.destroy(); + + this.host = host; + this.props = { ...props }; + this.expanded = false; + this.viewed = false; + this.hostInsideView = false; + + this.initObserver(); + this.render(); + + return this.getState(); + } + + update(nextProps: AngularContentTextProps): AngularContentTextRendererState { + this.props = { + ...this.props, + ...nextProps, + }; + + this.render(); + + return this.getState(); + } + + destroy(): void { + if (this.observer && this.host) { + this.observer.unobserve(this.host); + this.observer.disconnect(); + } + + this.observer = null; + + if (this.host) { + this.host.innerHTML = ""; + } + + this.host = null; + this.props = {}; + this.expanded = false; + this.viewed = false; + this.hostInsideView = false; + } + + getState(): AngularContentTextRendererState { + return { + props: { ...this.props }, + snapshot: this.createSnapshot(), + expanded: this.getMergedExpanded(this.resolveEllipsisConfig(), this.getText()), + }; + } + + private getText(): string { + return this.props.text ?? ""; + } + + private createSnapshot(): AngularContentSnapshot { + const text = this.getText(); + const entities = findAllEntities(text); + const tokens = createAngularContentTokens(text); + + return { + text, + entities, + tokens, + }; + } + + private resolveEllipsisConfig(): + | AngularCountEllipsisConfig + | AngularRowsEllipsisConfig + | null { + const ellipsis = this.props.ellipsis; + + if (!ellipsis || typeof ellipsis !== "object") { + return null; + } + + return ellipsis; + } + + private getMergedExpanded( + ellipsisConfig: AngularCountEllipsisConfig | AngularRowsEllipsisConfig | null, + text: string, + ): boolean { + const controlledExpanded = + ellipsisConfig && typeof ellipsisConfig.expanded === "boolean" + ? ellipsisConfig.expanded + : undefined; + + if (controlledExpanded !== undefined) { + return controlledExpanded; + } + + if (ellipsisConfig && "count" in ellipsisConfig) { + const count = ellipsisConfig.count ?? 0; + if (count <= 0 || text.length <= count) { + return true; + } + } + + return this.expanded; + } + + private createDefaultMentionNode(entity: MentionEntity): Node { + const span = document.createElement("span"); + span.style.color = LINK_COLOR; + span.style.fontWeight = this.props.weight === "bold" ? "700" : "400"; + span.textContent = entity.displayText; + return span; + } + + private createDefaultTagNode(entity: TagEntity): Node { + const span = document.createElement("span"); + span.style.color = LINK_COLOR; + span.style.fontWeight = this.props.weight === "bold" ? "700" : "400"; + span.textContent = entity.text; + return span; + } + + private createDefaultLinkNode(entity: LinkEntity): Node { + const anchor = document.createElement("a"); + anchor.href = entity.url; + anchor.target = "_blank"; + anchor.referrerPolicy = "no-referrer"; + anchor.style.color = LINK_COLOR; + anchor.style.fontWeight = this.props.weight === "bold" ? "700" : "400"; + anchor.textContent = entity.text; + return anchor; + } + + private renderEntity(entity: ContentEntity, index: number): Node | null { + if (entity.type === "mention") { + const customNode = this.props.renderMention?.(entity, index); + return toNode(customNode) ?? this.createDefaultMentionNode(entity); + } + + if (entity.type === "tag") { + const customNode = this.props.renderTag?.(entity, index); + return toNode(customNode) ?? this.createDefaultTagNode(entity); + } + + const customNode = this.props.renderLink?.(entity, index); + return toNode(customNode) ?? this.createDefaultLinkNode(entity); + } + + private buildTextNodes( + text: string, + entities: ContentEntity[], + upto: number | null = null, + ): Node[] { + let lastIndex = 0; + const nodes: Node[] = []; + + for (const [index, 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(document.createTextNode(text.slice(lastIndex, textEnd))); + } + + if (upto === null || entity.end <= upto) { + const entityNode = this.renderEntity(entity, index); + if (entityNode) { + nodes.push(entityNode); + } + } + + lastIndex = entity.end; + } + + if (upto === null) { + if (lastIndex < text.length) { + nodes.push(document.createTextNode(text.slice(lastIndex))); + } + } else if (lastIndex < upto) { + nodes.push(document.createTextNode(text.slice(lastIndex, upto))); + } + + return nodes; + } + + private applyBaseParagraphStyle(element: HTMLElement): void { + element.style.whiteSpace = "pre-wrap"; + element.style.fontWeight = this.props.weight === "bold" ? "700" : "400"; + element.style.setProperty("-webkit-touch-callout", "default"); + element.style.setProperty("-webkit-user-select", "text"); + element.style.setProperty("-khtml-user-select", "text"); + element.style.setProperty("-moz-user-select", "text"); + element.style.setProperty("-ms-user-select", "text"); + element.style.setProperty("user-select", "text"); + + if (this.props.blur) { + element.style.filter = "blur(3px)"; + element.style.setProperty("-webkit-user-select", "none"); + element.style.setProperty("-khtml-user-select", "none"); + element.style.setProperty("-moz-user-select", "none"); + element.style.setProperty("-ms-user-select", "none"); + element.style.setProperty("user-select", "none"); + element.style.pointerEvents = "none"; + } + + applyStyleObject(element, this.props.style); + } + + private buildReadMoreButton(symbol: AngularEllipsisSymbol | undefined): HTMLButtonElement { + const button = document.createElement("button"); + button.type = "button"; + button.textContent = resolveEllipsisSymbol(symbol, this.expanded); + button.style.border = "0"; + button.style.background = "none"; + button.style.padding = "0"; + button.style.marginLeft = "6px"; + button.style.cursor = "pointer"; + button.style.color = LINK_COLOR; + button.style.fontWeight = "700"; + button.onclick = event => { + event.preventDefault(); + event.stopPropagation(); + this.handleExpand(); + }; + return button; + } + + private handleExpand(): void { + const ellipsisConfig = this.resolveEllipsisConfig(); + + if (!ellipsisConfig) { + return; + } + + this.expanded = true; + ellipsisConfig.onExpand?.(true); + this.render(); + } + + private computeEntitySafeCutoff( + count: number, + entities: ContentEntity[], + ): number { + 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; + } + } + } + + return cutoff; + } + + private render(): void { + if (!this.host) { + return; + } + + const text = this.getText(); + const entities = findAllEntities(text); + const ellipsisConfig = this.resolveEllipsisConfig(); + const mergedExpanded = this.getMergedExpanded(ellipsisConfig, text); + + const wrapper = document.createElement("div"); + const paragraph = document.createElement("div"); + + if (this.props.className) { + paragraph.className = this.props.className; + } + + this.applyBaseParagraphStyle(paragraph); + + if (ellipsisConfig && "count" in ellipsisConfig) { + const count = ellipsisConfig.count ?? 0; + const shouldTruncate = !mergedExpanded && count > 0 && text.length > count; + + if (shouldTruncate) { + const cutoff = this.computeEntitySafeCutoff(count, entities); + const nodes = this.buildTextNodes(text, entities, cutoff); + + paragraph.append(...nodes); + paragraph.append(document.createTextNode("…")); + + if (ellipsisConfig.expandable) { + paragraph.append(this.buildReadMoreButton(ellipsisConfig.symbol)); + } + } else { + paragraph.append(...this.buildTextNodes(text, entities)); + } + } else { + paragraph.append(...this.buildTextNodes(text, entities)); + + if (ellipsisConfig && "rows" in ellipsisConfig && !mergedExpanded) { + paragraph.style.display = "-webkit-box"; + paragraph.style.setProperty("-webkit-box-orient", "vertical"); + paragraph.style.setProperty("-webkit-line-clamp", String(ellipsisConfig.rows)); + paragraph.style.overflow = "hidden"; + + if (ellipsisConfig.expandable) { + wrapper.append(paragraph); + wrapper.append(this.buildReadMoreButton(ellipsisConfig.symbol)); + this.host.replaceChildren(wrapper); + this.emitOnViewIfNeeded(mergedExpanded); + return; + } + } + } + + wrapper.append(paragraph); + this.host.replaceChildren(wrapper); + this.emitOnViewIfNeeded(mergedExpanded); + } + + private initObserver(): void { + if (!this.host) { + return; + } + + if (typeof IntersectionObserver === "undefined") { + this.hostInsideView = true; + return; + } + + this.observer = new IntersectionObserver( + entries => { + const entry = entries[0]; + if (!entry) { + return; + } + + if (entry.isIntersecting && !this.hostInsideView) { + this.hostInsideView = true; + const ellipsisConfig = this.resolveEllipsisConfig(); + const mergedExpanded = this.getMergedExpanded(ellipsisConfig, this.getText()); + this.emitOnViewIfNeeded(mergedExpanded); + } + }, + { threshold: 0.5 }, + ); + + this.observer.observe(this.host); + } + + private emitOnViewIfNeeded(mergedExpanded: boolean): void { + if (!mergedExpanded || this.viewed || !this.hostInsideView) { + return; + } + + this.viewed = true; + this.props.onView?.(); + } +} + +export class AngularContentTextWithSuggestionsRenderer extends AngularContentTextRenderer {} + +export class AngularContentTitleWithSuggestionsRenderer { + private readonly renderer = new AngularContentTextWithSuggestionsRenderer(); + + attach( + host: HTMLElement, + props: AngularContentTitleWithSuggestionsProps = {}, + ): AngularContentTextRendererState { + return this.renderer.attach(host, this.normalizeProps(props)); + } + + update(props: AngularContentTitleWithSuggestionsProps): AngularContentTextRendererState { + return this.renderer.update(this.normalizeProps(props)); + } + + destroy(): void { + this.renderer.destroy(); + } + + getState(): AngularContentTextRendererState { + return this.renderer.getState(); + } + + private normalizeProps( + props: AngularContentTitleWithSuggestionsProps, + ): AngularContentTextWithSuggestionsProps { + return { + ...props, + weight: "bold", + blur: props.blur ?? false, + ellipsis: props.ellipsis === undefined ? { rows: 2 } : props.ellipsis, + }; + } +} + +export { toKebabCase };