// ==UserScript==
// @name File Regexplorer
// @version 0.2.1
// @namespace file_regexplorer
// @description Implements local explorer - file manager and provide a means to sort the file list in custom order via regular expressions
// @description:en Implements local explorer - file manager and provide a means to sort the file list in custom order via regular expressions
// @description:it Implementa il file manager locale del browser e fornisce un sistema per ordinare l'elenco dei file in modo personalizzato tramite espressioni regolari
// @author OpenDec
// @match file:///*/
// @icon data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4NCjwhLS1HZW5lcmF0b3I6IFhhcmEgRGVzaWduZXIgKHd3dy54YXJhLmNvbSksIFNWRyBmaWx0ZXIgdmVyc2lvbjogNi4wLjAuNC0tPg0KPHN2ZyBzdHJva2Utd2lkdGg9IjAuNTAxIiBzdHJva2UtbGluZWpvaW49ImJldmVsIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgb3ZlcmZsb3c9InZpc2libGUiIHdpZHRoPSIyODQuNzE4cHQiIGhlaWdodD0iMjQyLjk5N3B0IiB2aWV3Qm94PSIwIDAgMjg0LjcxOCAyNDIuOTk3Ij4NCiA8ZGVmcz4NCgk8L2RlZnM+DQogPGcgaWQ9IkRvY3VtZW50IiBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBmb250LWZhbWlseT0iVGltZXMgTmV3IFJvbWFuIiBmb250LXNpemU9IjE2IiB0cmFuc2Zvcm09InNjYWxlKDEgLTEpIj4NCiAgPGcgaWQ9IlNwcmVhZCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAtMjQyLjk5NykiPg0KICAgPGcgaWQ9IkxheWVyIDMiPg0KICAgIDxwYXRoIGQ9Ik0gMCwyMTUuMDczIEMgMCwyMzAuNDg3IDEyLjUxLDI0Mi45OTcgMjcuOTIzLDI0Mi45OTcgTCA2MC41NzgsMjQyLjk5NyBDIDc1Ljk5MSwyNDIuOTk3IDg4LjUwMSwyMzAuNDg3IDg4LjUwMSwyMTUuMDczIEwgODguNTAxLDIxMC43NDkgTCAyMjQuNzM4LDIxMC43NSBDIDI0MS4wMjgsMjEwLjc1IDI1My4wMzUsMTk3LjU4NCAyNTEuNTM4LDE4MS4zNjMgTCAyMzcuNTEsMjkuMzg4IEwgMC4wMDEsMjkuMTUxIiBmaWxsPSIjY2Y5YzBlIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMC41IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1taXRlcmxpbWl0PSI3OS44NDAzMTkzNjEyNzc1Ii8+DQogICAgPHBhdGggZD0iTSA1Mi41LDE3NS4zMjQgTCA1Mi41LDczLjY3NSBMIDIzNi4yNTEsNzMuNjc1IEwgMjM2LjI1MSwxNzUuMzI0IEMgMjM2LjI1MSwxODMuMjg3IDIyOS43ODgsMTg5Ljc1IDIyMS44MjUsMTg5Ljc1IEwgNjYuOTI2LDE4OS43NSBDIDU4Ljk2MywxODkuNzUgNTIuNSwxODMuMjg3IDUyLjUsMTc1LjMyNCBaIiBmaWxsPSIjZmZmZmZmIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMC41IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIG1hcmtlci1zdGFydD0ibm9uZSIgbWFya2VyLWVuZD0ibm9uZSIgc3Ryb2tlLW1pdGVybGltaXQ9Ijc5Ljg0MDMxOTM2MTI3NzUiLz4NCiAgICA8cGF0aCBkPSJNIDIzLjQ3NSwxMjguNzQ0IEwgMS4wOSwzNy41MTEgQyAtMy45ODksMTYuODA1IDkuMTkxLDAgMzAuNTExLDAgTCAyMTMuNDE3LDAgQyAyMzQuNzM3LDAgMjU2LjE2NCwxNi44MDUgMjYxLjI0NCwzNy41MTEgTCAyODMuNjI5LDEyOC43NDQgQyAyODguNzA5LDE0OS40NDkgMjc1LjUyOSwxNjYuMjU0IDI1NC4yMDgsMTY2LjI1NCBMIDcxLjMwMywxNjYuMjU0IEMgNDkuOTgyLDE2Ni4yNTQgMjguNTU1LDE0OS40NDkgMjMuNDc1LDEyOC43NDQgWiIgZmlsbD0iI2ZlZDU1NyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjAuNSIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBtYXJrZXItc3RhcnQ9Im5vbmUiIG1hcmtlci1lbmQ9Im5vbmUiIHN0cm9rZS1taXRlcmxpbWl0PSI3OS44NDAzMTkzNjEyNzc1Ii8+DQogICAgPHBhdGggZD0iTSAxNzIuNzI1LDg5LjI4NCBMIDE3Mi43MjQsMTY5Ljc5NyBDIDE3Mi43MjQsMTcyLjczNCAxNzUuMTA3LDE3NS4xMTYgMTc4LjA0MywxNzUuMTE2IEwgMjE3LjMzLDE3NS4xMTYgQyAyMjAuMjY1LDE3NS4xMTYgMjIyLjY0OCwxNzIuNzM0IDIyMi42NDgsMTY5Ljc5NyBMIDIyMi42NDUsODkuMjg0IEMgMjIyLjY0NSw4NC4xMjUgMjE4Ljg2NSw4MS43MzkgMjE0LjIwNyw4My45NTYgTCAxOTkuMzc4LDkxLjAyMSBDIDE5Ny42ODUsOTEuODA2IDE5Ny42ODUsOTEuODA2IDE5NS45OTIsOTEuMDIxIEwgMTgxLjE2NSw4My45NTYgQyAxNzYuNTA2LDgxLjczOSAxNzIuNzI1LDg0LjEyNSAxNzIuNzI1LDg5LjI4NCBaIiBmaWxsPSIjZDAwMDAwIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMC41IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIG1hcmtlci1zdGFydD0ibm9uZSIgbWFya2VyLWVuZD0ibm9uZSIgc3Ryb2tlLW1pdGVybGltaXQ9Ijc5Ljg0MDMxOTM2MTI3NzUiLz4NCiAgICA8cGF0aCBkPSJNIDYyLjQ5NywxMTIuNjk1IEMgNTAuMzc1LDEwMi42MDUgNDEuODQyLDg3LjkxMSA0MS44NDIsNzIuNzYgQyA0MS44NDIsNTcuNjA5IDUwLjM3Niw0Mi45MTUgNjIuNDk4LDMyLjgyNSIgZmlsbD0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2Utd2lkdGg9IjE1IiBzdHJva2U9IiM2MzRkMjkiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS1taXRlcmxpbWl0PSI3OS44NDAzMTkzNjEyNzc1Ii8+DQogICAgPHBhdGggZD0iTSAxMzcuODQ0LDExMi42OTUgQyAxNDkuOTY2LDEwMi42MDUgMTU4LjQ5OSw4Ny45MTEgMTU4LjQ5OSw3Mi43NiBDIDE1OC40OTksNTcuNjA5IDE0OS45NjUsNDIuOTE1IDEzNy44NDMsMzIuODI1IiBmaWxsPSJub25lIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS13aWR0aD0iMTUiIHN0cm9rZT0iIzYzNGQyOSIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLW1pdGVybGltaXQ9Ijc5Ljg0MDMxOTM2MTI3NzUiLz4NCiAgICA8cGF0aCBkPSJNIDc5Ljk3Myw3MS4xNTggQyA4Ni40NTUsNzEuMTU4IDkxLjcxNyw2NS44OTYgOTEuNzE3LDU5LjQxNCBDIDkxLjcxNyw1Mi45MzIgODYuNDU1LDQ3LjY3IDc5Ljk3Myw0Ny42NyBDIDczLjQ5MSw0Ny42NyA2OC4yMjksNTIuOTMyIDY4LjIyOSw1OS40MTQgQyA2OC4yMjksNjUuODk2IDczLjQ5MSw3MS4xNTggNzkuOTczLDcxLjE1OCBaIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgZmlsbD0iIzYzNGQyOSIgc3Ryb2tlPSJub25lIiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBtYXJrZXItc3RhcnQ9Im5vbmUiIG1hcmtlci1lbmQ9Im5vbmUiIHN0cm9rZS1taXRlcmxpbWl0PSI3OS44NDAzMTkzNjEyNzc1Ii8+DQogICAgPHBhdGggZD0iTSAxMDIuNzg5LDgwLjEzOSBMIDEwNy4xNjMsOTEuOTYgTCA5Ni42NzYsOTguOTUxIEwgMTA5LjI3MSw5OC40NDQgTCAxMTIuNjc4LDExMC41NzggTCAxMTYuMDg4LDk4LjQ0MyBMIDEyOC42ODIsOTguOTUxIEwgMTE4LjE5NCw5MS45NiBMIDEyMi41NjksODAuMTM5IEwgMTEyLjY3OCw4Ny45NTIgTCAxMDIuNzg5LDgwLjEzOSBaIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS13aWR0aD0iOSIgc3Ryb2tlPSIjNjM0ZDI5IiBmaWxsPSIjNjM0ZDI5IiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBtYXJrZXItc3RhcnQ9Im5vbmUiIG1hcmtlci1lbmQ9Im5vbmUiIHN0cm9rZS1taXRlcmxpbWl0PSI3OS44NDAzMTkzNjEyNzc1Ii8+DQogICA8L2c+DQogIDwvZz4NCiA8L2c+DQo8L3N2Zz4NCg==
// @grant GM.getValue
// @grant GM.setValue
// @grant GM.deleteValue
// @run-at document-start
// @license MIT
// ==/UserScript==
/* jshint esversion: 11 */
// --------------------------------------------------
// --- START ---
// --------------------------------------------------
function start(){
'use strict';
// Exit if page is cached
const _html = document.documentElement;
if (_html.classList.contains('od_regexplorer')) return;
_html.classList.add('od_regexplorer');
const _name = GM.info.script.name;
const _vers = GM.info.script.version;
const _icon = GM.info.script.icon || GM.info.scriptMetaStr.match(/^\/\/ @icon +(.*)$/m)[1];
const _header = document.querySelector('#header, h1');
const _table = document.querySelector('table');
const _thead = _table.querySelector('#theader, thead > tr');
const _tbody = _table.querySelector('#tbody, tbody');
const _topbar = document.createElement('div');
const _form = document.createElement('div');
const _url = window.location.href;
const _urlHash = getHash(_url);
let _arrRows;
let _order;
let _orderBy;
let _userData;
// --------------------------------------------------
// --- DATA ---
// --------------------------------------------------
async function saveData(){
const data = {};
if (_inpPattern.value) data.pattern = _inpPattern.value;
if (_ckbFlagI.checked) data.flagI = 1;
if (_inpOutput.value) data.output = _inpOutput.value;
if (_ckbFilteredView.checked) data.filteredView = 1;
if (_orderBy) data.orderBy = _orderBy;
if (_order === -1) data.order = -1;
if (Object.keys(data).length === 0) GM.deleteValue(_urlHash);
else GM.setValue(_urlHash, JSON.stringify(data));
}
async function loadData(){
_html.removeAttribute('style');
const dataJSON = await GM.getValue(_urlHash);
if (!dataJSON) return;
const data = JSON.parse(dataJSON);
if (data.pattern) _inpPattern.value = data.pattern;
if (data.flagI) _ckbFlagI.checked = true;
if (data.output) _inpOutput.value = data.output;
if (data.filteredView) _ckbFilteredView.checked = true;
_orderBy = data.orderBy ? +data.orderBy : 0;
_order = data.order ? -1 : 1;
fillOutputColumn(_inpPattern.value, _inpOutput.value);
setFilteredView(_ckbFilteredView.checked, {skipTransition: true});
if (_orderBy || _order === -1){
_table.dataset.order = _order;
sortBy(_orderBy, true);
}
}
function getHash(s){
const hash = Array.from(s).reduce((hash, char) => 0 | (31 * hash + char.charCodeAt(0)), 0).toString(36);
return '_' + (hash[0] === '-' ? hash.slice(1).toUpperCase() : hash);
}
loadData();
// --------------------------------------------------
// --- TOP ---
// --------------------------------------------------
_topbar.id = 'od_topbar';
_topbar.innerHTML = `<a href="https://greasyfork.org/it/scripts/467835-file-regexplorer">${_name} v${_vers}</a>`;
document.body.appendChild(_topbar);
// --------------------------------------------------
// --- FORM ---
// --------------------------------------------------
// Pattern fields
_form.id = 'od_form';
_form.innerHTML = `
<div class="od_container_field" id="od_container_pattern">
<label class="od_lbl" id="od_lbl_pattern">Pattern (.*): <input class="od_inp" id="od_inp_pattern" value="" spellcheck="false"></label>
<label class="od_lbl od_lbl_top" id="od_lbl_pattern_flag_i"><input type="checkbox" class="od_ckb od_ckb_top" id="od_ckb_pattern_flag_i"> Case Insensitive</label>
</div>
<div class="od_container_field" id="od_container_output">
<label class="od_lbl" id="od_lbl_output">Output: <input class="od_inp" id="od_inp_output" value="" spellcheck="false"></label>
<label class="od_lbl od_lbl_top" id="od_lbl_output_filtered_view"><input type="checkbox" class="od_ckb od_ckb_top" id="od_ckb_output_filtered_view"> Filtered view</label>
</div>
`;
_header.parentNode.insertBefore(_form, _header.nextSibling);
const _inpPattern = document.getElementById('od_inp_pattern');
const _ckbFlagI = document.getElementById('od_ckb_pattern_flag_i');
const _inpOutput = document.getElementById('od_inp_output');
const _ckbFilteredView = document.getElementById('od_ckb_output_filtered_view');
// Check regex pattern validity
_inpPattern.addEventListener('input', e => {
const me = e.target;
const pat = me.value;
me.setCustomValidity('');
try {
new RegExp(pat);
} catch(err){
me.setCustomValidity('Invalid regex pattern');
}
});
// Redraw output column based on form values
function submitForm(){
fillOutputColumn(_inpPattern.value, _inpOutput.value);
if (_table.dataset.orderBy === '1') sortBy(1, true);
saveData();
}
// When press a key in the fields
function onKey(e){
const me = e.target;
if (e.keyCode === 13){
if (me ===_inpPattern) _inpOutput.focus();
else me.blur();
} else if (e.keyCode === 27){
me.value = me.dataset.currentValue;
me.blur();
}
}
// When field focus
function onFocus(e){
const me = e.target;
me.dataset.currentValue = me.value;
}
// Form fields listeners
[_inpPattern, _inpOutput].forEach(e => {
e.addEventListener('change', submitForm);
e.addEventListener('focus', onFocus);
e.addEventListener('keydown', onKey);
});
_ckbFlagI.addEventListener('change', e => {
submitForm();
});
_ckbFilteredView.addEventListener('change', e => {
const me = e.target;
saveData();
setFilteredView(me.checked);
});
// --------------------------------------------------
// --- TABLE ---
// --------------------------------------------------
(()=>{
const th = document.createElement('th');
th.innerText = '(.*)';
_thead.insertBefore(th, _thead.cells[1]);
// Clean & set column headers
setTimeout(()=>{
Array.from(_thead.cells).forEach(th => {
th.innerHTML = th.innerText;
th.tabIndex = '0';
th.setAttribute('role', 'button');
});
}, 100);
Array.from(_tbody.rows).forEach(tr => {
const td0 = tr.cells[0];
if ('value' in td0.dataset){
// On Chromium, prepend 1 or 2 for the value of folders or files respectively, to keep items separate when sorting, like Firefox does
const tdVal = td0.dataset.value;
if (tdVal){
td0.dataset.value = (tdVal.slice(-1) === '/')
? 1 + (td0.firstChild.innerHTML = tdVal.slice(0,-1))
: 2 + tdVal
;
}
} else if (td0.hasAttribute('sortable-data')){
// FF - pass the values to the data attribute for each td
Array.from(tr.cells).forEach(td => {
td.dataset.value = td.getAttribute('sortable-data');
td.removeAttribute('sortable-data');
});
// FF remove the nested table with the ellipsis from the file name cell
td0.innerHTML = td0.querySelector('a').outerHTML;
}
// Wrap cell content
Array.from(tr.cells).forEach((td, index) => {
if (td.innerHTML === '') return;
td.innerHTML = `<div>${td.innerHTML}</div>`;
});
// Add output column
let td = document.createElement('td');
tr.insertBefore(td, tr.cells[1]);
});
// Shadow thead sticky
const shadow = document.createElement('div');
shadow.id = 'od_shadow_thead_sticky';
document.body.insertBefore(shadow, _table);
// Change the sort order when you press on the headers
_thead.addEventListener('click', e => {
e.stopPropagation();
sortBy(e.target.cellIndex);
saveData();
}, true);
_thead.addEventListener('keydown', e => {
if (e.key == 'Enter' || e.key == ' ') {
e.stopPropagation();
e.preventDefault();
sortBy(e.target.cellIndex);
saveData();
}
}, true);
// Default array rows (sorted by filename ascending)
_arrRows = Array.from(_tbody.rows);
sortBy(0);
_arrRows = Array.from(_tbody.rows);
})();
function fillOutputColumn(pat, out){
let reg;
try {
reg = new RegExp(pat, _ckbFlagI.checked ? 'i' : '');
} catch(err) {
console.error('Invalid regex pattern');
return;
}
const nomatches = [];
_arrRows.forEach((tr, index) => {
const valName = tr.cells[0].innerText;
const matches = valName.match(reg);
const td = tr.cells[1];
if (matches === null){
nomatches[index] = true;
td.innerHTML = '<div></div>';
// Prepend 2 for empty values, otherwise 1, to keep items separate when sorting
td.dataset.value = 2;
} else {
const text = matches[0]?.replace(reg, out || '$&') || '';
td.innerHTML = `<div>${text}</div>`;
td.dataset.value = 1 + text;
}
});
// Apply nomatch class
applyNomatch(nomatches);
}
function applyNomatch(nomatches){
_arrRows.forEach((tr, index) => {
tr.classList.toggle('od_nomatch', nomatches[index] || false);
});
}
function sortBy(column, keepOrder = false){
const rows = Array.from(_arrRows);
_order = keepOrder ? +_table.dataset.order : (_orderBy == column && _table.dataset.order === '1') ? -1 : 1;
_table.dataset.orderBy = _orderBy = column;
_table.dataset.order = _order;
rows.sort((rowA, rowB) => {
let a = rowA.cells[_orderBy].dataset.value || '';
let b = rowB.cells[_orderBy].dataset.value || '';
return _order * a.localeCompare(b, false, {numeric: true});
});
_tbody.innerHTML = '';
for (let i = 0; i < rows.length; i++){
_tbody.appendChild(rows[i]);
}
}
function setFilteredView(b, o){
if (o?.skipTransition){
_table.classList.add('od_skip_transition');
setTimeout(()=>{_table.classList.remove('od_skip_transition');}, 0)
}
_table.classList.toggle('od_filtered_view', b);
}
// --------------------------------------------------
// --- STYLE ---
// --------------------------------------------------
addGlobalStyle(`
/* -------------------------------------------------- */
/* --- RESET --- */
/* -------------------------------------------------- */
html *,
html *::before,
html *::after {box-sizing: border-box}
:root {
padding-inline: 0;
}
/* -------------------------------------------------- */
/* --- MAIN --- */
/* -------------------------------------------------- */
body {
position: relative;
width: auto;
min-width: 500px;
margin: 4em auto;
padding: 2em 1em;
font: 12px "Segoe UI", "DejaVu Sans", "Bitstream Vera Sans", "Lucida Grande", Verdana, Tahoma, Arial, sans-serif;
border: 1px solid;
border-radius: 10px;
}
#header, h1 {
margin: 0 0 60px;
padding: 0;
white-space: normal;
word-break: break-word;
font-size: 160%;
font-weight: normal;
border-bottom: 1px solid;
}
#header, h1 {
margin: 0 0 60px;
padding: 0;
white-space: normal;
word-break: break-word;
font-size: 160%;
font-weight: normal;
border-bottom: 1px solid;
}
/* -------------------------------------------------- */
/* --- TOPBAR --- */
/* -------------------------------------------------- */
#od_topbar {
display: inline-flex;
align-items: center;
position: absolute;
top: -30px;
left: 0;
}
#od_topbar a {
display: inline-flex;
align-items: center;
text-decoration: none;
padding-left: 40px;
height: 30px;
background: 8px/24px url(${_icon}) no-repeat;
}
/* -------------------------------------------------- */
/* --- FORM --- */
/* -------------------------------------------------- */
#od_form {
position: relative;
z-index: 1;
display: grid;
grid-template-columns: 1fr;
grid-gap: 1rem;
margin: -50px 0 10px;
padding: 5px 10px;
}
input.od_inp {
width: 100%;
margin: 5px 0 0;
padding: .5em;
border: 1px solid;
}
input.od_inp:invalid {
outline: 1px solid;
}
.od_container_field {
position: relative;
}
.od_lbl {
user-select: none;
}
.od_lbl_top {
display: flex;
align-items: center;
position: absolute;
top: 0;
right: 0;
padding-left: 18px;
margin-right: 10px;
}
.od_ckb_top {
position: absolute;
left: 0;
margin: 0;
}
/* -------------------------------------------------- */
/* --- TABLE --- */
/* -------------------------------------------------- */
body > table {
min-width: 100%;
margin: 0 auto;
border-collapse: separate;
border-spacing: 0;
}
thead {
position: sticky;
top: 0;
z-index: 2;
}
#od_shadow_thead_sticky {
clear: both;
position: sticky;
top: 0;
width: 100%;
height: 2.5em;
margin-top: -2.5em;
pointer-events: none;
}
/* FF fix. Collapse margin after floated elements */
#UI_goUp, #UI_showHidden {
margin-bottom: -2.5em;
position: relative;
z-index: 1;
}
:root.od_regexplorer > body > table > * > tr > * {
padding-block: 4px;
padding-inline: 8px;
}
body > table > thead > tr > th {
position: relative;
border-width: 1px;
border-style: solid;
font-size: 15px;
font-weight: normal;
text-align: center;
white-space: nowrap;
cursor: pointer;
user-select: none;
}
body > table > tbody > tr {
outline-offset: -1px;
}
body > table > tbody > tr > td {
border: solid transparent;
border-width: 0 1px;
}
body > table > tbody > tr > td {
}
body > table > thead > tr > th:first-child {
text-align: start;
}
body > table > tbody > tr > td:nth-child(-n+2) {
text-align: start;
overflow-wrap: anywhere;
}
body > table > tbody > tr > td:nth-child(3) {
text-align: end;
}
body > table > tbody > tr > td:nth-child(4) {
text-align: center;
}
body > table > tbody > tr {
outline: 1px solid transparent;
}
body > table > tbody > tr > td:first-child a {
display: inline-block;
white-space: break-spaces;
}
/* Sorting icons - column headers */
table[data-order-by="0"] > thead > tr > th:nth-child(1)::after,
table[data-order-by="1"] > thead > tr > th:nth-child(2)::after,
table[data-order-by="2"] > thead > tr > th:nth-child(3)::after,
table[data-order-by="3"] > thead > tr > th:nth-child(4)::after {
display: inline-block;
}
table[data-order] > thead > tr > th::after {
display: none;
position: absolute;
top: -4px;
left: 0;
right: 0;
width: fit-content;
margin-inline: auto;
font-size: 9px;
text-align: center;
opacity: .4;
transform: scaleX(1.5);
}
table[data-order="1"] > thead > tr > th::after {
content: "˄";
}
table[data-order="-1"] > thead > tr > th::after {
content: "˅";
}
/* Unmatched items */
tr.od_nomatch td:nth-child(2) {
position: relative;
}
tr td:nth-child(2) > div:empty {
width: 0;
left: 50%;
transform: translateX(-50%);
}
tr.od_nomatch td:nth-child(2) > div {
position: absolute;
top: 50%;
border-bottom: 1px solid #e5683e;
width: 100%;
}
/* Filtered view - hide rows with unmatched items */
body > table > tbody > tr > td {
opacity: 1;
transition: .3s .2s;
}
body > table > tbody > tr > td > div {
overflow: hidden;
max-width: 75vw;
max-height: 10em;
transition: .3s .2s, max-width .2s;
}
body > table.od_filtered_view > tbody > tr.od_nomatch > td {
padding-block: 0;
opacity: 0;
transition: .5s;
}
body > table.od_filtered_view > tbody > tr.od_nomatch > td > div {
max-width: 0;
max-height: 0;
transition: .3s, max-width .3s .3s;
}
body > table.od_skip_transition > tbody > tr.od_nomatch > td,
body > table.od_skip_transition > tbody > tr.od_nomatch > td > div {
transition: none;
}
@media (min-width: 600px) {
#od_form {grid-template-columns: 3fr 2fr;}
body {
max-width: 800px;
min-width: fit-content;
padding: 3em;
}
}
/* -------------------------------------------------- */
/* --- COLORS --- */
/* -------------------------------------------------- */
thead > tr {box-shadow: -1px 0 0, 1px 0 0;}
:root {background-color: #efefef;}
body {background-color: #fff; border-color: #8888;}
#od_shadow_thead_sticky {box-shadow: 0 .3em .8em .3em #fff;}
#od_topbar a {color: #555;}
#od_topbar a:hover {color: #111;}
#od_form {background: #8882;}
#header, h1 {border-bottom-color: #8889;}
input.od_inp {background: #fff; border-color: #888;}
input.od_inp:invalid {background-color: #f003; outline-color: #f33;}
thead > tr {background-color: #eee; color: #eee;}
thead > tr > th {color: #333; border-color: transparent #888a #8884 transparent;}
thead > tr > th:last-child {border-right-color: transparent}
thead > tr > th:hover {background: #8883;}
body > table > tbody > tr:hover {background: #8881; outline-color: #8889;}
@media (prefers-color-scheme: dark) {
:root {background-color: #18181a;}
body {background-color: #272729;}
#od_shadow_thead_sticky {box-shadow: 0 .3em .8em .3em #272729;}
thead > tr {background-color: #333; color: #333;}
thead > tr > th {color: #eee;}
#od_topbar a {color: #bbb;}
#od_topbar a:hover {color: #eee;}
input.od_inp {background: #454546;}
}
`);
}
function addGlobalStyle(strCSS){
const h = document.querySelector('head');
if (!h) return;
const s = document.createElement('style');
s.type = 'text/css';
s.innerHTML = strCSS;
h.appendChild(s);
}
// --------------------------------------------------
// --- WHEN DOCUMENT IS READY ---
// --------------------------------------------------
if (['complete', 'interactive', 'loaded'].includes(document.readyState)){
// Document has at least been parsed
start();
} else {
// Document is not ready yet, so wait for the event
document.documentElement.style.opacity = '0';
document.addEventListener('DOMContentLoaded', start, false);
}