UNPKG

@kontent-ai/smart-link

Version:

Kontent.ai Smart Link SDK allowing to automatically inject [smart links](https://docs.kontent.ai/tutorials/develop-apps/build-strong-foundation/set-up-editing-from-preview#a-using-smart-links) to Kontent.ai according to manually specified [HTML data attri

324 lines (323 loc) 14.1 kB
import { AddButtonAction, AddButtonElementType, AddButtonPermission, AddButtonPermissionCheckResult, } from "../lib/IFrameCommunicatorTypes"; import { logError } from "../lib/Logger"; import { assert } from "../utils/assert"; import { MetadataAttribute } from "../utils/dataAttributes/attributes"; import { parseAddButtonDataAttributes } from "../utils/dataAttributes/parser"; import { createTemplateForCustomElement } from "../utils/domElement"; import { ElementPositionOffset, KSLPositionedElement } from "./abstract/KSLPositionedElement"; import { ButtonType, KSLButtonElement } from "./KSLButtonElement"; import { KSLContainerElement } from "./KSLContainerElement"; import { IconName } from "./KSLIconElement"; import { KSLPopoverElement } from "./KSLPopoverElement"; import { BaseZIndex } from "./tokens/zIndex"; const ContentIsPublishedTooltip = "Content is published"; const DefaultTooltipMessage = "Insert..."; const getCreateLinkedItemTooltip = (canUserCreateLinkedItem) => canUserCreateLinkedItem ? "Create new item" : "Your role cannot create items from the allowed types"; var PopoverButtonId; (function (PopoverButtonId) { PopoverButtonId["CreateComponent"] = "create-component"; PopoverButtonId["CreateLinkedItem"] = "create-linked-item"; PopoverButtonId["InsertLinkedItem"] = "insert-linked-item"; })(PopoverButtonId || (PopoverButtonId = {})); const getPopoverHtml = ({ elementType, isParentPublished, permissions, }) => { const canUserCreateLinkedItem = permissions.get(AddButtonPermission.CreateNew) === AddButtonPermissionCheckResult.Ok; return ` <style> .ksl-add-button__popover-button + .ksl-add-button__popover-button { margin-left: 4px; } </style> <ksl-button id="${PopoverButtonId.InsertLinkedItem}" class="ksl-add-button__popover-button" type="${ButtonType.Quinary}" tooltip-position="${ElementPositionOffset.Top}" tooltip-message="${isParentPublished ? ContentIsPublishedTooltip : "Insert existing item"}" ${isParentPublished && "disabled"} > <ksl-icon icon-name="${IconName.Puzzle}"/> </ksl-button> <ksl-button id="${PopoverButtonId.CreateLinkedItem}" class="ksl-add-button__popover-button" type="${ButtonType.Quinary}" tooltip-position="${ElementPositionOffset.Top}" tooltip-message="${isParentPublished ? ContentIsPublishedTooltip : getCreateLinkedItemTooltip(canUserCreateLinkedItem)}" ${(isParentPublished || !canUserCreateLinkedItem) && "disabled"} ${elementType !== AddButtonElementType.LinkedItems && "hidden"} > <ksl-icon icon-name="${IconName.PlusPuzzle}"/> </ksl-button> <ksl-button id="${PopoverButtonId.CreateComponent}" class="ksl-add-button__popover-button" type="${ButtonType.Quinary}" tooltip-position="${ElementPositionOffset.Top}" tooltip-message="${isParentPublished ? ContentIsPublishedTooltip : "Insert new component"}" ${isParentPublished && "disabled"} ${elementType !== AddButtonElementType.RichText && "hidden"} > <ksl-icon icon-name="${IconName.CollapseScheme}"/> </ksl-button> `; }; const templateHTML = ` <style> :host { display: inline-block; position: absolute; z-index: calc(var(--ksl-z-index, ${BaseZIndex}) + 20); pointer-events: all; touch-action: initial; } :host(:focus) { outline: none; } </style> <ksl-button type="${ButtonType.Primary}" tooltip-position="${ElementPositionOffset.Top}" > <ksl-icon icon-name="${IconName.Plus}"/> </ksl-button> `; export class KSLAddButtonElement extends KSLPositionedElement { static get is() { return "ksl-add-button"; } get position() { return (this.targetRef?.getAttribute(MetadataAttribute.AddButtonRenderPosition) ?? ElementPositionOffset.Bottom); } buttonRef; popoverRef = null; constructor() { super(); assert(this.shadowRoot, 'Shadow root must be available in "open" mode.'); this.buttonRef = this.shadowRoot.querySelector(KSLButtonElement.is); } static initializeTemplate() { return createTemplateForCustomElement(templateHTML); } connectedCallback() { super.connectedCallback(); window.addEventListener("click", this.handleClickOutside, { capture: true }); this.buttonRef.addEventListener("click", (event) => { void this.handleClick(event); }); this.buttonRef.tooltipMessage = DefaultTooltipMessage; } disconnectedCallback() { super.disconnectedCallback(); window.removeEventListener("click", this.handleClickOutside, { capture: true }); this.buttonRef.removeEventListener("click", (event) => { void this.handleClick(event); }); this.dismissPopover(); } adjustPosition = () => { if (!this.targetRef || !this.offsetParent) { return; } if (!(this.offsetParent instanceof KSLContainerElement)) { console.warn("KSLAddButtonElement: should be located inside KSLContainerElement to be positioned properly."); } const offsetParentRect = this.offsetParent.getBoundingClientRect(); const targetRect = this.targetRef.getBoundingClientRect(); const thisRect = this.getBoundingClientRect(); const verticalOffset = this.calculateTopOffset(thisRect, targetRect); const horizontalOffset = this.calculateLeftOffset(thisRect, targetRect); this.style.top = `${targetRect.top - offsetParentRect.top + verticalOffset}px`; this.style.left = `${targetRect.left - offsetParentRect.left + horizontalOffset}px`; }; calculateTopOffset(thisRect, targetRect) { const offset = super.calculateTopOffset(thisRect, targetRect); switch (this.position) { case ElementPositionOffset.TopStart: case ElementPositionOffset.Top: case ElementPositionOffset.TopEnd: return offset / 2; case ElementPositionOffset.BottomStart: case ElementPositionOffset.Bottom: case ElementPositionOffset.BottomEnd: return offset - thisRect.height / 2; case ElementPositionOffset.Left: case ElementPositionOffset.LeftEnd: case ElementPositionOffset.LeftStart: case ElementPositionOffset.None: case ElementPositionOffset.Right: case ElementPositionOffset.RightEnd: case ElementPositionOffset.RightStart: return offset; } } calculateLeftOffset(thisRect, targetRect) { const offset = super.calculateLeftOffset(thisRect, targetRect); switch (this.position) { case ElementPositionOffset.LeftStart: case ElementPositionOffset.Left: case ElementPositionOffset.LeftEnd: return offset / 2; case ElementPositionOffset.RightStart: case ElementPositionOffset.Right: case ElementPositionOffset.RightEnd: return targetRect.width - thisRect.width / 2; case ElementPositionOffset.Bottom: case ElementPositionOffset.BottomEnd: case ElementPositionOffset.BottomStart: case ElementPositionOffset.None: case ElementPositionOffset.Top: case ElementPositionOffset.TopEnd: case ElementPositionOffset.TopStart: return offset; } } handleClick = async (event) => { if (this.popoverRef) { return; } assert(this.targetRef, "Target node is not set for this add button."); event.preventDefault(); event.stopPropagation(); this.buttonRef.loading = true; const data = parseAddButtonDataAttributes(this.targetRef); try { const eventData = { data, targetNode: this.targetRef }; const response = await this.dispatchAsyncEvent("ksl:add-button:initial", eventData); const { permissions } = response; const isUserMissingPermissions = permissions.get(AddButtonPermission.ViewParent) !== AddButtonPermissionCheckResult.Ok || permissions.get(AddButtonPermission.Edit) !== AddButtonPermissionCheckResult.Ok; const areComponentsForbidden = permissions.get(AddButtonPermission.CreateNew) === AddButtonPermissionCheckResult.RteWithForbiddenComponents; if (isUserMissingPermissions || areComponentsForbidden) { this.buttonRef.loading = false; this.buttonRef.disabled = true; this.buttonRef.tooltipMessage = isUserMissingPermissions ? "You are not allowed to add content here" : "Components and items can't be added here"; } else { this.buttonRef.loading = false; this.buttonRef.disabled = false; this.buttonRef.tooltipMessage = DefaultTooltipMessage; this.displayPopover(response); } } catch (reason) { logError(reason); this.buttonRef.loading = false; this.buttonRef.disabled = true; if (typeof reason === "object" && reason !== null && "message" in reason && typeof reason.message === "string") { this.buttonRef.tooltipMessage = reason.message; } else { this.buttonRef.tooltipMessage = "Something went wrong"; } } }; handleClickOutside = (event) => { if (!this.popoverRef || !(event.target instanceof Element)) { return; } const clickedInside = this.isSameNode(event.target) || this.contains(event.target); if (!clickedInside) { this.dismissPopover(); } }; displayPopover = (response) => { assert(this.shadowRoot, 'Shadow root must be available in "open" mode.'); if (this.popoverRef) { this.dismissPopover(); } this.buttonRef.tooltipPosition = ElementPositionOffset.Bottom; const popover = document.createElement(KSLPopoverElement.is); popover.innerHTML = getPopoverHtml(response); const popoverParent = this.shadowRoot; this.popoverRef = popoverParent.appendChild(popover); this.popoverRef.position = ElementPositionOffset.Top; this.popoverRef.attachTo(this); this.addPopoverEventListeners(response.elementType); this.popoverRef.visible = true; this.popoverRef.adjustPosition(); }; dismissPopover = () => { this.buttonRef.tooltipPosition = ElementPositionOffset.Top; if (this.popoverRef) { this.removePopoverEventListeners(); this.popoverRef.visible = false; this.popoverRef.remove(); this.popoverRef = null; } }; addPopoverEventListeners = (elementType) => { if (!this.popoverRef) { return; } const createComponentButtonRef = this.popoverRef.querySelector(`#${PopoverButtonId.CreateComponent}`); const createLinkedItemButtonRef = this.popoverRef.querySelector(`#${PopoverButtonId.CreateLinkedItem}`); const insertLinkedItemButtonRef = this.popoverRef.querySelector(`#${PopoverButtonId.InsertLinkedItem}`); if (createComponentButtonRef && elementType === AddButtonElementType.RichText) { createComponentButtonRef.addEventListener("click", (event) => { this.handleCreateComponentClick(event); }); } if (createLinkedItemButtonRef && elementType === AddButtonElementType.LinkedItems) { createLinkedItemButtonRef.addEventListener("click", (event) => { this.handleCreateLinkedItemClick(event); }); } if (insertLinkedItemButtonRef) { insertLinkedItemButtonRef.addEventListener("click", (event) => { this.handleInsertLinkedItemClick(event); }); } }; removePopoverEventListeners = () => { if (!this.popoverRef) { return; } const createComponentButtonRef = this.popoverRef.querySelector(`#${PopoverButtonId.CreateComponent}`); const createLinkedItemButtonRef = this.popoverRef.querySelector(`#${PopoverButtonId.CreateLinkedItem}`); const insertLinkedItemButtonRef = this.popoverRef.querySelector(`#${PopoverButtonId.InsertLinkedItem}`); if (createComponentButtonRef) { createComponentButtonRef.removeEventListener("click", this.handleCreateComponentClick); } if (createLinkedItemButtonRef) { createLinkedItemButtonRef.removeEventListener("click", this.handleCreateLinkedItemClick); } if (insertLinkedItemButtonRef) { insertLinkedItemButtonRef.removeEventListener("click", this.handleInsertLinkedItemClick); } }; handleCreateComponentClick = (event) => { this.handleAddActionClick(event, AddButtonAction.CreateComponent); }; handleCreateLinkedItemClick = (event) => { this.handleAddActionClick(event, AddButtonAction.CreateLinkedItem); }; handleInsertLinkedItemClick = (event) => { this.handleAddActionClick(event, AddButtonAction.InsertLinkedItem); }; handleAddActionClick = (event, action) => { assert(this.targetRef, "Target node is not set for this add button."); event.preventDefault(); event.stopPropagation(); const data = parseAddButtonDataAttributes(this.targetRef); const customEvent = new CustomEvent("ksl:add-button:action", { detail: { data: { ...data, action }, targetNode: this.targetRef, }, }); this.dismissPopover(); this.dispatchEvent(customEvent); }; } //# sourceMappingURL=KSLAddButtonElement.js.map