{{{ICON_JS}}}

let {{JS_PREFIX}}_blocked_users = {};
let {{JS_PREFIX}}_blocked_keywords = {};
let {{JS_PREFIX}}_blocked_lists = {};
let {{JS_PREFIX}}_all_lists = {};
let {{JS_PREFIX}}_blocked_list_keywords = [];
let {{JS_PREFIX}}_supports_keywords = false;
const {{JS_PREFIX}}_request_id = '{{BUBBLE_REQUEST_ID}}';

let {{JS_PREFIX}}_unblocked_needs_refresh = false;

let {{JS_PREFIX}}_expand_state = {
    'recent': false,
    'users': false,
    'keywords': false,
    'lists': false,
};

let {{JS_PREFIX}}_interval = null;
let {{JS_PREFIX}}_last_applied = null;
let {{JS_PREFIX}}_idle_interval = 2500;

let {{JS_PREFIX}}_messages = null;

const {{PAGE_PREFIX}}_block_keyword_tally = {};
const {{PAGE_PREFIX}}_block_author_tally = {};
let {{PAGE_PREFIX}}_allow_tally = 0;

function {{JS_PREFIX}}_tally_keyword_block(name) {
    //console.log('blocking keyword: '+name);
    //{{PAGE_PREFIX}}_log('blocking keyword: '+name);
    {{JS_PREFIX}}_incr_tally(name, {{PAGE_PREFIX}}_block_keyword_tally);
}

function {{JS_PREFIX}}_incr_tally(name, tally) {
    if (!(name in tally)) {
        tally[name] = 1;
    } else {
        tally[name] = tally[name] + 1;
    }
}

function {{JS_PREFIX}}_decr_tally(name, tally) {
    if (!(name in tally)) {
        tally[name] = 0;
    } else {
        tally[name] = tally[name] - 1;
    }
}

function {{JS_PREFIX}}_tally_author_block(name) {
    //console.log('blocking author: '+name);
    //{{PAGE_PREFIX}}_log('blocking author: '+name);
    {{JS_PREFIX}}_incr_tally(name, {{PAGE_PREFIX}}_block_author_tally);
}

function {{JS_PREFIX}}_tally_allow() {
    {{PAGE_PREFIX}}_allow_tally++;
}

function {{JS_PREFIX}}_untally_allow() {
    {{PAGE_PREFIX}}_allow_tally--;
}

function {{JS_PREFIX}}_uuidv4() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
    });
}

const {{JS_PREFIX}}_create_block_img = function(size) {
    const img = document.createElement('img');
    img.style.all = 'revert';
    img.style.fontSize = 'x-small';
    img.src = {{JS_PREFIX}}_asset_img_url('icon');
    img.width = typeof size !== 'undefined' && size !== null ? size : 24;
    img.style.width = img.width+'px';
    return img;
}

const {{JS_PREFIX}}_create_unblock_img = function(size) {
    const img = document.createElement('img');
    img.src = {{JS_PREFIX}}_asset_img_url('unblock-icon');
    img.width = typeof size !== 'undefined' ? size : 16;
    return img;
}

const {{JS_PREFIX}}_stop_refreshing_blocks = function(e) {
    if ({{JS_PREFIX}}_interval !== null) {
        window.clearInterval({{JS_PREFIX}}_interval);
        {{JS_PREFIX}}_interval = null;
    }
}

const {{JS_PREFIX}}_refresh_blocks = function(e) {
    // {{PAGE_PREFIX}}_log('refresh_blocks starting');
    {{JS_PREFIX}}_last_applied = Date.now();
    {{JS_PREFIX}}_stop_refreshing_blocks();
    {{JS_PREFIX}}_apply_blocks({{JS_PREFIX}}_blocked_users);
    {{JS_PREFIX}}_interval = window.setInterval(function () {
        {{JS_PREFIX}}_last_applied = Date.now();
        {{JS_PREFIX}}_apply_blocks({{JS_PREFIX}}_blocked_users);
    }, {{JS_PREFIX}}_idle_interval);
}

function {{JS_PREFIX}}_fetch_blocks (do_apply) {
    const requestOptions = { method: 'GET' };
    const blocks_url = '/__bubble/api/filter/data/{{BUBBLE_DATA_ID}}/read?value=true';
    fetch(blocks_url, requestOptions)
        .then(resp => resp.json())
        .then(data => {
            const blocked_users = {};
            const blocked_keywords = {};
            for (let i=0; i<data.length; i++) {
                const token = data[i];
                if (token.startsWith('kw:')) {
                    blocked_keywords[token.substring('kw:'.length)] = true;
                } else if (token.startsWith('list:')) {
                    const list = token.substring('list:'.length);
                    // console.log('adding list: '+list);
                    {{JS_PREFIX}}_block_list(list);
                } else {
                    blocked_users[token] = true;
                }
            }
            {{JS_PREFIX}}_blocked_users = blocked_users;
            {{JS_PREFIX}}_blocked_keywords = blocked_keywords;
            {{JS_PREFIX}}_refresh_blocks();
        });
}

window.addEventListener('popstate', {{JS_PREFIX}}_refresh_blocks);

function {{JS_PREFIX}}_handleVisibilityChange() {
    if (document.hidden) {
        {{JS_PREFIX}}_stop_refreshing_blocks();
    } else  {
        {{JS_PREFIX}}_refresh_blocks();
    }
}

function {{JS_PREFIX}}_check_stale_refresher(ev) {
    if ({{JS_PREFIX}}_last_applied === null || Date.now() - {{JS_PREFIX}}_last_applied > 2*{{JS_PREFIX}}_idle_interval) {
        // {{PAGE_PREFIX}}_log('check_stale_refresher forcing refresh upon document event.type='+ev.type);
        {{JS_PREFIX}}_refresh_blocks();
    }
}

document.addEventListener("visibilitychange", {{JS_PREFIX}}_handleVisibilityChange, false);
document.addEventListener("click", {{JS_PREFIX}}_check_stale_refresher, false);
document.addEventListener("scroll", {{JS_PREFIX}}_check_stale_refresher, false);

function {{JS_PREFIX}}_update_block (name, do_block) {
    {{JS_PREFIX}}_apply_blocks({{JS_PREFIX}}_blocked_users);
    const block_user_url = '/__bubble/api/filter/data/{{BUBBLE_DATA_ID}}/write';
    const requestOptions = {
        method: 'POST',
        body: JSON.stringify({key: name, data: do_block})
    };
    fetch(block_user_url, requestOptions);
}

function {{JS_PREFIX}}_block_user (author) {
    if (!(author in {{JS_PREFIX}}_blocked_users)) {
        {{JS_PREFIX}}_blocked_users[author] = true;
        {{JS_PREFIX}}_update_block(author, true);
    }
}

function {{JS_PREFIX}}_block_keyword (keyword) {
    keyword = keyword.replace(/\s+/g, ' ').trim();
    if (keyword.length === 0) return false;
    if (!(keyword in {{JS_PREFIX}}_blocked_keywords)) {
        {{JS_PREFIX}}_blocked_keywords[keyword] = true;
        {{JS_PREFIX}}_update_block('kw:' + keyword, true);
        return true;
    }
    return false;
}

function {{JS_PREFIX}}_block_list (list) {
    if (!(list in {{JS_PREFIX}}_blocked_lists)) {
        const all_lists = {{JS_PREFIX}}_all_lists;
        if (list in all_lists) {
            {{JS_PREFIX}}_blocked_lists[list] = true;
            {{JS_PREFIX}}_update_block('list:'+list, true);
            all_lists[list].forEach(kw => {{JS_PREFIX}}_blocked_list_keywords[kw] = true);
        } else {
            {{JS_PREFIX}}_load_link_message(list, function (data) {
                const words = [];
                data.split('\n').forEach(kw => {
                    kw = kw.trim();
                    if (kw.length === 0 || kw.startsWith('#')) return;
                    words.push(kw);
                    if (!(kw in {{JS_PREFIX}}_blocked_list_keywords)) {
                        {{JS_PREFIX}}_blocked_list_keywords[kw] = {};
                    }
                    {{JS_PREFIX}}_blocked_list_keywords[kw][list] = true;
                });
                all_lists[list] = words;
                {{JS_PREFIX}}_blocked_lists[list] = true;
                {{JS_PREFIX}}_update_block('list:' + list, true);
            });
        }
    }
}

function {{JS_PREFIX}}_mark_evaluated(node) {
    const firstEval = node.className.indexOf('{{JS_PREFIX}}_bub_evaluated') === -1;
    if (firstEval) {
        node.className = node.className += ' {{JS_PREFIX}}_bub_evaluated';
    }
    return firstEval;
}

function {{JS_PREFIX}}_mark_removed(node) {
    const notRemoved = node.className.indexOf('{{JS_PREFIX}}_bub_removed_for_keyword') === -1;
    if (notRemoved) {
        node.className = node.className += ' {{JS_PREFIX}}_bub_removed_for_keyword';
    }
    return notRemoved;
}

function {{JS_PREFIX}}_includes_block_keyword (element, firstEval) {
    const keywords = Object.keys(Object.assign({}, {{JS_PREFIX}}_blocked_keywords, {{JS_PREFIX}}_blocked_list_keywords));
    if (keywords.length > 0) {
        const html = element.innerHTML;
        for (let i = 0; i < keywords.length; i++) {
            let kw = keywords[i];
            if (kw.startsWith('_')) {
                let cskw = kw.substring(1);
                if (html.indexOf(cskw) !== -1) {
                    // {{PAGE_PREFIX}}_log('>>> includes_block_keyword: blocking based on case-sensitive keyword: ' + cskw);
                    if ({{JS_PREFIX}}_mark_removed(element)) {
                        {{JS_PREFIX}}_tally_keyword_block(cskw);
                    }
                    if (!firstEval) {{JS_PREFIX}}_untally_allow();
                    return true;
                }
            } else {
                if (html.toLowerCase().indexOf(kw.toLowerCase()) !== -1) {
                    // {{PAGE_PREFIX}}_log('>>> includes_block_keyword: blocking based on case-insensitive keyword: ' + kw);
                    if ({{JS_PREFIX}}_mark_removed(element)) {{JS_PREFIX}}_tally_keyword_block(kw);
                    if (!firstEval) {{JS_PREFIX}}_untally_allow();
                    return true;
                }
            }
        }
    }
    return false;
}

function {{JS_PREFIX}}_unblock_user (author) {
    if (!(author in {{JS_PREFIX}}_blocked_users)) return false;
    delete {{JS_PREFIX}}_blocked_users[author];
    {{JS_PREFIX}}_update_block(author, false);
    return true;
}

function {{JS_PREFIX}}_unblock_keyword (keyword) {
    let kw = keyword.substring('kw:'.length);
    if (!(kw in {{JS_PREFIX}}_blocked_keywords)) return false;
    delete {{JS_PREFIX}}_blocked_keywords[kw];
    {{JS_PREFIX}}_update_block(keyword, false);
    return true;
}

function {{JS_PREFIX}}_unblock_list (listKey) {
    if (!(listKey in {{JS_PREFIX}}_blocked_lists)) return false;
    const listEntries = {{JS_PREFIX}}_blocked_lists[listKey];
    const listKeywords = {{JS_PREFIX}}_blocked_list_keywords;
    Object.keys(listEntries).forEach(kw => {
        delete listKeywords[kw][listKey];
        if (Object.keys(listKeywords[kw]).length === 0) {
            delete listKeywords[kw];
        }
    });
    delete {{JS_PREFIX}}_blocked_lists[listKey];
    {{JS_PREFIX}}_update_block('list:'+listKey, false);
    return true;
}

let {{JS_PREFIX}}_app_details = false;

function {{JS_PREFIX}}_create_unblock_author_control(authorName) {
    return {{JS_PREFIX}}_create_unblock_control(authorName, {{JS_PREFIX}}_unblock_user);
}

function {{JS_PREFIX}}_create_unblock_keyword_control(keyword) {
    return {{JS_PREFIX}}_create_unblock_control('kw:'+keyword, {{JS_PREFIX}}_unblock_keyword);
}

function {{JS_PREFIX}}_noop(e) {
    if (e) {
        e.stopPropagation();
        e.preventDefault();
    }
    return false;
}

function {{JS_PREFIX}}_create_unblock_control(name, unblock_func) {
    const imgHolder = {{JS_PREFIX}}_create_unblock_img();
    const blockSpan = document.createElement('span');
    const unblockLink = document.createElement('a');
    unblockLink.style.cursor = 'pointer';
    unblockLink.addEventListener("click", function (e) {
        if (unblock_func(name)) {
            {{JS_PREFIX}}_unblocked_needs_refresh = true;
            {{JS_PREFIX}}_hide_app_details();
            {{JS_PREFIX}}_show_app_details();
            return {{JS_PREFIX}}_noop(e);
        }
    });
    unblockLink.appendChild(imgHolder);
    blockSpan.appendChild(unblockLink)
    blockSpan.id = 'blockSpan_'+{{JS_PREFIX}}_uuidv4();
    return blockSpan;
}

function {{JS_PREFIX}}_hide_app_details() {
    // {{PAGE_PREFIX}}_log('hide_app_details called');
    const detailsDivId = '{{JS_PREFIX}}_detailsDiv';
    let detailsDiv = document.getElementById(detailsDivId);
    {{JS_PREFIX}}_app_details = false;
    if (detailsDiv != null) {
        detailsDiv.style.display = 'none';
        while (detailsDiv.firstChild) {
            detailsDiv.removeChild(detailsDiv.lastChild);
        }
    }
}

function {{JS_PREFIX}}_create_expandable_header(messageKey, defaultMessage, entryClassName, detailsDiv, stateVar) {
    const listsHeaderText = {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, messageKey, defaultMessage);
    const listsHeader = {{JS_PREFIX}}_app_header_element();
    const listsExpandControl = document.createElement('span');
    listsExpandControl.appendChild(document.createTextNode('[+] '));

    const listsHeaderLink = document.createElement('a');
    const listsCollapseControl = document.createElement('span');
    listsCollapseControl.appendChild(document.createTextNode('[-] '));
    listsHeader.appendChild({{JS_PREFIX}}_expand_state[stateVar] ? listsCollapseControl : listsExpandControl);
    const listsHeaderTextNode = document.createTextNode(listsHeaderText);
    listsHeader.appendChild(listsHeaderTextNode);

    listsHeaderLink.onclick = function (e) {
        const entries = detailsDiv.getElementsByClassName(entryClassName);
        if (!{{JS_PREFIX}}_expand_state[stateVar]) {
            // expand
            listsHeader.removeChild(listsExpandControl);
            listsHeader.insertBefore(listsCollapseControl, listsHeaderTextNode);
            for (let i=0; i<entries.length; i++) {
                entries[i].style.display = 'block';
            }
            {{JS_PREFIX}}_expand_state[stateVar] = true;
        } else {
            listsHeader.removeChild(listsCollapseControl);
            listsHeader.insertBefore(listsExpandControl, listsHeaderTextNode);
            for (let i=0; i<entries.length; i++) {
                entries[i].style.display = 'none';
            }
            {{JS_PREFIX}}_expand_state[stateVar] = false;
        }
        return {{JS_PREFIX}}_noop(e);
    }
    listsHeaderLink.appendChild(listsHeader);
    const linkHeaderDiv = document.createElement('div');
    linkHeaderDiv.appendChild(listsHeaderLink);
    return linkHeaderDiv;
}

function {{JS_PREFIX}}_show_app_details() {
    // {{PAGE_PREFIX}}_log('show_app_details called');
    {{JS_PREFIX}}_refresh_blocks();
    const detailsDivId = '{{JS_PREFIX}}_detailsDiv';
    let detailsDiv = document.getElementById(detailsDivId);
    {{JS_PREFIX}}_app_details = true;
    if (detailsDiv === null) {
        detailsDiv = {{PAGE_PREFIX}}_createDetailsDiv(detailsDivId);
        detailsDiv.onclick = function (e) {
            e.stopPropagation();
            e.preventDefault();
            return false;
        }
        document.getElementsByTagName('body')[0].appendChild(detailsDiv);
    }
    while (detailsDiv.firstChild) {
        detailsDiv.removeChild(detailsDiv.lastChild);
    }
    const screenWidth = {{PAGE_PREFIX}}_screenWidth();
    detailsDiv.style.display = 'block';
    detailsDiv.style.visibility = 'visible';
    detailsDiv.style.width = Math.max(240, Math.floor(0.6*screenWidth)) + 'px';
    detailsDiv.style.scrollTop = 0;

    const titleSpan = {{JS_PREFIX}}_app_title_span('ShadowBan');
    const topButtons = document.createElement('span');

    if ({{JS_PREFIX}}_unblocked_needs_refresh) {
        const refreshButton = {{JS_PREFIX}}_create_button('web_refreshPage', 'refresh', function (e) {
            e.stopPropagation();
            e.preventDefault();
            window.location.reload();
            return false;
        });
        topButtons.appendChild(refreshButton);
        topButtons.appendChild(document.createTextNode('\u00A0'));
    }

    const topCloseButton = {{JS_PREFIX}}_create_button('web_close', 'close', function (e) {
        e.stopPropagation();
        e.preventDefault();
        {{JS_PREFIX}}_hide_app_details();
        return false;
    });
    topButtons.style.float = 'right';
    topButtons.appendChild(topCloseButton);
    titleSpan.appendChild(topButtons);
    detailsDiv.appendChild(titleSpan);

    detailsDiv.appendChild(document.createElement('hr'));

    const recentBlocksDiv = document.createElement('div');
    const recentBlocksHeader = {{JS_PREFIX}}_create_expandable_header('web_recentBlocks', 'Recent Blocks', '{{JS_PREFIX}}_recent_entry', detailsDiv, 'recent');
    recentBlocksDiv.appendChild(recentBlocksHeader);
    detailsDiv.appendChild(recentBlocksDiv);

    const keywordTally = {{PAGE_PREFIX}}_block_keyword_tally;
    let keywordTallyKeys = Object.keys({{PAGE_PREFIX}}_block_keyword_tally);
    const authorTally = {{PAGE_PREFIX}}_block_author_tally;
    let authorTallyKeys = Object.keys({{PAGE_PREFIX}}_block_author_tally);
    let totalBlocks = 0;
    if (keywordTallyKeys.length === 0 && authorTallyKeys.length === 0) {
        const noRecentBlocksText = {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, 'web_noRecentBlocks', '(empty)');
        const entryDiv = {{JS_PREFIX}}_app_entry_div();
        entryDiv.className = '{{JS_PREFIX}}_recent_entry';
        entryDiv.style.display = {{JS_PREFIX}}_expand_state['recent'] ? 'block': 'none';
        entryDiv.appendChild(document.createTextNode(noRecentBlocksText));
        detailsDiv.appendChild(entryDiv);
    } else {
        if (keywordTallyKeys.length > 0) {
            keywordTallyKeys = keywordTallyKeys.slice();
            keywordTallyKeys.sort(function (a, b) {
                return a.localeCompare(b, '{{ACCOUNT_LANG}}', {'sensitivity': 'base'});
            });
            const openQuote = {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, 'web_openQuote', '“');
            const closeQuote = {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, 'web_closeQuote', '”');
            keywordTallyKeys.forEach(keyword => {
                const count = keywordTally[keyword];
                totalBlocks += count;
                const entryDiv = {{JS_PREFIX}}_app_entry_div();
                entryDiv.className = '{{JS_PREFIX}}_recent_entry';
                entryDiv.style.display = {{JS_PREFIX}}_expand_state['recent'] ? 'block': 'none';
                const em = document.createElement('em');
                em.appendChild(document.createTextNode(openQuote + keyword + closeQuote + ': ' + count));
                entryDiv.appendChild(em);
                detailsDiv.appendChild(entryDiv);
            });
        }
        if (authorTallyKeys.length > 0) {
            authorTallyKeys = authorTallyKeys.slice();
            authorTallyKeys.sort(function (a, b) {
                return a.localeCompare(b, '{{ACCOUNT_LANG}}', {'sensitivity': 'base'});
            });
            authorTallyKeys.forEach(author => {
                const count = authorTally[author];
                totalBlocks += count;
                const entryDiv = {{JS_PREFIX}}_app_entry_div();
                entryDiv.className = '{{JS_PREFIX}}_recent_entry';
                entryDiv.style.display = 'none';
                entryDiv.appendChild(document.createTextNode(author + ': ' + count));
                detailsDiv.appendChild(entryDiv);
            });
        }
    }

    const totalAllowed = {{PAGE_PREFIX}}_allow_tally;
    const fullTotal = totalAllowed + totalBlocks;
    const ratio = fullTotal === 0 ? 0 : 100.0 * (totalAllowed / fullTotal);
    const eqSign = totalBlocks === 0 ? '=' : '≈';
    const summaryHeader = {{JS_PREFIX}}_app_header('web_signalNoiseRatio', 'signal/noise',
        label => label + ': ' + totalAllowed + '/' + totalBlocks + ' '+eqSign+' ' + ratio.toLocaleString('{{ACCOUNT_LOCALE_HYPHEN}}', { maximumSignificantDigits: 3 }) + '%');

    recentBlocksDiv.appendChild(summaryHeader);
    detailsDiv.appendChild(document.createElement('hr'));

    let blocks = Object.keys({{JS_PREFIX}}_blocked_users);
    if (blocks !== null && blocks.length > 0) {
        blocks = blocks.slice(); // copy first, then sort case insensitive using user's locale
        blocks.sort(function (a, b) {
            return a.localeCompare(b, '{{ACCOUNT_LANG}}', {'sensitivity': 'base'});
        });
    }
    let keywords = Object.keys({{JS_PREFIX}}_blocked_keywords);
    if (keywords !== null && keywords.length > 0) {
        keywords = keywords.slice(); // copy first, then sort case insensitive using user's locale
        keywords.sort(function (a, b) {
            return a.localeCompare(b, '{{ACCOUNT_LANG}}', {'sensitivity': 'base'});
        });
    }
    const usersHeader = {{JS_PREFIX}}_create_expandable_header('web_blockedUsers', 'Block Users', '{{JS_PREFIX}}_user_entry', detailsDiv, 'users');
    detailsDiv.appendChild(usersHeader);
    if (blocks === null || blocks.length === 0) {
        const emptyMessage = {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, 'web_noUsersBlocked', '(empty)');
        const entryDiv = {{JS_PREFIX}}_app_entry_div();
        entryDiv.className = '{{JS_PREFIX}}_user_entry';
        entryDiv.style.display = {{JS_PREFIX}}_expand_state['users'] ? 'block': 'none';
        const entryText = document.createTextNode(emptyMessage);
        entryDiv.appendChild(entryText);
        detailsDiv.appendChild(entryDiv);
    } else {
        for (let i = 0; i < blocks.length; i++) {
            const entry = blocks[i];
            const entryDiv = {{JS_PREFIX}}_app_entry_div();
            entryDiv.className = '{{JS_PREFIX}}_user_entry';
            entryDiv.style.display = {{JS_PREFIX}}_expand_state['users'] ? 'block': 'none';
            const entryText = document.createTextNode(entry);
            entryDiv.appendChild(entryText);
            entryDiv.appendChild({{JS_PREFIX}}_create_unblock_author_control(entry));
            detailsDiv.appendChild(entryDiv);
        }
    }
    detailsDiv.appendChild(document.createElement('hr'));

    if ({{JS_PREFIX}}_supports_keywords) {
        const keywordsHeader = {{JS_PREFIX}}_create_expandable_header('web_blockedKeywords', 'Block Keywords', '{{JS_PREFIX}}_keyword_entry', detailsDiv, 'keywords');
        detailsDiv.appendChild(keywordsHeader);
        if (keywords === null || keywords.length === 0) {
            const emptyMessage = {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, 'web_noKeywordsBlocked', '(empty)');
            const entryDiv = {{JS_PREFIX}}_app_entry_div();
            entryDiv.className = '{{JS_PREFIX}}_keyword_entry';
            entryDiv.style.display = {{JS_PREFIX}}_expand_state['keyword'] ? 'block': 'none';
            const entryText = document.createTextNode(emptyMessage);
            entryDiv.appendChild(entryText);
            detailsDiv.appendChild(entryDiv);
        } else {
            for (let i = 0; i < keywords.length; i++) {
                const entry = keywords[i];
                const entryDiv = {{JS_PREFIX}}_app_entry_div();
                entryDiv.className = '{{JS_PREFIX}}_keyword_entry';
                entryDiv.style.display = {{JS_PREFIX}}_expand_state['keyword'] ? 'block': 'none';
                const entryText = document.createTextNode(entry);
                entryDiv.appendChild(entryText);
                entryDiv.appendChild({{JS_PREFIX}}_create_unblock_keyword_control(entry));
                detailsDiv.appendChild(entryDiv);
            }
        }
        const add_keyword = function(value) {
            if ({{JS_PREFIX}}_block_keyword(value)) {
                {{JS_PREFIX}}_hide_app_details();
                {{JS_PREFIX}}_show_app_details();
            }
        }
        const keywordInput = document.createElement('input');
        keywordInput.type = 'text';
        keywordInput.size = 14;
        keywordInput.style.width = '70%';
        keywordInput.style.fontSize = 'small';
        keywordInput.addEventListener('keypress', function (e) { if (e.key === 'Enter') { add_keyword(keywordInput.value); } });

        const keywordAdd = {{JS_PREFIX}}_create_button('web_addKeyword', 'add', function (e) { add_keyword(keywordInput.value); });
        keywordAdd.style.width = '20%';
        const kwForm = document.createElement('span');
        kwForm.style.fontSize = 'small';
        kwForm.appendChild(keywordInput);
        kwForm.appendChild(document.createTextNode('\u00A0'));
        kwForm.appendChild(keywordAdd);
        detailsDiv.appendChild(kwForm);
        detailsDiv.appendChild(document.createElement('hr'));

        const listKeys = Object.keys({{JS_PREFIX}}_messages).filter(k => k.startsWith('web_kwlist_') && !k.endsWith('_url'));
        if (listKeys.length > 0) {
            const listsHeader = {{JS_PREFIX}}_create_expandable_header('web_blockedKeywordLists', 'Keyword Lists', '{{JS_PREFIX}}_keylist_entry', detailsDiv, 'lists');
            detailsDiv.appendChild(listsHeader);

            listKeys.forEach(listKey => {
                const listName = {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, listKey, null);
                const listUrl = {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, listKey+'_url', null);
                if (listUrl === null) {
                    console.log('URL not defined for list: '+listKey);
                    return;
                }
                const entryDiv = {{JS_PREFIX}}_app_entry_div();
                entryDiv.className = '{{JS_PREFIX}}_keylist_entry';
                const entryLink = document.createElement('span');
                entryLink.onclick = function () {
                    window.open({{JS_PREFIX}}_link_message_url(listKey), '_blank').opener = null;
                };
                entryLink.style.textDecoration = 'underline';
                entryLink.style.color = 'blue';
                entryLink.style.cursor = 'pointer';
                const entryExtLink = document.createElement('img');
                entryExtLink.src = {{JS_PREFIX}}_asset_img_url('list-link-icon');
                entryExtLink.height = 12;
                entryExtLink.style.color = 'blue';
                entryExtLink.style.cursor = 'pointer';
                entryLink.appendChild(entryExtLink);

                const listNameNode = document.createElement('span');
                listNameNode.style.cursor = 'pointer';
                listNameNode.appendChild(document.createTextNode(listName));
                const entryCheckbox = document.createElement('input');
                entryCheckbox.type = 'checkbox'
                entryCheckbox.checked = listKey in {{JS_PREFIX}}_blocked_lists;

                function toggle_list(e) {
                    e.stopPropagation();
                    const isCheckbox = e.target === entryCheckbox;
                    let checked = entryCheckbox.checked;
                    if (!isCheckbox) {
                        checked = !checked;
                        entryCheckbox.checked = checked;
                    }
                    if (checked) {
                        {{JS_PREFIX}}_block_list(listKey);
                    } else {
                        {{JS_PREFIX}}_unblock_list(listKey);
                        {{JS_PREFIX}}_unblocked_needs_refresh = true;
                        {{JS_PREFIX}}_hide_app_details();
                        {{JS_PREFIX}}_show_app_details();
                    }
                }

                entryCheckbox.addEventListener('click', toggle_list);
                listNameNode.addEventListener('click', toggle_list);

                entryDiv.appendChild(entryCheckbox);
                entryDiv.appendChild(listNameNode);
                entryDiv.appendChild(document.createTextNode('\u00A0'));
                entryDiv.appendChild(entryLink);
                entryDiv.style.display = {{JS_PREFIX}}_expand_state['lists'] ? 'block': 'none';
                detailsDiv.appendChild(entryDiv);
            });

            detailsDiv.appendChild(document.createElement('hr'));
        }
    }
    const bottomButtons = document.createElement('span');
    const closeButton = {{JS_PREFIX}}_create_button('web_close', 'close', function (e) {
        e.stopPropagation();
        e.preventDefault();
        {{JS_PREFIX}}_hide_app_details();
        return false;
    });
    if ({{JS_PREFIX}}_unblocked_needs_refresh) {
        const refreshButton = {{JS_PREFIX}}_create_button('web_refreshPage', 'refresh', function (e) {
            e.stopPropagation();
            e.preventDefault();
            window.location.reload();
            return false;
        });
        bottomButtons.appendChild(refreshButton);
        bottomButtons.appendChild(document.createTextNode('\u00A0\u00A0'));
    }
    bottomButtons.appendChild(closeButton);
    detailsDiv.appendChild(bottomButtons);

}

function {{JS_PREFIX}}_toggle_app_details(ev) {
    if ({{JS_PREFIX}}_app_details) {
        {{JS_PREFIX}}_hide_app_details();
    } else {
        {{JS_PREFIX}}_show_app_details();
    }
}

{{{APPLY_BLOCKS_JS}}}

{{PAGE_PREFIX}}_addBubbleApp({
    jsPrefix: '{{JS_PREFIX}}',
    app: '{{BUBBLE_APP_NAME}}',
    link: {{JS_PREFIX}}_toggle_app_details,
    icon: 'icon',
    onReady: function () {
        {{JS_PREFIX}}_load_messages('web_', function (messages) {
            {{JS_PREFIX}}_messages = messages;
        });
        document.onkeydown = function(e) {
            e = e || window.event;
            let isEscape = false;
            if ("key" in e) {
                isEscape = (e.key === "Escape" || e.key === "Esc");
            } else {
                isEscape = (e.keyCode === 27);
            }
            if (isEscape) {{JS_PREFIX}}_hide_app_details();
        };
        {{JS_PREFIX}}_fetch_blocks();
        if (typeof {{JS_PREFIX}}_site_onload === 'function') {{JS_PREFIX}}_site_onload();
    }
});
