release(content-suggestions): v0.1.1
This commit is contained in:
351
packages/content-suggestions/dist/angular/index.cjs
vendored
351
packages/content-suggestions/dist/angular/index.cjs
vendored
@@ -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
|
||||
File diff suppressed because one or more lines are too long
@@ -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<string, string | number> | null;
|
||||
onView?: () => void;
|
||||
renderMention?: AngularMentionRenderer;
|
||||
renderTag?: AngularTagRenderer;
|
||||
renderLink?: AngularLinkRenderer;
|
||||
}
|
||||
type AngularContentTextWithSuggestionsProps = Omit<AngularContentTextProps, "renderMention" | "renderTag"> & {
|
||||
renderMention?: AngularMentionRenderer;
|
||||
renderTag?: AngularTagRenderer;
|
||||
};
|
||||
type AngularContentTitleWithSuggestionsProps = Omit<AngularContentTextWithSuggestionsProps, "weight">;
|
||||
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 };
|
||||
|
||||
@@ -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<string, string | number> | null;
|
||||
onView?: () => void;
|
||||
renderMention?: AngularMentionRenderer;
|
||||
renderTag?: AngularTagRenderer;
|
||||
renderLink?: AngularLinkRenderer;
|
||||
}
|
||||
type AngularContentTextWithSuggestionsProps = Omit<AngularContentTextProps, "renderMention" | "renderTag"> & {
|
||||
renderMention?: AngularMentionRenderer;
|
||||
renderTag?: AngularTagRenderer;
|
||||
};
|
||||
type AngularContentTitleWithSuggestionsProps = Omit<AngularContentTextWithSuggestionsProps, "weight">;
|
||||
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 };
|
||||
|
||||
349
packages/content-suggestions/dist/angular/index.js
vendored
349
packages/content-suggestions/dist/angular/index.js
vendored
@@ -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
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user