MediaWiki:KoFi.js

From Stylecards

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* config */

const kofiWidgetOverlayConfig = {
    'floating-chat.core.pageId': '',
    'floating-chat.core.closer': '<svg height="0px" width="15px"><line x1="2" y1="8" x2="13" y2="18" style="stroke:#000; stroke-width:3" /><line x1="13" y1="8" x2="2" y2="18" style="stroke:#000; stroke-width:3" /></svg>',
    'floating-chat.core.position.bottom-left': 'position: fixed; bottom: 50px; left: 10px; width: 160px; height: 65px;',

    'floating-chat.cssId': '',
    'floating-chat.notice.text': 'ko-fi.com/%HANDLE%',
    'floating-chat.donatebutton.image': 'https://storage.ko-fi.com/cdn/cup-border.png',
    'floating-chat.donateButton.background-color': '#00b9fe',
    'floating-chat.donateButton.text': 'Support me',
    'floating-chat.donateButton.text-color': '#fff',
    'floating-chat.stylesheets': '["https://fonts.googleapis.com/css?family=Nunito:400,700,800&display=swap"]',
};

var kofiWidgetOverlayFloatingChatBuilder = kofiWidgetOverlayFloatingChatBuilder || function (config, _utils) {

    const _configManager = _utils.getConfigManager(config);
    const _myType = 'floating-chat';
    const _topContainerWrapClass = 'floatingchat-container-wrap';
    const _topMobiContainerWrapClass = 'floatingchat-container-wrap-mobi';

    var widgetPageLoadInitiatedStates = [];

    var closeButtonActionBlocked = false;
   
    function getButtonId() {
        return `${_configManager.getValue(_myType, 'cssId')}-donate-button`;
    };

    function getContainerFrameId() {
        return 'kofi-wo-container' + _configManager.getValue(_myType, 'cssId');
    };

    function getMobiContainerFrameId() {
        return 'kofi-wo-container-mobi' + _configManager.getValue(_myType, 'cssId');
    };

    function getButtonImageId() {
        return `${_configManager.getValue(_myType, 'cssId')}-donate-button-image`
    };

    function createButtonContainerIframe(iframeId, mainStyleSheetFile) {

        var htmlBody = getHtml();
        var buttonBody = '<html>' +
            '<head>' +
            `<link rel="preconnect" href="https://ko-fi.com/">` +
            `<link rel="dns-prefetch" href="https://ko-fi.com/">` +
            `<link rel="preconnect" href="https://storage.ko-fi.com/">` +
            `<link rel="dns-prefetch" href="https://storage.ko-fi.com/">` +
            `<link href="${mainStyleSheetFile}" rel="stylesheet" type="text/css" />` +
            `</head>` +
            `<body style="margin: 0; position: absolute; bottom: 0;">${htmlBody}</body>` +
            '</html>';

        var iframeContainerElement = document.getElementById(iframeId).contentDocument;
        var iframe = document.getElementById(iframeId);

        var _timer = setInterval(function () {

            //delay the display of the button, so that the stylesheets get time to load
            //the stylesheet load event does not appear to work reliably
            //on safari on iOS
            var doc = iframe.contentDocument || iframe.contentWindow;
            if (doc && doc.readyState == 'complete') {
                clearInterval(_timer);

                var parentWrapper = document.getElementsByClassName(_topContainerWrapClass)[0];
                var mobiParentWrapper = document.getElementsByClassName(_topMobiContainerWrapClass)[0];


                parentWrapper.style = 'z-index:10000;';
                mobiParentWrapper.style = 'z-index:10000;';

                iframe.style = '';
            }
        }, 300);

        iframeContainerElement.write(buttonBody);
        iframeContainerElement.close();

        return iframeContainerElement;
    };

    function attachDonateButton(iframeContainerElement, iframeId, selectors, heightLimits) {

        const donateButton = iframeContainerElement.getElementById(`${getButtonId()}`);
        donateButton.addEventListener('click',
            function () {
                if (donateButton.classList.contains("closed")) {
                    activateKofiIframe(iframeId, selectors, heightLimits);
                } else if (!closeButtonActionBlocked) {
                    var popupId = _configManager.getValue(_myType, 'cssId') + `-${selectors.popupId}`;
                    var popup = document.getElementById(popupId);
                    closePopup(popup, donateButton);
                }
            });

        return donateButton;
    };

    var write = function (parentElementId) {

        var docHead = document.head;
        if (!docHead) {
            docHead = document.createElement('head');
            document.prepend(docHead);
        }

        var iframeId = getContainerFrameId();
        var mobiIframeId = getMobiContainerFrameId();


        var iframeHtml = `<div class="${_topContainerWrapClass}" style="height: 0px; transition: all 0.3s ease 0s; opacity:0;">` +
            `<iframe class="floatingchat-container" style="height: 0px; transition: all 0.6s ease 0s; opacity:0;" id="${iframeId}"></iframe>` +
            '</div>' +
            `<div class="${_topMobiContainerWrapClass}" style="height: 0px; transition: all 0.6s ease 0s; opacity:0;">` +
            `<iframe class="floatingchat-container-mobi" style="height: 0px; transition: all 0.6s ease 0s; opacity:0;" id="${mobiIframeId}"></iframe>` +
            '</div>';

        var existingPlaceHolder = document.getElementById(parentElementId);
        existingPlaceHolder.innerHTML = iframeHtml;

        var iframeContainerElement = createButtonContainerIframe(iframeId, 'https://storage.ko-fi.com/cdn/scripts/floating-chat-main.css');
        var mobiIframeContainerElement = createButtonContainerIframe(mobiIframeId, 'https://storage.ko-fi.com/cdn/scripts/floating-chat-main.css');

         _utils.loadStyleSheet('https://storage.ko-fi.com/cdn/scripts/floating-chat-wrapper.css', document);
        var styleSheetsValue = _configManager.getValue(_myType, 'stylesheets');

        if ('' !== styleSheetsValue) {

            styleSheets = JSON.parse(styleSheetsValue);

            styleSheets.forEach(stylesheetRef => {
                _utils.loadStyleSheet(stylesheetRef, document);
                _utils.loadStyleSheet(stylesheetRef, iframeContainerElement);
                _utils.loadStyleSheet(stylesheetRef, mobiIframeContainerElement);
            });
        }

        var desktopDonateButton = attachDonateButton(iframeContainerElement, iframeId, {
            popupId: 'kofi-popup-iframe',
            popupIframeContainerIdSuffix: 'popup-iframe-container'
        }, { maxHeight: 690, minHeight: 400, });
        widgetPageLoadInitiatedStates.push([desktopDonateButton, false]);
        var mobileDonateButton = attachDonateButton(mobiIframeContainerElement, mobiIframeId, {
            popupId: 'kofi-popup-iframe-mobi',
            popupIframeContainerIdSuffix: 'popup-iframe-container-mobi'
        }, { maxHeight: 690, minHeight: 350 });
        widgetPageLoadInitiatedStates.push([mobileDonateButton, false]);

        // Already create the widget popup iframe (hidden)
        insertPopupHtmlIntoBody(desktopDonateButton, {
            popupId: 'kofi-popup-iframe',
            popupClass: 'floating-chat-kofi-popup-iframe',
            noticeClass: 'floating-chat-kofi-popup-iframe-notice',
            closerClass: 'floating-chat-kofi-popup-iframe-closer',
            popupIframeContainerClass: 'floating-chat-kofi-popup-iframe-container',
            popupIframeContainerIdSuffix: 'popup-iframe-container',
            popuupKofiIframeHeightOffset: 42
        }, parentElementId);

        insertPopupHtmlIntoBody(mobileDonateButton, {
            popupId: 'kofi-popup-iframe-mobi',
            popupClass: 'floating-chat-kofi-popup-iframe-mobi',
            noticeClass: 'floating-chat-kofi-popup-iframe-notice-mobi',
            closerClass: 'floating-chat-kofi-popup-iframe-closer-mobi',
            popupIframeContainerClass: 'floating-chat-kofi-popup-iframe-container-mobi',
            popupIframeContainerIdSuffix: 'popup-iframe-container-mobi',
            popuupKofiIframeHeightOffset: 100
        }, parentElementId);
    };

    function activateKofiIframe(iframeId, selectors, heightLimits) {

        var iframeContainerElement = document.getElementById(iframeId).contentDocument;

        const donateButton = iframeContainerElement.getElementById(`${getButtonId()}`);
        const kofiIframeState = donateButton.classList.contains('closed') ? 'open' : 'close';
        toggleKofiIframe(iframeId, kofiIframeState, donateButton, selectors, heightLimits);
    };

    function updateClass(element, oldClass, newClass) {

        if (oldClass !== '') {
            element.classList.remove(oldClass);
        }

        if (newClass !== '') {
            element.classList.add(newClass);
        }
    };

    function slidePopupOpen(popup, finalHeight) {
        popup.style = `z-index:10000;width:328px!important;height: ${finalHeight}px!important; transition: height 0.5s ease, opacity 0.3s linear; opacity:1;`;
        document.getElementsByClassName("floating-chat-kofi-popup-iframe-notice-mobi")[0].style.display = "block";
        document.getElementsByClassName("floating-chat-kofi-popup-iframe-notice")[0].style.display = "block";
    };

    function closePopup(popup, donateButton) {
        // ar popup = document.getElementById(popupId);
        popup.style = 'height: 0px; width:0px; transition:height 0.3s ease 0s , width 1s linear,opacity 0.3s linear; opacity:0;';
        updateClass(donateButton, 'open', 'closed');
        document.getElementsByClassName("floating-chat-kofi-popup-iframe-notice-mobi")[0].style.display = "none";
        document.getElementsByClassName("floating-chat-kofi-popup-iframe-notice")[0].style.display = "none";
    }

    function insertPopupHtmlIntoBody(donateButton, selectors, parentElementId) {
        var popupId = _configManager.getValue(_myType, 'cssId') + `-${selectors.popupId}`;

        var popup = document.createElement('div');
        popup.id = popupId;
        popup.classList = selectors.popupClass;
        popup.style = `z-index:10000;height: 0px; width:0px; opacity: 0; transition: all 0.6s ease 0s;`;

        if (parentElementId) {
            document.getElementById(parentElementId).appendChild(popup);
        }
        else {
            document.body.appendChild(popup);
        }

        var notice = document.createElement('div');
        notice.classList = selectors.noticeClass;


        var noticeText = _configManager.getValue(_myType, 'notice.text');
        var pageId = _configManager.getValue(_myType, 'pageId', true);

        noticeText = noticeText.replace("%HANDLE%", pageId);
        handleLink = document.createElement('a');
        handleLink.setAttribute('href', "https://"+ noticeText);
        handleLink.setAttribute('target', "_blank");
        handleLink.setAttribute('class', 'kfds-text-is-link-dark');
        linkText = document.createTextNode(noticeText);
        handleLink.appendChild(linkText);
        notice.appendChild(handleLink);
        popup.appendChild(notice);

        var closer = document.createElement('div');
        var closerContent = document.createElement('span');

        closerContent.innerHTML = _configManager.getValue(_myType, 'closer', true);
        closer.appendChild(closerContent);
        closer.classList = selectors.closerClass;

        closer.addEventListener('click', function (event) {
            closePopup(popup, donateButton);
        });

        popup.appendChild(closer);

        var popupIFrameContainer = document.createElement('div');
        popupIFrameContainer.classList = selectors.popupIframeContainerClass;
        popupIFrameContainer.style = 'height:100%';
        popupIFrameContainer.id = popupId + selectors.popupIframeContainerIdSuffix;

        popup.appendChild(popupIFrameContainer);
    };

    function toggleKofiIframe(iframeId, state, donateButton, selectors, heightLimits) {

        var popupId = _configManager.getValue(_myType, 'cssId') + `-${selectors.popupId}`;
        var existingPopup = document.getElementById(popupId);

        if (state === 'open') {

            var iframeContainerParent = document.getElementById(iframeId).parentElement;;

            var finalHeight = window.innerHeight - (window.innerHeight - iframeContainerParent.offsetTop) - 60;
            //console.log('final height 1:' + finalHeight);
            if (finalHeight > heightLimits.maxHeight) {
                finalHeight = heightLimits.maxHeight;
            } else if (finalHeight < heightLimits.minHeight) {
                finalHeight = heightLimits.minHeight;
            }
            //console.log('final height 2:' + finalHeight);
            var widgetPageLoadStateIndex = widgetPageLoadInitiatedStates.findIndex(function (s) { return s[0] == donateButton; });
            // var widgetPageLoadState = widgetPageLoadInitiatedStates.find(function(s) { return s[0] == donateButton; });// 
            var widgetPageLoadInitiated = widgetPageLoadInitiatedStates[widgetPageLoadStateIndex][1];
            if (!widgetPageLoadInitiated) {
                var popupIFrameContainerId = popupId + selectors.popupIframeContainerIdSuffix;
                _utils.loadKofiIframe(_configManager.getValue(_myType, 'pageId', true), popupIFrameContainerId, 'width: 100%; height: 98%;');
                widgetPageLoadInitiatedStates[widgetPageLoadStateIndex] = [donateButton, true];
            }

            slidePopupOpen(existingPopup, finalHeight);

            updateClass(donateButton, 'closed', 'open');

            closeButtonActionBlocked = true;
            setTimeout(function () {
                closeButtonActionBlocked = false;
            }, 1000);

        }
    };

    var getHtml = function () {

        var donateButtonImage = _configManager.getValue(_myType, 'donatebutton.image');
        var donateButtonBackgroundColor = _configManager.getValue(_myType, 'donateButton.background-color');
        var donateButtonCTAText = _configManager.getValue(_myType, 'donateButton.text');
        var donateButtonTextColor = _configManager.getValue(_myType, 'donateButton.text-color');
        var body = '<style> .hiddenUntilReady { display: none; } </style>' +
            `<div id="${getButtonId()}" class="hiddenUntilReady closed floatingchat-donate-button" style="z-index:10000; background-color: ${donateButtonBackgroundColor};">` +
            `<img id="${getButtonImageId()}" src="${donateButtonImage}" class="kofiimg" data-rotation="0" />` +
            `<span style="margin-left: 8px; color:${donateButtonTextColor}">${donateButtonCTAText}</span>`
        '</div>';
        return body;
    };

    return {
        getHtml: getHtml,
        write: write
    }
};

var kofiWidgetOverlayConstants = kofiWidgetOverlayConstants || {
    optionKeys: {
        root: 'root',
        widgetType: 'type',
        pageId: 'pageId',
        ctaText: 'ctaText',
        donateButtonStyle: 'donateButtonStyle',
        ctaTextStyle: 'ctaTextStyle',
        cssId: 'cssid'
    },
    //kofiRoot: 'http://localhost:55640/',
    kofiRoot: 'https://ko-fi.com/',
    paymentModalId: 'paymentModal'
};

var kofiWidgetOverlayUtilities = kofiWidgetOverlayUtilities || function () {

    const uuidv4 = function () {
        return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
            (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
        )
    };

    const debounce = function (debounceRef, callback) {

        if (debounceRef === null) {
            debounceRef = setTimeout(function () {
                clearTimeout(debounceRef);
                debounceRef = null;
                callback();
            }, 100);
        }
    };

    const loadKofiIframe = function (pageId, parentElementId, iframeStyle) {

        var _iframeLoading = false;
        var _iframeDebounce = null;
        var _showFeed = false;

        const tryLoad = function () {

            if (!_iframeLoading) {

                _iframeLoading = true;
                let url = kofiWidgetOverlayConstants.kofiRoot + pageId + '/?hidefeed=true&widget=true&embed=true';
                if (_showFeed) {
                    url = kofiWidgetOverlayConstants.kofiRoot + pageId + '/?widget=true&embed=true';
                }
                const iframe = document.createElement('iframe');
                const parentElement = document.getElementById(parentElementId);

                iframe.src = url;
                iframe.style = iframeStyle;
                parentElement.appendChild(iframe);

            } else {
                debounce(_iframeDebounce, tryLoad)
            }
        };

        tryLoad();
    };

    const getWindowHeightRatio = function () {
        return (window.outerHeight / 100);
    };

    const getWindowWidthRatio = function () {
        return (window.outerWidth / 100);
    };

    const mergeOptions = function (optionSetA, optionSetB) {

        for (var property in optionSetA) {
            if (optionSetA.hasOwnProperty(property)) {
                optionSetA[property] = optionSetB[property] !== undefined ? optionSetB[property] : optionSetA[property];
            }
        }
    };

    const getConfigManager = function (config) {

        return new function () {

            var _tokens = [];

            const getValue = function (overlayType, key, isCore) {

                const coreElement = isCore ? '.core' : '';
                const configKey = `${overlayType}${coreElement}.${key}`;
                if (config[configKey] !== undefined) {
                    var configdata = config[configKey];

                    if (_tokens.length > 0) {
                        _tokens.forEach(t => {
                            configdata = configdata.replace(t.token, t.value);
                        });
                    }

                    return configdata;
                }

                return '';
            };

            const setToken = function (token, value) {
                _tokens.push({ token: token, value: value });
            };

            const clearTokens = function () {
                _tokens = [];
            };

            return {
                getValue: getValue,
                setToken: setToken,
                clearTokens: clearTokens
            }
        };
    };

    const loadStyleSheet = function (styleSheetHref, targetDocument) {

        var docHead = targetDocument.head;
        if (!docHead) {
            docHead = targetDocument.createElement('head');
            targetDocument.prepend(docHead);
        }

        var styleSheet = targetDocument.querySelectorAll('[href="' + styleSheetHref + '"]')
        if (styleSheet.length === 0) {

            var sslink = targetDocument.createElement('link');
            sslink.href = styleSheetHref;
            sslink.rel = 'stylesheet';
            sslink.type = 'text/css';
            docHead.append(sslink);
        }
    };

    return {
        uuidv4: uuidv4,
        debounce: debounce,
        loadKofiIframe: loadKofiIframe,
        getWindowHeightRatio: getWindowHeightRatio,
        getWindowWidthRatio: getWindowWidthRatio,
        mergeOptions: mergeOptions,
        getConfigManager: getConfigManager,
        loadStyleSheet: loadStyleSheet
    }
};

var kofiWidgetOverlay = kofiWidgetOverlay || (function () {

    const _utils = new kofiWidgetOverlayUtilities();
    var isFirstRender = true;
    var parentButtonWrapperId = null;

    var _root = '';
    var _buildStrategy = {
        'floating-chat': {
            src: _root + 'kofi-widget-overlay-floating-chat-builder.js',
            write: function (parentId, config, utils) { return new kofiWidgetOverlayFloatingChatBuilder(config, utils).write(parentId); },
            getBody: function (config, utils) { return new kofiWidgetOverlayFloatingChatBuilder(config, utils).getHtml(); },
            id: 'kofi-widget-overlay-ribbon-builder'
        },
    };

    function getBuilder(widgetType) {

        var buildStrategy = _buildStrategy[widgetType] === undefined ? 'empty' : widgetType;
        var builder = _buildStrategy[buildStrategy];

        return builder;
    };

    const doWrite = function (builder, instanceId, config) {

        var finalConfig = JSON.parse(JSON.stringify(kofiWidgetOverlayConfig));

        _utils.mergeOptions(finalConfig, config);
        builder.write(instanceId, finalConfig, _utils);
    };

    const setConfigDefaults = function (config, widgetType, pId, instanceId) {

        config[widgetType + '.core.pageId'] = pId;
        config[widgetType + '.cssId'] = config[widgetType + '.cssId'] !== undefined && config[widgetType + '.cssId'] !== '' ? config[widgetType + '.cssId'] : instanceId;
        config[widgetType + '.stylesheets'] = config[widgetType + '.stylesheets'] !== undefined ? config[widgetType + '.stylesheets'] : '["https://fonts.googleapis.com/css?family=Nunito:400,700,800&display=swap"]';

        return config;
    }

    const draw = function (pId, config, containerId) {
        if (isFirstRender) {
            parentButtonWrapperId = 'kofi-widget-overlay-' + _utils.uuidv4();

            if (containerId != null) {
                document.getElementById(containerId).innerHTML += `<div id="${parentButtonWrapperId}"></div>`;
            }
            else {
                var div = document.createElement('div');
                div.setAttribute("id", parentButtonWrapperId);
                document.body.appendChild(div);
            }
            isFirstRender = false;
        }

        var widgetType = config[kofiWidgetOverlayConstants.optionKeys.widgetType];
        config = setConfigDefaults(config, widgetType, pId, parentButtonWrapperId);

        var builder = getBuilder(widgetType);
        if (containerId != null) {
            doWrite(builder, containerId, config);
        }
        else { doWrite(builder, parentButtonWrapperId, config); }
    };

    return {
        draw: draw,
    }
}());