Board Game Geek - One page auction geeklists

Loads all geeklist items into a single view simmilar to Peyo61's external tool.

目前为 2022-09-05 提交的版本。查看 最新版本

// ==UserScript==
// @name         Board Game Geek - One page auction geeklists
// @namespace    http://tampermonkey.net/
// @version      0.8
// @description  Loads all geeklist items into a single view simmilar to Peyo61's external tool.
// @author       Kempeth @ boardgamegeek
// @match        https://boardgamegeek.com/geeklist/*
// @icon         https://cf.geekdo-static.com/icons/favicon2.ico
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// ==/UserScript==

const FETCHLIMIT = 20000;

var nodeBtnConvert;
var nodeProgressFull;
var nodeProgressEmpty;
var nodeItemList;
var nodeFirstItem;

var geeklistid;
var taggedItems = []; // list of ids
var items = []; // data from api

var progressData = { done: 0, total: 0};

(function() {
    'use strict';

    //console.log('root function');
    UT_extractEndDate();

    init();
})();

function retry(f)
{
    window.setTimeout(f, 500);
}

function init()
{
    var els = document.getElementsByTagName('gg-geeklist-page-ui');
    if (els.length == 0) { retry(init); return; }

    els = els[0].getElementsByTagName('header');
    if (els.length == 0) { retry(init); return; }

    els = els[0].getElementsByTagName('nav');
    if (els.length == 0) { retry(init); return; }

    var el = els[0];
    nodeBtnConvert = document.createElement('button');
    nodeBtnConvert.innerHTML = "One Page Auction";
    nodeBtnConvert.className = "btn btn-warning";
    nodeBtnConvert.onclick = convert;
    el.append(nodeBtnConvert);
}

function updateProgressUI()
{
    // This should ensure that the values are not changed during the execution of this method
    var copy = progressData;

    var percentage = copy.total > 0 ? (copy.done * 100.0 / copy.total) : 0;
    nodeProgressFull.style.width = percentage + '%';
    nodeProgressEmpty.style.width = (100 - percentage) + '%';
    nodeProgressFull.setAttribute('aria-valuemax', copy.total);
    nodeProgressFull.setAttribute('aria-valuenow', copy.done);
    if (percentage > 50)
    {
        nodeProgressFull.innerHTML = copy.done + ' / ' + copy.total;
        nodeProgressEmpty.innerHTML = '';
    }
    else
    {
        nodeProgressEmpty.innerHTML = copy.done + ' / ' + copy.total;
        nodeProgressFull.innerHTML = '';
    }
}

function setProgress(done, total)
{
    progressData = { done: done, total: total };
}

function setProgressAsync(done, total)
{
    return new Promise(res => {
        setProgress(done, total);
        //    res();
        window.setTimeout(() => {
            res();
        }, 0);
    });
}

function convert()
{
    var els = document.getElementsByTagName('gg-geeklist-page-ui');
    if (els.length == 0) { retry(convert); return; }

    els = els[0].getElementsByTagName('div');
    if (els.length == 0) { retry(convert); return; }

    var footerNav = els[0].getElementsByTagName('nav');
    if (els.length == 0) { retry(convert); return; }
    footerNav = footerNav[footerNav.length - 1];

    els = els[0].getElementsByTagName('header');
    if (els.length == 0) { retry(convert); return; }

    els = els[0].getElementsByTagName('nav');
    if (els.length == 0) { retry(convert); return; }
    var headerNav = els[0];

    els = document.getElementsByTagName('gg-geeklist-items-ui');
    if (els.length == 0) { retry(convert); return; }

    els = els[0].getElementsByTagName('div');
    if (els.length == 0) { retry(convert); return; }
    nodeItemList = els[0];

    // remove header items
    for (var child of headerNav.childNodes)
    {
        if (child.tagName != 'GG-THUMBS-GEEKGOLD-GIVEN' && child.tagName != 'GG-SUBSCRIPTION-BUTTON')
        {
            headerNav.removeChild(child);
        }
    }

    // add header items
    var progress = document.createElement('div');
    progress.className = "progress";
    progress.style.height = "32px";
    progress.style.width = "256px";
    headerNav.appendChild(progress);

    nodeProgressFull = document.createElement('div');
    nodeProgressFull.className = "progress-bar";
    nodeProgressFull.setAttribute('role', 'progressbar');
    nodeProgressFull.setAttribute('aria-valuemin', 0);
    nodeProgressFull.setAttribute('aria-valuemax', 0);
    nodeProgressFull.setAttribute('aria-valuenow', 0);
    nodeProgressFull.style.width = '0%';
    nodeProgressFull.innerHTML = '';
    progress.appendChild(nodeProgressFull);

    nodeProgressEmpty = document.createElement('div');
    nodeProgressEmpty.className = "progress-bar bg-secondary";
    nodeProgressEmpty.setAttribute('role', 'progressbar');
    nodeProgressEmpty.style.width = '100%';
    nodeProgressEmpty.innerHTML = '0 / ???';
    progress.appendChild(nodeProgressEmpty);

    var btn = document.createElement('button');
    btn.innerHTML = "Refresh";
    btn.className = "btn btn-primary";
    headerNav.appendChild(btn);

    // Sort Dropdown
    var ddSort = document.createElement('div');
    ddSort.className = "dropdown";
    headerNav.appendChild(ddSort);

    var ddSortBtn = document.createElement('button');
    ddSortBtn.innerText = "Sort ";
    ddSortBtn.className = "btn btn-secondary dropdown-toggle";
    ddSort.appendChild(ddSortBtn);

    var ddSortList = document.createElement('ul');
    ddSortList.className = "dropdown-menu";
    ddSort.appendChild(ddSortList);

    ddSortBtn.onclick = () => {
        ddSortList.style.display = (ddSortList.style.display == "block") ? "none" : "block";
    };
    ddSortBtn.onblur = () => {
        window.setTimeout(() => {
            ddSortList.style.display = "none";
        }, 200);
    };

    var ddSortPosted = document.createElement('li');
    ddSortPosted.innerHTML = "<button type=\"button\" class=\"dropdown-item\">Posted</button>";
    ddSortPosted.onclick = () => {
        console.log('sorting by default', new Date());
        sortBy('');
    };
    ddSortList.appendChild(ddSortPosted);

    var ddSortName = document.createElement('li');
    ddSortName.innerHTML = "<button type=\"button\" class=\"dropdown-item\">Name</button>";
    ddSortName.onclick = () => {
        console.log('sorting by name', new Date());
        sortBy('name');
    };
    ddSortList.appendChild(ddSortName);

    var ddSortDate = document.createElement('li');
    ddSortDate.innerHTML = "<button type=\"button\" class=\"dropdown-item\">Closing Date</button>";
    ddSortDate.onclick = () => {
        console.log('sorting by closing date', new Date());
        sortBy('closing');
    };
    ddSortList.appendChild(ddSortDate);

    // Filter Dropdown
    var ddFilter = document.createElement('div');
    ddFilter.className = "dropdown";
    headerNav.appendChild(ddFilter);

    var ddFilterBtn = document.createElement('button');
    ddFilterBtn.innerText = "Filter ";
    ddFilterBtn.className = "btn btn-secondary dropdown-toggle";
    ddFilter.appendChild(ddFilterBtn);

    var ddFilterList = document.createElement('ul');
    ddFilterList.className = "dropdown-menu";
    ddFilter.appendChild(ddFilterList);

    ddFilterBtn.onclick = () => {
        ddFilterList.style.display = (ddFilterList.style.display == "block") ? "none" : "block";
    };
    ddFilterBtn.onblur = () => {
        window.setTimeout(() => {
            ddFilterList.style.display = "none";
        }, 200);
    };

    var ddFilterBoardgames = document.createElement('li');
    ddFilterBoardgames.className = "dropdown-item";
    ddFilterBoardgames.innerHTML = "<input class=\"form-check-input\" type=\"checkbox\" value=\"\" checked id=\"filterBoardgames\"> " +
      "<label class=\"form-check-label\" for=\"filterBoardgames\">Boardgames</label>";
    ddFilterList.appendChild(ddFilterBoardgames);
    document.getElementById('filterBoardgames').onclick = () => {
        window.setTimeout(() => {
            filterList();
        }, 400);
    };

    var ddFilterExpansions = document.createElement('li');
    ddFilterExpansions.className = "dropdown-item";
    ddFilterExpansions.innerHTML = "<input class=\"form-check-input\" type=\"checkbox\" value=\"\" checked id=\"filterExpansions\"> " +
      "<label class=\"form-check-label\" for=\"filterExpansions\">Expansions</label>";
    ddFilterList.appendChild(ddFilterExpansions);
    document.getElementById('filterExpansions').onclick = () => {
        window.setTimeout(() => {
            filterList();
        }, 400);
    };

    var ddFilterDivider = document.createElement('li');
    ddFilterDivider.innerHTML = "<hr class=\"dropdown-divider\">";
    ddFilterList.appendChild(ddFilterDivider);

    var ddFilterUntagged = document.createElement('li');
    ddFilterUntagged.className = "dropdown-item";
    ddFilterUntagged.innerHTML = "<input class=\"form-check-input\" type=\"checkbox\" value=\"\" checked id=\"filterUntagged\"> " +
      "<label class=\"form-check-label\" for=\"filterUntagged\">Untagged</label>";
    ddFilterList.appendChild(ddFilterUntagged);
    document.getElementById('filterUntagged').onclick = () => {
        window.setTimeout(() => {
            filterList();
        }, 400);
    };

    var ddFilterTagged = document.createElement('li');
    ddFilterTagged.className = "dropdown-item";
    ddFilterTagged.innerHTML = "<input class=\"form-check-input\" type=\"checkbox\" value=\"\" checked id=\"filterTagged\"> " +
      "<label class=\"form-check-label\" for=\"filterTagged\">Tagged</label>";
    ddFilterList.appendChild(ddFilterTagged);
    document.getElementById('filterTagged').onclick = () => {
        window.setTimeout(() => {
            filterList();
        }, 400);
    };

    ddFilterDivider = document.createElement('li');
    ddFilterDivider.innerHTML = "<hr class=\"dropdown-divider\">";
    ddFilterList.appendChild(ddFilterDivider);

    var ddFilterClosed = document.createElement('li');
    ddFilterClosed.className = "dropdown-item";
    ddFilterClosed.innerHTML = "<input class=\"form-check-input\" type=\"checkbox\" value=\"\" checked id=\"filterClosed\"> " +
      "<label class=\"form-check-label\" for=\"filterClosed\">Closed</label>";
    ddFilterList.appendChild(ddFilterClosed);
    document.getElementById('filterClosed').onclick = () => {
        window.setTimeout(() => {
            filterList();
        }, 400);
    };

    var ddFilterSold = document.createElement('li');
    ddFilterSold.className = "dropdown-item";
    ddFilterSold.innerHTML = "<input class=\"form-check-input\" type=\"checkbox\" value=\"\" checked id=\"filterSold\"> " +
      "<label class=\"form-check-label\" for=\"filterSold\">Sold</label>";
    ddFilterList.appendChild(ddFilterSold);
    document.getElementById('filterSold').onclick = () => {
        window.setTimeout(() => {
            filterList();
        }, 400);
    };

    var ddFilterNotSold = document.createElement('li');
    ddFilterNotSold.className = "dropdown-item";
    ddFilterNotSold.innerHTML = "<input class=\"form-check-input\" type=\"checkbox\" value=\"\" checked id=\"filterNotSold\"> " +
      "<label class=\"form-check-label\" for=\"filterNotSold\">Not Sold</label>";
    ddFilterList.appendChild(ddFilterNotSold);
    document.getElementById('filterNotSold').onclick = () => {
        window.setTimeout(() => {
            filterList();
        }, 400);
    };

    var ddFilterOpen = document.createElement('li');
    ddFilterOpen.className = "dropdown-item";
    ddFilterOpen.innerHTML = "<input class=\"form-check-input\" type=\"checkbox\" value=\"\" checked id=\"filterOpen\"> " +
      "<label class=\"form-check-label\" for=\"filterOpen\">Open</label>";
    ddFilterList.appendChild(ddFilterOpen);
    document.getElementById('filterOpen').onclick = () => {
        window.setTimeout(() => {
            filterList();
        }, 400);
    };

    // remove convert button
    nodeBtnConvert.parentNode.removeChild(nodeBtnConvert);

    // remove geeklist items
    nodeItemList.innerHTML = '';

    nodeFirstItem = nodeItemList.childNodes[0];
    nodeItemList.className = "tw-grid tw-gap-2";

    // remove footer nav
    //footerNav.parentNode.removeChild(footerNav);
    footerNav.innerHTML = '';

    geeklistid = window.location.pathname.split('/')[2];

    window.setInterval(updateProgressUI, 250);
    getTaggedItems();
    items = [];
    fetchPage(1);
}

function fetchPage(page)
{
    // https://api.geekdo.com/api/listitems?page=1&listid=301669
    var url = "https://api.geekdo.com/api/listitems?page=" + page + "&listid=" + geeklistid;

    fetch(url)
        .then(response => response.json())
        .then(
        data => {
            var nr = (data.pagination.pageid - 1) * data.pagination.perPage;
            for (var item of data.data)
            {
                items[nr] = item;
                nr++;
                makeRow(nr, item);
            }
            setProgress(nr, data.pagination.total);

            if (data.data.length < data.pagination.perPage)
            {
                //console.log("last page");
            }
            else if (page < FETCHLIMIT)
            {
                //console.log("more pages");
                fetchPage(page + 1);
            }
        }
    );
}

function makeRow(nr, item)
{
    var row = document.createElement('span');
    row.className = "tw-relative tw-flex tw-flex-wrap tw-items-center tw-gap-x-1.5 tw-gap-y-2 tw-rounded-md tw-border tw-border-gray-400 tw-bg-gray-100 tw-p-2";

    const thingtype = item.item.href.split('/')[1];
    const bodylower = item.body.toLowerCase();
    const notsold = bodylower.includes("not sold");
    const unsold = bodylower.includes("unsold");
    const soldto = bodylower.includes("sold to");
    const sold = bodylower.includes("sold");
    const auctionclosed = bodylower.includes("auction closed");

    var today = new Date();
    today.setHours(23, 59, 59, 999);
    const auctionends = extractEndDate(item, false);
    if (!auctionends) console.warn(item.body);
    item.auctionends = auctionends;

    var struckout = getStruckoutTextLength(item, false);


    var tagButton = document.createElement("button");
    if (taggedItems.includes(item.id))
    {
        tagButton.className = "btn btn-sm btn-primary";
        tagButton.innerText = "Tagged";
    }
    else
    {
        tagButton.className = "btn btn-sm btn-outline-primary";
        tagButton.innerText = "Tag";
    }
    tagButton.onclick = () => {
        toggleTaggedItem(tagButton, item.id);
    };
    row.appendChild(tagButton);

    var tagGeeklistLink = document.createElement("a");
    tagGeeklistLink.href = "/geeklist/" + geeklistid + "/test?itemid=" + item.id + "#" + item.id;
    tagGeeklistLink.target = "_blank";
    tagGeeklistLink.innerText = nr + ".";
    row.appendChild(tagGeeklistLink);

    var txtThingType = document.createTextNode(thingtype == "boardgame" ? "Boardgame" : thingtype == "boardgameexpansion" ? "Expansion" : "Other");
    row.appendChild(txtThingType);

    var tagGeeklistItemLink = document.createElement("a");
    tagGeeklistItemLink.href = item.item.href;
    tagGeeklistItemLink.target = "_blank";
    tagGeeklistItemLink.style.maxWidth = "50%";
    tagGeeklistItemLink.style.textOverflow = "ellipsis";
    tagGeeklistItemLink.style.overflow = "hidden";
    tagGeeklistItemLink.style.whiteSpace = "nowrap";
    tagGeeklistItemLink.innerText = item.item.name;
    row.appendChild(tagGeeklistItemLink);

    var tagState = document.createElement("span");
    if (notsold || unsold)
    {
        item.state = "not sold";
        tagState.innerText = "not sold";
        tagState.className = "badge bg-danger";
    }
    else if (soldto || (sold && struckout > 100))
    {
        item.state = "sold";
        tagState.innerText = "sold";
        tagState.className = "badge bg-danger";
    }
    else if (auctionclosed || (auctionends && auctionends < today) || struckout > 150)
    {
        item.state = "closed";
        tagState.innerText = "closed";
        tagState.className = "badge bg-secondary";
    }
    else
    {
        item.state = "open";
        tagState.innerText = "open";
        tagState.className = "badge bg-success";
    }
    row.appendChild(tagState);

    if (auctionends)
    {
        var tagClosing = document.createElement("span");
        tagClosing.style.color = "gray";
        tagClosing.innerHTML = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\" style=\"height: 1em; vertical-align: -0.125em;\" fill=\"currentColor\">"+
            "<!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->"+
            "<path d=\"M152 64H296V24C296 10.75 306.7 0 320 0C333.3 0 344 10.75 344 24V64H384C419.3 64 448 92.65 448 128V448C448 483.3 419.3 512 384 512H64C28.65 512 0 483.3 0 448V128C0 92.65 28.65 64 64 64H104V24C104 10.75 114.7 0 128 0C141.3 0 152 10.75 152 24V64zM48 448C48 456.8 55.16 464 64 464H384C392.8 464 400 456.8 400 448V192H48V448z\"></path></svg>"+
            " " + auctionends.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' });
        row.appendChild(tagClosing);
    }

    var spanRight = document.createElement('span');
    spanRight.className = "tw-ml-auto tw-hidden tw-pr-0.5 tw-text-sm tw-text-muted md:tw-inline";
    row.appendChild(spanRight);

    var btnPreview = document.createElement('button');
    btnPreview.className = "btn btn-sm text-bg-secondary";
    btnPreview.innerText = "Preview";
    btnPreview.onclick = () => {
        if (btnPreview.data != null)
        {
            btnPreview.data.parentNode.removeChild(btnPreview.data);
            btnPreview.data = null;
        }
        else
        {
            makePreview(item, btnPreview);
        }
    };
    spanRight.appendChild(btnPreview);

    nodeItemList.insertBefore(row, nodeFirstItem);
    item.node = row;
}

function makePreview(item, btn)
{
    var gli = document.createElement('div');
    gli.className = "geeklist-item tw-grid tw-items-start tw-gap-2.5 sm:tw-grid-cols-[minmax(auto,_185px)_minmax(65%,_1fr)] lg:tw-gap-3 lg:tw-gap-x-4";
    if (item.node.nextSibling)
    {
        item.node.parentNode.insertBefore(gli, item.node.nextSibling);
    }
    else
    {
        item.node.parentNode.appendChild(gli);
    }
    btn.data = gli;

    const gliImg = makeItemImage(item);
    gli.appendChild(gliImg);

    var gliDiv = document.createElement('div');
    gliDiv.className = "tw-min-w-0 sm:tw-mt-0";
    gli.appendChild(gliDiv);

    const gliDivBody = makeItemBody(item);
    gliDiv.appendChild(gliDivBody);
}

function makeItemUser(item)
{
}

function makeItemBody(item)
{
    var body = document.createElement('div');

    var parser = new DOMParser();
    var xmlDoc = parser.parseFromString(item.bodyXml, "text/xml");

    for (var child of xmlDoc.firstChild.childNodes)
    {
        console.log(child);
        if (child.nodeName == 'safehtml')
        {
            var safehtml = document.createElement('div');
            safehtml.innerHTML = child.firstChild.data;
            body.appendChild(safehtml);
        }
        else
        {
            var other = document.createElement('div');
            other.className = "alert alert-warning";
            other.innerText = "displaying " + child.nodeName + " is not yet supported by one page auction userscript.";
            body.appendChild(other);
        }
    }

    return body;
}

function makeItemImage(item)
{
    var gliImg = document.createElement('a');
    gliImg.className = "geeklist-item__img-link tw-relative tw-block tw-overflow-hidden tw-rounded-md tw-bg-gray-100";
    gliImg.href = item.linkedImage.href;

    var gliImgDiv = document.createElement('div');
    gliImgDiv.className = "tw-h-44 tw-w-full sm:tw-aspect-w-1 sm:tw-aspect-h-1";
    gliImg.appendChild(gliImgDiv);

    var gliImgDivImg = document.createElement('img');
    gliImgDivImg.className = "img-fluid geeklist-item__img tw-absolute tw-w-full tw-h-full tw-p-2.5 md:tw-p-3.5 tw-object-contain tw-z-10";
    gliImgDivImg.loading = "lazy";
    gliImgDivImg.style.display = "inherit";
    gliImgDivImg.src = item.item.imageSets.square100.src;
    gliImgDivImg.srcset = item.item.imageSets.square100.src + " 1x," + item.item.imageSets.square100["src@2x"] + " 2x";
    gliImgDivImg.alt = item.linkedImage.alt;
    gliImgDivImg.sizes = "";
    gliImgDiv.appendChild(gliImgDivImg);

    gliImgDivImg = document.createElement('img');
    gliImgDivImg.className = "img-fluid tw-absolute tw-w-full tw-h-full tw-object-cover tw-scale-125 tw-opacity-30 tw-blur-xl tw-bg-white";
    gliImgDivImg.loading = "lazy";
    gliImgDivImg.style.display = "inherit";
    gliImgDivImg.src = item.item.imageSets.square100.src;
    gliImgDivImg.srcset = item.item.imageSets.square100.src + " 1x," + item.item.imageSets.square100["src@2x"] + " 2x";
    gliImgDivImg.sizes = "";
    gliImgDiv.appendChild(gliImgDivImg);

    if (item.stats)
    {
        gliImgDiv = document.createElement('div');
        gliImgDiv.className = "tw-absolute tw-right-2 tw-bottom-2 tw-z-20";
        gliImgDiv.innerHTML = "<gg-rating-indicator tooltip=\"Avg. Rating\" shape=\"hex\" size=\"md\">" +
            "<span container=\"body\" class=\"hex rating--" + Math.floor(item.stats.average) + " tw-flex tw-font-semibold tw-h-[var(--hex-h)] tw-items-center tw-justify-center tw-text-white tw-text-xs tw-w-[var(--hex-w)]\"" +
            " style=\"background:" + getColorFromRating(item.stats.average) + "; --hex-w: 1.625rem; --hex-h: 1.875rem; -webkit-clip-path: polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%); clip-path: polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%);\">" +
            "<span class=\"tw-pb-px\"> " + (Math.round(item.stats.average * 10) / 10) + " </span>" +
            "</span>" +
            "</gg-rating-indicator>";
        gliImg.appendChild(gliImgDiv);
    }

    return gliImg;
}

function encodeWithTextNode(htmlstring) {
	let textarea = document.createElement('textarea');
	let text = document.createTextNode(htmlstring);
	textarea.appendChild(text);
	return textarea.innerHTML;
}

function getTaggedItems()
{
    taggedItems = JSON.parse(GM_getValue("TaggedItems_" + geeklistid, "[]"));
}

function toggleTaggedItem(btn, itemid)
{
    if (taggedItems.includes(itemid))
    {
        taggedItems = taggedItems.filter(i => i != itemid);
        btn.className = "btn btn-sm btn-outline-primary";
        btn.innerText = "Tag";
    }
    else
    {
        taggedItems[taggedItems.length] = itemid;
        btn.className = "btn btn-sm btn-primary";
        btn.innerText = "Tagged";
    }
    GM_setValue("TaggedItems_" + geeklistid, JSON.stringify(taggedItems));
}

async function sortBy(type)
{
    await setProgressAsync(0, items.length);

    var sorted;
    if (type == null || type == '' || type == 'default')
    {
        sorted = items;
    }
    else if (type == 'name')
    {
        sorted = [...items];
        sorted = sorted.sort((a,b) => {
            var r = a.item.name > b.item.name ? 1 : -1;
            return r;
        });
    }
    else if (type == 'closing')
    {
        sorted = [...items];
        sorted = sorted.sort((a,b) => {
            var r = a.auctionends > b.auctionends ? 1 : -1;
            return r;
        });
    }

    nodeItemList.innerHTML = '';
    sortChunks(sorted, 0);
}

function sortChunks(sorted, start)
{
    sortAChunk(sorted, start)
        .then(async data => {
			//console.log(data.next, data.sorted.length);
            await setProgressAsync(data.next, data.sorted.length);

            if (data.next >= data.sorted.length)
            {
                console.log("sorting finished", new Date());
            }
            else
            {
                sortChunks(data.sorted, data.next);
            }
        }
    );
}

function sortAChunk(sorted, start)
{
    const chunk = 250;

	return new Promise(res => {
		window.setTimeout(() => {
			//console.log(start);
			for (var i = 0; i < chunk && start + i < sorted.length; i++)
			{
				var item = sorted[start + i];

				//nodeItemList.removeChild(item.node);
                nodeItemList.insertBefore(item.node, nodeFirstItem);
			}
			res({
				sorted: sorted,
				next: start + i
			});
		}, 0);
	});
}

function filterList()
{
    console.log("filtering list");
    const fBg = document.getElementById('filterBoardgames').checked;
    const fEx = document.getElementById('filterExpansions').checked;
    const fUT = document.getElementById('filterUntagged').checked;
    const fTg = document.getElementById('filterTagged').checked;
    const fCl = document.getElementById('filterClosed').checked;
    const fSo = document.getElementById('filterSold').checked;
    const fNS = document.getElementById('filterNotSold').checked;
    const fOp = document.getElementById('filterOpen').checked;

    for (var i = 0; i < items.length; i++)
    {
        var show = true;
        var item = items[i];
        const thingtype = item.item.href.split('/')[1];
        const tagged = taggedItems.includes(item.id);
        if (!fBg && thingtype == 'boardgame') show = false;
        if (!fEx && thingtype == 'boardgameexpansion') show = false;
        if (!fUT && !tagged) show = false;
        if (!fTg && tagged) show = false;

        if (!fCl && item.state == 'closed') show = false;
        if (!fSo && item.state == 'sold') show = false;
        if (!fNS && item.state == 'not sold') show = false;
        if (!fOp && item.state == 'open') show = false;

        var cls = (show ? "tw-relative tw-flex tw-flex-wrap tw-items-center tw-gap-x-1.5 tw-gap-y-2 tw-rounded-md tw-border tw-border-gray-400 tw-bg-gray-100 tw-p-2" : "tw-relative d-none tw-flex-wrap tw-items-center tw-gap-x-1.5 tw-gap-y-2 tw-rounded-md tw-border tw-border-gray-400 tw-bg-gray-100 tw-p-2");
        item.node.className = cls;

        if ((i + 1) % 100 == 0 || i == items.length - 1)
        {
            setProgress(i+1, items.length);
        }
    }
}

function getColorFromRating(rating)
{
    const i = Math.floor(rating);
    switch (i)
    {
        case 1:
        case 2: return "#b2151f";
        case 3:
        case 4: return "#d71925";
        case 5:
        case 6: return "#5369a2";
        case 7: return "#1978b3";
        case 8: return "#1d804c";
        case 9:
        case 10: return "#186b40";
        default: return "#666e75";
    }
}

function UT_extractEndDate()
{
    const september = 8;
    const october = 9;
    const cases = [
        {
            expected: new Date(2022, september, 25),
            item: {
                postdate: "2022-08-31T13:03:45+00:00",
                body: "Bar\n\nAuction ends: Sunday 25th September, random time\n\nFoo."
            }
        },
        {
            expected: new Date(2022, september, 30),
            item: {
                postdate: "2022-08-31T13:03:45+00:00",
                body: "Bar\n\n[b]Auction ends:[/b] [b][COLOR=#0066FF]September 30 at random time[/COLOR][/b]\nFoo"
            }
        },
        {
            expected: new Date(2022, october, 2),
            item: {
                postdate: "2022-08-31T13:03:45+00:00",
                body: "Bar\n\nAuction ends: Sun 2 Oct, \nFoo"
            }
        },
        {
            expected: new Date(2022, september, 30),
            item: {
                postdate: "2022-08-31T13:03:45+00:00",
                body: "Bar\n\n[size=12][b]Auction ends[/b]: [COLOR=#FF0000]Fri 30 Sept[/COLOR] [-][COLOR=#6699FF][b]Wed 5 Oct[/b][/COLOR][/-], random time.[/size]\nFoo"
            }
        },
        {
            expected: new Date(2022, september, 18),
            item: {
                postdate: "2022-08-31T13:03:45+00:00",
                body: "Bar\n\n[b]End of Auction: 18.09.2022\n[/b]\nFoo"
            }
        },
        {
            expected: new Date(2022, september, 30),
            item: {
                postdate: "2022-08-31T13:03:45+00:00",
                body: "Bar\n\n[-][size=12][b]Auction ends[/b]: [COLOR=#6699FF][b]Fry 30 Sep, random time[/b][/COLOR][/size][/-]\nFoo"
            }
        },
        {
            expected: new Date(2022, october, 1),
            item: {
                postdate: "2022-08-31T13:03:45+00:00",
                body: "Bar\n\nAuction ends: 1 of Okt, random time\nFoo"
            }
        }
    ];

    for (var test of cases)
    {
        var res = extractEndDate(test.item, false);

        if ((res instanceof Date && res.getTime() != test.expected.getTime()) || ( !(res instanceof Date) && res != test.expected))
        {
            console.warn("Expected|Actual|Data", test.expected, res, test.item);
            extractEndDate(test.item, true);
        }
    }
    console.info("Unit Test for extractEndDate are complete");
}

function extractEndDate(item, debug)
{
    var auctionends = false;
    var parsed;

    const posted = new Date(item.postdate);
    const bodytext = item.body.toLowerCase()
        //.replaceAll(/\[-\].*?\[\/-\]/gi, '') // remove all struck out information
        .replaceAll(/\[[^\]]+\]/gi, ''); // remove all tags
    if (debug) console.log(bodytext);

    // find and extract the line of the auction end date
    var linematch = /(?:auction ends|end of auction)(?: (?:on|at))?(?:\:)?(.+)/i.exec(bodytext);
    if (debug) console.log(linematch);

    if (linematch && linematch.length > 1)
    {
        var line = linematch[1]
            .replaceAll(/\[-\].*?\[\/-\]/gi, ''); // remove all struck out information. We do this only now in case the whole line was struck out. Otherwise we would lose the information

        //try obvious formats
        var datematch;

        datematch = /(\d\d)\.(\d\d)\.(\d\d\d\d)/.exec(line);
        if (datematch)
        {
            if (debug) console.log(datematch);
            return new Date(parseInt(datematch[3]), parseInt(datematch[2])-1, parseInt(datematch[1]));
        }

        // (Mon) 1 Jan
        datematch = /\d\d? (of )?\w{3}/.exec(line);
        if (datematch)
        {
            if (debug) console.log(datematch);
            var datetext = datematch[0]
                .replace(/of/i, '')
                .replace(/Okt/i, 'Oct');
            // Subbing year from post
            parsed = Date.parse(datetext + " " + posted.getFullYear());
            if (!isNaN(parsed))
            {
                // It's possible the end date is something like january X and the item is posted december Y
                if (new Date(parsed) < posted)
                {
                    // so we need to increment the supplied year
                    parsed = Date.parse(datetext + " " + (posted.getFullYear() + 1));
                }
                if (debug) console.log(parsed);
                return new Date(parsed);
            }
        }

        line = line.replace(/(\.|,)/gi, ''); // don't need these
        line = line.replaceAll(/(\d+)(th|nd|rd|st)/g, '$1'); // Cannot parse days like 25th, numbers only!
        line = line.replaceAll(/(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday|Mon|Tue|Wed|Thu|Fri|Sat|Sun|Fry)/gi, ''); // Don't need the weekday
        line = line.replaceAll(/Okt(ober)?/gi, 'October'); // german
        line = line.replaceAll(/(at )?random time/gi, ''); // remove random time
        line = line.replaceAll(/at .+/gi, ''); // remove specific time
        line = line.replaceAll(/\d\d?(:\d\d)?\s*(am|pm)(\s+\w{3,4})?/gi, ''); // remove time without leading words
        line = line.replaceAll(/\d\d?:\d\d(\s+\w{3,4})?/gi, ''); // remove time without leading words
        if (debug) console.log(line);

        var ms = Date.parse(line);
        if (isNaN(ms))
        {
            // There probably isn't a year supplied. Subbing year from post
            ms = Date.parse(line + " " + posted.getFullYear());
            if (!isNaN(ms))
            {
                // It's possible the end date is something like january X and the item is posted december Y
                if (new Date(ms) < posted)
                {
                    // so we need to increment the supplied year
                    ms = Date.parse(line + " " + (posted.getFullYear() + 1));
                }
                auctionends = new Date(ms);
            }
        }
        else
        {
            auctionends = new Date(ms);
        }
    }

    return auctionends;
}

function getStruckoutTextLength(item, debug)
{
    var bodytext = item.body.toLowerCase();
    var struckout = 0;
    var struckoutstart = bodytext.indexOf("[-]");
    var struckoutend = bodytext.indexOf("[/-]");
    while (struckoutstart >= 0 && struckoutend >= 0)
    {
        var text = bodytext.substring(struckoutstart+3, struckoutend)
            .replaceAll(/\[[^\]]+\]/gi, ''); // remove tags
        struckout += text.length;
        struckoutstart = bodytext.indexOf("[-]", struckoutstart + 1);
        struckoutend = bodytext.indexOf("[/-]", struckoutend + 1);
    }
    return struckout;
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址