Panorama Helper d3.ru

Panorama helper for d3.ru

// ==UserScript==
// @name         Panorama Helper d3.ru
// @namespace    d3.ru
// @version      1.007
// @description  Panorama helper for d3.ru
// @author       Anton aka RichDad
// @match        https://*.d3.ru/*
// @match        https://d3.ru/edit/*
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';

    const PANORAMA_PROTOCOL = 'panorama://';
    const TYPE_POST = 'post';
    const TYPE_COMMENT = 'comment';

    const PANORAMA_BUTTON_CLASS = 'panorama-helper-button0';
    const PANORAMA_SHOWCASE_CLASS = 'panorama-helper-showcase0';

    const IFRAME_SRC = 'https://cdn.pannellum.org/2.5/pannellum.htm#panorama=${img}&${params}';

    // noinspection JSUnresolvedVariable,JSUnresolvedFunction
    let version = (typeof GM_info == 'function' ? GM_info().script.version :
        (typeof GM_info == 'object' ? GM_info.script.version : '?'));

    // noinspection JSUnusedGlobalSymbols
    let Helper = {
        log: function (...args) {
            if (console) console.log(...args);
        },
        debug: function (...args) {
            if (console) console.debug(...args);
        },
        error: function (...args) {
            if (console) {
                const stack = (new Error).stack.split("\n");
                let caller_line = stack.length > 3 ? stack[3] : stack.pop();
                let index = caller_line.indexOf("at ");
                let clean = caller_line.slice(index + 3, caller_line.length);
                console.log('[' + clean + ']', ...args);
            }
        },
        arraysIntersect: function (haystack, arr) {
            return arr.some(function (v) {
                return haystack.indexOf(v) >= 0;
            });
        }
    };

    let PanoramaHelper = {
        isDeveloperMode: true,
        hasClass: function (element, className) {
            if (element && element.className) {
                let classes = element.className.split(' ');
                return classes.indexOf(className) > -1;
            }
            return false;
        },
        hasParentWithAnyClassOf: function (element, classname_arr) {
            if (!element) return false;
            if (element.className) {
                let classes = element.className.split(' ');
                if (Helper.arraysIntersect(classes, classname_arr)) {
                    return true;
                }
            }

            return element.parentNode && PanoramaHelper.hasParentWithAnyClassOf(element.parentNode, classname_arr);
        },
        getParentWithClass: function (element, classname) {
            if (!element) return null;
            if (element.className) {
                let classes = element.className.split(' ');
                if (classes.indexOf(classname) >= 0) {
                    return element;
                }
            }
            return element.parentNode ? PanoramaHelper.getParentWithClass(element.parentNode, classname) : null;
        },
        paramsToUri: function (params) {
            let paramParts = [];
            for (let paramName in params) {
                if (params.hasOwnProperty(paramName)) {
                    paramParts.push(paramName + '=' + params[paramName]);
                }
            }
            return paramParts.join('&');
        },
        prepareIframe: function (params) {
            let iframe = document.createElement('iframe');
            iframe.setAttribute('width', params.w);
            iframe.setAttribute('height', params.h);
            iframe.setAttribute('style', 'border-style:none;');
            iframe.setAttribute('allowfullscreen', '');
            let src = IFRAME_SRC.replace(/\${params}/, PanoramaHelper.paramsToUri({
                haov: params.haov,
                vaov: params.vaov,
                autoLoad: params.autoLoad,
                vOffset: params.vOffset
            })).replace(/\${img}/, params.img);
            iframe.setAttribute('src', src);
            return iframe;
        },
        parsePanoramaHref: function (href) {
            let result = {
                autoLoad: 'true',
                vOffset: 0
            };
            if (href.indexOf(PANORAMA_PROTOCOL) === 0) {
                href = href.substr(PANORAMA_PROTOCOL.length);
                href = href.toLocaleLowerCase();
                href = href.replace(/%20/g, '+');
                const parts = href.replace(/[+.,\/#!$%^&*;:{}=\-_`~()]/g, " ").split(/\s+/);

                // parse TYPE
                if (parts.length) {
                    if (parts[0] === 'post') {
                        result.type = TYPE_POST;
                        parts.shift();
                    } else if (parts[0] === 'comment') {
                        result.type = TYPE_COMMENT;
                        parts.shift();
                    } else {
                        result.type = TYPE_COMMENT;
                    }

                    // parse HAOV
                    if (parts.length) {
                        let haov = parseInt(parts.shift());
                        if (!isNaN(haov) && haov > 0) {
                            result.haov = haov;

                            // parse VAOV
                            if (parts.length) {
                                let vaov = parseInt(parts.shift());
                                if (!isNaN(vaov) && vaov > 0) {
                                    result.vaov = vaov;

                                    // parse vOffset
                                    if (parts.length) {
                                        let vOffset = parseInt(parts.shift());
                                        if (!isNaN(vOffset)) {
                                            result.vOffset = vOffset;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return result;
        },
        addDimensions: function (link, params) {
            let result = Object.assign(Object.create(null), params);
            params.w = 800;
            params.h = 600;
            if (params.type === TYPE_POST) {
                // TODO
            } else if (params.type === TYPE_COMMENT) {
                // TODO
            }
            return result;
        },
        addPostSelector: function (link, params) {
            let result = Object.assign(Object.create(null), params);
            if (PanoramaHelper.hasParentWithAnyClassOf(link, ['b-post__body', 'post_body', 'b-post-cut__post-cut'])) {
                result.type = TYPE_POST;
            }
            return result;
        },
        getPreviousComment: function (link) {
            let parent = link;
            while (parent.parentElement) {
                parent = parent.parentElement;
                if (PanoramaHelper.hasClass(parent, 'b-comment') &&
                    !PanoramaHelper.hasClass(parent, 'b-comment_mode_root')) {
                    return parent;
                }
            }
            return null;
        },
        addImage: function (link, params) {
            let result = Object.assign(Object.create(null), params);
            result.img = '?';
            if (params.type === TYPE_POST) {
                // SEARCH UP
                let post = PanoramaHelper.getParentWithClass(link, 'b-post__body');
                if (post) {
                    let post_image = post.querySelector('img[src]');
                    if (post_image) {
                        result.img = post_image.getAttribute('src');
                    }
                } else {
                    let post_small = PanoramaHelper.getParentWithClass(link, 'b-post-cut');
                    if (post_small) {
                        let post_small_image = post_small.querySelector('img[src]');
                        if (post_small_image) {
                            result.img = post_small_image.getAttribute('src');
                        }
                    } else {
                        let post_feed = PanoramaHelper.getParentWithClass(link, 'post_body');
                        if (post_feed) {
                            let post_feed_image = post_feed.querySelector('img[src]');
                            if (post_feed_image) {
                                result.img = post_feed_image.getAttribute('src');
                            }
                        } else {
                            // SEARCH DOWN
                            let post_image = document.querySelector('.b-post__body img[src]');
                            if (post_image) {
                                result.img = post_image.getAttribute('src');
                            }
                        }
                    }
                }
            } else if (params.type === TYPE_COMMENT) {
                let comment_image = link.parentElement.querySelector('img');
                if (comment_image) {
                    result.img = comment_image.getAttribute('src');
                } else {
                    let previous_comment = PanoramaHelper.getPreviousComment(link);
                    while (previous_comment) {
                        let comment_image = previous_comment.parentElement.querySelector('img');
                        if (comment_image) {
                            result.img = comment_image.getAttribute('src');
                            break;
                        }
                        previous_comment = PanoramaHelper.getPreviousComment(previous_comment);
                    }
                }
            }
            return result;
        },
        convertLinkToButton: function (link) {
            let linkHref = link.getAttribute('href');

            let params = PanoramaHelper.parsePanoramaHref(linkHref);
            params = PanoramaHelper.addPostSelector(link, params);
            params = PanoramaHelper.addDimensions(link, params);
            params = PanoramaHelper.addImage(link, params);

            let div = document.createElement('div');

            let button = document.createElement('button');
            button.innerText = 'Panorama (' + 
                params.type.toUpperCase() + ' ' + params.haov + '/' + params.vaov + (params.vOffset ? '/' + params.vOffset : '') + ')';
            button.onclick = function () {
                let buttonDiv = this.parentElement;
                let w = buttonDiv.offsetWidth;
                let h = Math.floor(w * 3 / 4);
                params.w = w;
                params.h = h;

                let iframe = PanoramaHelper.prepareIframe(params);
                buttonDiv.removeChild(this);
                buttonDiv.appendChild(iframe);
            };
            div.appendChild(button);

            return div;
        },
        appendLinkToButton: function (link) {
            let div = PanoramaHelper.convertLinkToButton(link);
            let linkParent = link.parentElement;
            linkParent.removeChild(link);
            linkParent.appendChild(div);
        },
        checkNewComment: function (commentBody, text) {
            if (!commentBody) return;

            let showcase = commentBody.querySelector('div.' + PANORAMA_SHOWCASE_CLASS);
            if (showcase) {
                showcase.parentElement.removeChild(showcase);
            }

            showcase = document.createElement('div');
            showcase.className = PANORAMA_SHOWCASE_CLASS;
            let textAppended = false;

            let textLines = text.split("\n");
            for (let textLine of textLines) {
                let line = textLine.trim();
                if (line.indexOf(PANORAMA_PROTOCOL) === 0) {
                    if (!textAppended) {
                        showcase.innerText = 'Panorama example:';
                        textAppended = true;
                    }
                    let a = document.createElement('a');
                    a.href = line;
                    showcase.append(a);
                }
            }
            commentBody.append(showcase);
        },
        hasPanoramaLinks: function (element) {
            if (element) {
                let text = element.innerText;
                let textLines = text.split("\n");
                for (let textLine of textLines) {
                    let line = textLine.trim();
                    if (line.indexOf(PANORAMA_PROTOCOL) === 0) {
                        return true;
                    }
                }
            }
            return false;
        },
        developerMode: function () {
            let commentButtons = document.querySelectorAll("span.b-comment__save-button");
            for (let commentButtonContatiner of commentButtons) {
                if (commentButtonContatiner.offsetParent !== null) { // it is visible
                    let commentBody = PanoramaHelper.getParentWithClass(commentButtonContatiner, 'b-comment__body');
                    let wysiwygEditor = commentBody.querySelector('div[contenteditable=true][id^=b-wysiwyg-editor-]');
                    if (!PanoramaHelper.hasPanoramaLinks(wysiwygEditor)) continue;

                    let panoramaButton = commentButtonContatiner.querySelector('div.' + PANORAMA_BUTTON_CLASS);
                    if (!panoramaButton) {
                        let buttonDiv = document.createElement('div');
                        buttonDiv.className = "b-button b-button_size_s b-button_mode_default b-button_color_yellow b-button_icon_false b-button_empty_false b-button_disabled_false " + PANORAMA_BUTTON_CLASS;
                        buttonDiv.innerText = "Panorama example"
                        buttonDiv.onclick = () => {
                            if (commentBody) {
                                if (wysiwygEditor) {
                                    let text = wysiwygEditor.innerText;
                                    PanoramaHelper.checkNewComment(commentBody, text);
                                } else {
                                    Helper.debug('wysiwygEditor?')
                                }
                            } else {
                                Helper.debug('commentBody?')
                            }
                        };
                        commentButtonContatiner.append(" ");
                        commentButtonContatiner.append(buttonDiv);
                    }
                }
            }
        },
        makeButtons: function () {
            let panoramaLinks = document.querySelectorAll("div:not(.b-wysiwyg__editor) > a[href^='" + PANORAMA_PROTOCOL + "']");
            if (panoramaLinks && panoramaLinks.length) {
                for (let i = 0; i < panoramaLinks.length; i++) {
                    PanoramaHelper.appendLinkToButton(panoramaLinks[i]);
                }
            }
            if (PanoramaHelper.isDeveloperMode) {
                PanoramaHelper.developerMode();
            }
        },
        init: function () {
            PanoramaHelper.makeButtons();
            setInterval(PanoramaHelper.makeButtons, 1000);
            Helper.log('[Panorama Helper] Started v.' + version);
        }
    };

    PanoramaHelper.init();

    // noinspection JSUnresolvedVariable
    if (typeof unsafeWindow !== 'undefined') {
        // noinspection JSUnresolvedVariable
        unsafeWindow.PanoramaHelper = PanoramaHelper;
    }

})();