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

495 lines
15 KiB
JavaScript

'use strict';
// src/core/parser.ts
var mentionLinkRegexp = /@\[[^\]]+]\([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\)/g;
var parseMention = (mention) => {
const regex = /@\[([^\]]+)\]\(([\w-]{36})\)/;
const match = mention.match(regex);
if (!match) {
return null;
}
const mentionText = match[1];
const mentionId = match[2];
if (!mentionText || !mentionId) {
return null;
}
return {
mention: `@${mentionText}`,
id: mentionId
};
};
var findMentions = (text) => {
let match;
const matches = [];
while ((match = mentionLinkRegexp.exec(text)) !== null) {
const parsed = parseMention(match[0]);
const baseMention = {
start: match.index,
end: mentionLinkRegexp.lastIndex,
text: match[0],
type: "mention",
displayText: parsed?.mention ?? match[0]
};
matches.push(parsed?.id ? { ...baseMention, userId: parsed.id } : baseMention);
}
return matches;
};
var findTags = (content) => {
const regex = /#[^\s]{1,201}/g;
const results = [];
let match;
while ((match = regex.exec(content)) !== null) {
const value = match[0];
results.push({
start: match.index,
end: match.index + value.length,
text: value,
type: "tag",
tag: value.replace("#", "")
});
}
return results;
};
var findLinks = (content) => {
const regex = /\b((https?:\/\/)?(?:[\w-]+\.)+[a-z]{2,}(\/[\w\-._~:/?#[\]@!$&'()*+,;=]*)?)/gi;
const results = [];
let match;
while ((match = regex.exec(content)) !== null) {
const rawUrl = match[0];
const hasProtocol = /^https?:\/\//i.test(rawUrl);
const fullUrl = hasProtocol ? rawUrl : `https://${rawUrl}`;
results.push({
start: match.index,
end: match.index + rawUrl.length,
text: rawUrl,
url: fullUrl,
type: "link"
});
}
return results;
};
var findAllEntities = (content) => {
const mentions = findMentions(content);
const tags = findTags(content);
const links = findLinks(content);
return [...mentions, ...tags, ...links].sort((a, b) => a.start - b.start);
};
// 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())}`;
};
var createAngularContentTokens = (inputText) => {
const text = inputText ?? "";
const entities = findAllEntities(text);
let cursor = 0;
const tokens = [];
for (const entity of entities) {
if (entity.start > cursor) {
tokens.push({
kind: "text",
text: text.slice(cursor, entity.start),
start: cursor,
end: entity.start
});
}
if (entity.type === "mention") {
tokens.push({
kind: "mention",
entity
});
} else if (entity.type === "tag") {
tokens.push({
kind: "tag",
entity
});
} else {
tokens.push({
kind: "link",
entity
});
}
cursor = entity.end;
}
if (cursor < text.length) {
tokens.push({
kind: "text",
text: text.slice(cursor),
start: cursor,
end: text.length
});
}
return tokens;
};
var AngularContentSuggestionsAdapter = class {
snapshot(inputText) {
const text = inputText ?? "";
const entities = findAllEntities(text);
const tokens = createAngularContentTokens(text);
return {
text,
entities,
tokens
};
}
};
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;
}
hasVerticalOverflow(element) {
return element.scrollHeight - element.clientHeight > 1;
}
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";
wrapper.append(paragraph);
this.host.replaceChildren(wrapper);
if (ellipsisConfig.expandable && this.hasVerticalOverflow(paragraph)) {
wrapper.append(this.buildReadMoreButton(ellipsisConfig.symbol));
}
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