MyAnimeList (MAL) Track Missing Relations

Allows to find missing relations and entries with wrong chapter/episode count

当前为 2017-01-02 提交的版本,查看 最新版本

// ==UserScript==
// @name        MyAnimeList (MAL) Track Missing Relations
// @namespace   https://greasyfork.org/users/7517
// @description Allows to find missing relations and entries with wrong chapter/episode count
// @icon        http://i.imgur.com/b7Fw8oH.png
// @version     7.9
// @author      akarin
// @include     /^https?:\/\/myanimelist\.net\/profile/
// @grant       none
// @noframes
// ==/UserScript==

/*jslint fudge, maxerr: 10, browser, devel, this, white, for, single */
/*global jQuery, window */

(function($) {
    'use strict';

if ($('#malLogin').length > 0) {
    return;
}

var mal = {
    name: 'Missing Relations',
    username: '',
    type: 'anime'
};

var USER_STATUS = {
    IN_PROCESS: 1, COMPLETED: 2, ON_HOLD: 3, DROPPED: 4, PLAN_TO: 6
};
var SERIES_STATUS = {
    IN_PROCESS: 1, COMPLETED: 2, NOT_YET: 3
};
var OPTS = {
    EXPAND_LONG: 100
};

mal.cache = {
    version: '7.0.4',
    objects: ['left', 'graph', 'title', 'status', 'wrong', 'hidden'],

    init: function() {
        mal.cache.objects.forEach(function(obj) {
            mal.cache[obj] = {};
        });
    },

    load: function() {
        mal.cache.clear();
        mal.cache.objects.forEach(function(obj) {
            mal.cache[obj] = mal.cache.loadValue('mal.entries.' + obj, mal.cache[obj], false);
        });
    },

    save: function() {
        mal.cache.objects.forEach(function(obj) {
            mal.cache.saveValue('mal.entries.' + obj, mal.cache[obj], false);
        });
    },

    clear: function() {
        var hidden = mal.cache.hasOwnProperty('hidden') ? mal.cache.hidden : {};
        mal.cache.init();
        mal.cache.hidden = hidden;
    },

    encodeKey: function(key, global) {
        return mal.cache.version + '#' + mal.username + '#' + (global ? '' : (mal.type + '#')) + key;
    },

    loadValue: function(key, value, global) {
        try {
            return JSON.parse(localStorage.getItem(mal.cache.encodeKey(key, global))) || value;
        } catch(e) {
            console.log(e.name + ': ' + e.message);
            return value;
        }
    },

    saveValue: function(key, value, global) {
        localStorage.setItem(mal.cache.encodeKey(key, global), JSON.stringify(value));
    }
};

mal.settings = {
    ajaxDelay: 300, ajaxTimeout: 30000,
    windowWidth: 700, windowHeight: 800,
    footerHeight: 88, footerSwitchHeight: 10,

    availableRelations: [
        'Alternative Setting', 'Alternative Version', 'Character', 'Full Story', 'Other',
        'Parent Story', 'Prequel', 'Sequel', 'Side Story', 'Spin-off', 'Summary'
    ],
    availableStatus: {
        anime: ['Watching', 'Completed', 'On-Hold', 'Dropped', 'Plan to Watch'],
        manga: ['Reading', 'Completed', 'On-Hold', 'Dropped', 'Plan to Read']
    },
    otherSettings: {
        anime: [
            { id: OPTS.EXPAND_LONG, text: 'Expand long lists', footer: true, def: false, val: false }
        ],
        manga: [
            { id: OPTS.EXPAND_LONG, text: 'Expand long lists', footer: true, def: false, val: false }
        ]
    },

    excludedRelations: { anime: [], manga: [] },
    excludedStatus: {
        left: { anime: [], manga: [] },
        right: { anime: [], manga: [] }
    },

    load: function() {
        ['anime', 'manga'].forEach(function(type) {
            mal.settings.excludedRelations[type] = ['Adaptation'];
            mal.settings.availableRelations.forEach(function(val) {
                var id = 'mr_xr' + type[0] + '_' + val.toHtmlId();
                if (mal.cache.loadValue(id, 'false', true) === 'true') {
                    mal.settings.excludedRelations[type].push(val);
                }
            });

            ['left', 'right'].forEach(function(status) {
                mal.settings.excludedStatus[status][type] = ['Empty Status'];
                mal.settings.availableStatus[type].forEach(function(val) {
                    var id = 'mr_xs' + status[0] + type[0] + '_' + val.toHtmlId();
                    if (mal.cache.loadValue(id, 'false', true) === 'true') {
                        mal.settings.excludedStatus[status][type].push(val);
                    }
                });
            });

            mal.settings.otherSettings[type].forEach(function(opt) {
                opt.val = mal.cache.loadValue('mr_xo' + type[0] + '_' + opt.id, opt.def ? 'true' : 'false', true) === 'true';
            });
        });
    },

    reset: function() {
        ['anime', 'manga'].forEach(function(type) {
            mal.settings.availableRelations.forEach(function(val) {
                mal.cache.saveValue('mr_xr' + type[0] + '_' + val.toHtmlId(), 'false', true);
            });

            ['left', 'right'].forEach(function(status) {
                mal.settings.availableStatus[type].forEach(function(val) {
                    mal.cache.saveValue('mr_xs' + status[0] + type[0] + '_' + val.toHtmlId(), 'false', true);
                });
            });
            mal.cache.saveValue('mr_xsr' + type[0] + '_empty_status', 'false', true);

            mal.settings.otherSettings[type].forEach(function(opt) {
                opt.val = opt.def;
                mal.cache.saveValue('mr_xo' + type[0] + '_' + opt.id, opt.def ? 'true' : 'false', true);
            });
            mal.cache.saveValue('mr_xo' + type[0] + '_quick_settings', 'false', true);
        });
    }
};

$.fn.myfancybox = function(onstart) {
    return $(this).click(function() {
        mal.fancybox.show(onstart);
    });
};

mal.fancybox = {
    body: $('<div id="mr_fancybox_inner">'),
    outer: $('<div id="mr_fancybox_outer">'),
    wrapper: $('<div id="mr_fancybox_wrapper">'),

    init: function(el) {
        mal.fancybox.outer.hide().append(mal.fancybox.body).insertAfter(el);
        mal.fancybox.wrapper.hide().click(mal.fancybox.hide).insertAfter(el);
    },

    show: function(onstart) {
        mal.fancybox.body.children().hide();
        if (!onstart()) {
            mal.fancybox.hide();
            return;
        }
        mal.fancybox.wrapper.show();
        mal.fancybox.outer.show();
    },

    hide: function() {
        mal.fancybox.outer.hide();
        mal.fancybox.wrapper.hide();
    }
};

mal.entries = {
    status: {
        updating: false,
        total: 0, done: 0, fail: 0, skip: 0,

        clear: function() {
            mal.entries.status.total = 0;
            mal.entries.status.done = 0;
            mal.entries.status.fail = 0;
            mal.entries.status.skip = 0;
        }
    },

    updating: function(strict) {
        return (strict && mal.entries.status.updating) ||
            ((mal.entries.status.done + mal.entries.status.fail + mal.entries.status.skip) < mal.entries.status.total);
    },

    update: function(recalc) {
        var userlist = [];
        var processed = {};
        var right = {};
        var excludedRelationsRe = new RegExp('^(' + mal.settings.excludedRelations[mal.type].join('|') + ')$', 'i');

        function setRelations(left_id, right_id) {
            [ { x: left_id, y: right_id }, { x: right_id, y: left_id } ].forEach(function(rel) {
                if (!mal.cache.graph.hasOwnProperty(rel.x)) {
                    mal.cache.graph[rel.x] = [];
                }
                if (mal.cache.graph[rel.x].indexOf(rel.y) < 0) {
                    mal.cache.graph[rel.x].push(rel.y);
                }
            });
        }

        function getRelations(left_id, data) {
            $('#dialog .normal_header + form > br ~ div > small', data).each(function() {
                var val = $('input[id^="relationGen"]', this).val();
                if (!val.match(/^\d+\s\(/) || val.match(/^\d+\s\(\)$/)) {
                    console.log('Empty Relation: /' + mal.type + '/' + left_id);
                    return;
                }

                var right_id = val.match(/\d+/)[0];
                if (!$('select[name^="relationTypeId"] option:selected', this).text().match(excludedRelationsRe)) {
                    setRelations(left_id, right_id);
                    right[right_id] = true;
                }
            });
        }

        function loadRight() {
            if (mal.entries.updating(false)) {
                return;
            }

            var count = 0;
            $.each(right, function(id) {
                if (processed.hasOwnProperty(id)) {
                    delete right[id];
                } else {
                    count += 1;
                }
            });

            mal.content.status.done += mal.entries.status.done;
            mal.content.status.fail += mal.entries.status.fail;
            mal.content.status.skip += mal.entries.status.skip;
            mal.content.status.total += mal.entries.status.total;

            mal.entries.status.clear();
            mal.entries.status.total = count;
            mal.content.status.update();

            if (mal.entries.status.total === 0) {
                mal.content.list.update(true);
                return;
            }

            var delay = 0;
            $.each(right, function(id) {
                setTimeout(function() {
                    $.ajax('/dbchanges.php?' + mal.type[0] + 'id=' + id + '&t=relations')
                        .done(function(data) {
                            mal.entries.status.done += 1;
                            processed[id] = true;
                            mal.cache.title[id] = $('#dialog .normal_header > a', data).text().toHtmlStr();
                            getRelations(id, data);
                            mal.content.status.update();
                            loadRight();
                        })
                        .fail(function() {
                            mal.entries.status.fail += 1;
                            mal.content.status.update();
                            console.log('Failed: /' + mal.type + '/' + id);
                            loadRight();
                        });
                }, delay);

                delay += mal.settings.ajaxDelay;
            });
        }

        function loadLeft() {
            if (mal.entries.updating(false)) {
                return;
            }

            $.each(mal.cache.left, function(id) {
                processed[id] = true;
            });

            mal.entries.status.clear();
            mal.entries.status.total = userlist.length;
            mal.content.status.update();

            if (mal.entries.status.total === 0) {
                mal.content.list.update(true);
                return;
            }

            var delay = 0;
            userlist.forEach(function(entry) {
                mal.cache.title[entry.id] = entry.title;

                if (mal.cache.left.hasOwnProperty(entry.id)) {
                    mal.entries.status.skip += 1;
                    mal.content.status.update();
                    mal.content.list.update(true);
                    return;
                }

                setTimeout(function() {
                    $.ajax('/dbchanges.php?' + mal.type[0] + 'id=' + entry.id + '&t=relations')
                        .done(function(data) {
                            mal.entries.status.done += 1;
                            mal.cache.left[entry.id] = true;
                            processed[entry.id] = true;
                            getRelations(entry.id, data);
                            mal.content.status.update();
                            loadRight();
                        })
                        .fail(function() {
                            mal.entries.status.fail += 1;
                            mal.content.status.update();
                            console.log('Failed: /' + mal.type + '/' + entry.id);
                            loadRight();
                        });
                }, delay);

                delay += mal.settings.ajaxDelay;
            });
        }

        function loadUserList() {
            $.get('/malappinfo.php?u=' + mal.username + '&status=all&type=' + mal.type, function(data) {
                $(mal.type, data).each(function() {
                    var id = $('series_' + mal.type + 'db_id', this).text().trim();
                    var title = $('series_title', this).text();
                    var status = parseInt($('series_status', this).text().trim() || '0');
                    var episodes = parseInt($('series_episodes', this).text().trim() || '0');
                    var chapters = parseInt($('series_chapters', this).text().trim() || '0');
                    var volumes = parseInt($('series_volumes', this).text().trim() || '0');
                    var userStatus = parseInt($('my_status', this).text().trim() || '0');
                    var userEpisodes = parseInt($('my_watched_episodes', this).text().trim() || '0');
                    var userChapters = parseInt($('my_read_chapters', this).text().trim() || '0');
                    var userVolumes = parseInt($('my_read_volumes', this).text().trim() || '0');
                    //my_rereadingg is not an error
                    var rewatching = parseInt($(mal.type === 'anime' ? 'my_rewatching' : 'my_rereadingg', this).text().trim() || '0');

                    // [ 1, 2, 3, 4, 6 ] --> [ 0, 1, 2, 3, 4 ]
                    var userStatusID = userStatus - (userStatus === USER_STATUS.PLAN_TO ? 2 : 1);

                    var entry = {
                        id: parseInt(id),
                        title: title.toHtmlStr(),
                        status: mal.settings.availableStatus[mal.type][userStatusID],
                        correct: !(
                            (userStatus !== USER_STATUS.PLAN_TO && status === SERIES_STATUS.NOT_YET) ||
                            (userStatus === USER_STATUS.COMPLETED && rewatching === 0 &&
                                (status !== SERIES_STATUS.COMPLETED || episodes !== userEpisodes ||
                                chapters !== userChapters || volumes !== userVolumes)) ||
                            (userStatus !== USER_STATUS.COMPLETED && status === SERIES_STATUS.COMPLETED &&
                                ((episodes > 0 && userEpisodes >= episodes) ||
                                (volumes > 0 && userVolumes >= volumes) ||
                                (chapters > 0 && userChapters >= chapters)))
                        )
                    };

                    mal.cache.status[entry.id] = '' + userStatusID;

                    if (!entry.correct) {
                        mal.cache.wrong[entry.id] = true;
                    }

                    userlist.push(entry);
                });

                loadLeft();
            }, 'text');
        }

        if (!mal.entries.updating(true)) {
            mal.entries.status.updating = true;
            mal.content.status.clear();
            mal.content.status.body.text('Loading...');

            if (recalc) {
                mal.entries.status.clear();
                mal.cache.clear();
            } else {
                mal.cache.wrong = {};
            }

            mal.content.list.body.empty()
                .append('<p id="mr_information">' + (recalc ? 'Recalculating' : 'Updating') + ' missing relations...</p>')
                .append('<input type="hidden" id="mr_list_type" value="' + mal.type + '">');

            loadUserList();
        }
    },

    getTitle: function(id) {
        var result = mal.cache.title.hasOwnProperty(id) ? mal.cache.title[id] : '';
        return result.length > 0 ? result : '?';
    },

    getStatus: function(id) {
        var userStatusID = mal.cache.status.hasOwnProperty(id) ? mal.cache.status[id] : '';
        return userStatusID.length > 0 ? mal.settings.availableStatus[mal.type][userStatusID] : '';
    },

    getComps: function() {
        var result = {};
        var used = {};
        var comp = [];

        function dfs(v) {
            used[v] = true;
            comp.push(v);

            if (!mal.cache.graph.hasOwnProperty(v)) {
                return;
            }

            mal.cache.graph[v].forEach(function(to) {
                if (!used.hasOwnProperty(to)) {
                    dfs(to);
                }
            });
        }

        $.each(mal.cache.graph, function(v) {
            if (!used.hasOwnProperty(v)) {
                comp = [];
                dfs(v);
                if (comp.length > 1) {
                    result[v] = comp;
                }
            }
        });

        return result;
    },

    comparator: function(a, b) {
        var aTitle = mal.entries.getTitle(a).toLowerCase();
        var bTitle = mal.entries.getTitle(b).toLowerCase();
        return aTitle.localeCompare(bTitle);
    }
};

mal.content = {
    body: $('<div class="mr_body mr_body_list">'),

    show: function(type) {
        if (type !== mal.type) {
            if (!mal.entries.updating(true)) {
                mal.content.status.body.empty();
            } else {
                alert('Updating in process!');
                return false;
            }
        }

        var listType = $('#mr_list_type', mal.content.body);
        mal.type = type;
        $('#mr_links_settings > #mr_link_switch', mal.content.body)
            .text(mal.type === 'anime' ? 'Manga' : 'Anime');

        if (listType.length === 0 || listType.val() !== mal.type) {
            mal.settings.load();
            mal.cache.load();
            mal.content.list.update(false);
        }

        mal.content.body.show();
        return true;
    },

    status: {
        body: $('<span id="mr_status_msg">'),
        done: 0, fail: 0, skip: 0, total: 0,

        update: function() {
            if (mal.content.status.total > 0 && mal.entries.status.total === 0) {
                mal.content.status.set(mal.content.status.done + mal.content.status.fail + mal.content.status.skip,
                               mal.content.status.fail, mal.content.status.skip, mal.content.status.total);
            } else if (mal.content.status.total === 0 && mal.entries.status.total > 0) {
                mal.content.status.set(mal.entries.status.done + mal.entries.status.fail + mal.entries.status.skip,
                               mal.entries.status.fail, mal.entries.status.skip, mal.entries.status.total);
            } else if (mal.content.status.total > 0 && mal.entries.status.total > 0) {
                mal.content.status.set((mal.content.status.done + mal.content.status.fail + mal.content.status.skip) + '+' +
                               (mal.entries.status.done + mal.entries.status.fail + mal.entries.status.skip),
                               mal.content.status.fail + '+' + mal.entries.status.fail,
                               mal.content.status.skip + '+' + mal.entries.status.skip,
                               mal.content.status.total + '+' + mal.entries.status.total);
            } else {
                mal.content.status.set(0, 0, 0, 0);
            }
        },

        set: function(done, fail, skip, total) {
            done = 'Done: <b><span style="color: green;">' + done + '</span></b>';
            fail = 'Failed: <b><span style="color: #c32;">' + fail + '</span></b>';
            skip = 'Skipped: <b><span style="color: gray;">' + skip + '</span></b>';
            total = 'Total: <b><span style="color: #444;">' + total + '</span></b>';
            mal.content.status.body.html(done + ' <small>(' + fail + ', ' + skip + ')</small> - ' + total);
        },

        clear: function() {
            mal.content.status.body.empty();
            mal.content.status.done = 0;
            mal.content.status.fail = 0;
            mal.content.status.skip = 0;
            mal.content.status.total = 0;
        }
    },

    footer: {
        body: $('<div class="mr_footer">'),
        footerSwitch: $('<div class="mr_footer_switch" title="Show/hide quick settings">···</div>').click(function() {
            mal.content.footer.toggle(!mal.content.list.body.hasClass('mr_has_footer'));
        }),

        show: function() {
            mal.content.footer.update();
            mal.content.footer.body.show();
            mal.content.list.body.addClass('mr_has_footer');
        },

        hide: function() {
            mal.content.footer.body.hide().empty();
            mal.content.list.body.removeClass('mr_has_footer');
        },

        toggle: function(state) {
            if (state) {
                mal.content.footer.show();
            } else {
                mal.content.footer.hide();
            }
            mal.cache.saveValue('mr_xo' + mal.type[0] + '_quick_settings', state.toString(), true);
        },

        update: function() {
            var table = $('<table class="mr_footer_table" border="0" cellpadding="0" cellspacing="0" width="100%"><tr>' +
                '<td class="mr_footer_td mr_footer_td_left"></td>' +
                '<td class="mr_footer_td mr_footer_td_right"></td>' +
                '<td class="mr_footer_td mr_footer_td_other"></td>' +
                '</tr></table>');

            var tableIgnoreLeft = $('<table class="relTable" border="0" cellpadding="0" cellspacing="0" width="100%"><thead><tr>' +
                '<th colspan="2">Treat as missing relations:</th></tr><tbody /></table>');

            var tableIgnoreRight = $('<table class="relTable" border="0" cellpadding="0" cellspacing="0" width="100%"><thead><tr>' +
                '<th colspan="2">Hide from missing relations:</th></tr><tbody /></table>');

            var tableOther = $('<table class="relTable" border="0" cellpadding="0" cellspacing="0" width="100%"><thead><tr>' +
                '<th>Other settings:</th></tr><tbody /></table>');

            var getCbSetting = function(str, id, state) {
                id = id.toHtmlId();
                return $('<div class="mr_checkbox">')
                    .append($('<input name="' + id + '" id="mr_footer_cb_' + id + '" type="checkbox">')
                        .prop('checked', mal.cache.loadValue(id, state.toString(), true) === 'true')
                        .change(function() {
                            var cb_id = this.id.replace(/^mr_footer_cb_/, '');
                            mal.cache.saveValue(cb_id, this.checked.toString(), true);
                            mal.settings.load();
                            mal.content.list.update(false);
                        }))
                    .append('<label for="mr_footer_cb_' + id + '">' + str + '</label>');
            };

            // Add ignore left & right statuses
            var tbody_left = $('tbody', tableIgnoreLeft);
            var tbody_right = $('tbody', tableIgnoreRight);
            var tr_left = $();
            var tr_right = $();

            $.each(mal.settings.availableStatus[mal.type], function(i, status) {
                if (i & 1) {
                    $('<td class="mr_td_right">')
                        .append(getCbSetting(status, 'mr_xsl' + mal.type[0] + '_' + status, false))
                        .appendTo(tr_left);
                    $('<td class="mr_td_right">')
                        .append(getCbSetting(status, 'mr_xsr' + mal.type[0] + '_' + status, false))
                        .appendTo(tr_right);

                    tr_left = $();
                    tr_right = $();
                } else {
                    tr_left = $('<tr class="mr_tr_data">').appendTo(tbody_left);
                    tr_right = $('<tr class="mr_tr_data">').appendTo(tbody_right);

                    $('<td class="mr_td_left">')
                        .append(getCbSetting(status, 'mr_xsl' + mal.type[0] + '_' + status, false))
                        .appendTo(tr_left);
                    $('<td class="mr_td_left">')
                        .append(getCbSetting(status, 'mr_xsr' + mal.type[0] + '_' + status, false))
                        .appendTo(tr_right);
                }
            });

            // Add 'Other' status
            var td;
            if (tr_right.length === 0) {
                tr_right = $('<tr class="mr_tr_data">').appendTo(tbody_right);
                td = $('<td class="mr_td_left">');
            } else {
                td = $('<td class="mr_td_right">');
            }
            td.append(getCbSetting('Other', 'mr_xsr' + mal.type[0] + '_empty_status', false))
                .appendTo(tr_right);

            // Add other settings
            var tbody = $('tbody', tableOther);
            mal.settings.otherSettings[mal.type].forEach(function(opt) {
                if (!opt.footer) {
                    return;
                }
                $('<tr class="mr_tr_data">')
                    .append($('<td class="mr_td_left">')
                        .append(getCbSetting(opt.text, 'mr_xo' + mal.type[0] + '_' + opt.id, false)))
                    .appendTo(tbody);
            });

            $('.mr_footer_td_left', table).append(tableIgnoreLeft);
            $('.mr_footer_td_right', table).append(tableIgnoreRight);
            $('.mr_footer_td_other', table).append(tableOther);
            mal.content.footer.body.empty().append(table);
        }
    },

    list: {
        body: $('<div class="mr_list">'),

        update: function(save) {
            if (mal.entries.updating(false)) {
                return;
            }

            mal.entries.status.updating = false;

            if (save) {
                mal.cache.save();
            }

            $('.mr_body_title', mal.content.body)
                .text(mal.name + ' — ' + (mal.type === 'anime' ? 'Anime' : 'Manga') + ' · ' + mal.username);

            var opts = {
                expandLong: false,
                quickSettings: false,
                hideEmpty: false
            };

            mal.settings.otherSettings[mal.type].forEach(function(opt) {
                switch (opt.id) {
                case OPTS.EXPAND_LONG:
                    opts.expandLong = opt.val;
                    break;
                }
            });

            opts.hideEmpty = mal.cache.loadValue('mr_xsr' + mal.type[0] + '_empty_status', 'false', true) === 'true';
            opts.quickSettings = mal.cache.loadValue('mr_xo' + mal.type[0] + '_quick_settings', 'false', true) === 'true';

            var listType = $('<input type="hidden" id="mr_list_type" value="' + mal.type + '">');
            var comps = mal.entries.getComps();
            var wrong = Object.keys(mal.cache.wrong);

            if (Object.keys(comps).length === 0 && wrong.length === 0) {
                mal.content.list.body.empty().append('<p id="mr_information">No missing relations found.</p>').append(listType);
                mal.content.footer.toggle(opts.quickSettings);
                return;
            }

            var table = $('<table class="relTable" border="0" cellpadding="0" cellspacing="0" width="100%"><thead><tr>' +
                '<th>Entries in your list:</th><th>Found missing relations:</th></tr></table>');

            var undel = $('<div id="mr_undelete"><p id="mr_undelete_msg" style="display: none;">' +
                'There are <span id="mr_undelete_num" style="font-weight: bold;" /> hidden relations. ' +
                '<a href="javascript:void(0);" title="Show hidden relations" onclick="window.showHiddenRelations();">Show them</a></p></div>');

            var tfoot = $('<tfoot><tr><td class="mr_td_left"><div class="mr_count"><span /></div></td>' +
                '<td class="mr_td_right"><div class="mr_count"><span /></div></td></tr></tfoot>');

            var getEntryLink = function(id, title) {
                var hint = mal.entries.getStatus(id) || '';
                var status = hint.length > 0 ? hint.toHtmlId() : 'none';
                var tooltip = hint.length > 0 ? (' title="' + hint + '"') : '';
                var url = '/' + mal.type + '/' + id + '/' + title
                    .replace(/[)(]/g, '')
                    .replace(/[^\w\d\-]/g, ' ')
                    .replace(/\s/g, '_')
                    .replace(/^_+/, '')
                    .replace(/_+$/, '');
                return $('<a title="' + title + '" href="' + url + '" target="_blank">' + title + '</a>')
                    .prepend('<i class="mr_icon mr_icon-' + status + '"' + tooltip + '>');
            };

            var getLiLeft = function(id) {
                return $('<li>').append(getEntryLink(id, mal.entries.getTitle(id)));
            };

            var getLiRight = function(id) {
                var btnHide = $('<div class="mr_hide"><span><a href="javascript:void(0);" ' +
                    'title="Hide this relation" onclick="window.hideRelation(' + id + ');">x</a></span></div>');
                return $('<li>').append(btnHide).append(getEntryLink(id, mal.entries.getTitle(id)));
            };

            var getMoreLink = function() {
                return $('<td colspan="2">').append(
                    $('<a class="mr_more" href="javascript:void(0);">show more</a>').click(function() {
                        var tr = $(this).closest('tr');
                        tr.prev('.mr_tr_data').removeClass('mr_tr_collapsed');
                        tr.remove();
                    })
                );
            };

            // red relations
            if (wrong.length > 0) {
                var ul = $('<ul>');
                var size = 0;

                wrong.sort(mal.entries.comparator).forEach(function(id) {
                    getLiLeft(id).prop('id', 'mr_li_red_' + id).appendTo(ul);
                    size += 1;
                });

                var warning = $('<div class="mr_warning"><span>Wrong status or ' +
                    (mal.type === 'anime' ? 'episode' : 'volume/chapter') + ' count</span></div>');

                var tbody_red = $('<tbody>');
                var tr_red = $('<tr class="mr_tr_data">')
                    .append($('<td class="mr_td_left">').append(ul))
                    .append($('<td class="mr_td_right">').append(warning))
                    .appendTo(tbody_red);

                if (!opts.expandLong && size > 6) {
                    tr_red.addClass('mr_tr_collapsed');
                    $('<tr class="mr_tr_more">').append(getMoreLink()).insertAfter(tr_red);
                }

                tbody_red.appendTo(table);
            }

            var excludedStatusLeftRe = new RegExp('^(' + mal.settings.excludedStatus.left[mal.type].join('|') + ')$', 'i');
            var excludedStatusRightRe = new RegExp('^(' + mal.settings.excludedStatus.right[mal.type].join('|') + ')$', 'i');

            // normal relations
            Object.keys(comps).sort(mal.entries.comparator).forEach(function(key) {
                var ulLeft = $('<ul>');
                var ulRight = $('<ul>');
                var lSize = 0;
                var rSize = 0;

                comps[key].sort(mal.entries.comparator).forEach(function(id) {
                    var status = mal.entries.getStatus(id);
                    if (mal.cache.left.hasOwnProperty(id)) {
                        if (!status.match(excludedStatusLeftRe)) {
                            getLiLeft(id).prop('id', 'mr_li_' + id).appendTo(ulLeft);
                            lSize += 1;
                        } else if (!status.match(excludedStatusRightRe)) {
                            getLiRight(id).prop('id', 'mr_li_' + id).appendTo(ulRight);
                            rSize += 1;
                        }
                    } else if (!opts.hideEmpty || status.length > 0) {
                        getLiRight(id).prop('id', 'mr_li_' + id).appendTo(ulRight);
                        rSize += 1;
                    }
                });

                if (lSize > 0 && rSize > 0) {
                    var tbody = $('<tbody>');
                    var tr = $('<tr class="mr_tr_data">')
                        .append($('<td class="mr_td_left">').append(ulLeft))
                        .append($('<td class="mr_td_right">').append(ulRight))
                        .appendTo(tbody);

                    if (!opts.expandLong && (lSize > 6 || rSize > 6)) {
                        tr.addClass('mr_tr_collapsed');
                        $('<tr class="mr_tr_more">').append(getMoreLink()).insertAfter(tr);
                    }

                    tbody.appendTo(table);
                }
            });

            mal.content.list.body.empty()
                .append(undel)
                .append(table.append(tfoot))
                .append(listType);

            mal.content.list.updateHiddenRelations();
            mal.content.list.updateLineCount();
            mal.content.footer.toggle(opts.quickSettings);
        },

        updateLineCount: function() {
            var totalLeft = $('.relTable td.mr_td_left li', mal.content.list.body).length;
            var visibleLeft = $('.relTable tbody:not([style*="display: none"]) td.mr_td_left li', mal.content.list.body).length;

            $('tfoot td.mr_td_left .mr_count span', mal.content.list.body)
                .text('Total: ' + totalLeft + ', Visible: ' + visibleLeft);

            var totalRight = $('.relTable td.mr_td_right li', mal.content.list.body).length;
            var visibleRight = $('.relTable td.mr_td_right li:not([style*="display: none"])', mal.content.list.body).length;

            $('tfoot td.mr_td_right .mr_count span', mal.content.list.body)
                .text('Total: ' + totalRight + ', Visible: ' + visibleRight);
        },

        hideRelation: function(id, save) {
            var li = $('td.mr_td_right li[id="mr_li_' + id + '"]', mal.content.list.body);
            if (li.length === 0) {
                if (mal.cache.hidden.hasOwnProperty(id)) {
                    delete mal.cache.hidden[id];
                }
                if (save) {
                    mal.cache.saveHiddenRelations();
                }
                return;
            }

            var row = li.hide().closest('tbody');
            var lSize = $('td.mr_td_left li', row).length;
            var rSize = $('td.mr_td_right li:not([style*="display: none;"])', row).length;

            row.toggle(rSize > 0);
            if (lSize <= 6 && rSize <= 6) {
                $('a.mr_more', row).trigger('click');
            }

            mal.cache.hidden[id] = true;
            if (save) {
                mal.cache.saveValue('mal.entries.hidden', mal.cache.hidden, false);
                var count = Object.keys(mal.cache.hidden).length;
                $('#mr_undelete_num', mal.content.list.body).text(count);
                $('#mr_undelete_msg', mal.content.list.body).toggle(count > 0);
            }
        },

        showHiddenRelations: function(save) {
            $('#mr_undelete_msg', mal.content.list.body).hide();
            $('li[id^="mr_li_"]', mal.content.list.body).show();
            $('tbody', mal.content.list.body).show();
            if (save) {
                mal.cache.hidden = {};
                mal.cache.saveValue('mal.entries.hidden', mal.cache.hidden, false);
            }
        },

        updateHiddenRelations: function() {
            mal.content.list.showHiddenRelations(false);
            $.each(mal.cache.hidden, function(id) {
                mal.content.list.hideRelation(id, false);
            });

            var count = Object.keys(mal.cache.hidden).length;
            $('#mr_undelete_num', mal.content.list.body).text(count);
            $('#mr_undelete_msg', mal.content.list.body).toggle(count > 0);
            mal.cache.saveValue('mal.entries.hidden', mal.cache.hidden, false);
        }
    },

    settings: {
        body: $('<div class="mr_body mr_body_settings"><div class="mr_list" /><div class="mr_buttons" /></div>'),

        update: function() {
            $('.mr_body_title', mal.content.settings.body).text(mal.name + ' — Settings');

            var tableExclude = $('<table class="relTable" border="0" cellpadding="0" cellspacing="0" width="100%"><thead><tr>' +
                '<th>Exclude anime relations from scan:</th><th>Exclude manga relations from scan:</th></tr><tbody /></table>');

            var tableIgnoreLeft = $('<table class="relTable" border="0" cellpadding="0" cellspacing="0" width="100%"><thead><tr>' +
                '<th>Treat anime entries as missing relations:</th><th>Treat manga entries as missing relations:</th></tr><tbody /></table>');

            var tableIgnoreRight = $('<table class="relTable" border="0" cellpadding="0" cellspacing="0" width="100%"><thead><tr>' +
                '<th>Hide anime entries from missing relations:</th><th>Hide manga entries from missing relations:</th></tr><tbody /></table>');

            var tableOther = $('<table class="relTable" border="0" cellpadding="0" cellspacing="0" width="100%"><thead><tr>' +
                '<th>Other anime settings:</th><th>Other manga settings:</th></tr><tbody /></table>');

            var getCbSetting = function(str, id, state) {
                id = id.toHtmlId();
                return $('<div class="mr_checkbox">')
                    .append($('<input name="' + id + '" id="' + id + '" type="checkbox">')
                        .prop('checked', mal.cache.loadValue(id, state.toString(), true) === 'true'))
                    .append('<label for="' + id + '">' + str + '</label>');
            };

            // Add exclude relations
            var tbody = $('tbody', tableExclude);
            mal.settings.availableRelations.forEach(function(rel) {
                $('<tr class="mr_tr_data">')
                    .append($('<td class="mr_td_left">')
                        .append(getCbSetting(rel, 'mr_xra_' + rel, false)))
                    .append($('<td class="mr_td_right">')
                        .append(getCbSetting(rel, 'mr_xrm_' + rel, false)))
                    .appendTo(tbody);
            });

            // Add ignore left & right statuses
            var tbody_left = $('tbody', tableIgnoreLeft);
            var tbody_right = $('tbody', tableIgnoreRight);

            $.each(mal.settings.availableStatus.anime, function(i, statusA) {
                var statusM = mal.settings.availableStatus.manga[i];

                $('<tr class="mr_tr_data">')
                    .append($('<td class="mr_td_left">')
                        .append(getCbSetting(statusA, 'mr_xsla_' + statusA, false)))
                    .append($('<td class="mr_td_right">')
                        .append(getCbSetting(statusM, 'mr_xslm_' + statusM, false)))
                    .appendTo(tbody_left);

                $('<tr class="mr_tr_data">')
                    .append($('<td class="mr_td_left">')
                        .append(getCbSetting(statusA, 'mr_xsra_' + statusA, false)))
                    .append($('<td class="mr_td_right">')
                        .append(getCbSetting(statusM, 'mr_xsrm_' + statusM, false)))
                    .appendTo(tbody_right);
            });

            $('<tr class="mr_tr_data">')
                .append($('<td class="mr_td_left">')
                    .append(getCbSetting('Other', 'mr_xsra_empty_status', false)))
                .append($('<td class="mr_td_right">')
                    .append(getCbSetting('Other', 'mr_xsrm_empty_status', false)))
                .appendTo(tbody_right);

            // Add other settings
            tbody = $('tbody', tableOther);
            $.each(mal.settings.otherSettings.anime, function(i, optA) {
                var optM = mal.settings.otherSettings.manga[i];
                $('<tr class="mr_tr_data">')
                    .append($('<td class="mr_td_left">')
                        .append(getCbSetting(optA.text, 'mr_xoa_' + optA.id, false)))
                    .append($('<td class="mr_td_right">')
                        .append(getCbSetting(optM.text, 'mr_xom_' + optM.id, false)))
                    .appendTo(tbody);
            });

            var list = $('.mr_list', mal.content.settings.body).empty()
                .append(tableExclude)
                .append(tableIgnoreLeft)
                .append(tableIgnoreRight)
                .append(tableOther);

            var buttons = $('.mr_buttons', mal.content.settings.body).empty();

            if (mal.entries.updating(true)) {
                buttons
                    .append('<div id="mr_warning">The settings can\'t be changed during relations calculation!</div>')
                    .append($('<input class="inputButton" value="OK" type="button">').click(function() {
                        mal.fancybox.show(function() {
                            mal.content.body.show();
                            return true;
                        });
                    }))
                    .insertAfter(list);
            } else {
                buttons
                    .append($('<input class="inputButton" value="Save" type="button">').click(function() {
                        $('input[type="checkbox"]', mal.content.settings.body).each(function() {
                            mal.cache.saveValue(this.id, this.checked.toString(), true);
                        });
                        mal.settings.load();
                        mal.fancybox.show(function() {
                            mal.content.list.update(false);
                            mal.content.body.show();
                            return true;
                        });
                    }))
                    .append($('<input class="inputButton" value="Cancel" type="button">').click(function() {
                        mal.fancybox.show(function() {
                            mal.content.body.show();
                            return true;
                        });
                    }))
                    .append($('<input class="inputButton" value="Reset" type="button">').click(function() {
                        if (!confirm('Reset all settings?')) {
                            return;
                        }
                        mal.settings.reset();
                        mal.settings.load();
                        mal.fancybox.show(function() {
                            mal.content.list.update(false);
                            mal.content.body.show();
                            return true;
                        });
                    }));
            }

            mal.content.settings.body.show();
        }
    }
};

window.hideRelation = function(id) {
    mal.content.list.hideRelation(id, true);
    mal.content.list.updateLineCount();
};

window.showHiddenRelations = function() {
    mal.content.list.showHiddenRelations(true);
    mal.content.list.updateLineCount();
};

String.prototype.toHtmlId = function() {
    return this.trim().toLowerCase().replace(/\s/g, '_').replace(/[^\w]/g, '_');
};

String.prototype.toHtmlStr = function() {
    return this.trim().replace(/"/g, '&quot;');
};

function main() {
    mal.username = $('.icon-rss + div > a:first').prop('href').match(/&u=(.+)$/)[1].trim();
    if (mal.username.length === 0) {
        return;
    }

    $.ajaxSetup({ timeout: mal.settings.ajaxTimeout });

    mal.cache.init();
    mal.fancybox.init('#contentWrapper');

    var panel = $('<div id="mr_panel">').append(mal.content.status.body).prependTo(mal.content.body);
    var links = $('<div id="mr_links">').prependTo(panel);

    $('<span id="mr_links_settings">').prependTo(links)
        .append($('<a href="javascript:void(0);" title="Switch lists" id="mr_link_switch">Manga</a>').click(function() {
            if (mal.entries.updating(true)) {
                alert('Updating in process!');
            } else {
                mal.fancybox.show(function() {
                    return mal.content.show(mal.type === 'anime' ? 'manga' : 'anime');
                });
            }
        }))
        .append(' - ').append($('<a href="javascript:void(0);" title="Change calculation settings">Settings</a>').myfancybox(function() {
            mal.content.settings.update();
            return true;
        }))
        .append(' - ').append($('<a href="javascript:void(0);" title="Recalculate missing relations">Rescan</a>').click(function() {
            if (mal.entries.updating(true)) {
                alert('Updating in process!');
            } else if (confirm('Recalculate missing relations?')) {
                mal.entries.update(true);
            }
        }))
        .append(' - ').append($('<a href="javascript:void(0);" title="Find new missing relations">Update</a>').click(function() {
            if (mal.entries.updating(true)) {
                alert('Updating in process!');
            } else if (confirm('Find new missing relations?')) {
                mal.entries.update(false);
            }
        }));

    mal.content.body
        .prepend('<h2 class="mr_body_title">' + mal.name + '</h2>')
        .append(mal.content.list.body)
        .append(mal.content.footer.footerSwitch)
        .append(mal.content.footer.body.hide())
        .appendTo(mal.fancybox.body);

    mal.content.settings.body
        .prepend('<h2 class="mr_body_title">' + mal.name + '</h2>')
        .appendTo(mal.fancybox.body);

    ['anime', 'manga'].forEach(function(type) {
        var el = $('.profile .user-statistics .user-statistics-stats .updates.' + type + ' > h5 > a[href*="/history/"]');
        if (el.length > 0) {
            el.text(el.text().replace(/^(Anime|Manga)\sHistory$/i, 'History'));
        }

        $('<a class="floatRightHeader ff-Verdana mr4" href="javascript:void(0);">' + mal.name + '</a>').myfancybox(function() {
                return mal.content.show(type);
            })
            .appendTo('.profile .user-statistics .user-statistics-stats .updates.' + type + ' > h5')
            .before('<span class="floatRightHeader ff-Verdana mr4">-</span>');
    });
}

$('<style type="text/css">').html(
    'div#mr_fancybox_wrapper { position: fixed; width: 100%; height: 100%; top: 0; left: 0; background: rgba(102, 102, 102, 0.3); z-index: 99990; }' +
    'div#mr_fancybox_inner { width: ' + mal.settings.windowWidth + 'px !important; height: ' + mal.settings.windowHeight + 'px !important; overflow: hidden; }' +
    'div#mr_fancybox_outer { position: absolute; display: block; width: auto; height: auto; padding: 10px; border-radius: 8px; top: 80px; left: 50%; margin-top: 0 !important; margin-left: ' + (-mal.settings.windowWidth/2) + 'px !important; background: #fff; box-shadow: 0 0 15px rgba(32, 32, 32, 0.4); z-index: 99991; }' +
    'div.mr_body { text-align: center; width: 100%; height: 100%; padding: 42px 0 0; box-sizing: border-box; }' +
    'div.mr_body.mr_body_list { padding-top: 65px; }' +
    'div.mr_body a, div.mr_body a:visited { color: #1969cb; text-decoration: none; }' +
    'div.mr_body a:hover { color: #2d7de0; text-decoration: underline; }' +
    'div.mr_body .mr_body_title { position: absolute; top: 10px; left: 10px; width: ' + mal.settings.windowWidth + 'px; font-size: 16px; font-weight: normal; text-align: center; margin: 0; border: 0; }' +
    'div.mr_body .mr_body_title:after { content: ""; position: absolute; left: 0; bottom: -14px; width: 100%; height: 8px; border-top: 1px solid #eee; background: center bottom no-repeat radial-gradient(#f6f6f6, #fff 70%); background-size: 100% 16px; }' +
    'div.mr_body #mr_panel { position: absolute; top: 50px; left: 10px; text-align: left; width: ' + mal.settings.windowWidth + 'px; height: 2em; margin: 0 0 1em; }' +
    'div.mr_body #mr_links { float: right; }' +
    'div.mr_body p#mr_information { margin: 10px 0; }' +
    'div.mr_body #mr_undelete { background-color: #fff; padding: 0; margin: 0; }' +
    'div.mr_body #mr_undelete_msg { margin: 4px 0 6px; font-weight: normal; text-align: center; line-height: 20px; font-size: 11px; }' +
    'div.mr_body .mr_list { width: 100%; height: ' + (mal.settings.windowHeight-mal.settings.footerSwitchHeight-65) + 'px; overflow-x: hidden; overflow-y: auto; margin: 0 auto; border: 1px solid #eee; box-sizing: border-box; }' +
    'div.mr_body .relTable { border: none; }' +
    'div.mr_body .relTable thead { background-color: #f5f5f5; }' +
    'div.mr_body .relTable th { background-color: transparent; width: 50%; padding: 5px 0 5px 6px; font-size: 12px; font-weight: bold; text-align: left; line-height: 20px !important; box-shadow: none; cursor: default; }' +
    'div.mr_body .relTable tbody { background-color: #fff; }' +
    'div.mr_body.mr_body_list .mr_list .relTable th { padding-left: 26px; }' +
    'div.mr_body.mr_body_list .mr_list .relTable tbody:hover { background-color: #f5f5f5; }' +
    'div.mr_body.mr_body_list .mr_list .relTable tbody tr:first-of-type td { box-shadow: 0px 1em 1em -1em #ddd inset; }' +
    'div.mr_body .relTable td { background-color: transparent; width: 50%; padding: 5px 0 5px 6px; font-size: 13px; font-weight: normal; text-align: left; line-height: 20px !important; vertical-align: top; }' +
    'div.mr_body .relTable td div span { line-height: 20px !important; }' +
    'div.mr_body .relTable td ul { list-style-type: none; margin: 0; padding: 0; }' +
    'div.mr_body .relTable tr.mr_tr_collapsed td ul { height: 100px; overflow-y: hidden; }' +
    'div.mr_body .relTable td ul li { width: 100%; padding: 0; margin: 0; }' +
    'div.mr_body .relTable td ul li > a { display: block; width: ' + (mal.settings.windowWidth/2 - 40) + 'px !important; line-height: 20px !important; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; text-decoration: none !important; transition: all 0.2s; }' +
    'div.mr_body .relTable td.mr_td_left ul li > a { width: ' + (mal.settings.windowWidth/2 - 25) + 'px !important; }' +
    'div.mr_body .relTable i.mr_icon { display: inline-block; vertical-align: text-top; line-height: 16px; width: 16px; height: 16px; margin: 1px 4px 0 0; background-repeat: no-repeat; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFUAAAAQCAMAAABQvsO3AAABI1BMVEUAAAD///8ZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZacsZactvPr9pAAAAYXRSTlMAAAsMDQ4PFhcaGx4gJCYnKSwuMDE3PEBBRUlKTE1UVldZWltdX2FiZGZoaWpsbnByc3R3eHl9foKEh4mLjY+QlJebnJ6foaOkq6yvs7S1uru8v8DDxMbJys/T2d3n6PH7ZwpR9wAAAXZJREFUeNq11FlTwjAUBeAjpSAuKLjvuCMgWlEUN5BFURG1LlVKwf//Kwy5SSbOyEtn+CaZ5GRuD29gaBDQ2z5cCfiH/9bCASdbV3T+W0/kQW51f1oDCSiB/QDfSng+kZgJQ7mUB7nX6a1GqXkIqHtpmG0DZKFUPW8yF3EIG5tgNjdk1Jds3TFgVGzbXgXJ2SRHv1HIDAPXLJ7ZBQOcUZ4FxssiYVtfovXUqUfqjuPcySHjjiWZo3kDy3MrLN9g+rEelSN7e2oeyWTSYZIctW61yH0QUoQ/ROgOIMNzFcHa5wS44EMq9aA+SAOteLzFL2lqNRvtnoYJyXziL0/qZYTn7O53Y1JOsMukGsgC7ampNr9kqRXmi+d5LyaUokeKEBZrveh+rUE4smKuG7OOQCzL8hiLE60IvXffQ5Aoj/55iy3lcuujUD7Q7Hab7CDH+pKtCFW0Usp89/U69sOMvYLk9SVafXCfO8yzC9LR+W99E/r9uwzCL22kXUBCr0XlAAAAAElFTkSuQmCC); }' +
    'div.mr_body .relTable i.mr_icon-none { background-image: none; }' +
    'div.mr_body .relTable i.mr_icon-watching { background-position: -34px 0; }' +
    'div.mr_body .relTable i.mr_icon-reading { background-position: -34px 0; }' +
    'div.mr_body .relTable i.mr_icon-completed { background-position: 0 0; }' +
    'div.mr_body .relTable i.mr_icon-on_hold { background-position: -51px 0; }' +
    'div.mr_body .relTable i.mr_icon-dropped { background-position: -17px 0; }' +
    'div.mr_body .relTable i.mr_icon-plan_to_watch { background-position: -68px 0; }' +
    'div.mr_body .relTable i.mr_icon-plan_to_read { background-position: -68px 0; }' +
    'div.mr_body .relTable tfoot td { border-top: 1px solid #f5f5f5; }' +
    'div.mr_body .relTable td .mr_count { color: #666; font-size: 11px; font-weight: normal; text-align: left; padding-left: 20px; }' +
    'div.mr_body .relTable td .mr_warning { width: ' + (mal.settings.windowWidth/2 - 75) + 'px; color: #c56; font-size: 12px; font-weight: bold; text-align: left; padding-left: 20px; }' +
    'div.mr_body .relTable td .mr_hide { display: inline-block !important; width: 15px; float: right; text-align: left; font-size: 11px; }' +
    'div.mr_body .relTable td .mr_hide a { color: #888 !important; line-height: 20px !important; font-style: normal !important; text-decoration: none !important; }' +
    'div.mr_body .relTable tr.mr_tr_more td { padding: 0 0 2px 0; }' +
    'div.mr_body .relTable td .mr_more { display: block !important; text-align: center; color: #c0c0c0 !important; font-style: normal !important; font-size: 0.9em; text-decoration: none !important; }' +
    'div.mr_body .relTable .mr_checkbox > * { vertical-align: middle; font-size: 11px; cursor: pointer; }' +
    'div.mr_body .relTable .mr_comment { background-color: #f6f6f6; border: 1px solid #ebebeb; font-size: 11px; line-height: 16px; padding: 1px 4px; }' +
    'div.mr_body .mr_list.mr_has_footer { height: ' + (mal.settings.windowHeight-mal.settings.footerHeight-mal.settings.footerSwitchHeight-65) + 'px; }' +
    'div.mr_body .mr_footer { position: absolute; bottom: 10px; width: ' + mal.settings.windowWidth + 'px; height: ' + mal.settings.footerHeight + 'px; overflow-x: hidden; overflow-y: auto; border: 1px solid #eee; border-top: 0; box-sizing: border-box; }' +
    'div.mr_body .mr_footer .mr_footer_td { vertical-align: top; padding: 2px 0 0; width: 40%; }' +
    'div.mr_body .mr_footer .mr_footer_td:first-of-type { padding-left: 6px; }' +
    'div.mr_body .mr_footer .mr_footer_td_other { width: 20%; }' +
    'div.mr_body .mr_footer .relTable { color: #323232; }' +
    'div.mr_body .mr_footer .relTable thead { background-color: transparent; }' +
    'div.mr_body .mr_footer .relTable th { padding: 0 0 3px; font-size: 11px; line-height: 16px !important; }' +
    'div.mr_body .mr_footer .relTable td { padding: 0; }' +
    'div.mr_body .mr_footer_switch { position: absolute; bottom: 10px; width: ' + mal.settings.windowWidth + 'px; height: ' + mal.settings.footerSwitchHeight + 'px; overflow: hidden; border: 1px solid #eee; border-width: 0 1px 1px; font-size: 10px; text-align: center; line-height: ' + mal.settings.footerSwitchHeight + 'px; cursor: pointer; color: #555; box-sizing: border-box; }' +
    'div.mr_body .mr_footer_switch:hover { background-color: #f5f5f5; }' +
    'div.mr_body .mr_list.mr_has_footer ~ .mr_footer_switch { bottom: ' + (mal.settings.footerHeight+10) + 'px; border-width: 0 1px; }' +
    'div.mr_body.mr_body_settings .relTable { margin-bottom: 10px; }' +
    'div.mr_body.mr_body_settings .relTable td { padding: 0 0 0 6px; }' +
    'div.mr_body.mr_body_settings .mr_list { height: ' + (mal.settings.windowHeight-75) + 'px !important; }' +
    'div.mr_body.mr_body_settings .mr_buttons { position: absolute; bottom: 10px; width: ' + mal.settings.windowWidth + 'px; text-align: center; padding: 5px 0 0; box-sizing: border-box; }' +
    'div.mr_body.mr_body_settings .mr_buttons > .inputButton { margin: 2px 5px !important; font-size: 12px; }' +
    'div.mr_body.mr_body_settings #mr_warning { display: inline-block; font-size: 12px; font-weight: bold; color: #c56; margin-bottom: 2px; }'
).appendTo('head');

main();

}(jQuery));