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

294 lines (293 loc) 13.1 kB
import { ButtonType, KSLButtonElement } from './KSLButtonElement'; import { IconName } from './KSLIconElement'; import { assert } from '../utils/assert'; import { KSLPopoverElement } from './KSLPopoverElement'; import { ElementPositionOffset, KSLPositionedElement } from './abstract/KSLPositionedElement'; import { KSLContainerElement } from './KSLContainerElement'; import { createTemplateForCustomElement } from '../utils/domElement'; import { AddButtonAction, AddButtonElementType, AddButtonPermission, AddButtonPermissionCheckResult, } from '../lib/IFrameCommunicatorTypes'; import { logError } from '../lib/Logger'; import { BaseZIndex } from './tokens/zIndex'; import { MetadataAttribute } from '../utils/dataAttributes/attributes'; import { parseAddButtonDataAttributes } from '../utils/dataAttributes/parser'; 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', this.handleClick); this.buttonRef.tooltipMessage = DefaultTooltipMessage; } disconnectedCallback() { super.disconnectedCallback(); window.removeEventListener('click', this.handleClickOutside, { capture: true }); this.buttonRef.removeEventListener('click', this.handleClick); 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; default: 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; default: 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 || 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 (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', this.handleCreateComponentClick); } if (createLinkedItemButtonRef && elementType === AddButtonElementType.LinkedItems) { createLinkedItemButtonRef.addEventListener('click', this.handleCreateLinkedItemClick); } if (insertLinkedItemButtonRef) { insertLinkedItemButtonRef.addEventListener('click', this.handleInsertLinkedItemClick); } }; 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