<commit_before>idrinth.tier = {
    list: { },
    addTagged: function ( name ) {
        var isValidParameter = function ( name ) {
            return name && idrinth.tier.list.hasOwnProperty ( name ) && typeof idrinth.tier.list[name] !== 'function' && !document.getElementById ( 'idrinth-tier-box-' + name );
        };
        var isFreeSlot = function ( key ) {
            return idrinth.tier.taggedSlots.hasOwnProperty ( key ) && typeof key !== 'function' && idrinth.tier.taggedSlots[key] === null;
        };
        if ( !isValidParameter ( name ) ) {
            return;
        }
        var boss = this.list[name];
        var make = function ( x, name ) {
            var makeElement = function ( label, number, description ) {
                return {
                    content: label + ' ' + idrinth.ui.formatNumber ( number ),
                    attributes: [ {
                            name: 'title',
                            value: description
                        } ]
                };
            };
            var info = [
                makeElement ( 'FS', boss.fs.nm, 'Fair share' ),
                makeElement ( 'AP', boss.ap, 'Achievement point damage' )
            ];
            if ( boss.os && boss.os.nm ) {
                info.push ( makeElement ( 'OS', boss.os.nm, 'Optimal share' ) );
                info.unshift ( makeElement ( 'MA', boss.nm[boss.nm.length - 1], 'Maximum/highest tier' ) );
                info.unshift ( makeElement ( 'MI', boss.nm[0], 'Minimum/lowest tier' ) );
            }
            info.unshift (
                    {
                        type: 'strong',
                        content: boss.name.replace ( /\(.*$/, '' )
                    } );
            idrinth.tier.taggedSlots[x] = idrinth.ui.buildElement (
                    {
                        id: 'idrinth-tier-box-' + name,
                        css: 'idrinth-hovering-box idrinth-tier-box',
                        children: [ {
                                children: info
                            } ],
                        attributes: [
                            {
                                name: 'title',
                                value: 'click to close'
                            },
                            {
                                name: 'onclick',
                                value: 'idrinth.ui.removeElement(this.id);idrinth.tier.taggedSlots[\'' + x + '\']=null;'
                            },
                            {
                                name: 'style',
                                value: 'left:' + x + 'px;background-image: url(https://dotd.idrinth.de/static/raid-image-service/' + boss.url + '/);'
                            }
                        ]
                    }
            );
            idrinth.ui.body.appendChild ( idrinth.tier.taggedSlots[x] );
        };
        for (var key in this.taggedSlots) {
            if ( isFreeSlot ( key ) ) {
                return make ( key, name );
            }
        }
        idrinth.core.alert ( idrinth.text.get ( "tier.maxBoxes" ) );
    },
    taggedSlots: { },
    start: function ( ) {
        'use strict';
        var pos = 1;
        while ( 0 < window.innerWidth - 140 * ( pos + 1 ) ) {
            this.taggedSlots[( pos * 140 ).toString ( )] = null;
            pos++;
        }
        idrinth.core.ajax.runHome (
                'tier-service/',
                function ( text ) {
                    idrinth.tier.import ( text );
                },
                function ( ) {
                    window.setTimeout ( idrinth.tier.start, 10000 );
                },
                function ( ) {
                    window.setTimeout ( idrinth.tier.start, 10000 );
                }
        );
    },
    import: function ( data ) {
        'use strict';
        data = JSON.parse ( data );
        if ( data ) {
            idrinth.tier.list = data;
            var create = function ( name, url ) {
                'use strict';
                if ( !idrinth.settings.bannedRaids[name] ) {
                    idrinth.settings.data.bannedRaids[name] = false;
                    window.localStorage.setItem ( 'idotd', JSON.stringify ( idrinth.settings.data ) );
                }
                document.getElementById ( 'idrinth-raid-may-join-list' ).appendChild ( idrinth.ui.buildElement ( {
                    name: 'bannedRaids#' + name,
                    rType: '#input',
                    type: 'checkbox',
                    id: 'idrinth-raid-may-join-list-' + name,
                    label: idrinth.text.get ( "raids.disableJoining" ) + name
                } ) );
                document.getElementById ( 'idrinth-raid-may-join-list' ).lastChild.setAttribute ( 'style',
                        'background-image:url(https://dotd.idrinth.de/static/raid-image-service/' + url + '/);' );
            };
            for (var key in data) {
                if ( data[key].name ) {
                    create ( data[key].name, data[key].url );
                }
            }
        } else {
            window.setTimeout ( idrinth.tier.start, 1000 );
        }
    },
    getTierForName: function ( name ) {
        var clearInnerHtml = function ( elem ) {
            elem.innerHTML = '';
        };
        var makeList = function ( list ) {
            var makeField = function ( listKey, difficulty, ic ) {
                var ln = {
                    type: 'td'
                };
                try {
                    ln.styles = idrinth.tier.list[listKey].os[difficulty] === idrinth.tier[listKey][difficulty][ic] ? 'is-os' : '';
                } catch ( E ) {
                    idrinth.core.log ( E.toString ( ) );
                }
                try {
                    ln.content = idrinth.ui.formatNumber ( idrinth.tier.list[listKey][difficulty][ic] ) + ' ' +
                            idrinth.tier.list[listKey].epics[difficulty][ic] + 'E';
                } catch ( E2 ) {
                    idrinth.core.log ( E2.toString ( ) );
                    try {
                        ln.content = idrinth.ui.formatNumber ( idrinth.tier.list[listKey][difficulty][ic] );
                    } catch ( E3 ) {
                        idrinth.core.log ( E3.toString ( ) );
                    }
                }
                return ln;
            };
            var makeRow = function ( title, dataset ) {
                return {
                    type: 'tr',
                    children: [
                        {
                            type: 'th',
                            content: title
                        },
                        {
                            type: 'td',
                            content: idrinth.ui.formatNumber ( dataset.n )
                        },
                        {
                            type: 'td',
                            content: idrinth.ui.formatNumber ( dataset.h )
                        },
                        {
                            type: 'td',
                            content: idrinth.ui.formatNumber ( dataset.l )
                        },
                        {
                            type: 'td',
                            content: idrinth.ui.formatNumber ( dataset.nm )
                        }
                    ]
                };
            };
            var wrapper = document.getElementById ( 'idrinth-tierlist' );
            clearInnerHtml ( wrapper );
            for (var count = list.length - 1; count >= 0; count--) {
                var sub = idrinth.ui.buildElement ( {
                    css: 'tier-wrapper',
                    children: [
                        {
                            type: 'img',
                            attributes: [ {
                                    name: 'src',
                                    value: 'https://dotd.idrinth.de/static/raid-image-service/' + idrinth.tier.list[list[count]].url + '/'
                                } ]
                        },
                        {
                            type: 'strong',
                            content: idrinth.tier.list[list[count]].name
                        },
                        {
                            type: 'button',
                            content: idrinth.text.get ( "tier.tag" ),
                            attributes: [
                                {
                                    name: 'onclick',
                                    value: 'idrinth.tier.addTagged(\'' + list[count].replace ( /'/g, '\\\'' ) + '\');'
                                },
                                {
                                    name: 'type',
                                    value: 'action'
                                }
                            ]
                        },
                        {
                            type: 'span',
                            content: 'AP: ' + idrinth.ui.formatNumber ( idrinth.tier.list[list[count]].ap )
                        },
                        {
                            type: 'table',
                            children: [
                                {
                                    type: 'thead',
                                    children: [
                                        {
                                            type: 'tr',
                                            children: [
                                                {
                                                    type: 'th',
                                                    content: '#'
                                                },
                                                {
                                                    type: 'th',
                                                    content: idrinth.text.get ( "tier.diff.normal" )
                                                },
                                                {
                                                    type: 'th',
                                                    content: idrinth.text.get ( "tier.diff.hard" )
                                                },
                                                {
                                                    type: 'th',
                                                    content: idrinth.text.get ( "tier.diff.legend" )
                                                },
                                                {
                                                    type: 'th',
                                                    content: idrinth.text.get ( "tier.diff.night" )
                                                }
                                            ]
                                        }
                                    ]
                                },
                                {
                                    type: 'tbody',
                                    children: [
                                        makeRow ( 'FS', idrinth.tier.list[list[count]].fs ),
                                        makeRow ( 'OS', idrinth.tier.list[list[count]].os ),
                                        makeRow ( '', {
                                            n: '',
                                            l: '',
                                            h: '',
                                            nm: ''
                                        } )
                                    ]
                                }
                            ]
                        }
                    ]
                } );
                var maxTiers = Math.max ( idrinth.tier.list[list[count]].n.length, idrinth.tier.list[list[count]].h.length, idrinth.tier.list[list[count]].l.length, idrinth.tier.list[list[count]].nm.length );
                for (var ic = 0; ic < maxTiers; ic++) {
                    sub.lastChild.lastChild.appendChild ( idrinth.ui.buildElement ( {
                        type: 'tr',
                        children: [
                            {
                                type: 'th',
                                content: ic + 1
                            },
                            makeField ( list[count], 'n', ic ),
                            makeField ( list[count], 'h', ic ),
                            makeField ( list[count], 'l', ic ),
                            makeField ( list[count], 'nm', ic )
                        ]
                    } ) );
                }
                wrapper.appendChild ( sub );
            }
        };
        if ( !name || name.length === 0 ) {
            clearInnerHtml ( document.getElementById ( 'idrinth-tierlist' ) );
            return;
        }
        var result = [ ];
        var regExp = new RegExp ( name, 'i' );
        for (var key in idrinth.tier.list) {
            if ( key.match ( regExp ) ) {
                result.push ( key );
            }
        }
        makeList ( result );
    }
};<commit_msg>now checking correct object<commit_after>idrinth.tier = {
    list: { },
    addTagged: function ( name ) {
        var isValidParameter = function ( name ) {
            return name && idrinth.tier.list.hasOwnProperty ( name ) && typeof idrinth.tier.list[name] !== 'function' && !document.getElementById ( 'idrinth-tier-box-' + name );
        };
        var isFreeSlot = function ( key ) {
            return idrinth.tier.taggedSlots.hasOwnProperty ( key ) && typeof key !== 'function' && idrinth.tier.taggedSlots[key] === null;
        };
        if ( !isValidParameter ( name ) ) {
            return;
        }
        var boss = this.list[name];
        var make = function ( x, name ) {
            var makeElement = function ( label, number, description ) {
                return {
                    content: label + ' ' + idrinth.ui.formatNumber ( number ),
                    attributes: [ {
                            name: 'title',
                            value: description
                        } ]
                };
            };
            var info = [
                makeElement ( 'FS', boss.fs.nm, 'Fair share' ),
                makeElement ( 'AP', boss.ap, 'Achievement point damage' )
            ];
            if ( boss.os && boss.os.nm ) {
                info.push ( makeElement ( 'OS', boss.os.nm, 'Optimal share' ) );
                info.unshift ( makeElement ( 'MA', boss.nm[boss.nm.length - 1], 'Maximum/highest tier' ) );
                info.unshift ( makeElement ( 'MI', boss.nm[0], 'Minimum/lowest tier' ) );
            }
            info.unshift (
                    {
                        type: 'strong',
                        content: boss.name.replace ( /\(.*$/, '' )
                    } );
            idrinth.tier.taggedSlots[x] = idrinth.ui.buildElement (
                    {
                        id: 'idrinth-tier-box-' + name,
                        css: 'idrinth-hovering-box idrinth-tier-box',
                        children: [ {
                                children: info
                            } ],
                        attributes: [
                            {
                                name: 'title',
                                value: 'click to close'
                            },
                            {
                                name: 'onclick',
                                value: 'idrinth.ui.removeElement(this.id);idrinth.tier.taggedSlots[\'' + x + '\']=null;'
                            },
                            {
                                name: 'style',
                                value: 'left:' + x + 'px;background-image: url(https://dotd.idrinth.de/static/raid-image-service/' + boss.url + '/);'
                            }
                        ]
                    }
            );
            idrinth.ui.body.appendChild ( idrinth.tier.taggedSlots[x] );
        };
        for (var key in this.taggedSlots) {
            if ( isFreeSlot ( key ) ) {
                return make ( key, name );
            }
        }
        idrinth.core.alert ( idrinth.text.get ( "tier.maxBoxes" ) );
    },
    taggedSlots: { },
    start: function ( ) {
        'use strict';
        var pos = 1;
        while ( 0 < window.innerWidth - 140 * ( pos + 1 ) ) {
            this.taggedSlots[( pos * 140 ).toString ( )] = null;
            pos++;
        }
        idrinth.core.ajax.runHome (
                'tier-service/',
                function ( text ) {
                    idrinth.tier.import ( text );
                },
                function ( ) {
                    window.setTimeout ( idrinth.tier.start, 10000 );
                },
                function ( ) {
                    window.setTimeout ( idrinth.tier.start, 10000 );
                }
        );
    },
    import: function ( data ) {
        'use strict';
        data = JSON.parse ( data );
        if ( data ) {
            idrinth.tier.list = data;
            var create = function ( name, url ) {
                'use strict';
                if ( !idrinth.settings.data.bannedRaids[name] ) {
                    idrinth.settings.data.bannedRaids[name] = false;
                    window.localStorage.setItem ( 'idotd', JSON.stringify ( idrinth.settings.data ) );
                }
                document.getElementById ( 'idrinth-raid-may-join-list' ).appendChild ( idrinth.ui.buildElement ( {
                    name: 'bannedRaids#' + name,
                    rType: '#input',
                    type: 'checkbox',
                    id: 'idrinth-raid-may-join-list-' + name,
                    label: idrinth.text.get ( "raids.disableJoining" ) + name
                } ) );
                document.getElementById ( 'idrinth-raid-may-join-list' ).lastChild.setAttribute ( 'style',
                        'background-image:url(https://dotd.idrinth.de/static/raid-image-service/' + url + '/);' );
            };
            for (var key in data) {
                if ( data[key].name ) {
                    create ( data[key].name, data[key].url );
                }
            }
        } else {
            window.setTimeout ( idrinth.tier.start, 1000 );
        }
    },
    getTierForName: function ( name ) {
        var clearInnerHtml = function ( elem ) {
            elem.innerHTML = '';
        };
        var makeList = function ( list ) {
            var makeField = function ( listKey, difficulty, ic ) {
                var ln = {
                    type: 'td'
                };
                try {
                    ln.styles = idrinth.tier.list[listKey].os[difficulty] === idrinth.tier[listKey][difficulty][ic] ? 'is-os' : '';
                } catch ( E ) {
                    idrinth.core.log ( E.toString ( ) );
                }
                try {
                    ln.content = idrinth.ui.formatNumber ( idrinth.tier.list[listKey][difficulty][ic] ) + ' ' +
                            idrinth.tier.list[listKey].epics[difficulty][ic] + 'E';
                } catch ( E2 ) {
                    idrinth.core.log ( E2.toString ( ) );
                    try {
                        ln.content = idrinth.ui.formatNumber ( idrinth.tier.list[listKey][difficulty][ic] );
                    } catch ( E3 ) {
                        idrinth.core.log ( E3.toString ( ) );
                    }
                }
                return ln;
            };
            var makeRow = function ( title, dataset ) {
                return {
                    type: 'tr',
                    children: [
                        {
                            type: 'th',
                            content: title
                        },
                        {
                            type: 'td',
                            content: idrinth.ui.formatNumber ( dataset.n )
                        },
                        {
                            type: 'td',
                            content: idrinth.ui.formatNumber ( dataset.h )
                        },
                        {
                            type: 'td',
                            content: idrinth.ui.formatNumber ( dataset.l )
                        },
                        {
                            type: 'td',
                            content: idrinth.ui.formatNumber ( dataset.nm )
                        }
                    ]
                };
            };
            var wrapper = document.getElementById ( 'idrinth-tierlist' );
            clearInnerHtml ( wrapper );
            for (var count = list.length - 1; count >= 0; count--) {
                var sub = idrinth.ui.buildElement ( {
                    css: 'tier-wrapper',
                    children: [
                        {
                            type: 'img',
                            attributes: [ {
                                    name: 'src',
                                    value: 'https://dotd.idrinth.de/static/raid-image-service/' + idrinth.tier.list[list[count]].url + '/'
                                } ]
                        },
                        {
                            type: 'strong',
                            content: idrinth.tier.list[list[count]].name
                        },
                        {
                            type: 'button',
                            content: idrinth.text.get ( "tier.tag" ),
                            attributes: [
                                {
                                    name: 'onclick',
                                    value: 'idrinth.tier.addTagged(\'' + list[count].replace ( /'/g, '\\\'' ) + '\');'
                                },
                                {
                                    name: 'type',
                                    value: 'action'
                                }
                            ]
                        },
                        {
                            type: 'span',
                            content: 'AP: ' + idrinth.ui.formatNumber ( idrinth.tier.list[list[count]].ap )
                        },
                        {
                            type: 'table',
                            children: [
                                {
                                    type: 'thead',
                                    children: [
                                        {
                                            type: 'tr',
                                            children: [
                                                {
                                                    type: 'th',
                                                    content: '#'
                                                },
                                                {
                                                    type: 'th',
                                                    content: idrinth.text.get ( "tier.diff.normal" )
                                                },
                                                {
                                                    type: 'th',
                                                    content: idrinth.text.get ( "tier.diff.hard" )
                                                },
                                                {
                                                    type: 'th',
                                                    content: idrinth.text.get ( "tier.diff.legend" )
                                                },
                                                {
                                                    type: 'th',
                                                    content: idrinth.text.get ( "tier.diff.night" )
                                                }
                                            ]
                                        }
                                    ]
                                },
                                {
                                    type: 'tbody',
                                    children: [
                                        makeRow ( 'FS', idrinth.tier.list[list[count]].fs ),
                                        makeRow ( 'OS', idrinth.tier.list[list[count]].os ),
                                        makeRow ( '', {
                                            n: '',
                                            l: '',
                                            h: '',
                                            nm: ''
                                        } )
                                    ]
                                }
                            ]
                        }
                    ]
                } );
                var maxTiers = Math.max ( idrinth.tier.list[list[count]].n.length, idrinth.tier.list[list[count]].h.length, idrinth.tier.list[list[count]].l.length, idrinth.tier.list[list[count]].nm.length );
                for (var ic = 0; ic < maxTiers; ic++) {
                    sub.lastChild.lastChild.appendChild ( idrinth.ui.buildElement ( {
                        type: 'tr',
                        children: [
                            {
                                type: 'th',
                                content: ic + 1
                            },
                            makeField ( list[count], 'n', ic ),
                            makeField ( list[count], 'h', ic ),
                            makeField ( list[count], 'l', ic ),
                            makeField ( list[count], 'nm', ic )
                        ]
                    } ) );
                }
                wrapper.appendChild ( sub );
            }
        };
        if ( !name || name.length === 0 ) {
            clearInnerHtml ( document.getElementById ( 'idrinth-tierlist' ) );
            return;
        }
        var result = [ ];
        var regExp = new RegExp ( name, 'i' );
        for (var key in idrinth.tier.list) {
            if ( key.match ( regExp ) ) {
                result.push ( key );
            }
        }
        makeList ( result );
    }
};<|endoftext|>


<commit_before>import {bookDialogTemplate, bookBasicInfoTemplate, bookDialogChaptersTemplate, bookBibliographyDataTemplate,
    bookEpubDataTemplate, bookPrintDataTemplate,
    bookChapterListTemplate, bookChapterDialogTemplate,
    bookEpubDataCoverTemplate, bookSanityCheckTemplate
} from "./templates"
import {exportMenuModel} from "./menu"
import {bookSanityCheck} from "./sanity_check"
import {ImageSelectionDialog} from "../images/selection_dialog"
import {addAlert, postJson, post, Dialog, findTarget, FileSelector, longFilePath, escapeText, ContentMenu} from "../common"

export class BookActions {

    constructor(bookOverview) {
        bookOverview.mod.actions = this
        this.bookOverview = bookOverview
        this.exportMenu = exportMenuModel()
        this.onSave = []
        this.dialogParts = [
            {
                title: gettext('Basic info'),
                description: gettext('Basic book information'),
                template: bookBasicInfoTemplate
            },
            {
                title: gettext('Chapters'),
                description: gettext('Documents assigned as chapters'),
                template: bookDialogChaptersTemplate
            },
            {
                title: gettext('Bibliography'),
                description: gettext('Bibliography related settings'),
                template: bookBibliographyDataTemplate
            },
            {
                title: gettext('Epub'),
                description: gettext('Epub related settings'),
                template: bookEpubDataTemplate
            },
            {
                title: gettext('Print/PDF'),
                description: gettext('Print related settings'),
                template: bookPrintDataTemplate
            },
            {
                title: gettext('Sanity check'),
                description: gettext('Perform sanity check on book'),
                template: bookSanityCheckTemplate
            }
        ]
    }

    deleteBook(id) {
        const book = this.bookOverview.bookList.find(book => book.id === id)
        if (!book) {
            return Promise.return()
        }

        return post(
            '/api/book/delete/',
            {id}
        ).catch(
            error => {
                addAlert('error', `${gettext('Could not delete book')}: '${longFilePath(book.title, book.path)}'`)
                throw (error)
            }
        ).then(() => {
            addAlert('success', `${gettext('Book has been deleted')}: '${longFilePath(book.title, book.path)}'`)
            this.bookOverview.bookList = this.bookOverview.bookList.filter(book => book.id !== id)
            this.bookOverview.initTable()
        })

    }

    deleteBookDialog(ids) {
        const bookPaths = ids.map(id => {
            const book = this.bookOverview.bookList.find(book => book.id === id)
            return escapeText(longFilePath(book.title, book.path))
        })
        const buttons = [
            {
                text: gettext('Delete'),
                classes: "fw-dark",
                click: () => {
                    Promise.all(ids.map(id => this.deleteBook(id))).then(
                        () => {
                            dialog.close()
                            this.bookOverview.initTable()
                        }
                    )
                }
            },
            {
                type: 'close'
            }
        ]

        const dialog = new Dialog({
            title: gettext('Confirm deletion'),
            id: 'confirmdeletion',
            icon: 'exclamation-triangle',
            height: Math.min(50 + 15 * ids.length, 500),
            body: `
${
                ids.length > 1 ?
                    gettext('Do you really want to delete the following books?') :
                    gettext('Do you really want to delete the following book?')
            }

                ${bookPaths.join('
')}

`,
            buttons
        })
        dialog.open()
    }

    editChapterDialog(chapter, book) {
        const doc = this.bookOverview.documentList.find(doc => doc.id === chapter.text)
        let docTitle = doc.title
        if (!docTitle.length) {
            docTitle = gettext('Untitled')
        }

        const buttons = [
            {
                text: gettext('Submit'),
                classes: "fw-dark",
                click: () => {
                    chapter.part = document.getElementById('book-chapter-part').value
                    document.getElementById('book-chapter-list').innerHTML =
                        bookChapterListTemplate({
                            book,
                            documentList: this.bookOverview.documentList
                        })
                    dialog.close()
                }
            },
            {
                type: 'cancel'
            }
        ]

        const dialog = new Dialog({
            title: `${gettext('Edit Chapter')}: ${chapter.number}. ${docTitle}`,
            body: bookChapterDialogTemplate({chapter}),
            width: 300,
            height: 100,
            buttons
        })
        dialog.open()
    }

    saveBook(book, oldBook = false) {
        if (book.rights !== 'write') {
            return Promise.resolve()
        }
        book.title = document.getElementById('book-title').value
        book.metadata.author = document.getElementById('book-metadata-author').value
        book.metadata.subtitle = document.getElementById('book-metadata-subtitle').value
        book.metadata.copyright = document.getElementById('book-metadata-copyright').value
        book.metadata.publisher = document.getElementById('book-metadata-publisher').value
        book.metadata.keywords = document.getElementById('book-metadata-keywords').value
        book.path = oldBook?.path || this.bookOverview.path
        const bookData = Object.assign({}, book)
        delete bookData.cover_image_data

        return postJson(
            '/api/book/save/',
            {book: JSON.stringify(bookData)}
        ).catch(
            error => {
                addAlert('error', gettext('The book could not be saved'))
                throw (error)
            }
        ).then(
            ({status, json}) => {
                if (status == 201) {
                    book.id = json.id
                    book.added = json.added
                }
                book.updated = json.updated
                if (oldBook) {
                    this.bookOverview.bookList = this.bookOverview.bookList.filter(
                        book => book !== oldBook
                    )
                }
                this.bookOverview.bookList.push(book)
                this.bookOverview.initTable()
                return Promise.all(this.onSave.map(method => method(book)))
            }
        )
    }

    copyBook(oldBook) {
        const book = Object.assign({}, oldBook)
        book.is_owner = true
        book.owner = this.bookOverview.user
        book.rights = 'write'
        const path = longFilePath(oldBook.title, oldBook.path, `${gettext('Copy of')} `)
        return postJson(
            '/api/book/copy/',
            {id: book.id, path}
        ).catch(
            error => {
                addAlert('error', gettext('The book could not be copied'))
                throw (error)
            }
        ).then(
            ({json}) => {
                book.id = json['id']
                book.path = json['path']
                this.bookOverview.bookList.push(book)
                this.bookOverview.initTable()
            }
        )
    }

    createBookDialog(bookId, imageDB) {
        let title, book, oldBook
        const bookImageDB = {db: {}}

        if (bookId === 0) {
            title = gettext('Create Book')
            book = {
                title: '',
                id: 0,
                chapters: [],
                is_owner: true,
                owner: this.bookOverview.user,
                rights: 'write',
                metadata: {
                    author: '',
                    subtitle: '',
                    publisher: '',
                    copyright: '',
                    keywords: ''
                },
                settings: {
                    bibliography_header: gettext('Bibliography'),
                    citationstyle: 'apa',
                    book_style: this.bookOverview.styles[0] ? this.bookOverview.styles[0].slug : false,
                    papersize: 'octavo'
                }
            }
        } else {
            oldBook = this.bookOverview.bookList.find(book => book.id === bookId)
            book = Object.assign({}, oldBook)

            if (book.cover_image && !imageDB.db[book.cover_image]) {
                // The cover image is not in the current user's image DB --
                // it was either deleted or another user originally added
                // it. As we don't do anything fancy with it, we simply add
                // the current cover image to the DB locally so that image
                // selection works as expected.
                bookImageDB.db[book.cover_image] = book.cover_image_data
            }
            title = gettext('Edit Book')
        }
        const body = bookDialogTemplate({
            title,
            dialogParts: this.dialogParts,
            bookInfo: {
                book,
                documentList: this.bookOverview.documentList,
                citationStyles: this.bookOverview.citationStyles,
                bookStyleList: this.bookOverview.styles,
                imageDB: {db: Object.assign({}, imageDB.db, bookImageDB.db)}
            }
        })

        const buttons = []
        buttons.push({
            text: gettext('Export'),
            dropdown: true,
            classes: "fw-dark",
            click: event => {
                const contentMenu = new ContentMenu({
                    page: {
                        saveBook: () => this.saveBook(book, oldBook),
                        book,
                        overview: this.bookOverview
                    },
                    menu: this.exportMenu,
                    menuPos: {X: event.pageX, Y: event.pageY},
                    width: 250
                })
                return contentMenu.open()
            }
        })
        if (book.rights === 'write') {
            buttons.push({
                text: gettext('Submit'),
                classes: "fw-dark",
                click: () => {
                    return this.saveBook(book, oldBook).then(
                        () => dialog.close()
                    )
                }
            })
            buttons.push({type: 'cancel'})
        } else {
            buttons.push({type: 'close'})
        }

        const dialog = new Dialog({
            width: 840,
            height: 460,
            title,
            body,
            buttons
        })
        dialog.open()

        // Hide all but first tab
        dialog.dialogEl.querySelectorAll('#bookoptions-tab .tab-content').forEach((el, index) => {
            if (index) {
                el.style.display = 'none'
            }
        })
        let fileSelector
        if (book.rights === 'write') {
            fileSelector = new FileSelector({
                dom: dialog.dialogEl.querySelector('#book-document-list'),
                files: this.bookOverview.documentList,
                multiSelect: true,
                selectFolders: false,
            })
            fileSelector.init()
        }

        // Handle tab link clicking
        dialog.dialogEl.querySelectorAll('#bookoptions-tab .tab-link a').forEach(el => el.addEventListener('click', event => {
            event.preventDefault()

            el.parentNode.parentNode.querySelectorAll('.tab-link.current-tab').forEach(el => el.classList.remove('current-tab'))
            el.parentNode.classList.add('current-tab')

            const link = el.getAttribute('href')
            dialog.dialogEl.querySelectorAll('#bookoptions-tab .tab-content').forEach(el => {
                if (el.matches(link)) {
                    el.style.display = ''
                } else {
                    el.style.display = 'none'
                }
            })

        }))

        dialog.dialogEl.addEventListener('click', event => {
            const el = {}
            let chapterId, chapter
            switch (true) {
            case findTarget(event, '.book-sort-up', el): {
                chapterId = parseInt(el.target.dataset.id)
                chapter = book.chapters.find(
                    chapter => chapter.text === chapterId
                )

                const higherChapter = book.chapters.find(
                    bChapter => bChapter.number === (chapter.number - 1)
                )
                chapter.number--
                higherChapter.number++
                document.getElementById('book-chapter-list').innerHTML =
                        bookChapterListTemplate({
                            book,
                            documentList: this.bookOverview.documentList
                        })
                break
            }
            case findTarget(event, '.book-sort-down', el): {
                chapterId = parseInt(el.target.dataset.id)
                chapter = book.chapters.find(
                    chapter => chapter.text === chapterId
                )

                const lowerChapter = book.chapters.find(
                    bChapter => bChapter.number === (chapter.number + 1)
                )

                chapter.number++
                lowerChapter.number--
                document.getElementById('book-chapter-list').innerHTML =
                        bookChapterListTemplate({
                            book,
                            documentList: this.bookOverview.documentList
                        })
                break
            }
            case findTarget(event, '.delete-chapter', el):
                chapterId = parseInt(el.target.dataset.id)
                chapter = book.chapters.find(
                    chapter => chapter.text === chapterId
                )

                book.chapters.forEach(bChapter => {
                    if (bChapter.number > chapter.number) {
                        bChapter.number--
                    }
                })

                book.chapters = book.chapters.filter(bChapter => bChapter !== chapter)

                document.getElementById('book-chapter-list').innerHTML = bookChapterListTemplate({
                    book,
                    documentList: this.bookOverview.documentList
                })

                break
            case findTarget(event, '#add-chapter', el): {
                fileSelector.selected.forEach(entry => {
                    const chapNums = book.chapters.map(chapter => chapter.number),
                        number = chapNums.length ?
                            Math.max(...chapNums) + 1 :
                            1
                    book.chapters.push({
                        text: entry.file.id,
                        number,
                        part: ''
                    })
                })
                fileSelector.deselectAll()

                document.getElementById('book-chapter-list').innerHTML = bookChapterListTemplate({
                    book,
                    documentList: this.bookOverview.documentList
                })
                break
            }
            case findTarget(event, '.edit-chapter', el):
                chapterId = parseInt(el.target.dataset.id)
                chapter = book.chapters.find(
                    chapter => chapter.text === chapterId
                )
                this.editChapterDialog(chapter, book)
                break
            case findTarget(event, '#select-cover-image-button', el): {
                const imageSelection = new ImageSelectionDialog(
                    bookImageDB,
                    imageDB,
                    book.cover_image,
                    this.bookOverview
                )

                imageSelection.init().then(
                    image => {
                        if (!image) {
                            delete book.cover_image
                        } else {
                            book.cover_image = image.id
                            book.cover_image_data = image.db === 'user' ?
                                imageDB.db[image.id] : bookImageDB.db[image.id]
                        }
                        document.getElementById('figure-preview-row').innerHTML = bookEpubDataCoverTemplate({
                            imageDB: {db: Object.assign({}, imageDB.db, bookImageDB.db)},
                            book
                        })
                    }
                )
                break
            }
            case findTarget(event, '#remove-cover-image-button', el):
                delete book.cover_image
                document.getElementById('figure-preview-row').innerHTML = bookEpubDataCoverTemplate({
                    book,
                    imageDB: {db: {}} // We just deleted the cover image, so we don't need a full DB
                })
                break
            case findTarget(event, '#perform-sanity-check-button', el): {
                this.saveBook(book, oldBook).then(
                    () => bookSanityCheck(book, this.bookOverview.documentList, this.bookOverview.schema)
                ).then(
                    sanityCheckOutputHTML => document.getElementById('sanity-check-output').innerHTML = sanityCheckOutputHTML
                )
                break
            }
            default:
                break
            }
        })

        dialog.dialogEl.querySelector('#book-settings-citationstyle').addEventListener('change', event => {
            book.settings.citationstyle = event.target.value
        })

        dialog.dialogEl.querySelector('#book-settings-bookstyle').addEventListener('change', event => {
            book.settings.book_style = event.target.value
        })

        dialog.dialogEl.querySelector('#book-settings-papersize').addEventListener('change', event => {
            book.settings.papersize = event.target.value
        })
    }
}
<commit_msg>don't duplicate book entries<commit_after>import {bookDialogTemplate, bookBasicInfoTemplate, bookDialogChaptersTemplate, bookBibliographyDataTemplate,
    bookEpubDataTemplate, bookPrintDataTemplate,
    bookChapterListTemplate, bookChapterDialogTemplate,
    bookEpubDataCoverTemplate, bookSanityCheckTemplate
} from "./templates"
import {exportMenuModel} from "./menu"
import {bookSanityCheck} from "./sanity_check"
import {ImageSelectionDialog} from "../images/selection_dialog"
import {addAlert, postJson, post, Dialog, findTarget, FileSelector, longFilePath, escapeText, ContentMenu} from "../common"

export class BookActions {

    constructor(bookOverview) {
        bookOverview.mod.actions = this
        this.bookOverview = bookOverview
        this.exportMenu = exportMenuModel()
        this.onSave = []
        this.dialogParts = [
            {
                title: gettext('Basic info'),
                description: gettext('Basic book information'),
                template: bookBasicInfoTemplate
            },
            {
                title: gettext('Chapters'),
                description: gettext('Documents assigned as chapters'),
                template: bookDialogChaptersTemplate
            },
            {
                title: gettext('Bibliography'),
                description: gettext('Bibliography related settings'),
                template: bookBibliographyDataTemplate
            },
            {
                title: gettext('Epub'),
                description: gettext('Epub related settings'),
                template: bookEpubDataTemplate
            },
            {
                title: gettext('Print/PDF'),
                description: gettext('Print related settings'),
                template: bookPrintDataTemplate
            },
            {
                title: gettext('Sanity check'),
                description: gettext('Perform sanity check on book'),
                template: bookSanityCheckTemplate
            }
        ]
    }

    deleteBook(id) {
        const book = this.bookOverview.bookList.find(book => book.id === id)
        if (!book) {
            return Promise.return()
        }

        return post(
            '/api/book/delete/',
            {id}
        ).catch(
            error => {
                addAlert('error', `${gettext('Could not delete book')}: '${longFilePath(book.title, book.path)}'`)
                throw (error)
            }
        ).then(() => {
            addAlert('success', `${gettext('Book has been deleted')}: '${longFilePath(book.title, book.path)}'`)
            this.bookOverview.bookList = this.bookOverview.bookList.filter(book => book.id !== id)
            this.bookOverview.initTable()
        })

    }

    deleteBookDialog(ids) {
        const bookPaths = ids.map(id => {
            const book = this.bookOverview.bookList.find(book => book.id === id)
            return escapeText(longFilePath(book.title, book.path))
        })
        const buttons = [
            {
                text: gettext('Delete'),
                classes: "fw-dark",
                click: () => {
                    Promise.all(ids.map(id => this.deleteBook(id))).then(
                        () => {
                            dialog.close()
                            this.bookOverview.initTable()
                        }
                    )
                }
            },
            {
                type: 'close'
            }
        ]

        const dialog = new Dialog({
            title: gettext('Confirm deletion'),
            id: 'confirmdeletion',
            icon: 'exclamation-triangle',
            height: Math.min(50 + 15 * ids.length, 500),
            body: `
${
                ids.length > 1 ?
                    gettext('Do you really want to delete the following books?') :
                    gettext('Do you really want to delete the following book?')
            }

                ${bookPaths.join('
')}

`,
            buttons
        })
        dialog.open()
    }

    editChapterDialog(chapter, book) {
        const doc = this.bookOverview.documentList.find(doc => doc.id === chapter.text)
        let docTitle = doc.title
        if (!docTitle.length) {
            docTitle = gettext('Untitled')
        }

        const buttons = [
            {
                text: gettext('Submit'),
                classes: "fw-dark",
                click: () => {
                    chapter.part = document.getElementById('book-chapter-part').value
                    document.getElementById('book-chapter-list').innerHTML =
                        bookChapterListTemplate({
                            book,
                            documentList: this.bookOverview.documentList
                        })
                    dialog.close()
                }
            },
            {
                type: 'cancel'
            }
        ]

        const dialog = new Dialog({
            title: `${gettext('Edit Chapter')}: ${chapter.number}. ${docTitle}`,
            body: bookChapterDialogTemplate({chapter}),
            width: 300,
            height: 100,
            buttons
        })
        dialog.open()
    }

    saveBook(book, oldBookId = false) {
        const oldBook = oldBookId ? this.bookOverview.bookList.find(
            book => book.id === oldBookId
        ) : false
        if (book.rights !== 'write') {
            return Promise.resolve()
        }
        book.title = document.getElementById('book-title').value
        book.metadata.author = document.getElementById('book-metadata-author').value
        book.metadata.subtitle = document.getElementById('book-metadata-subtitle').value
        book.metadata.copyright = document.getElementById('book-metadata-copyright').value
        book.metadata.publisher = document.getElementById('book-metadata-publisher').value
        book.metadata.keywords = document.getElementById('book-metadata-keywords').value
        book.path = oldBook?.path || this.bookOverview.path
        const bookData = Object.assign({}, book)
        delete bookData.cover_image_data

        return postJson(
            '/api/book/save/',
            {book: JSON.stringify(bookData)}
        ).catch(
            error => {
                addAlert('error', gettext('The book could not be saved'))
                throw (error)
            }
        ).then(
            ({status, json}) => {
                if (status == 201) {
                    book.id = json.id
                    book.added = json.added
                }
                book.updated = json.updated
                if (oldBookId) {
                    this.bookOverview.bookList = this.bookOverview.bookList.filter(
                        book => book.id !== oldBookId
                    )
                }
                this.bookOverview.bookList.push(book)
                this.bookOverview.initTable()
                return Promise.all(this.onSave.map(method => method(book)))
            }
        )
    }

    copyBook(oldBook) {
        const book = Object.assign({}, oldBook)
        book.is_owner = true
        book.owner = this.bookOverview.user
        book.rights = 'write'
        const path = longFilePath(oldBook.title, oldBook.path, `${gettext('Copy of')} `)
        return postJson(
            '/api/book/copy/',
            {id: book.id, path}
        ).catch(
            error => {
                addAlert('error', gettext('The book could not be copied'))
                throw (error)
            }
        ).then(
            ({json}) => {
                book.id = json['id']
                book.path = json['path']
                this.bookOverview.bookList.push(book)
                this.bookOverview.initTable()
            }
        )
    }

    createBookDialog(bookId, imageDB) {
        let title, book, oldBookId
        const bookImageDB = {db: {}}

        if (bookId === 0) {
            title = gettext('Create Book')
            book = {
                title: '',
                id: 0,
                chapters: [],
                is_owner: true,
                owner: this.bookOverview.user,
                rights: 'write',
                metadata: {
                    author: '',
                    subtitle: '',
                    publisher: '',
                    copyright: '',
                    keywords: ''
                },
                settings: {
                    bibliography_header: gettext('Bibliography'),
                    citationstyle: 'apa',
                    book_style: this.bookOverview.styles[0] ? this.bookOverview.styles[0].slug : false,
                    papersize: 'octavo'
                }
            }
        } else {
            const oldBook = this.bookOverview.bookList.find(book => book.id === bookId)
            book = Object.assign({}, oldBook)
            oldBookId = oldBook.id

            if (book.cover_image && !imageDB.db[book.cover_image]) {
                // The cover image is not in the current user's image DB --
                // it was either deleted or another user originally added
                // it. As we don't do anything fancy with it, we simply add
                // the current cover image to the DB locally so that image
                // selection works as expected.
                bookImageDB.db[book.cover_image] = book.cover_image_data
            }
            title = gettext('Edit Book')
        }
        const body = bookDialogTemplate({
            title,
            dialogParts: this.dialogParts,
            bookInfo: {
                book,
                documentList: this.bookOverview.documentList,
                citationStyles: this.bookOverview.citationStyles,
                bookStyleList: this.bookOverview.styles,
                imageDB: {db: Object.assign({}, imageDB.db, bookImageDB.db)}
            }
        })

        const buttons = []
        buttons.push({
            text: gettext('Export'),
            dropdown: true,
            classes: "fw-dark",
            click: event => {
                const contentMenu = new ContentMenu({
                    page: {
                        saveBook: () => this.saveBook(book, oldBookId),
                        book,
                        overview: this.bookOverview
                    },
                    menu: this.exportMenu,
                    menuPos: {X: event.pageX, Y: event.pageY},
                    width: 250
                })
                return contentMenu.open()
            }
        })
        if (book.rights === 'write') {
            buttons.push({
                text: gettext('Submit'),
                classes: "fw-dark",
                click: () => {
                    return this.saveBook(book, oldBookId).then(
                        () => dialog.close()
                    )
                }
            })
            buttons.push({type: 'cancel'})
        } else {
            buttons.push({type: 'close'})
        }

        const dialog = new Dialog({
            width: 840,
            height: 460,
            title,
            body,
            buttons
        })
        dialog.open()

        // Hide all but first tab
        dialog.dialogEl.querySelectorAll('#bookoptions-tab .tab-content').forEach((el, index) => {
            if (index) {
                el.style.display = 'none'
            }
        })
        let fileSelector
        if (book.rights === 'write') {
            fileSelector = new FileSelector({
                dom: dialog.dialogEl.querySelector('#book-document-list'),
                files: this.bookOverview.documentList,
                multiSelect: true,
                selectFolders: false,
            })
            fileSelector.init()
        }

        // Handle tab link clicking
        dialog.dialogEl.querySelectorAll('#bookoptions-tab .tab-link a').forEach(el => el.addEventListener('click', event => {
            event.preventDefault()

            el.parentNode.parentNode.querySelectorAll('.tab-link.current-tab').forEach(el => el.classList.remove('current-tab'))
            el.parentNode.classList.add('current-tab')

            const link = el.getAttribute('href')
            dialog.dialogEl.querySelectorAll('#bookoptions-tab .tab-content').forEach(el => {
                if (el.matches(link)) {
                    el.style.display = ''
                } else {
                    el.style.display = 'none'
                }
            })

        }))

        dialog.dialogEl.addEventListener('click', event => {
            const el = {}
            let chapterId, chapter
            switch (true) {
            case findTarget(event, '.book-sort-up', el): {
                chapterId = parseInt(el.target.dataset.id)
                chapter = book.chapters.find(
                    chapter => chapter.text === chapterId
                )

                const higherChapter = book.chapters.find(
                    bChapter => bChapter.number === (chapter.number - 1)
                )
                chapter.number--
                higherChapter.number++
                document.getElementById('book-chapter-list').innerHTML =
                        bookChapterListTemplate({
                            book,
                            documentList: this.bookOverview.documentList
                        })
                break
            }
            case findTarget(event, '.book-sort-down', el): {
                chapterId = parseInt(el.target.dataset.id)
                chapter = book.chapters.find(
                    chapter => chapter.text === chapterId
                )

                const lowerChapter = book.chapters.find(
                    bChapter => bChapter.number === (chapter.number + 1)
                )

                chapter.number++
                lowerChapter.number--
                document.getElementById('book-chapter-list').innerHTML =
                        bookChapterListTemplate({
                            book,
                            documentList: this.bookOverview.documentList
                        })
                break
            }
            case findTarget(event, '.delete-chapter', el):
                chapterId = parseInt(el.target.dataset.id)
                chapter = book.chapters.find(
                    chapter => chapter.text === chapterId
                )

                book.chapters.forEach(bChapter => {
                    if (bChapter.number > chapter.number) {
                        bChapter.number--
                    }
                })

                book.chapters = book.chapters.filter(bChapter => bChapter !== chapter)

                document.getElementById('book-chapter-list').innerHTML = bookChapterListTemplate({
                    book,
                    documentList: this.bookOverview.documentList
                })

                break
            case findTarget(event, '#add-chapter', el): {
                fileSelector.selected.forEach(entry => {
                    const chapNums = book.chapters.map(chapter => chapter.number),
                        number = chapNums.length ?
                            Math.max(...chapNums) + 1 :
                            1
                    book.chapters.push({
                        text: entry.file.id,
                        number,
                        part: ''
                    })
                })
                fileSelector.deselectAll()

                document.getElementById('book-chapter-list').innerHTML = bookChapterListTemplate({
                    book,
                    documentList: this.bookOverview.documentList
                })
                break
            }
            case findTarget(event, '.edit-chapter', el):
                chapterId = parseInt(el.target.dataset.id)
                chapter = book.chapters.find(
                    chapter => chapter.text === chapterId
                )
                this.editChapterDialog(chapter, book)
                break
            case findTarget(event, '#select-cover-image-button', el): {
                const imageSelection = new ImageSelectionDialog(
                    bookImageDB,
                    imageDB,
                    book.cover_image,
                    this.bookOverview
                )

                imageSelection.init().then(
                    image => {
                        if (!image) {
                            delete book.cover_image
                        } else {
                            book.cover_image = image.id
                            book.cover_image_data = image.db === 'user' ?
                                imageDB.db[image.id] : bookImageDB.db[image.id]
                        }
                        document.getElementById('figure-preview-row').innerHTML = bookEpubDataCoverTemplate({
                            imageDB: {db: Object.assign({}, imageDB.db, bookImageDB.db)},
                            book
                        })
                    }
                )
                break
            }
            case findTarget(event, '#remove-cover-image-button', el):
                delete book.cover_image
                document.getElementById('figure-preview-row').innerHTML = bookEpubDataCoverTemplate({
                    book,
                    imageDB: {db: {}} // We just deleted the cover image, so we don't need a full DB
                })
                break
            case findTarget(event, '#perform-sanity-check-button', el): {
                this.saveBook(book, oldBookId).then(
                    () => bookSanityCheck(book, this.bookOverview.documentList, this.bookOverview.schema)
                ).then(
                    sanityCheckOutputHTML => document.getElementById('sanity-check-output').innerHTML = sanityCheckOutputHTML
                )
                break
            }
            default:
                break
            }
        })

        dialog.dialogEl.querySelector('#book-settings-citationstyle').addEventListener('change', event => {
            book.settings.citationstyle = event.target.value
        })

        dialog.dialogEl.querySelector('#book-settings-bookstyle').addEventListener('change', event => {
            book.settings.book_style = event.target.value
        })

        dialog.dialogEl.querySelector('#book-settings-papersize').addEventListener('change', event => {
            book.settings.papersize = event.target.value
        })
    }
}
<|endoftext|>


<commit_before>idrinth.tier = {
    list: { },
    addTagged: function ( name ) {
        var isValidParameter = function ( name ) {
            return name && idrinth.tier.list.hasOwnProperty ( name ) && typeof idrinth.tier.list[name] !== 'function' && !document.getElementById ( 'idrinth-tier-box-' + name );
        };
        var isFreeSlot = function ( key ) {
            return idrinth.tier.taggedSlots.hasOwnProperty ( key ) && typeof key !== 'function' && idrinth.tier.taggedSlots[key] === null;
        };
        if ( !isValidParameter ( name ) ) {
            return;
        }
        var boss = this.list[name];
        var make = function ( x, name ) {
            var makeElement = function ( label, number, description ) {
                return {
                    content: label + ' ' + idrinth.ui.formatNumber ( number ),
                    attributes: [ {
                            name: 'title',
                            value: description
                        } ]
                };
            };
            var info = [
                makeElement ( 'FS', boss.fs.nm, 'Fair share' ),
                makeElement ( 'AP', boss.ap, 'Achievement point damage' )
            ];
            if ( boss.os && boss.os.nm ) {
                info.push ( makeElement ( 'OS', boss.os.nm, 'Optimal share' ) );
                info.unshift ( makeElement ( 'MA', boss.nm[boss.nm.length - 1], 'Maximum/highest tier' ) );
                info.unshift ( makeElement ( 'MI', boss.nm[0], 'Minimum/lowest tier' ) );
            }
            info.unshift (
                    {
                        type: 'strong',
                        content: boss.name.replace ( /\(.*$/, '' )
                    } );
            idrinth.tier.taggedSlots[x] = idrinth.ui.buildElement (
                    {
                        id: 'idrinth-tier-box-' + name,
                        css: 'idrinth-hovering-box idrinth-tier-box',
                        children: [ {
                                children: info
                            } ],
                        attributes: [
                            {
                                name: 'title',
                                value: 'click to close'
                            },
                            {
                                name: 'onclick',
                                value: 'idrinth.ui.removeElement(this.id);idrinth.tier.taggedSlots[\'' + x + '\']=null;'
                            },
                            {
                                name: 'style',
                                value: 'left:' + x + 'px;background-image: url(https://dotd.idrinth.de/static/raid-image-service/' + boss.url + '/);'
                            }
                        ]
                    }
            );
            idrinth.ui.body.appendChild ( idrinth.tier.taggedSlots[x] );
        };
        for (var key in this.taggedSlots) {
            if ( isFreeSlot ( key ) ) {
                return make ( key, name );
            }
        }
        idrinth.core.alert ( idrinth.text.get ( "tier.maxBoxes" ) );
    },
    taggedSlots: { },
    start: function ( ) {
        'use strict';
        var pos = 1;
        while ( 0 < window.innerWidth - 140 * ( pos + 1 ) ) {
            this.taggedSlots[( pos * 140 ).toString ( )] = null;
            pos++;
        }
        idrinth.core.ajax.runHome (
                'tier-service/',
                function ( text ) {
                    idrinth.tier.import ( text );
                },
                function ( ) {
                    window.setTimeout ( idrinth.tier.start, 10000 );
                },
                function ( ) {
                    window.setTimeout ( idrinth.tier.start, 10000 );
                }
        );
    },
    import: function ( data ) {
        'use strict';
        data = JSON.parse ( data );
        if ( data ) {
            idrinth.tier.list = data;
            var create = function ( name, url ) {
                'use strict';
                if ( !idrinth.settings.data.bannedRaids[name] ) {
                    idrinth.settings.data.bannedRaids[name] = false;
                    window.localStorage.setItem ( 'idotd', JSON.stringify ( idrinth.settings.data ) );
                }
                document.getElementById ( 'idrinth-raid-may-join-list' ).appendChild ( idrinth.ui.buildElement ( {
                    name: 'bannedRaids#' + name,
                    rType: '#input',
                    type: 'checkbox',
                    id: 'idrinth-raid-may-join-list-' + name,
                    label: idrinth.text.get ( "raids.disableJoining" ) + name
                } ) );
                document.getElementById ( 'idrinth-raid-may-join-list' ).lastChild.setAttribute ( 'style',
                        'background-image:url(https://dotd.idrinth.de/static/raid-image-service/' + url + '/);' );
            };
            for (var key in data) {
                if ( data[key].name ) {
                    create ( data[key].name, data[key].url );
                }
            }
        } else {
            window.setTimeout ( idrinth.tier.start, 1000 );
        }
    },
    getTierForName: function ( name ) {
        var clearInnerHtml = function ( elem ) {
            elem.innerHTML = '';
        };
        var makeList = function ( list ) {
            var makeField = function ( listKey, difficulty, ic ) {
                var ln = {
                    type: 'td'
                };
                try {
                    ln.styles = idrinth.tier.list[listKey].os[difficulty] === idrinth.tier[listKey][difficulty][ic] ? 'is-os' : '';
                } catch ( E ) {
                    idrinth.core.log ( E.toString ( ) );
                }
                try {
                    ln.content = idrinth.ui.formatNumber ( idrinth.tier.list[listKey][difficulty][ic] ) + ' ' +
                            idrinth.tier.list[listKey].epics[difficulty][ic] + 'E';
                } catch ( E2 ) {
                    idrinth.core.log ( E2.toString ( ) );
                    try {
                        ln.content = idrinth.ui.formatNumber ( idrinth.tier.list[listKey][difficulty][ic] );
                    } catch ( E3 ) {
                        idrinth.core.log ( E3.toString ( ) );
                    }
                }
                return ln;
            };
            var makeRow = function ( title, dataset ) {
                return {
                    type: 'tr',
                    children: [
                        {
                            type: 'th',
                            content: title
                        },
                        {
                            type: 'td',
                            content: idrinth.ui.formatNumber ( dataset.n )
                        },
                        {
                            type: 'td',
                            content: idrinth.ui.formatNumber ( dataset.h )
                        },
                        {
                            type: 'td',
                            content: idrinth.ui.formatNumber ( dataset.l )
                        },
                        {
                            type: 'td',
                            content: idrinth.ui.formatNumber ( dataset.nm )
                        }
                    ]
                };
            };
            var wrapper = document.getElementById ( 'idrinth-tierlist' );
            clearInnerHtml ( wrapper );
            for (var count = list.length - 1; count >= 0; count--) {
                var sub = idrinth.ui.buildElement ( {
                    css: 'tier-wrapper',
                    children: [
                        {
                            type: 'img',
                            attributes: [ {
                                    name: 'src',
                                    value: 'https://dotd.idrinth.de/static/raid-image-service/' + idrinth.tier.list[list[count]].url + '/'
                                } ]
                        },
                        {
                            type: 'strong',
                            content: idrinth.tier.list[list[count]].name
                        },
                        {
                            type: 'button',
                            content: idrinth.text.get ( "tier.tag" ),
                            attributes: [
                                {
                                    name: 'onclick',
                                    value: 'idrinth.tier.addTagged(\'' + list[count].replace ( /'/g, '\\\'' ) + '\');'
                                },
                                {
                                    name: 'type',
                                    value: 'action'
                                }
                            ]
                        },
                        {
                            type: 'span',
                            content: idrinth.tier.list[list[count]].types.join(', ')

                        },
                        {
                            type: 'span',
                            content: 'AP: ' + idrinth.ui.formatNumber ( idrinth.tier.list[list[count]].ap )
                        },
                        {
                            type: 'table',
                            children: [
                                {
                                    type: 'thead',
                                    children: [
                                        {
                                            type: 'tr',
                                            children: [
                                                {
                                                    type: 'th',
                                                    content: '#'
                                                },
                                                {
                                                    type: 'th',
                                                    content: idrinth.text.get ( "tier.diff.normal" )
                                                },
                                                {
                                                    type: 'th',
                                                    content: idrinth.text.get ( "tier.diff.hard" )
                                                },
                                                {
                                                    type: 'th',
                                                    content: idrinth.text.get ( "tier.diff.legend" )
                                                },
                                                {
                                                    type: 'th',
                                                    content: idrinth.text.get ( "tier.diff.night" )
                                                }
                                            ]
                                        }
                                    ]
                                },
                                {
                                    type: 'tbody',
                                    children: [
                                        makeRow ( 'FS', idrinth.tier.list[list[count]].fs ),
                                        makeRow ( 'OS', idrinth.tier.list[list[count]].os ),
                                        makeRow ( '', {
                                            n: '',
                                            l: '',
                                            h: '',
                                            nm: ''
                                        } )
                                    ]
                                }
                            ]
                        }
                    ]
                } );
                var maxTiers = Math.max ( idrinth.tier.list[list[count]].n.length, idrinth.tier.list[list[count]].h.length, idrinth.tier.list[list[count]].l.length, idrinth.tier.list[list[count]].nm.length );
                for (var ic = 0; ic < maxTiers; ic++) {
                    sub.lastChild.lastChild.appendChild ( idrinth.ui.buildElement ( {
                        type: 'tr',
                        children: [
                            {
                                type: 'th',
                                content: ic + 1
                            },
                            makeField ( list[count], 'n', ic ),
                            makeField ( list[count], 'h', ic ),
                            makeField ( list[count], 'l', ic ),
                            makeField ( list[count], 'nm', ic )
                        ]
                    } ) );
                }
                wrapper.appendChild ( sub );
            }
        };
        if ( !name || name.length === 0 ) {
            clearInnerHtml ( document.getElementById ( 'idrinth-tierlist' ) );
            return;
        }
        var result = [ ];
        var regExp = new RegExp ( name, 'i' );
        for (var key in idrinth.tier.list) {
            if ( key.match ( regExp ) ) {
                result.push ( key );
            }
        }
        makeList ( result );
    }
};<commit_msg>Moved types under the boss name, as shown in the example Removed white space<commit_after>idrinth.tier = {
    list: { },
    addTagged: function ( name ) {
        var isValidParameter = function ( name ) {
            return name && idrinth.tier.list.hasOwnProperty ( name ) && typeof idrinth.tier.list[name] !== 'function' && !document.getElementById ( 'idrinth-tier-box-' + name );
        };
        var isFreeSlot = function ( key ) {
            return idrinth.tier.taggedSlots.hasOwnProperty ( key ) && typeof key !== 'function' && idrinth.tier.taggedSlots[key] === null;
        };
        if ( !isValidParameter ( name ) ) {
            return;
        }
        var boss = this.list[name];
        var make = function ( x, name ) {
            var makeElement = function ( label, number, description ) {
                return {
                    content: label + ' ' + idrinth.ui.formatNumber ( number ),
                    attributes: [ {
                            name: 'title',
                            value: description
                        } ]
                };
            };
            var info = [
                makeElement ( 'FS', boss.fs.nm, 'Fair share' ),
                makeElement ( 'AP', boss.ap, 'Achievement point damage' )
            ];
            if ( boss.os && boss.os.nm ) {
                info.push ( makeElement ( 'OS', boss.os.nm, 'Optimal share' ) );
                info.unshift ( makeElement ( 'MA', boss.nm[boss.nm.length - 1], 'Maximum/highest tier' ) );
                info.unshift ( makeElement ( 'MI', boss.nm[0], 'Minimum/lowest tier' ) );
            }
            info.unshift (
                    {
                        type: 'strong',
                        content: boss.name.replace ( /\(.*$/, '' )
                    } );
            idrinth.tier.taggedSlots[x] = idrinth.ui.buildElement (
                    {
                        id: 'idrinth-tier-box-' + name,
                        css: 'idrinth-hovering-box idrinth-tier-box',
                        children: [ {
                                children: info
                            } ],
                        attributes: [
                            {
                                name: 'title',
                                value: 'click to close'
                            },
                            {
                                name: 'onclick',
                                value: 'idrinth.ui.removeElement(this.id);idrinth.tier.taggedSlots[\'' + x + '\']=null;'
                            },
                            {
                                name: 'style',
                                value: 'left:' + x + 'px;background-image: url(https://dotd.idrinth.de/static/raid-image-service/' + boss.url + '/);'
                            }
                        ]
                    }
            );
            idrinth.ui.body.appendChild ( idrinth.tier.taggedSlots[x] );
        };
        for (var key in this.taggedSlots) {
            if ( isFreeSlot ( key ) ) {
                return make ( key, name );
            }
        }
        idrinth.core.alert ( idrinth.text.get ( "tier.maxBoxes" ) );
    },
    taggedSlots: { },
    start: function ( ) {
        'use strict';
        var pos = 1;
        while ( 0 < window.innerWidth - 140 * ( pos + 1 ) ) {
            this.taggedSlots[( pos * 140 ).toString ( )] = null;
            pos++;
        }
        idrinth.core.ajax.runHome (
                'tier-service/',
                function ( text ) {
                    idrinth.tier.import ( text );
                },
                function ( ) {
                    window.setTimeout ( idrinth.tier.start, 10000 );
                },
                function ( ) {
                    window.setTimeout ( idrinth.tier.start, 10000 );
                }
        );
    },
    import: function ( data ) {
        'use strict';
        data = JSON.parse ( data );
        if ( data ) {
            idrinth.tier.list = data;
            var create = function ( name, url ) {
                'use strict';
                if ( !idrinth.settings.data.bannedRaids[name] ) {
                    idrinth.settings.data.bannedRaids[name] = false;
                    window.localStorage.setItem ( 'idotd', JSON.stringify ( idrinth.settings.data ) );
                }
                document.getElementById ( 'idrinth-raid-may-join-list' ).appendChild ( idrinth.ui.buildElement ( {
                    name: 'bannedRaids#' + name,
                    rType: '#input',
                    type: 'checkbox',
                    id: 'idrinth-raid-may-join-list-' + name,
                    label: idrinth.text.get ( "raids.disableJoining" ) + name
                } ) );
                document.getElementById ( 'idrinth-raid-may-join-list' ).lastChild.setAttribute ( 'style',
                        'background-image:url(https://dotd.idrinth.de/static/raid-image-service/' + url + '/);' );
            };
            for (var key in data) {
                if ( data[key].name ) {
                    create ( data[key].name, data[key].url );
                }
            }
        } else {
            window.setTimeout ( idrinth.tier.start, 1000 );
        }
    },
    getTierForName: function ( name ) {
        var clearInnerHtml = function ( elem ) {
            elem.innerHTML = '';
        };
        var makeList = function ( list ) {
            var makeField = function ( listKey, difficulty, ic ) {
                var ln = {
                    type: 'td'
                };
                try {
                    ln.styles = idrinth.tier.list[listKey].os[difficulty] === idrinth.tier[listKey][difficulty][ic] ? 'is-os' : '';
                } catch ( E ) {
                    idrinth.core.log ( E.toString ( ) );
                }
                try {
                    ln.content = idrinth.ui.formatNumber ( idrinth.tier.list[listKey][difficulty][ic] ) + ' ' +
                            idrinth.tier.list[listKey].epics[difficulty][ic] + 'E';
                } catch ( E2 ) {
                    idrinth.core.log ( E2.toString ( ) );
                    try {
                        ln.content = idrinth.ui.formatNumber ( idrinth.tier.list[listKey][difficulty][ic] );
                    } catch ( E3 ) {
                        idrinth.core.log ( E3.toString ( ) );
                    }
                }
                return ln;
            };
            var makeRow = function ( title, dataset ) {
                return {
                    type: 'tr',
                    children: [
                        {
                            type: 'th',
                            content: title
                        },
                        {
                            type: 'td',
                            content: idrinth.ui.formatNumber ( dataset.n )
                        },
                        {
                            type: 'td',
                            content: idrinth.ui.formatNumber ( dataset.h )
                        },
                        {
                            type: 'td',
                            content: idrinth.ui.formatNumber ( dataset.l )
                        },
                        {
                            type: 'td',
                            content: idrinth.ui.formatNumber ( dataset.nm )
                        }
                    ]
                };
            };
            var wrapper = document.getElementById ( 'idrinth-tierlist' );
            clearInnerHtml ( wrapper );
            for (var count = list.length - 1; count >= 0; count--) {
                var sub = idrinth.ui.buildElement ( {
                    css: 'tier-wrapper',
                    children: [
                        {
                            type: 'img',
                            attributes: [ {
                                    name: 'src',
                                    value: 'https://dotd.idrinth.de/static/raid-image-service/' + idrinth.tier.list[list[count]].url + '/'
                                } ]
                        },
                        {
                            type: 'strong',
                            content: idrinth.tier.list[list[count]].name
                        },
                        {
                            type: 'span',
                            content: idrinth.tier.list[list[count]].types.join( ', ' )
                        },
                        {
                            type: 'button',
                            content: idrinth.text.get ( "tier.tag" ),
                            attributes: [
                                {
                                    name: 'onclick',
                                    value: 'idrinth.tier.addTagged(\'' + list[count].replace ( /'/g, '\\\'' ) + '\');'
                                },
                                {
                                    name: 'type',
                                    value: 'action'
                                }
                            ]
                        },
                        {
                            type: 'span',
                            content: 'AP: ' + idrinth.ui.formatNumber ( idrinth.tier.list[list[count]].ap )
                        },
                        {
                            type: 'table',
                            children: [
                                {
                                    type: 'thead',
                                    children: [
                                        {
                                            type: 'tr',
                                            children: [
                                                {
                                                    type: 'th',
                                                    content: '#'
                                                },
                                                {
                                                    type: 'th',
                                                    content: idrinth.text.get ( "tier.diff.normal" )
                                                },
                                                {
                                                    type: 'th',
                                                    content: idrinth.text.get ( "tier.diff.hard" )
                                                },
                                                {
                                                    type: 'th',
                                                    content: idrinth.text.get ( "tier.diff.legend" )
                                                },
                                                {
                                                    type: 'th',
                                                    content: idrinth.text.get ( "tier.diff.night" )
                                                }
                                            ]
                                        }
                                    ]
                                },
                                {
                                    type: 'tbody',
                                    children: [
                                        makeRow ( 'FS', idrinth.tier.list[list[count]].fs ),
                                        makeRow ( 'OS', idrinth.tier.list[list[count]].os ),
                                        makeRow ( '', {
                                            n: '',
                                            l: '',
                                            h: '',
                                            nm: ''
                                        } )
                                    ]
                                }
                            ]
                        }
                    ]
                } );
                var maxTiers = Math.max ( idrinth.tier.list[list[count]].n.length, idrinth.tier.list[list[count]].h.length, idrinth.tier.list[list[count]].l.length, idrinth.tier.list[list[count]].nm.length );
                for (var ic = 0; ic < maxTiers; ic++) {
                    sub.lastChild.lastChild.appendChild ( idrinth.ui.buildElement ( {
                        type: 'tr',
                        children: [
                            {
                                type: 'th',
                                content: ic + 1
                            },
                            makeField ( list[count], 'n', ic ),
                            makeField ( list[count], 'h', ic ),
                            makeField ( list[count], 'l', ic ),
                            makeField ( list[count], 'nm', ic )
                        ]
                    } ) );
                }
                wrapper.appendChild ( sub );
            }
        };
        if ( !name || name.length === 0 ) {
            clearInnerHtml ( document.getElementById ( 'idrinth-tierlist' ) );
            return;
        }
        var result = [ ];
        var regExp = new RegExp ( name, 'i' );
        for (var key in idrinth.tier.list) {
            if ( key.match ( regExp ) ) {
                result.push ( key );
            }
        }
        makeList ( result );
    }
};<|endoftext|>