4chan Image Viewer

Opens current thread Images in 4chan into a popup viewer, tested in Tampermonkey

// ==UserScript==
// @name       4chan Image Viewer
// @namespace  IdontKnowWhatToDoWithThis
// @description Opens current thread Images in 4chan into a popup viewer, tested in Tampermonkey
// @match   *://*.4chan.org/*/res/*
// @match   *://*.4chan.org/*/thread/*
// @match   *://*.4channel.org/*/thread/*
// @version 8.4
// @copyright  2019+, Nicholas Perkins
// @source https://github.com/nicholas-s-perkins/4chanImageViewer
// ==/UserScript==
"use strict";
var Viewer;
(function (Viewer) {
    /**
     * Didn't want to use any external libraries.  This is my handy library for dealing with the DOM
     */
    var DomUtil = /** @class */ (function () {
        function DomUtil(obj) {
            this._elements = [];
            this._listeners = [];
            if (obj) {
                if (obj instanceof NodeList) {
                    for (var i = 0; i < obj.length; ++i) {
                        this._elements.push(obj[i]);
                    }
                }
                else {
                    this._elements.push(obj);
                }
            }
        }
        Object.defineProperty(DomUtil.prototype, "elementList", {
            get: function () {
                return this._elements;
            },
            enumerable: true,
            configurable: true
        });
        DomUtil.prototype.concat = function (collection) {
            if (collection instanceof DomUtil) {
                this._elements = this._elements.concat(collection._elements);
            }
            else {
                this._elements = this._elements.concat(DomUtil.formatNodeList(collection));
            }
            return this;
        };
        /** Adds a click handler */
        DomUtil.prototype.on = function (handler, func) {
            var _this = this;
            var handlers = handler.split(' ');
            this.each(function (element) {
                for (var _i = 0, handlers_1 = handlers; _i < handlers_1.length; _i++) {
                    var handler_1 = handlers_1[_i];
                    _this._listeners.push(new Listener(element, handler_1, func));
                    element.addEventListener(handler_1, func, false);
                }
            });
            return this;
        };
        DomUtil.prototype.appendTo = function (obj) {
            if (typeof obj === 'string') {
                DomUtil.get(obj).append(this);
            }
            else if (obj instanceof DomUtil) {
                obj.append(this);
            }
            else {
                new DomUtil(obj).append(this);
            }
            return this;
        };
        DomUtil.prototype.off = function (handlerType) {
            var remaining = [];
            for (var _i = 0, _a = this._listeners; _i < _a.length; _i++) {
                var listener = _a[_i];
                if (handlerType == null || listener.type === handlerType) {
                    listener.element.removeEventListener(listener.type, listener.func);
                }
                else {
                    remaining.push(listener);
                }
            }
            this._listeners = remaining;
            return this;
        };
        DomUtil.prototype.remove = function () {
            for (var _i = 0, _a = this._elements; _i < _a.length; _i++) {
                var element = _a[_i];
                if (element.parentElement) {
                    element.parentElement.removeChild(element);
                }
            }
            return this;
        };
        DomUtil.prototype.prepend = function (obj) {
            for (var _i = 0, _a = this._elements; _i < _a.length; _i++) {
                var thisElement = _a[_i];
                for (var _b = 0, _c = obj._elements; _b < _c.length; _b++) {
                    var objElement = _c[_b];
                    if (thisElement.parentElement) {
                        thisElement.parentElement.insertBefore(objElement, thisElement);
                    }
                }
            }
            return this;
        };
        DomUtil.prototype.append = function (obj) {
            if (typeof obj === 'string') {
                this.each(function (element) {
                    element.insertAdjacentHTML('beforeend', obj);
                });
            }
            else if (obj instanceof DomUtil) {
                for (var _i = 0, _a = this._elements; _i < _a.length; _i++) {
                    var element = _a[_i];
                    for (var _b = 0, _c = obj._elements; _b < _c.length; _b++) {
                        var objEle = _c[_b];
                        element.appendChild(objEle);
                    }
                }
            }
            else {
                for (var _d = 0, _e = this._elements; _d < _e.length; _d++) {
                    var element = _e[_d];
                    element.appendChild(obj);
                }
            }
            return this;
        };
        DomUtil.prototype.empty = function () {
            this.each(function (element) {
                while (element.firstChild) {
                    element.removeChild(element.firstChild);
                }
            });
            return this;
        };
        DomUtil.prototype.scrollToTop = function () {
            if (this._elements.length > 0) {
                this._elements[0].scrollTop = 0;
            }
            return this;
        };
        DomUtil.prototype.focus = function () {
            if (this._elements.length > 0) {
                this._elements[0].focus();
            }
            return this;
        };
        Object.defineProperty(DomUtil.prototype, "tabIndex", {
            set: function (index) {
                if (this._elements.length > 0) {
                    this._elements[0].tabIndex = index;
                }
            },
            enumerable: true,
            configurable: true
        });
        DomUtil.prototype.setAttr = function (attr, value) {
            this.each(function (element) {
                element[attr] = value;
            });
            return this;
        };
        DomUtil.prototype.setText = function (text) {
            this.each(function (element) { return element.innerText = "" + text; });
            return this;
        };
        DomUtil.prototype.setStyle = function (styleConfig) {
            for (var _i = 0, _a = this._elements; _i < _a.length; _i++) {
                var element = _a[_i];
                for (var propName in styleConfig) {
                    // @ts-ignore
                    element.style[propName] = styleConfig[propName];
                }
            }
            return this;
        };
        DomUtil.prototype.setData = function (data) {
            var _loop_1 = function (element) {
                Object.keys(data).forEach(function (propName) {
                    element.dataset[propName] = data[propName];
                });
            };
            for (var _i = 0, _a = this._elements; _i < _a.length; _i++) {
                var element = _a[_i];
                _loop_1(element);
            }
            return this;
        };
        DomUtil.prototype.replaceWith = function (replacement) {
            var replaceEle = replacement._elements;
            this.each(function (element) {
                if (element.parentElement) {
                    for (var i = replaceEle.length - 1; i >= 0; i--) {
                        element.parentElement.insertBefore(replaceEle[i], element);
                    }
                    element.parentElement.removeChild(element);
                }
            });
            return this;
        };
        DomUtil.prototype.html = function (html) {
            if (typeof html === 'string') {
                for (var _i = 0, _a = this._elements; _i < _a.length; _i++) {
                    var element = _a[_i];
                    element.innerHTML = html;
                }
            }
            else {
                this.each(function (element) {
                    DomUtil.get(element).remove();
                });
                this.append(html);
            }
            return this;
        };
        Object.defineProperty(DomUtil.prototype, "length", {
            get: function () {
                return this._elements.length;
            },
            enumerable: true,
            configurable: true
        });
        Object.defineProperty(DomUtil.prototype, "id", {
            get: function () {
                return this._elements.length > 0 ? this._elements[0].id : null;
            },
            enumerable: true,
            configurable: true
        });
        Object.defineProperty(DomUtil.prototype, "clientHeight", {
            get: function () {
                return this._elements.length > 0 ? this._elements[0].clientHeight : 0;
            },
            enumerable: true,
            configurable: true
        });
        Object.defineProperty(DomUtil.prototype, "clientWidth", {
            get: function () {
                return this._elements.length > 0 ? this._elements[0].clientWidth : 0;
            },
            enumerable: true,
            configurable: true
        });
        Object.defineProperty(DomUtil.prototype, "offsetHeight", {
            get: function () {
                return this._elements.length > 0 ? this._elements[0].offsetHeight : 0;
            },
            enumerable: true,
            configurable: true
        });
        Object.defineProperty(DomUtil.prototype, "offsetWidth", {
            get: function () {
                return this._elements.length > 0 ? this._elements[0].offsetWidth : 0;
            },
            enumerable: true,
            configurable: true
        });
        Object.defineProperty(DomUtil.prototype, "tagName", {
            get: function () {
                return this._elements.length > 0 ? this._elements[0].tagName : null;
            },
            enumerable: true,
            configurable: true
        });
        DomUtil.prototype.hasClass = function (className) {
            return this._elements.length > 0 ? this._elements[0].classList.contains(className) : false;
        };
        DomUtil.prototype.getAttr = function (attr) {
            if (this._elements.length > 0) {
                var ele = this._elements[0];
                return ele[attr];
            }
            else {
                return null;
            }
        };
        DomUtil.prototype.lightClone = function () {
            var newCollection = new DomUtil();
            this.each(function (element) {
                var newEle = document.createElement(element.tagName);
                newEle.className = element.className;
                newEle.innerHTML = element.innerHTML;
                newCollection._elements.push(newEle);
            });
            return newCollection;
        };
        DomUtil.prototype.addClass = function () {
            var classNames = [];
            for (var _i = 0; _i < arguments.length; _i++) {
                classNames[_i] = arguments[_i];
            }
            this.each(function (element) {
                element.classList.add.apply(element.classList, classNames);
            });
            return this;
        };
        DomUtil.prototype.removeClass = function () {
            var classNames = [];
            for (var _i = 0; _i < arguments.length; _i++) {
                classNames[_i] = arguments[_i];
            }
            this.each(function (element) {
                element.classList.remove.apply(element.classList, classNames);
            });
            return this;
        };
        DomUtil.prototype.each = function (func) {
            for (var i = 0; i < this._elements.length; ++i) {
                func(this._elements[i], i);
            }
            return this;
        };
        /** Finds all sub-elements matching the queryString */
        DomUtil.prototype.find = function (queryString) {
            var collection = new DomUtil();
            for (var _i = 0, _a = this._elements; _i < _a.length; _i++) {
                var element = _a[_i];
                collection.concat(element.querySelectorAll(queryString));
            }
            return collection;
        };
        Object.defineProperty(DomUtil.prototype, "exists", {
            get: function () {
                return this._elements.length > 0;
            },
            enumerable: true,
            configurable: true
        });
        /** because screw node lists */
        DomUtil.formatNodeList = function (nodes) {
            var arr = [];
            for (var i = 0; i < nodes.length; ++i) {
                arr.push(nodes[i]);
            }
            return arr;
        };
        DomUtil.get = function (query) {
            if (typeof query === 'string') {
                switch (query) {
                    case 'body':
                        return new DomUtil(document.body);
                    case 'head':
                        return new DomUtil(document.head);
                    default:
                        var nodes = document.querySelectorAll(query);
                        return new DomUtil(nodes);
                }
            }
            else {
                return new DomUtil(query);
            }
        };
        DomUtil.getById = function (id) {
            var ele = document.getElementById(id);
            return new DomUtil(ele);
        };
        DomUtil.createElement = function (tagName, props) {
            var newEle = document.createElement(tagName);
            if (props) {
                Object.keys(props).forEach(function (propName) {
                    if (propName == "style") {
                        newEle.style.cssText = props.style.cssText;
                    }
                    else {
                        newEle[propName] = props[propName];
                    }
                });
            }
            return new DomUtil(newEle);
        };
        return DomUtil;
    }());
    Viewer.DomUtil = DomUtil;
    var Listener = /** @class */ (function () {
        function Listener(element, type, func) {
            this.type = type;
            this.func = func;
            this.element = element;
        }
        return Listener;
    }());
    Viewer.Listener = Listener;
})(Viewer || (Viewer = {}));
var Viewer;
(function (Viewer) {
    //IDs for important elements
    Viewer.VIEW_ID = "mainView";
    Viewer.IMG_ID = "mainImg";
    Viewer.CENTER_BOX_ID = "imageBox";
    Viewer.TOP_LAYER_ID = "viewerTopLayer";
    Viewer.IMG_WRAPPER_ID = 'mainImgWrapper';
    Viewer.TEXT_WRAPPER_ID = 'viewerTextWrapper';
    Viewer.STYLE_ID = 'viewerStyle';
    Viewer.MENU_ID = 'viewerBottomMenu';
    Viewer.LEFT_ARROW = 'previousImageButton';
    Viewer.RIGHT_ARROW = 'nextImageButton';
    Viewer.TOP_MENU_ID = 'viewerMenuHeader';
    Viewer.VIEWER_PAGE_DISPLAY = "viewerPageDisplay";
    Viewer.VIEWER_TOTAL_DISPLAY = "viewerTotalDisplay";
    Viewer.VIEWER_IMG_NAME_DISPLAY = "viewerNameDisplay";
    Viewer.STYLE_TEXT = "\n        div.reply.highlight,div.reply.highlight-anti{z-index:100 !important;position:fixed !important; top:1%;left:1%;}\n        body{overflow:hidden !important;}\n        #quote-preview{z-index:100;}\n        a.quotelink, div.viewerBacklinks a.quotelink{color:#5c5cff !important;}\n        a.quotelink:hover, div.viewerBacklinks a:hover{color:red !important;}\n        #" + Viewer.IMG_ID + "{display:block !important; margin:auto;max-width:100%;height:auto;-webkit-user-select: none;cursor:pointer;}\n        #" + Viewer.VIEW_ID + "{\n            background-color:rgba(0,0,0,0.9);\n            z-index:10;\n            position:fixed;\n            top:0;left:0;bottom:0;right:0;\n            overflow:auto;\n            text-align:center;\n            -webkit-user-select: none;\n        }\n        #" + Viewer.CENTER_BOX_ID + " {display:flex;align-items:center;justify-content:center;flex-direction: column;min-height:100%;}\n        #" + Viewer.IMG_WRAPPER_ID + " {width:100%;}\n        #" + Viewer.TOP_LAYER_ID + "{position:fixed;top:0;bottom:0;left:0;right:0;z-index:20;opacity:0;visibility:hidden;transition:all .25s ease;}\n        .viewerBlockQuote{color:white;}\n        #" + Viewer.TEXT_WRAPPER_ID + "{max-width:60em;display:inline-block; color:gray;-webkit-user-select: all;}\n        .bottomMenuShow{visibility:visible;}\n        #" + Viewer.MENU_ID + "{box-shadow: -1px -1px 5px #888888;font-size:20px;padding:5px;background-color:white;position:fixed;bottom:0;right:0;z-index:200;}\n        #" + Viewer.TOP_MENU_ID + "{font-size:20px;padding:5px;background-color:white;position:fixed;top:0;left:0;text-align:center;width:100%;color:black;z-index:200;}\n        .hideCursor{cursor:none !important;}\n        .hidden{visibility:hidden}\n        .displayNone{display:none;}\n        .pagingButtons{font-size:100px;color:white;text-shadow: 1px 1px 10px #27E3EB;z-index: 11;top: 50%;position: fixed;margin-top: -57px;width:100px;cursor:pointer;-webkit-user-select: none;}\n        .pagingButtons:hover{color:#27E3EB;text-shadow: 1px 1px 10px #000}\n        #" + Viewer.LEFT_ARROW + "{left:0;text-align:left;}\n        #" + Viewer.RIGHT_ARROW + "{right:0;text-align:right;}\n        @-webkit-keyframes flashAnimation{0%{ text-shadow: none;}100%{text-shadow: 0px 0px 5px blue;}}\n        .flash{-webkit-animation: flashAnimation 1s alternate infinite  linear;cursor:pointer;}\n        .disableClick, .disableClick a{pointer-events: none;}\n        ";
})(Viewer || (Viewer = {}));
var Viewer;
(function (Viewer) {
    //cookieInfo
    var INDEX_KEY = "imageBrowserIndexCookie";
    var THREAD_KEY = "imageBrowserThreadCookie";
    var WIDTH_KEY = "imageBrowserWidthCookie";
    var HEIGHT_KEY = "imageBrowserHeightCookie";
    //keycode object.  Better than remembering what each code does.
    var KEYS = { 38: 'up', 40: 'down', 37: 'left', 39: 'right', 27: 'esc', 86: 'v' };
    var BODY = Viewer.DomUtil.get(document.body);
    var WINDOW = Viewer.DomUtil.get(window);
    var UNSAFE_WINDOW = Viewer.DomUtil.get(typeof unsafeWindow === 'undefined' ? window : unsafeWindow);
    var MainView = /** @class */ (function () {
        function MainView(imagePostIndex) {
            var _this = this;
            this.postData = [];
            this.linkIndex = 0;
            /** Determines if pre-loading can happen*/
            this.canPreload = false;
            /** determines if height of the image should be fit */
            this.shouldFitHeight = false;
            this.lastMousePos = { x: 0, y: 0 };
            console.log("Building 4chan Image Viewer");
            var currentThreadId = Viewer.DomUtil.get('.thread').id;
            if (imagePostIndex != undefined) {
                this.linkIndex = imagePostIndex;
                MainView.setPersistentValue(INDEX_KEY, imagePostIndex);
            }
            //check if its the last thread opened, if so, remember where the index was.
            else if (MainView.getPersistentValue(THREAD_KEY) === currentThreadId) {
                var savedVal = MainView.getPersistentValue(INDEX_KEY);
                if (savedVal != undefined) {
                    this.linkIndex = parseInt(savedVal);
                }
                else {
                    this.linkIndex = 0;
                }
            }
            else {
                this.linkIndex = 0;
                MainView.setPersistentValue(INDEX_KEY, 0);
            }
            //set thread id
            MainView.setPersistentValue(THREAD_KEY, currentThreadId);
            //Create postData based on 4chan posts
            this.postData = Viewer.PostData.getImagePosts(true);
            if (this.linkIndex > (this.postData.length - 1)) {
                alert('Last saved image index is too large, a thread may have been deleted.  Index will be reset. ');
                this.linkIndex = 0;
                MainView.setPersistentValue(INDEX_KEY, 0);
            }
            //set shouldFit Height so image can know about it if it loads before menuInit()
            var isHeight = MainView.getPersistentValue(HEIGHT_KEY);
            this.shouldFitHeight = isHeight ? true : false;
            var menuHtml = "\n                <label><input id=\"" + WIDTH_KEY + "\" type=\"checkbox\" checked=\"checked\" />Fit Image to Width</label>\n                <span>|</span>\n                <label><input id=\"" + HEIGHT_KEY + "\" type=\"checkbox\" />Fit Image to Height</label>\n            ";
            var viewFrag = "\n                <style id=\"" + Viewer.STYLE_ID + "\">" + Viewer.STYLE_TEXT + "</style>\n                <div id=\"" + Viewer.TOP_MENU_ID + "\" class=\"hidden\">\n                  <div><span id=\"" + Viewer.VIEWER_PAGE_DISPLAY + "\"></span><span> of </span><span id=\"" + Viewer.VIEWER_TOTAL_DISPLAY + "\"></span></div>\n                  <div><span id=\"" + Viewer.VIEWER_IMG_NAME_DISPLAY + "\"></span></div>\n                </div>\n                <div id=\"" + Viewer.VIEW_ID + "\">\n                    <div id=\"" + Viewer.CENTER_BOX_ID + "\">\n                        <div id=\"" + Viewer.IMG_WRAPPER_ID + "\">\n                            <img id=\"" + Viewer.IMG_ID + "\" class=\"hideCursor\"/>\n                        </div>\n                        <div id=\"" + Viewer.TEXT_WRAPPER_ID + "\"></div>\n                    </div>\n                    <div id=\"" + Viewer.LEFT_ARROW + "\" class=\"pagingButtons hidden\"><span>&#9001;</span></div>\n                    <div id=\"" + Viewer.RIGHT_ARROW + "\" class=\"pagingButtons hidden\"><span>&#9002;</span></div>\n                </div>\n                <div id=\"" + Viewer.TOP_LAYER_ID + "\">&nbsp;</div>\n                <form id=\"" + Viewer.MENU_ID + "\" class=\"hidden\">" + menuHtml + "</form>\n            ";
            BODY.append(viewFrag);
            this.mainView = Viewer.DomUtil.getById(Viewer.VIEW_ID);
            this.centerBox = Viewer.DomUtil.getById(Viewer.CENTER_BOX_ID);
            this.mainImg = Viewer.DomUtil.getById(Viewer.IMG_ID);
            this.textWrapper = Viewer.DomUtil.getById(Viewer.TEXT_WRAPPER_ID);
            this.topLayer = Viewer.DomUtil.getById(Viewer.TOP_LAYER_ID);
            this.customStyle = Viewer.DomUtil.getById(Viewer.STYLE_ID);
            this.bottomMenu = Viewer.DomUtil.getById(Viewer.MENU_ID);
            this.leftArrow = Viewer.DomUtil.getById(Viewer.LEFT_ARROW);
            this.rightArrow = Viewer.DomUtil.getById(Viewer.RIGHT_ARROW);
            this.topMenu = Viewer.DomUtil.getById(Viewer.TOP_MENU_ID);
            this.pageDisplay = Viewer.DomUtil.getById(Viewer.VIEWER_PAGE_DISPLAY);
            this.totalDisplay = Viewer.DomUtil.getById(Viewer.VIEWER_TOTAL_DISPLAY);
            this.nameDisplay = Viewer.DomUtil.getById(Viewer.VIEWER_IMG_NAME_DISPLAY);
            //add handlers
            this.centerBox.on('click', function () {
                _this.confirmExit();
            });
            this.textWrapper.on('click', function (event) {
                _this.eventStopper(event);
            });
            this.bottomMenu.on('click', function () {
                _this.menuClickHandler();
            });
            this.leftArrow.on('click', function (event) {
                event.stopImmediatePropagation();
                _this.previousImg();
            });
            this.rightArrow.on('click', function (event) {
                event.stopImmediatePropagation();
                _this.nextImg();
            });
            //build first image/video tag
            this.changeData(0);
            //initialize menu
            this.menuInit();
            //start preloading to next image index
            this.canPreload = true;
            window.setTimeout(function () {
                _this.runImagePreloading(_this.linkIndex);
            }, 100);
            //some fixes for weird browser behaviors
            this.centerBox.setStyle({ outline: '0' });
            this.centerBox.tabIndex = 1;
            this.centerBox.focus();
            //add keybinding listener, unsafeWindow is used here instead because at least in Tampermonkey
            //the safe window can fail to remove event listeners.
            UNSAFE_WINDOW
                .on('keydown', function (event) {
                _this.arrowKeyListener(event);
            })
                .on('mousemove', function (event) {
                _this.menuWatcher(event);
            });
        }
        MainView.prototype.menuInit = function () {
            var _this = this;
            var menuControls = this.bottomMenu.find('input');
            menuControls.each(function (input) {
                var typedInput = input;
                var cookieValue = MainView.getPersistentValue(input.id);
                if (cookieValue === 'true') {
                    typedInput.checked = true;
                }
                else if (cookieValue === 'false') {
                    typedInput.checked = false;
                }
                typedInput.parentElement.classList.toggle('flash', typedInput.checked);
                switch (typedInput.id) {
                    case WIDTH_KEY:
                        _this.setFitToScreenWidth(typedInput.checked);
                        break;
                    case HEIGHT_KEY:
                        _this.setFitToScreenHeight(typedInput.checked);
                        break;
                }
            });
        };
        MainView.prototype.menuClickHandler = function () {
            var _this = this;
            var menuControls = this.bottomMenu.find('input');
            menuControls.each(function (ele) {
                var input = ele;
                switch (input.id) {
                    case WIDTH_KEY:
                        _this.setFitToScreenWidth(input.checked);
                        break;
                    case HEIGHT_KEY:
                        _this.setFitToScreenHeight(input.checked);
                        break;
                }
                input.parentElement.classList.toggle('flash', input.checked);
                MainView.setPersistentValue(input.id, input.checked);
            });
        };
        MainView.prototype.windowClick = function (event) {
            if (!this) {
                return;
            }
            event.preventDefault();
            event.stopImmediatePropagation();
            this.nextImg();
        };
        /* Event function for determining behavior of viewer keypresses */
        MainView.prototype.arrowKeyListener = function (event) {
            switch (KEYS[event.keyCode]) {
                case 'right':
                    this.nextImg();
                    break;
                case 'left':
                    this.previousImg();
                    break;
                case 'esc':
                    this.destroy();
                    break;
            }
        };
        /* preloads images starting with the index provided */
        MainView.prototype.runImagePreloading = function (index) {
            var _this = this;
            if (this && index < this.postData.length) {
                if (this.canPreload) {
                    //console.log('preloading: ' + index +' of '+(this.postData.length - 1) +' | '+ this.postData[index].imgSrc);
                    var loadFunc = function () {
                        _this.runImagePreloading(index + 1);
                    };
                    //have yet to figure out how to properly preload video, skip for now
                    if (this.postData[index].tagType === Viewer.TagType.VIDEO) {
                        window.setTimeout(loadFunc, 1);
                    }
                    else {
                        var newImage = document.createElement(this.postData[index].tagTypeName);
                        switch (this.postData[index].tagType) {
                            case Viewer.TagType.VIDEO:
                                newImage.oncanplaythrough = loadFunc;
                                break;
                            case Viewer.TagType.IMG:
                                newImage.onload = loadFunc;
                                break;
                        }
                        newImage.onerror = function () {
                            console.log("imageError");
                            _this.runImagePreloading(index + 1);
                        };
                        newImage.src = this.postData[index].imgSrc;
                    }
                }
            }
        };
        /* Sets the img and message to the next one in the list*/
        MainView.prototype.nextImg = function () {
            var _this = this;
            if (this.linkIndex === this.postData.length - 1) {
                this.topLayer.setStyle({
                    background: 'linear-gradient(to right,rgba(0,0,0,0) 90%,rgba(125,185,232,1) 100%)',
                    opacity: '.5',
                    visibility: 'visible'
                });
                window.setTimeout(function () {
                    _this.topLayer.setStyle({
                        opacity: '0',
                        visibility: 'hidden'
                    });
                }, 500);
            }
            else {
                this.changeData(1);
            }
        };
        /* Sets the img and message to the previous one in the list*/
        MainView.prototype.previousImg = function () {
            var _this = this;
            if (this.linkIndex === 0) {
                this.topLayer.setStyle({
                    background: 'linear-gradient(to left,rgba(0,0,0,0) 90%,rgba(125,185,232,1) 100%)',
                    opacity: '.5',
                    visibility: 'visible'
                });
                window.setTimeout(function () {
                    _this.topLayer.setStyle({ opacity: '0' });
                    window.setTimeout(function () {
                        _this.topLayer.setStyle({ visibility: 'hidden' });
                    }, 200);
                }, 500);
            }
            else {
                this.changeData(-1);
            }
        };
        MainView.prototype.changeData = function (delta) {
            MainView.cleanLinks();
            //ignore out of bounds
            var newIndex = this.linkIndex + delta;
            if (newIndex > this.postData.length - 1 || newIndex < 0) {
                return;
            }
            if (this.postData[newIndex].tagTypeName !== this.mainImg.tagName || delta === 0) {
                this.mainImg = this.replaceElement(this.mainImg, this.postData[newIndex].tagTypeName);
            }
            //console.log('Opening: "' + this.postData[this.linkIndex].imgSrc +'" at index ' + this.linkIndex);
            this.mainImg.setAttr('src', this.postData[newIndex].imgSrc);
            var nextLinks = this.postData[newIndex].linksContainer;
            var nextQuote = this.postData[newIndex].quoteContainer;
            this.textWrapper.empty();
            this.textWrapper.append(nextLinks);
            this.textWrapper.append(nextQuote);
            this.linkIndex = newIndex;
            this.mainView.scrollToTop();
            MainView.setPersistentValue(INDEX_KEY, this.linkIndex);
            //update menu info
            this.pageDisplay.setText(this.linkIndex + 1);
            this.totalDisplay.setText(this.postData.length);
            this.nameDisplay.setText(this.postData[newIndex].imgSrc);
        };
        MainView.cleanLinks = function () {
            var links = document.getElementsByClassName('quotelink');
            for (var i = 0; i < links.length; ++i) {
                links[i].dispatchEvent(new MouseEvent('mouseout'));
            }
        };
        MainView.prototype.replaceElement = function (element, newTagType) {
            var _this = this;
            var rawElement = element.elementList[0];
            var newElement = Viewer.DomUtil.createElement(newTagType, {
                id: element.id,
                className: rawElement.className,
                style: rawElement.style,
                autoplay: true,
                controls: false,
                loop: true
            });
            newElement
                .on('click', function (event) {
                event.stopPropagation();
                _this.nextImg();
            })
                .on('load', function () {
                _this.imageLoadHandler();
            })
                .on('progress', function (e) {
                //console.log(e);
            });
            element.prepend(newElement);
            element.remove();
            return newElement;
        };
        MainView.prototype.eventStopper = function (event) {
            event.stopPropagation();
            if (event.target.nodeName === 'A') {
                var confirmed = this.confirmExit('Exit Viewer to navigate to link?');
                if (!confirmed) {
                    event.preventDefault();
                }
            }
        };
        MainView.prototype.confirmExit = function (message) {
            var confirmed = window.confirm(message || 'Exit Viewer?');
            if (confirmed) {
                this.destroy();
            }
            return confirmed;
        };
        /* Removes the view and cleans up handlers*/
        MainView.prototype.destroy = function () {
            MainView.cleanLinks();
            UNSAFE_WINDOW.off();
            WINDOW.off();
            BODY.off();
            this.topLayer.remove();
            this.mainView.remove();
            this.customStyle.remove();
            this.bottomMenu.remove();
            BODY.setStyle({ overflow: 'auto' });
            this.canPreload = false;
        };
        /*Mouse-move Handler that watches for when menus should appear and mouse behavior*/
        MainView.prototype.menuWatcher = function (event) {
            var _this = this;
            var height_offset = window.innerHeight - this.bottomMenu.offsetHeight;
            var width_offset = window.innerWidth - this.bottomMenu.offsetWidth;
            var center = window.innerHeight / 2;
            var halfArrow = this.leftArrow.offsetHeight / 2;
            if (event.clientX >= width_offset && event.clientY >= height_offset) {
                this.bottomMenu.removeClass('hidden').addClass('bottomMenuShow');
                this.topMenu.removeClass('hidden').addClass('bottomMenuShow');
            }
            else if (this.bottomMenu.hasClass('bottomMenuShow')) {
                this.bottomMenu.removeClass('bottomMenuShow').addClass('hidden');
                this.topMenu.removeClass('bottomMenuShow').addClass('hidden');
            }
            if ((event.clientX <= (100) || event.clientX >= (window.innerWidth - 100)) &&
                (event.clientY <= (center + halfArrow) && event.clientY >= (center - halfArrow))) {
                this.rightArrow.removeClass('hidden');
                this.leftArrow.removeClass('hidden');
            }
            else {
                this.rightArrow.addClass('hidden');
                this.leftArrow.addClass('hidden');
            }
            //avoids chrome treating mouseclicks as mousemoves
            if (event.clientX !== this.lastMousePos.x && event.clientY !== this.lastMousePos.y) {
                //mouse click moves to next image when invisible
                this.mainImg.removeClass('hideCursor');
                window.clearTimeout(this.mouseTimer);
                BODY.off('click');
                BODY.removeClass('hideCursor');
                this.textWrapper.removeClass('disableClick');
                this.mainImg.removeClass('disableClick');
                this.centerBox.removeClass('disableClick');
                if (event.target.id === this.mainImg.id) {
                    //hide cursor if it stops, show if it moves
                    this.mouseTimer = window.setTimeout(function () {
                        _this.mainImg.addClass('hideCursor');
                        _this.textWrapper.addClass('disableClick');
                        _this.mainImg.addClass('disableClick');
                        _this.centerBox.addClass('disableClick');
                        BODY.addClass('hideCursor')
                            .on('click', function (event) {
                            _this.windowClick(event);
                        });
                    }, 200);
                }
            }
            this.lastMousePos.x = event.clientX;
            this.lastMousePos.y = event.clientY;
        };
        /*Stores a key value pair as a cookie*/
        MainView.setPersistentValue = function (key, value) {
            document.cookie = key + '=' + value + ';expires=Thu, 01 Jan 3000 00:00:00 UTC;domain=.4chan.org;path=/';
        };
        /* Retrieves a cookie value via its key*/
        MainView.getPersistentValue = function (key) {
            var cookieMatch = document.cookie.match(new RegExp(key + '\\s*=\\s*([^;]+)'));
            if (cookieMatch) {
                return cookieMatch[1];
            }
            else {
                return undefined;
            }
        };
        MainView.prototype.setFitToScreenHeight = function (shouldFitImage) {
            this.shouldFitHeight = shouldFitImage;
            //ignore if image has no height as it is likely not loaded.
            if (shouldFitImage && this.mainImg.getAttr('naturalHeight')) {
                this.fitHeightToScreen();
            }
            else {
                this.mainImg.setStyle({ maxHeight: '' });
            }
        };
        ;
        MainView.prototype.setFitToScreenWidth = function (shouldFitImage) {
            this.mainImg.setStyle({
                maxWidth: shouldFitImage ? '100%' : 'none'
            });
        };
        MainView.prototype.imageLoadHandler = function () {
            if (this.shouldFitHeight) {
                this.fitHeightToScreen();
            }
        };
        /* Fits image to screen height*/
        MainView.prototype.fitHeightToScreen = function () {
            //sets the changeable properties to the image's real size
            var height = this.mainImg.getAttr('naturalHeight');
            this.mainImg.setStyle({ maxHeight: (height + 'px') });
            //actually tests if it is too high including padding
            var heightDiff = (this.mainImg.clientHeight > height) ?
                this.mainImg.clientHeight - this.mainView.clientHeight :
                height - this.mainView.clientHeight;
            if (heightDiff > 0) {
                this.mainImg.setStyle({ maxHeight: (height - heightDiff) + 'px' });
            }
            else {
                this.mainImg.setStyle({ maxHeight: (height + 'px') });
            }
        };
        return MainView;
    }());
    Viewer.MainView = MainView;
})(Viewer || (Viewer = {}));
var Viewer;
(function (Viewer) {
    var TagType;
    (function (TagType) {
        TagType[TagType["IMG"] = 0] = "IMG";
        TagType[TagType["VIDEO"] = 1] = "VIDEO";
    })(TagType = Viewer.TagType || (Viewer.TagType = {}));
    var PostData = /** @class */ (function () {
        function PostData(imgSrc, quoteContainer, linksContainer, imageLink) {
            this.imgSrc = imgSrc;
            this.linksContainer = linksContainer;
            this.quoteContainer = quoteContainer;
            this.tagType = PostData.getElementType(imgSrc);
            this.imageLink = imageLink;
        }
        Object.defineProperty(PostData.prototype, "tagTypeName", {
            get: function () {
                return TagType[this.tagType];
            },
            enumerable: true,
            configurable: true
        });
        PostData.getElementType = function (src) {
            if (src.match(/\.(?:(?:webm)|(?:ogg)|(?:mp4))$/)) {
                return TagType.VIDEO;
            }
            else {
                return TagType.IMG;
            }
        };
        PostData.add4chanListenersToLinks = function (linkCollection) {
            linkCollection.find('.quotelink')
                .on('mouseover', Main.onThreadMouseOver)
                .on('mouseout', Main.onThreadMouseOut);
        };
        PostData.getImagePosts = function (asCopy) {
            var postData = [];
            var postFiles = Viewer.DomUtil.get('#delform').find('.postContainer');
            postFiles.each(function (post) {
                var _post = Viewer.DomUtil.get(post);
                var currentLinkTag = _post.find('.file .fileThumb');
                var currentLink = currentLinkTag.getAttr('href');
                if (!currentLink) {
                    return;
                }
                var currentPostBlock = _post.find('.postMessage');
                var currentPostBacklinks = _post.find('.backlink');
                var newPostBlock = currentPostBlock;
                var newBackLinks = currentPostBacklinks;
                if (asCopy) {
                    if (currentPostBlock.exists) {
                        newPostBlock = currentPostBlock.lightClone();
                        newPostBlock.addClass('viewerBlockQuote');
                        PostData.add4chanListenersToLinks(newPostBlock);
                    }
                    if (currentPostBacklinks.exists) {
                        newBackLinks = currentPostBacklinks.lightClone();
                        newBackLinks.addClass('viewerBacklinks');
                        PostData.add4chanListenersToLinks(newBackLinks);
                    }
                }
                postData.push(new PostData(currentLink, newPostBlock, newBackLinks, currentLinkTag));
            });
            return postData;
        };
        return PostData;
    }());
    Viewer.PostData = PostData;
})(Viewer || (Viewer = {}));
/// <reference path="../MetaData.ts"/>
/// <reference path="DomUtil.ts"/>
/// <reference path="Css.ts"/>
/// <reference path="MainView.ts"/>
/// <reference path="PostData.ts"/>
var Viewer;
(function (Viewer) {
    function main() {
        // ========= Build the main Button ========= //
        Viewer.DomUtil.createElement('button')
            .setStyle({ position: 'fixed', bottom: '0', right: '0', })
            .html("Open Viewer")
            .on('click', function () {
            new Viewer.MainView();
        })
            .appendTo(document.body);
        // ========= Build buttons for each image thumbnail ========= //
        var posts = Viewer.PostData.getImagePosts(false);
        var imagePostCount = 0;
        for (var _i = 0, posts_1 = posts; _i < posts_1.length; _i++) {
            var post = posts_1[_i];
            Viewer.DomUtil.createElement('button')
                .setStyle({
                display: 'inline',
                float: 'left',
                clear: 'both',
                fontSize: '11px',
                cursor: 'pointer'
            })
                .setData({
                postIndex: imagePostCount
            })
                .html('Open Viewer')
                .on('click', function (e) {
                e.preventDefault();
                e.stopPropagation();
                //make the viewer and put it on the window so we can clean it up later
                new Viewer.MainView(parseInt(this.dataset.postIndex));
            })
                .appendTo(post.imageLink);
            ++imagePostCount;
        }
    }
    Viewer.main = main;
})(Viewer || (Viewer = {}));
//run the module
Viewer.main();
//# sourceMappingURL=viewer.js.map