MediaWiki:ScmcCatalog.js: различия между версиями
Страница интерфейса MediaWiki
Дополнительные действия
Defer (обсуждение | вклад) Новая страница: «(function () { if (typeof mw === 'undefined' || !window.document) return; var statusMap = { green: { emoji: '🟢', label: 'Готово', words: ['готово', 'готов'] }, yellow: { emoji: '🟡', label: 'Нужно обновить', words: ['нужно обновить', 'обновить', 'устарело'] }, red: { emoji: '🔴', label: 'Нет страницы', words: ['нет страницы', 'нет', 'п...» |
Defer (обсуждение | вклад) Нет описания правки |
||
| Строка 295: | Строка 295: | ||
['blue', '🔵 Заморожено'], | ['blue', '🔵 Заморожено'], | ||
['problem', 'Проблемные'], | ['problem', 'Проблемные'], | ||
['stop', ' | ['stop', 'Сканер: stop'], | ||
['note', 'С заметками'] | ['note', 'С заметками'] | ||
].forEach(function (item, index) { | ].forEach(function (item, index) { | ||
| Строка 327: | Строка 327: | ||
function visibleNote(data) { | function visibleNote(data) { | ||
return clean(data.note); | |||
} | } | ||
function getSections() { | function getSections() { | ||
var order = [ | |||
'Морпехи', | |||
'Ксеноморфы', | |||
'Правила и процедуры', | |||
'Справочник', | |||
'Лор и материалы', | |||
'Общее' | |||
]; | |||
var map = {}; | var map = {}; | ||
| Строка 348: | Строка 346: | ||
}); | }); | ||
Object.keys(map).forEach(function (section) { | |||
return | if (order.indexOf(section) === -1) { | ||
order.push(section); | |||
} | |||
}); | |||
return order.filter(function (section) { | |||
return map[section]; | |||
}); | }); | ||
} | } | ||
| Строка 370: | Строка 374: | ||
position.addEventListener('click', function () { | position.addEventListener('click', function () { | ||
var answer = prompt('На какое место переместить страницу? Сейчас: ' + | var currentIndex = rowElements.indexOf(row) + 1; | ||
var total = rowElements.length; | |||
var answer = prompt('На какое место переместить страницу? Сейчас: ' + currentIndex + '. Всего: ' + total + '.', String(currentIndex)); | |||
if (answer === null) return; | if (answer === null) return; | ||
var target = parseInt(answer, 10); | var target = parseInt(answer, 10); | ||
if (!target || target < 1 || target > | if (!target || target < 1 || target > total) { | ||
alert('Нужно число от 1 до ' + | alert('Нужно число от 1 до ' + total + '.'); | ||
return; | return; | ||
} | } | ||
moveRow( | moveRow(row, getRowData(row), target); | ||
}); | }); | ||
| Строка 392: | Строка 398: | ||
link.textContent = data.title || data.page; | link.textContent = data.title || data.page; | ||
titleWrap.appendChild(link); | titleWrap.appendChild(link); | ||
main.appendChild(titleWrap); | main.appendChild(titleWrap); | ||
| Строка 416: | Строка 415: | ||
noteButton.addEventListener('click', function () { | noteButton.addEventListener('click', function () { | ||
var answer = prompt('Заметка для страницы "' + ( | var currentData = getRowData(row); | ||
var answer = prompt('Заметка для страницы "' + (currentData.title || currentData.page) + '". Пустое поле удалит data-note.', currentData.note || ''); | |||
if (answer === null) return; | if (answer === null) return; | ||
saveRowChange( | saveRowChange(row, currentData, function (sourceData) { | ||
sourceData.note = clean(answer); | sourceData.note = clean(answer); | ||
}, 'Обновление заметки страницы: ' + ( | }, 'Обновление заметки страницы: ' + (currentData.title || currentData.page)); | ||
}); | }); | ||
| Строка 433: | Строка 433: | ||
scanToggle.addEventListener('click', function () { | scanToggle.addEventListener('click', function () { | ||
saveRowChange( | var currentData = getRowData(row); | ||
saveRowChange(row, currentData, function (sourceData) { | |||
sourceData.scan = sourceData.scan === 'stop' ? '' : 'stop'; | sourceData.scan = sourceData.scan === 'stop' ? '' : 'stop'; | ||
}, 'Изменение режима сканирования страницы: ' + ( | }, 'Изменение режима сканирования страницы: ' + (currentData.title || currentData.page)); | ||
}); | }); | ||
| Строка 477: | Строка 479: | ||
if (status === data.status) return; | if (status === data.status) return; | ||
saveRowChange(data, function (sourceData) { | saveRowChange(row, data, function (sourceData) { | ||
sourceData.status = status; | sourceData.status = status; | ||
}, 'Обновление статуса страницы: ' + (data.title || data.page)); | }, 'Обновление статуса страницы: ' + (data.title || data.page)); | ||
| Строка 511: | Строка 513: | ||
if (section === data.section) return; | if (section === data.section) return; | ||
saveRowChange(data, function (sourceData) { | saveRowChange(row, data, function (sourceData) { | ||
sourceData.section = section; | sourceData.section = section; | ||
}, 'Изменение раздела страницы: ' + (data.title || data.page)); | }, 'Изменение раздела страницы: ' + (data.title || data.page)); | ||
| Строка 522: | Строка 524: | ||
} | } | ||
function saveRowChange(targetData, updater, summary) { | function applyLocalRowUpdate(row, data) { | ||
var className = clean(data.className || row.getAttribute('class') || 'scmc-catalog-row scmc-level-1'); | |||
var status = normalizeStatus(data.status); | |||
var scan = clean(data.scan); | |||
var noteText; | |||
if (className.indexOf('scmc-catalog-row') === -1) { | |||
className = 'scmc-catalog-row ' + getLevelClass(className); | |||
} | |||
row.className = className; | |||
row.setAttribute('data-section', clean(data.section)); | |||
row.setAttribute('data-page', clean(data.page)); | |||
row.setAttribute('data-title', clean(data.title || data.page)); | |||
row.setAttribute('data-status', status); | |||
if (scan) { | |||
row.setAttribute('data-scan', scan); | |||
} else { | |||
row.removeAttribute('data-scan'); | |||
} | |||
if (clean(data.note)) { | |||
row.setAttribute('data-note', clean(data.note)); | |||
} else { | |||
row.removeAttribute('data-note'); | |||
} | |||
row.setAttribute('data-scmc-enhanced', 'true'); | |||
var main = row.querySelector('.scmc-catalog-main'); | |||
if (main) { | |||
main.innerHTML = ''; | |||
var titleWrap = document.createElement('div'); | |||
titleWrap.className = 'scmc-catalog-title'; | |||
var link = document.createElement('a'); | |||
link.href = /^https?:\/\//i.test(data.page) ? data.page : mw.util.getUrl(data.page); | |||
link.textContent = data.title || data.page; | |||
titleWrap.appendChild(link); | |||
main.appendChild(titleWrap); | |||
noteText = visibleNote(data); | |||
if (noteText) { | |||
var note = document.createElement('div'); | |||
note.className = 'scmc-catalog-note'; | |||
note.textContent = noteText; | |||
main.appendChild(note); | |||
} | |||
} | |||
var scanToggle = row.querySelector('.scmc-scan-toggle'); | |||
if (scanToggle) { | |||
scanToggle.setAttribute('data-active', scan === 'stop' ? 'true' : 'false'); | |||
scanToggle.title = scan === 'stop' ? 'Stop включён. Нажми, чтобы убрать stop.' : 'Нажми, чтобы добавить stop.'; | |||
} | |||
var sectionButton = row.querySelector('.scmc-catalog-section-btn'); | |||
if (sectionButton) { | |||
sectionButton.textContent = data.section || 'Без раздела'; | |||
} | |||
var statusButton = row.querySelector('.scmc-catalog-status'); | |||
if (statusButton) { | |||
statusButton.textContent = statusMap[status].emoji; | |||
statusButton.title = statusMap[status].label; | |||
statusButton.setAttribute('data-status', status); | |||
} | |||
} | |||
function refreshIndexes() { | |||
rowElements = Array.prototype.slice.call(catalog.querySelectorAll('.scmc-catalog-row')); | |||
rowElements.forEach(function (row, index) { | |||
var indexButton = row.querySelector('.scmc-catalog-index'); | |||
if (indexButton) { | |||
indexButton.textContent = String(index + 1); | |||
} | |||
}); | |||
} | |||
function saveRowChange(row, targetData, updater, summary) { | |||
setSaving(true); | setSaving(true); | ||
| Строка 536: | Строка 621: | ||
var newText = source.slice(0, target.start) + newLine + source.slice(target.end); | var newText = source.slice(0, target.start) + newLine + source.slice(target.end); | ||
return saveSource(newText, summary); | return saveSource(newText, summary).then(function () { | ||
return changed; | |||
}); | |||
}) | }) | ||
.then(function () { | .then(function (changed) { | ||
applyLocalRowUpdate(row, changed); | |||
refreshIndexes(); | |||
update(); | |||
setSaving(false); | |||
}) | }) | ||
.catch(function (error) { | .catch(function (error) { | ||
| Строка 547: | Строка 637: | ||
} | } | ||
function moveRow(targetData, targetPosition) { | function moveRow(row, targetData, targetPosition) { | ||
setSaving(true); | setSaving(true); | ||
| Строка 573: | Строка 663: | ||
var newText = source.slice(0, first.start) + lines.join('\n') + source.slice(last.end); | var newText = source.slice(0, first.start) + lines.join('\n') + source.slice(last.end); | ||
return saveSource(newText, 'Изменение порядка страниц в каталоге Marine Corps'); | return saveSource(newText, 'Изменение порядка страниц в каталоге Marine Corps').then(function () { | ||
return newIndex; | |||
}); | |||
}) | }) | ||
.then(function ( | .then(function (newIndex) { | ||
if ( | if (newIndex !== null) { | ||
var list = catalog.querySelector('.scmc-catalog-list'); | |||
var rowsWithoutMoving; | |||
rowElements = Array.prototype.slice.call(list.querySelectorAll('.scmc-catalog-row')); | |||
rowsWithoutMoving = rowElements.filter(function (item) { | |||
return item !== row; | |||
}); | |||
list.insertBefore(row, rowsWithoutMoving[newIndex] || null); | |||
refreshIndexes(); | |||
update(); | |||
} | |||
setSaving(false); | |||
}) | }) | ||
.catch(function (error) { | .catch(function (error) { | ||
| Строка 609: | Строка 715: | ||
data.note, | data.note, | ||
noteText, | noteText, | ||
data.scan === 'stop' ? ' | data.scan === 'stop' ? 'stop сканер не сканировать' : '', | ||
status, | status, | ||
statusWords | statusWords | ||
Версия от 10:36, 15 июня 2026
(function () {
if (typeof mw === 'undefined' || !window.document) return;
var statusMap = {
green: { emoji: '🟢', label: 'Готово', words: ['готово', 'готов'] },
yellow: { emoji: '🟡', label: 'Нужно обновить', words: ['нужно обновить', 'обновить', 'устарело'] },
red: { emoji: '🔴', label: 'Нет страницы', words: ['нет страницы', 'нет', 'пусто'] },
blue: { emoji: '🔵', label: 'Заморожено', words: ['заморожено', 'заморожен'] }
};
function ready(fn) {
if (document.readyState === 'complete' || document.readyState === 'interactive') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
function clean(text) {
return String(text || '').replace(/\s+/g, ' ').trim();
}
function key(text) {
return clean(text).replace(/_/g, ' ').toLowerCase();
}
function encodeAttr(value) {
return String(value || '')
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/</g, '<')
.replace(/>/g, '>');
}
function decodeAttr(value) {
var textarea = document.createElement('textarea');
textarea.innerHTML = String(value || '');
return textarea.value;
}
function attrFromTag(tag, name) {
var re = new RegExp(name + '\\s*=\\s*(["\\\'])(.*?)\\1', 'i');
var match = tag.match(re);
return match ? decodeAttr(match[2]) : '';
}
function normalizeStatus(status) {
return statusMap[status] ? status : 'blue';
}
function getLevelClass(className) {
var match = String(className || '').match(/\bscmc-level-[1-5]\b/);
return match ? match[0] : 'scmc-level-1';
}
function buildFallbackLink(page, title) {
if (/^https?:\/\//i.test(page)) {
return '[' + page + ' ' + title + ']';
}
return '[[' + page + '|' + title + ']]';
}
function makeButton(className, text, title) {
var button = document.createElement('button');
button.type = 'button';
button.className = className;
button.textContent = text;
if (title) button.title = title;
return button;
}
function closeMenus() {
var statusMenu = document.querySelector('.scmc-status-menu');
var sectionMenu = document.querySelector('.scmc-section-menu');
if (statusMenu) statusMenu.remove();
if (sectionMenu) sectionMenu.remove();
}
function placeMenu(menu, rect) {
document.body.appendChild(menu);
var left = Math.min(rect.left, window.innerWidth - menu.offsetWidth - 12);
var top = Math.min(rect.bottom + 8, window.innerHeight - menu.offsetHeight - 12);
menu.style.left = Math.max(12, left) + 'px';
menu.style.top = Math.max(12, top) + 'px';
}
function getSource() {
var api = new mw.Api();
var currentPage = mw.config.get('wgPageName');
return api.get({
action: 'query',
prop: 'revisions',
titles: currentPage,
rvprop: 'content',
rvslots: 'main',
formatversion: 2
}).then(function (data) {
var pageData = data.query && data.query.pages ? data.query.pages[0] : null;
var rev = pageData && pageData.revisions ? pageData.revisions[0] : null;
if (rev && rev.slots && rev.slots.main && typeof rev.slots.main.content === 'string') {
return rev.slots.main.content;
}
if (rev && typeof rev.content === 'string') {
return rev.content;
}
throw new Error('Не удалось прочитать код страницы.');
});
}
function saveSource(text, summary) {
var api = new mw.Api();
return api.postWithToken('csrf', {
action: 'edit',
title: mw.config.get('wgPageName'),
text: text,
summary: summary || 'Обновление каталога Marine Corps',
minor: true,
formatversion: 2
});
}
function getRowsFromSource(source) {
var result = [];
var re = /<div\b[^>]*\bscmc-catalog-row\b[^>]*>[\s\S]*?<\/div>/gi;
var match;
while ((match = re.exec(source)) !== null) {
var full = match[0];
var open = full.match(/^<div\b[^>]*>/i);
if (!open) continue;
var tag = open[0];
var data = {
className: attrFromTag(tag, 'class') || 'scmc-catalog-row scmc-level-1',
section: clean(attrFromTag(tag, 'data-section')),
page: clean(attrFromTag(tag, 'data-page')),
title: clean(attrFromTag(tag, 'data-title')),
scan: clean(attrFromTag(tag, 'data-scan')),
status: normalizeStatus(clean(attrFromTag(tag, 'data-status'))),
note: clean(attrFromTag(tag, 'data-note'))
};
if (!data.page) continue;
if (!data.title) data.title = data.page;
result.push({
start: match.index,
end: match.index + full.length,
full: full,
data: data
});
}
return result;
}
function findSourceRow(sourceRows, targetData) {
var matches = sourceRows.filter(function (item) {
return key(item.data.page) === key(targetData.page) &&
key(item.data.section) === key(targetData.section) &&
key(item.data.title) === key(targetData.title);
});
if (!matches.length) {
throw new Error('Не нашёл строку: ' + (targetData.title || targetData.page));
}
if (matches.length > 1) {
throw new Error('Нашлось несколько одинаковых строк: ' + (targetData.title || targetData.page) + '. Лучше поправить вручную.');
}
return matches[0];
}
function buildRowLine(data) {
var className = clean(data.className || 'scmc-catalog-row scmc-level-1');
var level = getLevelClass(className);
if (className.indexOf('scmc-catalog-row') === -1) {
className = 'scmc-catalog-row ' + level;
}
var section = clean(data.section);
var page = clean(data.page);
var title = clean(data.title || data.page);
var scan = clean(data.scan);
var status = normalizeStatus(data.status);
var note = clean(data.note);
var out = '<div class="' + encodeAttr(className) + '"';
out += ' data-section="' + encodeAttr(section) + '"';
out += ' data-page="' + encodeAttr(page) + '"';
out += ' data-title="' + encodeAttr(title) + '"';
if (scan) {
out += ' data-scan="' + encodeAttr(scan) + '"';
}
out += ' data-status="' + encodeAttr(status) + '"';
if (note) {
out += ' data-note="' + encodeAttr(note) + '"';
}
out += '>' + buildFallbackLink(page, title) + '</div>';
return out;
}
ready(function () {
var catalog = document.querySelector('.scmc-catalog');
if (!catalog) return;
var tools = catalog.querySelector('.scmc-catalog-tools');
var rowElements = Array.prototype.slice.call(catalog.querySelectorAll('.scmc-catalog-row'));
if (!tools || !rowElements.length) return;
var activeFilter = 'all';
var search;
var filters;
var counter;
buildTools(tools);
enhanceRows(rowElements);
search = tools.querySelector('.scmc-catalog-search');
filters = Array.prototype.slice.call(tools.querySelectorAll('.scmc-catalog-filter'));
counter = tools.querySelector('.scmc-catalog-counter');
update();
search.addEventListener('input', update);
filters.forEach(function (button) {
button.addEventListener('click', function () {
activeFilter = button.getAttribute('data-filter') || 'all';
filters.forEach(function (other) {
other.classList.toggle('is-active', other === button);
});
update();
});
});
document.addEventListener('click', function (event) {
var statusButton = event.target.closest('.scmc-catalog-status');
var sectionButton = event.target.closest('.scmc-catalog-section-btn');
if (statusButton && catalog.contains(statusButton)) {
event.preventDefault();
event.stopPropagation();
openStatusMenu(statusButton);
return;
}
if (sectionButton && catalog.contains(sectionButton)) {
event.preventDefault();
event.stopPropagation();
openSectionMenu(sectionButton);
return;
}
if (!event.target.closest('.scmc-status-menu') && !event.target.closest('.scmc-section-menu')) {
closeMenus();
}
});
function buildTools(target) {
target.innerHTML = '';
var input = document.createElement('input');
input.className = 'scmc-catalog-search';
input.type = 'search';
input.placeholder = 'Поиск по названию, странице, разделу, статусу или заметке...';
var filtersWrap = document.createElement('div');
filtersWrap.className = 'scmc-catalog-filters';
[
['all', 'Все'],
['green', '🟢 Готово'],
['yellow', '🟡 Нужно обновить'],
['red', '🔴 Нет страницы'],
['blue', '🔵 Заморожено'],
['problem', 'Проблемные'],
['stop', 'Сканер: stop'],
['note', 'С заметками']
].forEach(function (item, index) {
var button = document.createElement('button');
button.type = 'button';
button.className = 'scmc-catalog-filter' + (index === 0 ? ' is-active' : '');
button.setAttribute('data-filter', item[0]);
button.textContent = item[1];
filtersWrap.appendChild(button);
});
var count = document.createElement('div');
count.className = 'scmc-catalog-counter';
target.appendChild(input);
target.appendChild(filtersWrap);
target.appendChild(count);
}
function getRowData(row) {
return {
className: row.getAttribute('class') || 'scmc-catalog-row scmc-level-1',
section: clean(row.getAttribute('data-section')),
page: clean(row.getAttribute('data-page')),
title: clean(row.getAttribute('data-title') || row.textContent),
scan: clean(row.getAttribute('data-scan')),
status: normalizeStatus(clean(row.getAttribute('data-status'))),
note: clean(row.getAttribute('data-note'))
};
}
function visibleNote(data) {
return clean(data.note);
}
function getSections() {
var order = [
'Морпехи',
'Ксеноморфы',
'Правила и процедуры',
'Справочник',
'Лор и материалы',
'Общее'
];
var map = {};
rowElements.forEach(function (row) {
var section = clean(row.getAttribute('data-section'));
if (section) map[section] = true;
});
Object.keys(map).forEach(function (section) {
if (order.indexOf(section) === -1) {
order.push(section);
}
});
return order.filter(function (section) {
return map[section];
});
}
function enhanceRows(rows) {
rows.forEach(function (row, index) {
if (row.getAttribute('data-scmc-enhanced') === 'true') return;
var data = getRowData(row);
var status = normalizeStatus(data.status);
var isStop = data.scan === 'stop';
row.setAttribute('data-status', status);
if (isStop) row.setAttribute('data-scan', 'stop');
row.setAttribute('data-scmc-enhanced', 'true');
row.innerHTML = '';
var position = makeButton('scmc-catalog-index', String(index + 1), 'Изменить место');
position.addEventListener('click', function () {
var currentIndex = rowElements.indexOf(row) + 1;
var total = rowElements.length;
var answer = prompt('На какое место переместить страницу? Сейчас: ' + currentIndex + '. Всего: ' + total + '.', String(currentIndex));
if (answer === null) return;
var target = parseInt(answer, 10);
if (!target || target < 1 || target > total) {
alert('Нужно число от 1 до ' + total + '.');
return;
}
moveRow(row, getRowData(row), target);
});
var main = document.createElement('div');
main.className = 'scmc-catalog-main';
var titleWrap = document.createElement('div');
titleWrap.className = 'scmc-catalog-title';
var link = document.createElement('a');
link.href = /^https?:\/\//i.test(data.page) ? data.page : mw.util.getUrl(data.page);
link.textContent = data.title || data.page;
titleWrap.appendChild(link);
main.appendChild(titleWrap);
var noteText = visibleNote(data);
if (noteText) {
var note = document.createElement('div');
note.className = 'scmc-catalog-note';
note.textContent = noteText;
main.appendChild(note);
}
var actions = document.createElement('div');
actions.className = 'scmc-catalog-actions';
var noteButton = makeButton('scmc-catalog-btn scmc-catalog-note-btn', '✎', 'Изменить заметку');
noteButton.addEventListener('click', function () {
var currentData = getRowData(row);
var answer = prompt('Заметка для страницы "' + (currentData.title || currentData.page) + '". Пустое поле удалит data-note.', currentData.note || '');
if (answer === null) return;
saveRowChange(row, currentData, function (sourceData) {
sourceData.note = clean(answer);
}, 'Обновление заметки страницы: ' + (currentData.title || currentData.page));
});
var scanToggle = makeButton(
'scmc-catalog-btn scmc-scan-toggle',
'',
isStop ? 'Из основного проекта. Нажми, чтобы убрать stop.' : 'Нажми, чтобы добавить stop.'
);
scanToggle.setAttribute('data-active', isStop ? 'true' : 'false');
scanToggle.addEventListener('click', function () {
var currentData = getRowData(row);
saveRowChange(row, currentData, function (sourceData) {
sourceData.scan = sourceData.scan === 'stop' ? '' : 'stop';
}, 'Изменение режима сканирования страницы: ' + (currentData.title || currentData.page));
});
var sectionButton = makeButton('scmc-catalog-section-btn', data.section || 'Без раздела', 'Изменить раздел');
var statusButton = makeButton('scmc-catalog-status', statusMap[status].emoji, statusMap[status].label);
statusButton.setAttribute('data-status', status);
actions.appendChild(noteButton);
actions.appendChild(scanToggle);
actions.appendChild(sectionButton);
actions.appendChild(statusButton);
row.appendChild(position);
row.appendChild(main);
row.appendChild(actions);
});
}
function openStatusMenu(button) {
closeMenus();
var row = button.closest('.scmc-catalog-row');
if (!row) return;
var data = getRowData(row);
var rect = button.getBoundingClientRect();
var menu = document.createElement('div');
menu.className = 'scmc-status-menu';
Object.keys(statusMap).forEach(function (status) {
var option = document.createElement('button');
option.type = 'button';
option.textContent = statusMap[status].emoji + ' ' + statusMap[status].label;
option.addEventListener('click', function (event) {
event.preventDefault();
event.stopPropagation();
menu.remove();
if (status === data.status) return;
saveRowChange(row, data, function (sourceData) {
sourceData.status = status;
}, 'Обновление статуса страницы: ' + (data.title || data.page));
});
menu.appendChild(option);
});
placeMenu(menu, rect);
}
function openSectionMenu(button) {
closeMenus();
var row = button.closest('.scmc-catalog-row');
if (!row) return;
var data = getRowData(row);
var rect = button.getBoundingClientRect();
var menu = document.createElement('div');
menu.className = 'scmc-section-menu';
getSections().forEach(function (section) {
var option = document.createElement('button');
option.type = 'button';
option.textContent = section;
option.addEventListener('click', function (event) {
event.preventDefault();
event.stopPropagation();
menu.remove();
if (section === data.section) return;
saveRowChange(row, data, function (sourceData) {
sourceData.section = section;
}, 'Изменение раздела страницы: ' + (data.title || data.page));
});
menu.appendChild(option);
});
placeMenu(menu, rect);
}
function applyLocalRowUpdate(row, data) {
var className = clean(data.className || row.getAttribute('class') || 'scmc-catalog-row scmc-level-1');
var status = normalizeStatus(data.status);
var scan = clean(data.scan);
var noteText;
if (className.indexOf('scmc-catalog-row') === -1) {
className = 'scmc-catalog-row ' + getLevelClass(className);
}
row.className = className;
row.setAttribute('data-section', clean(data.section));
row.setAttribute('data-page', clean(data.page));
row.setAttribute('data-title', clean(data.title || data.page));
row.setAttribute('data-status', status);
if (scan) {
row.setAttribute('data-scan', scan);
} else {
row.removeAttribute('data-scan');
}
if (clean(data.note)) {
row.setAttribute('data-note', clean(data.note));
} else {
row.removeAttribute('data-note');
}
row.setAttribute('data-scmc-enhanced', 'true');
var main = row.querySelector('.scmc-catalog-main');
if (main) {
main.innerHTML = '';
var titleWrap = document.createElement('div');
titleWrap.className = 'scmc-catalog-title';
var link = document.createElement('a');
link.href = /^https?:\/\//i.test(data.page) ? data.page : mw.util.getUrl(data.page);
link.textContent = data.title || data.page;
titleWrap.appendChild(link);
main.appendChild(titleWrap);
noteText = visibleNote(data);
if (noteText) {
var note = document.createElement('div');
note.className = 'scmc-catalog-note';
note.textContent = noteText;
main.appendChild(note);
}
}
var scanToggle = row.querySelector('.scmc-scan-toggle');
if (scanToggle) {
scanToggle.setAttribute('data-active', scan === 'stop' ? 'true' : 'false');
scanToggle.title = scan === 'stop' ? 'Stop включён. Нажми, чтобы убрать stop.' : 'Нажми, чтобы добавить stop.';
}
var sectionButton = row.querySelector('.scmc-catalog-section-btn');
if (sectionButton) {
sectionButton.textContent = data.section || 'Без раздела';
}
var statusButton = row.querySelector('.scmc-catalog-status');
if (statusButton) {
statusButton.textContent = statusMap[status].emoji;
statusButton.title = statusMap[status].label;
statusButton.setAttribute('data-status', status);
}
}
function refreshIndexes() {
rowElements = Array.prototype.slice.call(catalog.querySelectorAll('.scmc-catalog-row'));
rowElements.forEach(function (row, index) {
var indexButton = row.querySelector('.scmc-catalog-index');
if (indexButton) {
indexButton.textContent = String(index + 1);
}
});
}
function saveRowChange(row, targetData, updater, summary) {
setSaving(true);
getSource()
.then(function (source) {
var sourceRows = getRowsFromSource(source);
var target = findSourceRow(sourceRows, targetData);
var changed = Object.assign({}, target.data);
updater(changed);
var newLine = buildRowLine(changed);
var newText = source.slice(0, target.start) + newLine + source.slice(target.end);
return saveSource(newText, summary).then(function () {
return changed;
});
})
.then(function (changed) {
applyLocalRowUpdate(row, changed);
refreshIndexes();
update();
setSaving(false);
})
.catch(function (error) {
setSaving(false);
alert(error.message || 'Не удалось сохранить изменение. Проверьте вход в аккаунт и права редактора.');
});
}
function moveRow(row, targetData, targetPosition) {
setSaving(true);
getSource()
.then(function (source) {
var sourceRows = getRowsFromSource(source);
var target = findSourceRow(sourceRows, targetData);
var oldIndex = sourceRows.indexOf(target);
var newIndex = Math.max(0, Math.min(sourceRows.length - 1, targetPosition - 1));
if (oldIndex === newIndex) {
setSaving(false);
return null;
}
var lines = sourceRows.map(function (item) {
return item.full;
});
var moved = lines.splice(oldIndex, 1)[0];
lines.splice(newIndex, 0, moved);
var first = sourceRows[0];
var last = sourceRows[sourceRows.length - 1];
var newText = source.slice(0, first.start) + lines.join('\n') + source.slice(last.end);
return saveSource(newText, 'Изменение порядка страниц в каталоге Marine Corps').then(function () {
return newIndex;
});
})
.then(function (newIndex) {
if (newIndex !== null) {
var list = catalog.querySelector('.scmc-catalog-list');
var rowsWithoutMoving;
rowElements = Array.prototype.slice.call(list.querySelectorAll('.scmc-catalog-row'));
rowsWithoutMoving = rowElements.filter(function (item) {
return item !== row;
});
list.insertBefore(row, rowsWithoutMoving[newIndex] || null);
refreshIndexes();
update();
}
setSaving(false);
})
.catch(function (error) {
setSaving(false);
alert(error.message || 'Не удалось изменить место строки.');
});
}
function setSaving(state) {
catalog.classList.toggle('scmc-status-saving', !!state);
Array.prototype.forEach.call(catalog.querySelectorAll('button'), function (button) {
button.disabled = !!state;
});
}
function update() {
var query = clean(search.value).toLowerCase();
var words = query ? query.split(/\s+/).filter(Boolean) : [];
var visible = 0;
rowElements.forEach(function (row) {
var data = getRowData(row);
var status = normalizeStatus(data.status);
var noteText = visibleNote(data);
var statusWords = statusMap[status] ? statusMap[status].words.join(' ') : '';
var text = [
data.title,
data.page,
data.section,
data.note,
noteText,
data.scan === 'stop' ? 'stop сканер не сканировать' : '',
status,
statusWords
].join(' ').toLowerCase();
var filterOk =
activeFilter === 'all' ||
activeFilter === status ||
(activeFilter === 'problem' && status !== 'green') ||
(activeFilter === 'stop' && data.scan === 'stop') ||
(activeFilter === 'note' && !!data.note);
var textOk = words.every(function (word) {
return text.indexOf(word) !== -1;
});
var show = filterOk && textOk;
row.classList.toggle('scmc-catalog-hidden', !show);
if (show) visible++;
});
counter.textContent = 'Показано: ' + visible + ' из ' + rowElements.length;
}
});
})();