您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
(我已经安装了用户样式管理器,让我安装!)
// ==UserScript==
// @name Add non-MFC figure
// @namespace https://tharglet.me.uk
// @version 2.6
// @description Track preordered non-MFC items on collection screen
// @author Tharglet
// @match https://myfigurecollection.net/?*mode=view&*tab=collection&*
// @icon https://www.google.com/s2/favicons?sz=64&domain=myfigurecollection.net
// @grant GM_addStyle
// @license CC-BY-NC-SA-4.0; https://creativecommons.org/licenses/by-nc-sa/4.0/
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
//Polyfill for GM_addStyle for Greasemonkey...
if(typeof GM_addStyle == 'undefined') {
GM_addStyle = (aCss) => {
'use strict';
let head = document.getElementsByTagName('head')[0];
if (head) {
let style = document.createElement('style');
style.setAttribute('type', 'text/css');
style.textContent = aCss;
head.appendChild(style);
return style;
}
return null;
};
}
GM_addStyle(`
.non-mfc-item__tray-icon {
border: green 1px solid;
display: block;
margin: 8px;
padding: 1px;
}
.non-mfc-item__icon-container {
float: left;
margin-bottom: 20px;
}
.non-mfc-item__tray-icon img {
width: 60px;
height: 60px;
border-radius: 2px;
}
.non-mfc-item__edit, .non-mfc-item__delete {
display: inline-block;
}
.non-mfc-item__edit span, .non-mfc-item__delete span {
font-size: 16px;
margin: 0 4px;
}
/* Pip styles */
.item-custompip {
display: block;
position: absolute;
right: 1px;
bottom: 1px;
height: 16px;
padding: 1px 2px 2px 3px;
line-height: 16px;
color: white;
}
.item-is-paid {
background-color: green;
}
.item-is-shipped {
background-color: gold;
}
.item-is-stored {
background-color: orangered;
}
.icon-dollar:before {
font-family: serif !important;
content: "$";
font-weight: bolder !important;
}
.icon-plane:before {
font-family: serif !important;
content: "🛩️";
font-weight: bolder !important;
}
.icon-stored:before {
font-family: serif !important;
content: "🏭";
font-weight: bolder !important;
}
`);
(function() {
'use strict';
/** Amout of figures user has already preordered */
let BASE_COUNT = 0;
/** Parameter value for detailed list */
const DETAILED_LIST = '0';
/** Array of non-MFC figures the user has preordered */
let additionalFigures = JSON.parse(GM_getValue("additionalFigures", "[]"));
/** Pips for paid and shipped */
const PAID_PIP = '<span class="item-custompip item-is-paid"><span class="tiny-icon-only icon-dollar"></span></span>';
const SHIPPED_PIP = '<span class="item-custompip item-is-shipped"><span class="tiny-icon-only icon-plane"></span></span>';
const STORED_PIP = '<span class="item-custompip item-is-stored"><span class="tiny-icon-only icon-stored"></span></span>';
const ORDERED_COUNT_SELECTOR = '.result-tabs .tab .selected .count';
/**
* Converts string to HTML elements
*/
const htmlToElement = (html) => {
let template = document.createElement('template');
html = html.trim();
template.innerHTML = `${html}`;
return template.content;
}
/**
* Tests if we're viewing the logged in user's preorders page
*/
const isUsersPreorderPage = () => {
const urlParams = new URLSearchParams(window.location.search);
const status = urlParams.get('status');
if(status == 1) {
let loggedInUser = document.querySelector('.user-menu .handle');
if(loggedInUser) {
let userLink = loggedInUser.getAttribute('href');
if(userLink === '/session/signup') {
return false;
} else {
let userParam;
const windowLocation = window.location.href;
if(windowLocation.startsWith('https://myfigurecollection.net/profile/')) {
userParam = windowLocation.match(/https:\/\/myfigurecollection\.net\/profile\/([^\/]*)/)[1];
} else {
const urlParams = new URLSearchParams(window.location.search);
userParam = urlParams.get('username');
}
return userParam === userLink.substring('9');
}
} else {
return false;
}
} else {
return false;
}
};
/** Helper function to reset out MFC item form */
const resetNonMfcItemForm = () => {
document.getElementById('non-mfc-item__form-title').innerText = 'Add non-MFC figure';
document.getElementById('addNonMfcItem').innerText = 'Add';
document.getElementById('nonMfcItemEditId').value = '';
document.getElementById('nonMfcItemName').value = '';
document.getElementById('nonMfcItemImage').value = '';
document.getElementById('nonMfcItemLink').value = '';
document.getElementById('nonMfcItemReleaseDate').value = '';
document.getElementById('nonMfcItemHasShipped').checked = false;
document.getElementById('nonMfcItemIsStored').checked = false;
document.getElementById('nonMfcItemHasPaid').checked = false;
}
const drawAdditionalFigures = () => {
const urlParams = new URLSearchParams(window.location.search);
const groupBy = urlParams.get('groupBy');
const output = urlParams.get('output');
//Clear all additions before redo
const additions = document.getElementById('nonMfcAdditions');
if(additions) {
additions.remove();
}
document.querySelectorAll('.non-mfc-item__icon').forEach(e => e.remove());
document.querySelectorAll('.non-mfc-item__edit').forEach(e => e.remove());
document.querySelectorAll('.non-mfc-item__delete').forEach(e => e.remove());
document.querySelectorAll('.non-mfc-item__year-header').forEach(e => e.remove());
//Create a lookup list of existing date headings and their associated object
//Created so we can splice and keep track of new entries
const dateHeadingsList = [];
if(groupBy == 'releaseDates') {
let dateHeadings;
if(output === '0') {
dateHeadings = document.querySelectorAll('div.results-title');
} else {
dateHeadings = document.querySelectorAll('.item-group-by h3');
}
dateHeadings.forEach((heading) => {
const dateHeadingElement = {};
dateHeadingElement.year = heading.innerText.substring(0,4);
dateHeadingElement.month = heading.innerText.substring(5);
dateHeadingElement.element = heading;
dateHeadingsList.push(dateHeadingElement);
});
}
if(additionalFigures.length > 0) {
//update item count
let totalFigureCount = BASE_COUNT + additionalFigures.length;
document.querySelector(ORDERED_COUNT_SELECTOR).innerText = totalFigureCount;
document.querySelector('.results-count-value').lastChild.nodeValue = `${totalFigureCount} items`;
//Pre-add setup
const addList = document.querySelector('#nonMfcFigureList');
if(groupBy !== 'releaseDates' && !!groupBy) {
if(output === DETAILED_LIST) {
document.querySelector('.results').append(htmlToElement('<div id="nonMfcAdditions" class="results-title">Non-MFC</div>'));
} else {
document.querySelector('.result .item-icons').prepend(htmlToElement('<div class="item-group-by" id="nonMfcAdditions"><h3>Non-MFC</h3></div>'));
}
}
//Add icons to page and delete box
additionalFigures.forEach((fig, idx) => {
let linkLine;
const figYear = fig.date.substring(0, 4);
const figMonth = fig.date.substring(5);
let pip = '';
if(fig.hasShipped) {
pip += SHIPPED_PIP;
} else if(fig.isStored) {
pip += STORED_PIP;
} else if(fig.hasPaid) {
pip += PAID_PIP;
}
const figureThumb = `<span class="non-mfc-item__icon item-icon">
<a href="${fig.link}" class="anchor tbx-tooltip item-root-0 item-category-1">
<img src="${fig.image}"/>
${pip}
</a>
</span>`;
if(output === DETAILED_LIST) {
linkLine = `<div class="result">
<div class="dgst item-dgst item-stamp">
<div class="dgst-wrapper">
<a href="${fig.link}" class="dgst-icon has-flags">
<img src="${fig.image}" alt="${fig.name}">
</a>
<div class="dgst-data">
<div class="dgst-anchor">
<a href="${fig.link}" title="${fig.name}">${fig.name}</a>
</div>
<div class="dgst-meta">
<span class="meta category item-category-1" href="#">Non-MFC</span>
</div>
<div class="dgst-meta">
<span class="meta time">${fig.date}</span>
</div></div></div></div></div>`
}
addList.append(htmlToElement(`<span class='non-mfc-item__icon-container'>
<span class="non-mfc-item__icon non-mfc-item__tray-icon">
<a href="${fig.link}" class="tbx-tooltip item-root-0 item-category-1">
<img src="${fig.image}"/>
</a>
</span>
<a href="#" class='non-mfc-item__edit' title="Edit" data-index="${idx}"><span class="tiny-icon-only icon-pencil-square-o" data-index="${idx}"></span></a>
<a href="#" class='non-mfc-item__delete' title="Delete" data-index="${idx}"><span class="tiny-icon-only icon-trash-o" data-index="${idx}"></span></a>
</span>`));
let toAppend = true;
const yearMonthString = `${figYear}-${figMonth}`;
if(groupBy == 'releaseDates') {
dateHeadingsList.forEach((heading, headingIndex) => {
if(toAppend) {
const headingYear = heading.year;
const headingMonth = heading.month;
if(figYear == headingYear && figMonth == headingMonth) {
if(output === DETAILED_LIST) {
heading.element.after(htmlToElement(linkLine));
} else {
heading.element.after(htmlToElement(figureThumb));
}
toAppend = false;
} else if(new Date(figYear, figMonth, 1) > new Date(headingYear, headingMonth, 1)) {
if(output === DETAILED_LIST) {
const newHeading = `<div class="results-title non-mfc-item__year-header" id='nhi_${yearMonthString}'>${yearMonthString}</div>`
heading.element.before(htmlToElement(newHeading + linkLine));
} else {
const newHeading = `<div class="item-group-by"><h3 class="non-mfc-item__year-header" id='nhi_${yearMonthString}'>${yearMonthString}</h3></div>`
heading.element.parentElement.parentElement.insertBefore(htmlToElement(newHeading), heading.element.parentElement);
document.querySelector(`#nhi_${yearMonthString}`).after(htmlToElement(figureThumb));
}
dateHeadingsList.splice(
headingIndex,
0,
{
year: figYear,
month: figMonth,
element: document.querySelector(`#nhi_${yearMonthString}`)
}
);
toAppend = false;
}
}
});
//Append if trailing
if(toAppend) {
if(output === DETAILED_LIST) {
const newHeading = `<div class="results-title non-mfc-item__year-header" id='nhi_${yearMonthString}'>${yearMonthString}</div>`
document.querySelector('.results').append(htmlToElement('<div class="item-group-by">' + newHeading + linkLine + '</div>'));
} else {
const newHeading = `<h3 class="non-mfc-item__year-header" id='nhi_${yearMonthString}'>${yearMonthString}</h3>`
document.querySelector('.item-group-by:last-child').after(htmlToElement('<div class="item-group-by">' + newHeading + figureThumb + '</div>'));
}
dateHeadingsList.push({
year: figYear,
month: figMonth,
element: document.querySelector(`#nhi_${yearMonthString}`)
});
}
} else if(!!groupBy) {
if(output === DETAILED_LIST) {
document.getElementById('nonMfcAdditions').after(htmlToElement(linkLine))
} else {
document.querySelector('#nonMfcAdditions h3').after(htmlToElement(figureThumb));
}
} else {
if(output === DETAILED_LIST) {
document.querySelector('.results').append(htmlToElement(linkLine));
} else {
document.querySelector('.result .item-icons').prepend(htmlToElement(figureThumb));
}
}
});
document.querySelectorAll('.non-mfc-item__delete').forEach(ele => {
ele.addEventListener('click', (evt) => {
evt.preventDefault();
if(confirm('Delete this figure?')) {
additionalFigures.splice(evt.target.getAttribute('data-index'), 1);
additionalFigures.sort((a, b) => (a.date < b.date) ? 1 : -1);
GM_setValue("additionalFigures", JSON.stringify(additionalFigures));
drawAdditionalFigures();
resetNonMfcItemForm();
}
});
});
document.querySelectorAll('.non-mfc-item__edit').forEach(ele => {
ele.addEventListener('click', (evt) => {
evt.preventDefault();
const itemIndex = evt.target.getAttribute('data-index');
const figToEdit = additionalFigures[itemIndex];
document.getElementById('non-mfc-item__form-title').innerText = `Editing ${figToEdit.name}`;
document.getElementById('addNonMfcItem').innerText = 'Edit';
document.getElementById('nonMfcItemEditId').value = itemIndex;
document.getElementById('nonMfcItemName').value = figToEdit.name;
document.getElementById('nonMfcItemImage').value = figToEdit.image;
document.getElementById('nonMfcItemLink').value = figToEdit.link;
document.getElementById('nonMfcItemReleaseDate').value = figToEdit.date;
document.getElementById('nonMfcItemHasShipped').checked = figToEdit.hasShipped;
document.getElementById('nonMfcItemIsStored').checked = figToEdit.isStored;
document.getElementById('nonMfcItemHasPaid').checked = figToEdit.hasPaid;
window.scrollTo(
document.getElementById('non-mfc-item__section').offsetLeft,
document.getElementById('non-mfc-item__section').offsetTop - 100
);
});
});
}
}
// Initialise non-MFC
BASE_COUNT = parseInt(document.querySelector(ORDERED_COUNT_SELECTOR).innerText);
if(isUsersPreorderPage()) {
const addSection = `<section id='non-mfc-item__section'>
<h2 id='non-mfc-item__form-title'>Add non-MFC figure</h2>
<div class='form'>
<input type='hidden' id='nonMfcItemEditId' value=''/>
<div class='bigchar form-field'>
<div class='form-label'>Figure name</div>
<div class='form-input'><input type='text' id='nonMfcItemName'/></div>
</div>
<div class='bigchar form-field'>
<div class='form-label'>Image URL *</div>
<div class='form-input'><input type='text' id='nonMfcItemImage'/></div>
</div>
<div class='bigchar form-field'>
<div class='form-label'>Item link *</div>
<div class='form-input'><input type='text' id='nonMfcItemLink'/></div>
</div>
<div class='bigchar form-field'>
<div class='form-label'>Release date (YYYY-MM) *</div>
<div class='form-input'><input type='text' id='nonMfcItemReleaseDate'/></div>
</div>
<div class="checkbox form-field">
<div class="form-input">
<input id="nonMfcItemHasPaid" type="checkbox"> <label for="nonMfcItemHasPaid">Paid?</label>
</div>
</div>
<div class="checkbox form-field">
<div class="form-input">
<input id="nonMfcItemIsStored" type="checkbox"> <label for="nonMfcItemIsStored">Stored?</label>
</div>
</div>
<div class="checkbox form-field">
<div class="form-input">
<input id="nonMfcItemHasShipped" type="checkbox"> <label for="nonMfcItemHasShipped">Shipped?</label>
</div>
</div>
<div class='form-field'>
<div class='form-input'>
<button id='addNonMfcItem'>Add</button>
<button id='cancelNonMfcItem'>Cancel</button>
</div>
</div>
</div>
</section>
<section>
<h2>Added non-MFC figures</h2>
<div class='form'>
<div id='nonMfcFigureList' class='item-icons'>
</div>
</div>
</section>`;
const sidebar = document.querySelectorAll('#side section');
const lastSidebarSection = sidebar[sidebar.length - 1]
lastSidebarSection.after(htmlToElement(addSection));
drawAdditionalFigures();
document.getElementById('addNonMfcItem').addEventListener('click', (evt) => {
evt.preventDefault();
const name = document.getElementById('nonMfcItemName').value || 'Non-MFC figure';
const image = document.getElementById('nonMfcItemImage').value;
const link = document.getElementById('nonMfcItemLink').value;
const date = document.getElementById('nonMfcItemReleaseDate').value;
const editId = document.getElementById('nonMfcItemEditId').value;
const hasShipped = document.getElementById('nonMfcItemHasShipped').checked;
const isStored = document.getElementById('nonMfcItemIsStored').checked;
const hasPaid = document.getElementById('nonMfcItemHasPaid').checked;
if(image && link && date && name) {
if(date.match(/^\d{4}-0[1-9]|1[0-2]$/)) {
if(editId) {
additionalFigures[editId] = {
name,
image,
link,
date,
hasPaid,
isStored,
hasShipped
}
} else {
additionalFigures.push({
name,
image,
link,
date,
hasPaid,
isStored,
hasShipped
});
}
additionalFigures.sort((a, b) => (a.date < b.date) ? 1 : -1);
GM_setValue('additionalFigures', JSON.stringify(additionalFigures));
drawAdditionalFigures();
resetNonMfcItemForm();
} else {
alert('Please enter a valid date in YYYY-MM format');
}
} else {
alert('Please fill in all mandatory fields');
}
});
const cancelButton = document.getElementById('cancelNonMfcItem');
cancelButton.addEventListener('click', (evt) => {
evt.preventDefault();
if(confirm('Are you sure you want to cancel?')) {
resetNonMfcItemForm();
}
});
}
})();