@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
JavaScript
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