4chan Image Viewer

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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