NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name UI improvements for CaliberFan operators upgrades page
// @namespace http://tampermonkey.net/
// @version 2025-06-05--0745
// @description UI improvements for CaliberFan operators upgrades page
// @author Nexus <artem.nexus94@gmail.com>
// @include /^https?:\/\/caliberfan\.com\/wp-admin\/post.php.*$/
// @grant none
// @updateURL https://openuserjs.org/meta/Nexus/UI_improvements_for_CaliberFan_operators_upgrades_page.meta.js
// @downloadURL https://openuserjs.org/install/Nexus/UI_improvements_for_CaliberFan_operators_upgrades_page.user.js
// @copyright 2025, Nexus (https://openuserjs.org/users/Nexus)
// @license MIT
// ==/UserScript==
var styles = {
'.operators-upgrades-matrix': [
'margin-top: 35px;',
'position: relative;'
],
'.operators-upgrades-matrix__row': [
'display: flex;',
'flex-wrap: nowrap;',
'margin-bottom: -32px;'
],
'.operators-upgrades-matrix__row:last-child': [
'margin-bottom: 0;'
],
'.operators-upgrades-matrix__row--shifted': [
'margin-left: 46px;'
],
'.operators-upgrades-matrix__cell': [
'width: 60px;',
'height: 60px;',
'background: #a6a6a6;',
'-webkit-clip-path: polygon(25% 5%, 75% 5%, 100% 50%, 75% 95%, 25% 95%, 0% 50%);',
'clip-path: polygon(25% 5%, 75% 5%, 100% 50%, 75% 95%, 25% 95%, 0% 50%);',
'margin: 0 16px;',
'cursor: pointer;',
'transition: border 0.3s, transform 0.3s, opacity 0.3s;',
'position: relative;',
'display: flex;',
'align-items: center;',
'justify-content: center;',
'color: #fff;',
'padding: 10px;',
'box-sizing: border-box;'
],
'.operators-upgrades-matrix__cell--empty': [
'opacity: 0.2;'
],
'.operators-upgrades-matrix__cell--empty:hover': [
'opacity: 0.5;'
],
'.operators-upgrades-matrix__cell--active': [
'background: #ffcc00;'
],
'.operators-upgrades-matrix__cell::before': [
'content: \'\';',
'position: absolute;',
'top: 2px;',
'left: 2px;',
'width: calc(100% - 4px);',
'height: calc(100% - 4px);',
'background: #333;',
'-webkit-clip-path: polygon(25% 5%, 75% 5%, 100% 50%, 75% 95%, 25% 95%, 0% 50%);',
'clip-path: polygon(25% 5%, 75% 5%, 100% 50%, 75% 95%, 25% 95%, 0% 50%);',
'z-index: -1;'
],
'.operators-upgrades-matrix__cell img': [
'max-width: 100%;',
],
'.fast-phrases': [
'margin-top: 5px;'
],
'.fast-phrases span': [
'cursor: pointer;',
'margin-right: 15px;',
'padding: 1px 5px;',
'border-radius: 3px;'
],
'.fast-phrases span:hover': [
'background: #999;',
'color: #fff;'
],
};
function debounce(callback, wait) {
var timeoutId = null;
return function (...args) {
var that = this;
timeoutId != null && clearTimeout(timeoutId);
timeoutId = setTimeout(function () {
callback.apply(that, args);
}, wait);
};
};
(function() {
'use strict';
var addNewEntryLink = document.querySelector('#wpbody-content .wrap a.page-title-action'),
container = document.querySelector('#post-body-content');
if (!addNewEntryLink || !container || addNewEntryLink.href.indexOf('?post_type=prokachka') === -1) {
return;
}
var styleNode = document.createElement('style');
styleNode.textContent = '';
styleNode.id = 'UIImprovementsForCaliberFanOperatorsUpgradesPage--runtime-styles';
Object.keys(styles).forEach(function (key) {
styleNode.textContent += key + '{' + styles[key].join(';') + '}';
});
(document.head || container).appendChild(styleNode);
var tabControls = [].slice.call(
document.querySelectorAll('.acf-tab-wrap li a.acf-tab-button'), 0, 6
),
activeTabControl = tabControls.find(function (tab) {
return tab.closest('li.active') != null;
}),
makeClick = function (nodeToClick) {
if (nodeToClick) {
nodeToClick.dispatchEvent(
new Event('click', {bubbles: true})
);
};
};
var groupsMatrix = tabControls.map(function (tabControl) {
makeClick(tabControl);
return {
tabControl: tabControl,
groups: [].slice.call(
document.querySelectorAll('.acf-field.acf-field-group:not(.acf-hidden)')
)
};
}),
getInputGroupNodesByCellIndexes = function (rowIndex, cellIndex) {
if (!(rowIndex in groupsMatrix) || !(cellIndex in groupsMatrix[rowIndex].groups)) {
return null;
};
return {
tabControl: groupsMatrix[rowIndex].tabControl,
group: groupsMatrix[rowIndex].groups[cellIndex],
};
};
makeClick(activeTabControl || tabControls[0]);
activeTabControl = null;
function makeOperatorUpgradesMatrix() {
var container = document.createElement('div');
container.className = 'operators-upgrades-matrix';
var onChange = function (groupWrapper, cell) {
var imageUploader = groupWrapper ? groupWrapper.querySelector('.acf-input .acf-image-uploader') : null,
previewImg = imageUploader ? imageUploader.querySelector('.show-if-value.image-wrap img') : null,
isEmpty = !imageUploader || imageUploader.className.indexOf('has-value') === -1,
cellHasEmptyClass = cell.className.indexOf('operators-upgrades-matrix__cell--empty') !== -1;
if (isEmpty && !cellHasEmptyClass) {
cell.className += ' operators-upgrades-matrix__cell--empty';
} else if (!isEmpty && cellHasEmptyClass) {
cell.className = cell.className.replace('operators-upgrades-matrix__cell--empty', '');
}
cell.innerHTML = (isEmpty || !previewImg) ?
'<div>' + cell.title + '</div>' :
'<img src="' + (previewImg.src || '') + '" />';
};
var subscriptions = [],
getListener = function (wrapper, cell) {
var listener = function (e) {
onChange(wrapper, cell);
};
subscriptions.push(function () {
wrapper.removeEventListener('change', listener);
});
return listener;
};
for (var rowIndex = 0; rowIndex < 6; rowIndex++) {
var row = document.createElement('div');
row.className = 'operators-upgrades-matrix__row';
var isOdd = rowIndex % 2 === 1;
if (isOdd) {
row.className += ' operators-upgrades-matrix__row--shifted';
}
for (var cellIndex = 0; cellIndex < (isOdd ? 7 : 8); cellIndex++) {
var cell = document.createElement('div'),
levelNumber = cellIndex * 2 + 1 + +isOdd,
optionNumber = Math.floor(rowIndex / 2) + 1,
nodes = getInputGroupNodesByCellIndexes(rowIndex, cellIndex),
groupWrapper = nodes ? nodes.group : null;
cell.className = 'operators-upgrades-matrix__cell';
cell.setAttribute('data-row-index', String(rowIndex));
cell.setAttribute('data-cell-index', String(cellIndex));
cell.title = [levelNumber, optionNumber].join('-');
cell.innerHTML = '<div>' + cell.title + '</div>';
if (groupWrapper) {
groupWrapper.addEventListener('change', getListener(groupWrapper, cell));
};
onChange(groupWrapper, cell);
row.appendChild(cell);
}
container.appendChild(row);
};
container.addEventListener('click', function (e) {
var target = e.target;
if (!target || !target.closest('.operators-upgrades-matrix__cell')) {
return;
}
var cell = target.closest('.operators-upgrades-matrix__cell'),
rowIndex = +cell.getAttribute('data-row-index'),
cellIndex = +cell.getAttribute('data-cell-index'),
nodes = getInputGroupNodesByCellIndexes(rowIndex, cellIndex);
if (!nodes) {
return void console.error(
new Error('Cannot find ACF\'s group of inputs')
);
};
makeClick(nodes.tabControl);
window.scrollTo({
top: document.documentElement.scrollTop + nodes.group.getBoundingClientRect().top - 30
});
container.dispatchEvent(new CustomEvent('cell-click', {bubbles: true}));
});
container.cancelAllSubscriptions = function () {
subscriptions.forEach(function (unsubscribe) {
unsubscribe();
});
};
return container;
};
container.appendChild(
makeOperatorUpgradesMatrix()
);
// add Jump To button
var groupToItsIndexesMap = new WeakMap(),
groups = groupsMatrix.reduce(function (res, item, rowIndex) {
return res.concat(
item.groups.map(function (cell, cellIndex) {
groupToItsIndexesMap.set(cell, {
rowIndex: rowIndex,
cellIndex: cellIndex
});
return cell;
})
);
}, []);
var helper = null,
activeButton = null;
groups.forEach(function (groupWrapper) {
var label = groupWrapper.querySelector('.acf-label > label');
var container = document.createElement('div');
container.style.marginRight = '15px';
container.style.display = 'inline-block';
var button = document.createElement('button');
button.type = 'button';
button.className = 'button';
button.innerHTML = 'Jump to <span style="transform: rotate(90deg); display: inline-block;">➦</span>';
button.addEventListener('click', function () {
if (activeButton === button && helper) {
helper.cancelAllSubscriptions();
helper.remove();
helper = activeButton = null;
return;
}
var rect = button.getBoundingClientRect();
var container = helper || document.createElement('div');
container.innerHTML = '';
container.style.padding = '5px';
container.style.border = 'solid 1px #999';
container.style.borderRadius = '3px';
container.style.backgroundColor = '#eee';
container.style.position = 'absolute';
container.style.top = (document.documentElement.scrollTop + rect.y + rect.height + 5) + 'px';
container.style.left = rect.x + 'px';
var helperMatrix = makeOperatorUpgradesMatrix();
helperMatrix.style.marginTop = 0;
helperMatrix.addEventListener('cell-click', function () {
helperMatrix.cancelAllSubscriptions();
container.remove();
helper = activeButton = null;
});
if (groupToItsIndexesMap.has(groupWrapper)) {
[].forEach.call(
helperMatrix.querySelectorAll('.operators-upgrades-matrix__cell--active'),
function (node) {
node.className = node.clasName.replace('operators-upgrades-matrix__cell--active', '');
}
);
var indexes = groupToItsIndexesMap.get(groupWrapper),
cell = helperMatrix.querySelector(
'.operators-upgrades-matrix__cell'
+'[data-row-index="' + indexes.rowIndex + '"]'
+'[data-cell-index="' + indexes.cellIndex + '"]'
);
if (cell) {
cell.className += ' operators-upgrades-matrix__cell--active';
}
}
container.appendChild(helperMatrix);
document.body.appendChild(container);
activeButton = button;
container.cancelAllSubscriptions = helperMatrix.cancelAllSubscriptions.bind(helperMatrix);
helper = container;
});
container.appendChild(button);
label.insertBefore(container, label.firstChild);
});
// fix bug with mediafiles "silent pick" that doesn't trigger change event
if (('MutationObserver' in window)) {
new MutationObserver(function (entries) {
[].forEach.call(entries, function (record) {
if (
record.type !== 'attributes' ||
record.attributeName !== 'class' ||
!record.target.closest('.acf-image-uploader')
) {
return;
};
var input = record.target
.closest('.acf-image-uploader')
.querySelector('input[name^="acf["]');
if (input) {
input.dispatchEvent(new Event('change', {bubbles: true}));
};
});
}).observe(document.querySelector('#post-body'), {
subtree: true,
attributes: true,
attributefilter: ['class'],
});
};
// add fast fields filling by using already used values
var inputsSelector = '.acf-input .acf-input-wrap input[type="text"][name^="acf["]',
inputsValues = {},
getVocabularyKey = function (string) {
return string.toLowerCase().replace(/[\s\.,]+?/guim, '');
},
inputsToObserve = groupsMatrix.reduce(function (res, item) {
item.groups.forEach(function (group) {
[].slice.call(
group.querySelectorAll(inputsSelector)
).forEach(function (input, index) {
if (!(index in inputsValues)) {
inputsValues[index] = {};
}
var wrapper = input.closest('.acf-input-wrap');
var container = document.createElement('div');
wrapper.appendChild(container);
container.className = 'fast-phrases';
var value = input.value.trim(),
key = getVocabularyKey(value);
if (!(key in inputsValues[index])) {
inputsValues[index][key] = {
phrase: value,
inputs: [],
nodes: [],
};
};
inputsValues[index][key].inputs.push(input);
(res[index] || (res[index] = [])).push({
input: input,
container: container
});
});
});
return res;
}, {}),
addNode = function (vocabularyItem, input) {
var value = vocabularyItem.phrase;
if (!value.length) {
return null;
}
var node = document.createElement('span');
node.textContent = value;
node.addEventListener('click', function () {
input.value = value;
input.dispatchEvent(new Event('input', {bubbles: true}));
input.dispatchEvent(new Event('change', {bubbles: true}));
input.focus();
});
return node;
};
Object.keys(inputsToObserve).forEach(function (fieldIndex) {
inputsToObserve[fieldIndex].forEach(function (item, index) {
var container = item.container,
input = item.input;
Object.values(inputsValues[fieldIndex] || {}).forEach(function (item) {
var node = addNode(item, input);
if (node) {
item.nodes.push(node);
container.appendChild(node);
}
});
(function (fieldIndex, input, container) {
var previousValue = input.value.trim();
input.addEventListener('input', debounce(function () {
var value = input.value.trim(),
key = getVocabularyKey(previousValue);
if (previousValue === value) {
return;
};
if (key in inputsValues[fieldIndex]) {
inputsValues[fieldIndex][key].inputs = inputsValues[fieldIndex][key].inputs.filter(function (node) {
return node !== input;
});
if (!inputsValues[fieldIndex][key].inputs.length) {
inputsValues[fieldIndex][key].nodes.forEach(function (node) {
node.remove();
});
delete inputsValues[fieldIndex][key];
}
};
previousValue = value;
key = getVocabularyKey(value);
if (!(key in inputsValues[fieldIndex])) {
inputsValues[fieldIndex][key] = {
phrase: value,
inputs: [],
nodes: [],
};
}
inputsValues[fieldIndex][key].inputs.push(input);
// it's not newly added phrase, skip list modifying
if (inputsValues[fieldIndex][key].inputs.length !== 1) {
return;
}
inputsToObserve[fieldIndex].forEach(function (item, index) {
var container = item.container,
input = item.input;
var node = addNode(inputsValues[fieldIndex][key], input);
if (node) {
inputsValues[fieldIndex][key].nodes.push(node);
container.appendChild(node);
}
});
}, 300));
input.addEventListener('input', debounce(function () {
var words = input.value.trim().toLowerCase().split(' ');
[].forEach.call(container.querySelectorAll('span'), function (node) {
var matched = !words.length || words.find(function (word) {
return node.textContent.toLowerCase().indexOf(word) !== -1;
}) != null;
node.style.display = matched ? '' : 'none';
});
}, 300));
})(fieldIndex, input, container);
});
});
})();