Открыть меню
Переключить меню настроек
Открыть персональное меню
Вы не представились системе
Ваш IP-адрес будет виден всем, если вы внесёте какие-либо изменения.

MediaWiki:ScmcCatalog.jss

Страница интерфейса MediaWiki
Версия от 10:08, 15 июня 2026; Defer (обсуждение | вклад) (Новая страница: «(function () { if (typeof mw === 'undefined' || !window.document) return; var statusMap = { green: { emoji: '🟢', label: 'Готово', words: ['готово', 'готов'] }, yellow: { emoji: '🟡', label: 'Нужно обновить', words: ['нужно обновить', 'обновить', 'устарело'] }, red: { emoji: '🔴', label: 'Нет страницы', words: ['нет страницы', 'нет', 'п...»)
(разн.) ← Предыдущая версия | Текущая версия (разн.) | Следующая версия → (разн.)

(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 '' + 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 = '

' + buildFallbackLink(page, title) + '

';

       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', 'Из основного проекта'],
               ['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) {
           var parts = [];
           if (data.scan === 'stop') {
               parts.push('Из основного проекта.');
           }
           if (data.note) {
               parts.push(data.note);
           }
           return parts.join(' ');
       }
       function getSections() {
           var map = {};
           rowElements.forEach(function (row) {
               var section = clean(row.getAttribute('data-section'));
               if (section) map[section] = true;
           });
           return Object.keys(map).sort(function (a, b) {
               return a.localeCompare(b, 'ru');
           });
       }
       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 answer = prompt('На какое место переместить страницу? Сейчас: ' + (index + 1) + '. Всего: ' + rows.length + '.', String(index + 1));
                   if (answer === null) return;
                   var target = parseInt(answer, 10);
                   if (!target || target < 1 || target > rows.length) {
                       alert('Нужно число от 1 до ' + rows.length + '.');
                       return;
                   }
                   moveRow(data, 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);
               if (data.page && data.page !== data.title) {
                   var pageHint = document.createElement('span');
                   pageHint.className = 'scmc-catalog-page';
                   pageHint.textContent = data.page;
                   titleWrap.appendChild(pageHint);
               }
               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 answer = prompt('Заметка для страницы "' + (data.title || data.page) + '". Пустое поле удалит data-note.', data.note || );
                   if (answer === null) return;
                   saveRowChange(data, function (sourceData) {
                       sourceData.note = clean(answer);
                   }, 'Обновление заметки страницы: ' + (data.title || data.page));
               });
               var scanToggle = makeButton(
                   'scmc-catalog-btn scmc-scan-toggle',
                   ,
                   isStop ? 'Из основного проекта. Нажми, чтобы убрать stop.' : 'Нажми, чтобы добавить stop.'
               );
               scanToggle.setAttribute('data-active', isStop ? 'true' : 'false');
               scanToggle.addEventListener('click', function () {
                   saveRowChange(data, function (sourceData) {
                       sourceData.scan = sourceData.scan === 'stop' ?  : 'stop';
                   }, 'Изменение режима сканирования страницы: ' + (data.title || data.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(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(data, function (sourceData) {
                       sourceData.section = section;
                   }, 'Изменение раздела страницы: ' + (data.title || data.page));
               });
               menu.appendChild(option);
           });
           placeMenu(menu, rect);
       }
       function saveRowChange(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 () {
                   location.reload();
               })
               .catch(function (error) {
                   setSaving(false);
                   alert(error.message || 'Не удалось сохранить изменение. Проверьте вход в аккаунт и права редактора.');
               });
       }
       function moveRow(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 (result) {
                   if (result !== null) location.reload();
               })
               .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;
       }
   });

})();