Source: control2/bubble/content/Html.js

(function() {
    /**
     * @typedef {Help4.control2.bubble.content.BubbleContent.Params} Help4.control2.bubble.content.Html.Params
     * @property {string} [content = ''] - the HTML content
     */

    /**
     * Control to show HTML content.
     * @augments Help4.control2.bubble.content.BubbleContent
     * @property {string} content - the HTML content
     */
    Help4.control2.bubble.content.Html = class extends Help4.control2.bubble.content.BubbleContent {
        /**
         * @override
         * @param {Help4.control2.bubble.content.Html.Params} [params]
         */
        constructor(params) {
            const {
                TYPES: T,
                TEXT_TYPES: TT
            } = Help4.jscore.ControlBase;

            super(params, {
                params: {
                    tag:     {init: 'article'},

                    // all defaults are handled by bubble; do not handle here!
                    // keep in sync with bubble!
                    content: {type: T.string}
                },
                config: {
                    css: 'control-bubble-content-html'
                },
                texts: {
                    content: TT.innerHTML
                }
            });
        }

        /**
         * @override
         * @param {Object} event - the received event
         */
        onEvent(event) {
            if (event.type === 'click' || event.type === 'touchend') {
                _handleLinkClick.call(this, event);
                this._fireEvent({type: 'click', data: event});
            } else {
                super.onEvent(event);
            }
        }

        /**
         * @override
         * @param {Help4.jscore.ControlBase.PropertyChangeEvent} event - the change event
         */
        _applyPropertyToDom({name, value, oldValue}) {
            if (name === 'content') {
                const dom = this.getDom();
                dom.innerHTML = value;  // XXX: security?
                this._applyTextAttribute(dom, 'content', !!value);
            } else {
                super._applyPropertyToDom({name, value, oldValue});
            }
        }
    }

    /**
     * @memberof Help4.control2.bubble.content.Html#
     * @param {Object} event
     * @returns {?boolean}
     * @private
     */
    function _handleLinkClick(event) {
        // XRAY-1883: removed all "javascript:" links because of CSP
        // see Help4.control.input.HtmlEditor.transformForPlayback

        // XRAY-2019: clicked item might be inside of link
        // e.g. <a><span>text</span></a>
        const target = event.target.closest('A');
        const {cmd, docuLink} = target?.dataset || {};

        // XRAY-4837: special handling for DOCU_LINK
        // docuLink needs to be sent with actual href/cmd to show link underline, ignore cmd in this case
        if (docuLink) {
            const {__bubble} = this;
            const controller = __bubble && Help4.getController();
            const urEngine = controller?.getEngine('urHarmonization');
            // when ML translations are enabled - content language res_langu EN > language en-US conversion?
            //   until then res_langu is not used
            const {content, short_text/*, res_langu*/} = urEngine?.getDocuLinkContent(docuLink) || {};
            if (content) {
                // update bubble texts
                const newTexts = {};
                const {HtmlEditor} = Help4.control.input;
                for (const tid in __bubble.getTexts(true)) {
                    if (tid.endsWith('caption')) {
                        // 1 - short_text from backend, not available for glossary links
                        // 2 - innerText, some link texts are not capitalized, either solve here or wait for query to be extended to also return the caption/label
                        //     capitalize first letter of each word (non-whitespace following whitespace/start)
                        // 3 - command, DOCU_LINK id as fallback
                        newTexts[tid] = short_text || target.innerText.replace(/(^|\s)\S/g, c => c.toUpperCase()) || docuLink;
                    } else if (tid.endsWith('content')) {
                        newTexts[tid] = HtmlEditor.transformForPlayback({text: content, controller});
                    }
                }
                __bubble.setTexts(newTexts);

                // re-align bubble
                let connectionPoints;
                let params;
                const tileId = __bubble.getMetadata('tileId');
                const handler = controller.getHandler();
                if (tileId && handler) {  // CMP3
                    // cache not always available, get connectionPoints from hotspot
                    const hotspots = handler.getHotspots();
                    const hotspot = hotspots.get({byMetadata: {tileId}});
                    connectionPoints = hotspot?.getConnectionPoints({useMidPoint: true});
                } else {  // CMP4
                    // re-use previously cached values
                    const {conditions} = __bubble.__alignmentCache || {};
                    connectionPoints = conditions?.connectionPoints;
                    params = {ignoreArea: true, orientation: conditions?.orientation || 'auto'};
                }
                __bubble.align(connectionPoints, params);
            }

            return Help4.Event.cancel(event);  // suppress link execution
        }

        let command = cmd;
        if (command && (command = command.match(/^Help4\.CtxWPB\.show\((.*?)\)$/)) && command.length > 1) {
            command = command[1].split(/\,\s?(?![^\(]*\))/);  // split by commas, ignore text between the parentheses

            const data = {};
            for (const key of ['id', 'url', 'win']) {
                data[key] = command.shift().trim().replace(/^["']|["']$/g, '');
            }

            // XRAY-4259: by default the free links in bubble should be opened in new window (ignore newWindow setting)
            data.win = true;

            command = command.join().replace(/^["']|["']$/g, '').trim();
            data.params = command && command !== 'null' ? command : null;

            setTimeout(() => {
                Help4.WM.trackExternalLink(data);
                Help4.CtxWPB.show(data.id, data.url, data.win, data.params)
            }, 1);

            return Help4.Event.cancel(event);  // suppress link execution
        }
    }
})();