MediaWiki:ScmcScanner.js: различия между версиями
Страница интерфейса MediaWiki
Дополнительные действия
Defer (обсуждение | вклад) Новая страница: «(function () { if (!document.querySelector('.scmc-link-scanner')) return; var STATUS = { waiting: 'Ожидает запуска', scanning: 'Сканирование...', done: 'Готово', error: 'Ошибка' }; var EXCLUDED_PREFIXES = [ 'Файл:', 'File:', 'Категория:', 'Category:', 'Шаблон:', 'Template:', 'Участник:',...» |
Defer (обсуждение | вклад) Нет описания правки |
||
| Строка 1: | Строка 1: | ||
(function () { | (function () { | ||
var scannerRoot = document.querySelector('.scmc-link-scanner'); | |||
if (!scannerRoot) return; | |||
var EXCLUDED_PREFIXES = [ | var EXCLUDED_PREFIXES = [ | ||
| Строка 32: | Строка 26: | ||
return String(title || '') | return String(title || '') | ||
.replace(/_/g, ' ') | .replace(/_/g, ' ') | ||
.replace(/\s+/g, ' ') | |||
.trim(); | .trim(); | ||
} | } | ||
function | function titleKey(title) { | ||
return | return normalizeTitle(title).toLowerCase(); | ||
} | } | ||
function | function titleToUrl(title) { | ||
return mw.util.getUrl(title); | |||
} | } | ||
function | function makeEl(tag, className, text) { | ||
var el = document.createElement(tag); | var el = document.createElement(tag); | ||
if (className) el.className = className; | if (className) el.className = className; | ||
| Строка 57: | Строка 45: | ||
} | } | ||
function | function makeButton(text, className) { | ||
var | var btn = document.createElement('button'); | ||
btn.type = 'button'; | |||
btn.className = className || 'scmc-scan-btn'; | |||
btn.textContent = text; | |||
return | return btn; | ||
} | } | ||
function | function splitDataList(value) { | ||
return | return String(value || '') | ||
.split('|') | |||
.map(normalizeTitle) | |||
.filter(Boolean); | |||
} | } | ||
function uniqueSorted( | function uniqueSorted(list) { | ||
var | var seen = {}; | ||
list.forEach(function (item) { | |||
var title = normalizeTitle(item); | |||
if (title) seen[titleKey(title)] = title; | |||
}); | }); | ||
return Object.keys(seen) | |||
.map(function (key) { | |||
return seen[key]; | |||
}) | |||
.sort(function (a, b) { | |||
return a.localeCompare(b, 'ru'); | |||
}); | |||
} | } | ||
function | function buildScanner(container) { | ||
var rootTitle = normalizeTitle(container.getAttribute('data-root') || 'Marine_Corps'); | |||
var maxDepth = parseInt(container.getAttribute('data-depth') || '5', 10); | |||
var maxPages = parseInt(container.getAttribute('data-max-pages') || '250', 10); | |||
var extraExcludedTitles = splitDataList(container.getAttribute('data-exclude')); | |||
function isExcludedTitle(title) { | |||
var clean = normalizeTitle(title); | |||
if (!clean) return true; | |||
if (clean.indexOf('#') !== -1) { | |||
clean = normalizeTitle(clean.split('#')[0]); | |||
} | |||
} | |||
if ( | if (extraExcludedTitles.some(function (excluded) { | ||
return titleKey(excluded) === titleKey(clean); | |||
})) { | |||
return true; | |||
} | } | ||
return | return EXCLUDED_PREFIXES.some(function (prefix) { | ||
return clean.indexOf(prefix) === 0; | |||
}); | |||
} | |||
function isValidLink(link) { | |||
if (!link || !link.title) return false; | |||
if (typeof link.ns === 'number' && link.ns !== 0) return false; | |||
if (isExcludedTitle(link.title)) return false; | |||
return true; | |||
} | |||
}; | |||
function getApi() { | |||
return new mw.Api(); | |||
} | |||
function getPageLinks(api, requestedTitle) { | |||
var links = []; | |||
var redirects = []; | |||
var normalizedRequested = normalizeTitle(requestedTitle); | |||
function request(plcontinue) { | |||
var params = { | |||
action: 'query', | |||
prop: 'links', | |||
titles: normalizedRequested, | |||
pllimit: 'max', | |||
redirects: 1, | |||
formatversion: 2 | |||
}; | |||
if (plcontinue) { | |||
params.plcontinue = plcontinue; | |||
} | } | ||
if ( | return api.get(params).then(function (data) { | ||
if (data.query && data.query.redirects) { | |||
data.query.redirects.forEach(function (redirect) { | |||
links | redirects.push({ | ||
} | from: normalizeTitle(redirect.from), | ||
}); | to: normalizeTitle(redirect.to) | ||
}); | |||
}); | |||
} | |||
var pages = data.query && data.query.pages ? data.query.pages : []; | |||
var page = pages[0]; | |||
if (!page) { | |||
return { | |||
requestedTitle: normalizedRequested, | |||
finalTitle: normalizedRequested, | |||
exists: false, | |||
links: [], | |||
redirects: redirects | |||
}; | |||
} | |||
var finalTitle = normalizeTitle(page.title || normalizedRequested); | |||
if (page.missing !== undefined) { | |||
return { | |||
requestedTitle: normalizedRequested, | |||
finalTitle: finalTitle, | |||
exists: false, | |||
links: [], | |||
redirects: redirects | |||
}; | |||
} | |||
if (typeof page.ns === 'number' && page.ns !== 0) { | |||
return { | |||
requestedTitle: normalizedRequested, | |||
finalTitle: finalTitle, | |||
exists: true, | |||
links: [], | |||
redirects: redirects | |||
}; | |||
} | |||
if (page.links) { | |||
page.links.forEach(function (link) { | |||
if (isValidLink(link)) { | |||
links.push(normalizeTitle(link.title)); | |||
} | |||
}); | |||
} | |||
if (data.continue && data.continue.plcontinue) { | |||
return request(data.continue.plcontinue); | |||
} | |||
return { | |||
requestedTitle: normalizedRequested, | |||
finalTitle: finalTitle, | |||
exists: true, | |||
links: uniqueSorted(links), | |||
redirects: redirects | |||
}; | |||
}); | |||
} | |||
return request(); | |||
} | |||
var header = | var header = makeEl('div', 'scmc-scan-header'); | ||
var title = | var title = makeEl('div', 'scmc-scan-title', 'Сканер ссылок Marine Corps'); | ||
var subtitle = | var subtitle = makeEl( | ||
'div', | |||
'scmc-scan-subtitle', | |||
'Старт: ' + rootTitle + ' · глубина: ' + maxDepth + ' · лимит страниц: ' + maxPages | |||
); | |||
header.appendChild(title); | header.appendChild(title); | ||
header.appendChild(subtitle); | header.appendChild(subtitle); | ||
var controls = | var controls = makeEl('div', 'scmc-scan-controls'); | ||
var | var scanBtn = makeButton('Сканировать ссылки'); | ||
var | var clearBtn = makeButton('Очистить', 'scmc-scan-btn scmc-scan-btn-secondary'); | ||
controls.appendChild( | controls.appendChild(scanBtn); | ||
controls.appendChild( | controls.appendChild(clearBtn); | ||
var | var statusBox = makeEl('div', 'scmc-scan-status', 'Ожидает запуска'); | ||
var | var statsBox = makeEl('div', 'scmc-scan-stats'); | ||
var | var resultWrap = makeEl('div', 'scmc-scan-result'); | ||
var treeBox = | var treeBox = makeEl('div', 'scmc-scan-box'); | ||
var listBox = | var listBox = makeEl('div', 'scmc-scan-box'); | ||
var redirectBox = makeEl('div', 'scmc-scan-box scmc-scan-box-wide'); | |||
resultWrap.appendChild(treeBox); | |||
resultWrap.appendChild(listBox); | |||
resultWrap.appendChild(redirectBox); | |||
container.innerHTML = ''; | container.innerHTML = ''; | ||
container.appendChild(header); | container.appendChild(header); | ||
container.appendChild(controls); | container.appendChild(controls); | ||
container.appendChild( | container.appendChild(statusBox); | ||
container.appendChild( | container.appendChild(statsBox); | ||
container.appendChild( | container.appendChild(resultWrap); | ||
function setStatus(text, mode) { | function setStatus(text, mode) { | ||
statusBox.textContent = text; | |||
statusBox.setAttribute('data-mode', mode || ''); | |||
} | } | ||
function | function clearResults() { | ||
treeBox.innerHTML = ''; | treeBox.innerHTML = ''; | ||
listBox.innerHTML = ''; | listBox.innerHTML = ''; | ||
redirectBox.innerHTML = ''; | |||
setStatus( | statsBox.innerHTML = ''; | ||
setStatus('Ожидает запуска', ''); | |||
} | } | ||
function | function renderTree(root, childrenMap, pageInfo, firstParent, alsoLinkedFrom) { | ||
treeBox.innerHTML = ''; | |||
treeBox.appendChild(makeEl('div', 'scmc-scan-box-title', 'Дерево ссылок')); | |||
function makeNode(title, path) { | |||
var key = titleKey(title); | |||
var info = pageInfo[key]; | |||
var node = makeEl('div', 'scmc-scan-node'); | |||
var line = makeEl('div', 'scmc-scan-node-line'); | |||
var link = document.createElement('a'); | |||
link.href = titleToUrl(title); | |||
link.textContent = title; | |||
line.appendChild(link); | |||
if (info) { | |||
line.appendChild(makeEl('span', 'scmc-scan-depth', 'ур. ' + info.depth)); | |||
if (info.requestedTitle && titleKey(info.requestedTitle) !== titleKey(info.finalTitle)) { | |||
line.appendChild(makeEl('span', 'scmc-scan-redirect-mini', info.requestedTitle + ' → ' + info.finalTitle)); | |||
} | |||
if (info.exists === false) { | |||
line.appendChild(makeEl('span', 'scmc-scan-missing', 'нет страницы')); | |||
} | |||
} | |||
node.appendChild(line); | |||
if (path[key]) { | |||
node.appendChild(makeEl('div', 'scmc-scan-loop', '↳ уже встречалась выше')); | |||
return node; | |||
} | |||
var extraLinks = alsoLinkedFrom[key] || []; | |||
if (extraLinks.length) { | |||
node.appendChild(makeEl('div', 'scmc-scan-also', 'Ещё ссылки из: ' + extraLinks.join(', '))); | |||
} | |||
var nextPath = Object.assign({}, path); | |||
nextPath[key] = true; | |||
var | var children = childrenMap[key] || []; | ||
var | if (children.length) { | ||
var childrenWrap = makeEl('div', 'scmc-scan-children'); | |||
children.forEach(function (childTitle) { | |||
var childKey = titleKey(childTitle); | |||
if (firstParent[childKey] && titleKey(firstParent[childKey]) !== key) { | |||
var refNode = makeEl('div', 'scmc-scan-node'); | |||
var refLine = makeEl('div', 'scmc-scan-node-line scmc-scan-node-ref'); | |||
var refLink = document.createElement('a'); | |||
refLink.href = titleToUrl(childTitle); | |||
refLink.textContent = childTitle; | |||
refLine.appendChild(refLink); | |||
refLine.appendChild(makeEl('span', 'scmc-scan-ref', 'уже найдено в: ' + firstParent[childKey])); | |||
refNode.appendChild(refLine); | |||
childrenWrap.appendChild(refNode); | |||
return; | |||
} | |||
childrenWrap.appendChild(makeNode(childTitle, nextPath)); | |||
}); | }); | ||
node.appendChild( | node.appendChild(childrenWrap); | ||
} | } | ||
| Строка 261: | Строка 330: | ||
treeBox.appendChild(makeNode(root, {})); | treeBox.appendChild(makeNode(root, {})); | ||
} | |||
function renderList(pageInfo) { | |||
listBox.innerHTML = ''; | |||
listBox.appendChild(makeEl('div', 'scmc-scan-box-title', 'Все найденные страницы')); | |||
var list = makeEl('ol', 'scmc-scan-page-list'); | |||
var pages = Object.keys(pageInfo) | |||
.map(function (key) { | |||
return pageInfo[key]; | |||
}) | |||
.sort(function (a, b) { | |||
if (a.depth !== b.depth) return a.depth - b.depth; | |||
return a.finalTitle.localeCompare(b.finalTitle, 'ru'); | |||
}); | |||
pages.forEach(function (info) { | |||
var li = document.createElement('li'); | |||
var link = document.createElement('a'); | |||
link.href = titleToUrl(info.finalTitle); | |||
link.textContent = info.finalTitle; | |||
li.appendChild(link); | |||
li.appendChild(makeEl('span', 'scmc-scan-depth', 'ур. ' + info.depth)); | |||
if (info.requestedTitle && titleKey(info.requestedTitle) !== titleKey(info.finalTitle)) { | |||
li.appendChild(makeEl('span', 'scmc-scan-redirect-mini', 'найдено как: ' + info.requestedTitle)); | |||
} | |||
if (info.exists === false) { | |||
li.appendChild(makeEl('span', 'scmc-scan-missing', 'нет страницы')); | |||
} | |||
list.appendChild(li); | |||
}); | |||
listBox.appendChild(list); | |||
} | |||
function renderRedirects(redirectsFound) { | |||
redirectBox.innerHTML = ''; | |||
redirectBox.appendChild(makeEl('div', 'scmc-scan-box-title', 'Редиректы')); | |||
var keys = Object.keys(redirectsFound).sort(function (a, b) { | |||
return a.localeCompare(b, 'ru'); | |||
}); | |||
if (!keys.length) { | |||
redirectBox.appendChild(makeEl('div', 'scmc-scan-empty', 'Редиректы не найдены.')); | |||
return; | |||
} | |||
var list = makeEl('ol', 'scmc-scan-page-list'); | |||
keys.forEach(function (from) { | |||
var to = redirectsFound[from]; | |||
var li = document.createElement('li'); | |||
var fromLink = document.createElement('a'); | |||
fromLink.href = titleToUrl(from); | |||
fromLink.textContent = from; | |||
var toLink = document.createElement('a'); | |||
toLink.href = titleToUrl(to); | |||
toLink.textContent = to; | |||
li.appendChild(fromLink); | |||
li.appendChild(document.createTextNode(' → ')); | |||
li.appendChild(toLink); | |||
list.appendChild(li); | |||
}); | |||
redirectBox.appendChild(list); | |||
} | } | ||
function scan() { | function scan() { | ||
clearResults(); | |||
var api = getApi(); | var api = getApi(); | ||
var queue = [{ | |||
var queue = [{ | |||
requestedTitle: rootTitle, | |||
depth: 0, | |||
parent: null | |||
}]; | |||
var scanned = {}; | var scanned = {}; | ||
var queued = {}; | var queued = {}; | ||
var | var pageInfo = {}; | ||
var childrenMap = {}; | var childrenMap = {}; | ||
var | var firstParent = {}; | ||
var alsoLinkedFrom = {}; | |||
var redirectsFound = {}; | |||
var stoppedByLimit = false; | |||
queued[rootTitle] = true | queued[titleKey(rootTitle)] = true; | ||
scanBtn.disabled = true; | |||
clearBtn.disabled = true; | |||
setStatus( | |||
setStatus('Сканирование...', 'scanning'); | |||
function addAlsoLinked(childTitle, parentTitle) { | |||
var childKey = titleKey(childTitle); | |||
if (!alsoLinkedFrom[childKey]) { | |||
alsoLinkedFrom[childKey] = []; | |||
} | |||
if (alsoLinkedFrom[childKey].indexOf(parentTitle) === -1) { | |||
alsoLinkedFrom[childKey].push(parentTitle); | |||
} | |||
} | |||
function step() { | function step() { | ||
| Строка 288: | Строка 457: | ||
if (Object.keys(scanned).length >= maxPages) { | if (Object.keys(scanned).length >= maxPages) { | ||
stoppedByLimit = true; | |||
finish(); | finish(); | ||
return; | return; | ||
} | } | ||
var | var item = queue.shift(); | ||
var | var requestedTitle = normalizeTitle(item.requestedTitle); | ||
var | var requestedKey = titleKey(requestedTitle); | ||
if (scanned[ | if (scanned[requestedKey]) { | ||
step(); | step(); | ||
return; | return; | ||
} | } | ||
scanned[ | scanned[requestedKey] = true; | ||
setStatus('Сканирую: ' + | setStatus('Сканирую: ' + requestedTitle + ' · уровень ' + item.depth, 'scanning'); | ||
getPageLinks(api, | getPageLinks(api, requestedTitle).then(function (data) { | ||
data.redirects.forEach(function (redirect) { | |||
if (redirect.from && redirect.to) { | |||
redirectsFound[redirect.from] = redirect.to; | |||
} | |||
}); | |||
var finalTitle = normalizeTitle(data.finalTitle || requestedTitle); | |||
var finalKey = titleKey(finalTitle); | |||
if (!pageInfo[finalKey]) { | |||
pageInfo[finalKey] = { | |||
requestedTitle: requestedTitle, | |||
finalTitle: finalTitle, | |||
depth: item.depth, | |||
exists: data.exists | |||
}; | |||
}); | } else if (item.depth < pageInfo[finalKey].depth) { | ||
pageInfo[finalKey].depth = item.depth; | |||
} | |||
if (item.parent && !firstParent[finalKey]) { | |||
firstParent[finalKey] = item.parent; | |||
} | } | ||
childrenMap[finalKey] = data.links; | |||
data.links.forEach(function (linkTitle) { | |||
var linkKey = titleKey(linkTitle); | |||
if (!firstParent[linkKey]) { | |||
firstParent[linkKey] = finalTitle; | |||
} else if (titleKey(firstParent[linkKey]) !== finalKey) { | |||
addAlsoLinked(linkTitle, finalTitle); | |||
} | |||
if (!pageInfo[linkKey]) { | |||
pageInfo[linkKey] = { | |||
requestedTitle: linkTitle, | |||
finalTitle: linkTitle, | |||
depth: item.depth + 1, | |||
exists: true | |||
}; | |||
} else if (item.depth + 1 < pageInfo[linkKey].depth) { | |||
pageInfo[linkKey].depth = item.depth + 1; | |||
} | |||
if (item.depth < maxDepth && !queued[linkKey] && !isExcludedTitle(linkTitle)) { | |||
queued[linkKey] = true; | |||
queue.push({ | |||
requestedTitle: linkTitle, | |||
depth: item.depth + 1, | |||
parent: finalTitle | |||
}); | |||
} | |||
}); | |||
statsBox.textContent = | |||
'Просканировано: ' + Object.keys(scanned).length + | |||
' · В очереди: ' + queue.length + | |||
' · Найдено страниц: ' + Object.keys(pageInfo).length + | |||
' · Редиректов: ' + Object.keys(redirectsFound).length; | |||
setTimeout(step, 120); | setTimeout(step, 120); | ||
}).catch(function (error) { | }).catch(function (error) { | ||
console.error(error); | console.error(error); | ||
setStatus('Ошибка при сканировании | |||
setStatus('Ошибка при сканировании: ' + requestedTitle, 'error'); | |||
scanBtn.disabled = false; | |||
clearBtn.disabled = false; | |||
}); | }); | ||
} | } | ||
function finish() { | function finish() { | ||
var | var rootFinal = rootTitle; | ||
Object.keys(pageInfo).some(function (key) { | |||
var info = pageInfo[key]; | |||
if (titleKey(info.requestedTitle) === titleKey(rootTitle)) { | |||
rootFinal = info.finalTitle; | |||
return true; | |||
} | |||
return false; | |||
}); | }); | ||
renderTree( | renderTree(rootFinal, childrenMap, pageInfo, firstParent, alsoLinkedFrom); | ||
renderList( | renderList(pageInfo); | ||
renderRedirects(redirectsFound); | |||
var finalText = | |||
'Готово. Найдено страниц: ' + Object.keys(pageInfo).length + | |||
'. Просканировано: ' + Object.keys(scanned).length + | |||
'. Редиректов: ' + Object.keys(redirectsFound).length + '.'; | |||
if (stoppedByLimit) { | |||
finalText += ' Остановлено по лимиту страниц.'; | |||
} | |||
statsBox.textContent = finalText; | |||
setStatus('Готово', 'done'); | |||
scanBtn.disabled = false; | |||
clearBtn.disabled = false; | |||
} | } | ||
| Строка 351: | Строка 585: | ||
} | } | ||
scanBtn.addEventListener('click', scan); | |||
clearBtn.addEventListener('click', clearResults); | |||
} | } | ||
buildScanner(scannerRoot); | |||
})(); | })(); | ||
Версия от 08:46, 15 июня 2026
(function () {
var scannerRoot = document.querySelector('.scmc-link-scanner');
if (!scannerRoot) return;
var EXCLUDED_PREFIXES = [
'Файл:',
'File:',
'Категория:',
'Category:',
'Шаблон:',
'Template:',
'Участник:',
'User:',
'Обсуждение:',
'Talk:',
'Служебная:',
'Special:',
'MediaWiki:',
'Модуль:',
'Module:',
'Справка:',
'Help:'
];
function normalizeTitle(title) {
return String(title || '')
.replace(/_/g, ' ')
.replace(/\s+/g, ' ')
.trim();
}
function titleKey(title) {
return normalizeTitle(title).toLowerCase();
}
function titleToUrl(title) {
return mw.util.getUrl(title);
}
function makeEl(tag, className, text) {
var el = document.createElement(tag);
if (className) el.className = className;
if (text !== undefined) el.textContent = text;
return el;
}
function makeButton(text, className) {
var btn = document.createElement('button');
btn.type = 'button';
btn.className = className || 'scmc-scan-btn';
btn.textContent = text;
return btn;
}
function splitDataList(value) {
return String(value || '')
.split('|')
.map(normalizeTitle)
.filter(Boolean);
}
function uniqueSorted(list) {
var seen = {};
list.forEach(function (item) {
var title = normalizeTitle(item);
if (title) seen[titleKey(title)] = title;
});
return Object.keys(seen)
.map(function (key) {
return seen[key];
})
.sort(function (a, b) {
return a.localeCompare(b, 'ru');
});
}
function buildScanner(container) {
var rootTitle = normalizeTitle(container.getAttribute('data-root') || 'Marine_Corps');
var maxDepth = parseInt(container.getAttribute('data-depth') || '5', 10);
var maxPages = parseInt(container.getAttribute('data-max-pages') || '250', 10);
var extraExcludedTitles = splitDataList(container.getAttribute('data-exclude'));
function isExcludedTitle(title) {
var clean = normalizeTitle(title);
if (!clean) return true;
if (clean.indexOf('#') !== -1) {
clean = normalizeTitle(clean.split('#')[0]);
}
if (extraExcludedTitles.some(function (excluded) {
return titleKey(excluded) === titleKey(clean);
})) {
return true;
}
return EXCLUDED_PREFIXES.some(function (prefix) {
return clean.indexOf(prefix) === 0;
});
}
function isValidLink(link) {
if (!link || !link.title) return false;
if (typeof link.ns === 'number' && link.ns !== 0) return false;
if (isExcludedTitle(link.title)) return false;
return true;
}
function getApi() {
return new mw.Api();
}
function getPageLinks(api, requestedTitle) {
var links = [];
var redirects = [];
var normalizedRequested = normalizeTitle(requestedTitle);
function request(plcontinue) {
var params = {
action: 'query',
prop: 'links',
titles: normalizedRequested,
pllimit: 'max',
redirects: 1,
formatversion: 2
};
if (plcontinue) {
params.plcontinue = plcontinue;
}
return api.get(params).then(function (data) {
if (data.query && data.query.redirects) {
data.query.redirects.forEach(function (redirect) {
redirects.push({
from: normalizeTitle(redirect.from),
to: normalizeTitle(redirect.to)
});
});
}
var pages = data.query && data.query.pages ? data.query.pages : [];
var page = pages[0];
if (!page) {
return {
requestedTitle: normalizedRequested,
finalTitle: normalizedRequested,
exists: false,
links: [],
redirects: redirects
};
}
var finalTitle = normalizeTitle(page.title || normalizedRequested);
if (page.missing !== undefined) {
return {
requestedTitle: normalizedRequested,
finalTitle: finalTitle,
exists: false,
links: [],
redirects: redirects
};
}
if (typeof page.ns === 'number' && page.ns !== 0) {
return {
requestedTitle: normalizedRequested,
finalTitle: finalTitle,
exists: true,
links: [],
redirects: redirects
};
}
if (page.links) {
page.links.forEach(function (link) {
if (isValidLink(link)) {
links.push(normalizeTitle(link.title));
}
});
}
if (data.continue && data.continue.plcontinue) {
return request(data.continue.plcontinue);
}
return {
requestedTitle: normalizedRequested,
finalTitle: finalTitle,
exists: true,
links: uniqueSorted(links),
redirects: redirects
};
});
}
return request();
}
var header = makeEl('div', 'scmc-scan-header');
var title = makeEl('div', 'scmc-scan-title', 'Сканер ссылок Marine Corps');
var subtitle = makeEl(
'div',
'scmc-scan-subtitle',
'Старт: ' + rootTitle + ' · глубина: ' + maxDepth + ' · лимит страниц: ' + maxPages
);
header.appendChild(title);
header.appendChild(subtitle);
var controls = makeEl('div', 'scmc-scan-controls');
var scanBtn = makeButton('Сканировать ссылки');
var clearBtn = makeButton('Очистить', 'scmc-scan-btn scmc-scan-btn-secondary');
controls.appendChild(scanBtn);
controls.appendChild(clearBtn);
var statusBox = makeEl('div', 'scmc-scan-status', 'Ожидает запуска');
var statsBox = makeEl('div', 'scmc-scan-stats');
var resultWrap = makeEl('div', 'scmc-scan-result');
var treeBox = makeEl('div', 'scmc-scan-box');
var listBox = makeEl('div', 'scmc-scan-box');
var redirectBox = makeEl('div', 'scmc-scan-box scmc-scan-box-wide');
resultWrap.appendChild(treeBox);
resultWrap.appendChild(listBox);
resultWrap.appendChild(redirectBox);
container.innerHTML = '';
container.appendChild(header);
container.appendChild(controls);
container.appendChild(statusBox);
container.appendChild(statsBox);
container.appendChild(resultWrap);
function setStatus(text, mode) {
statusBox.textContent = text;
statusBox.setAttribute('data-mode', mode || '');
}
function clearResults() {
treeBox.innerHTML = '';
listBox.innerHTML = '';
redirectBox.innerHTML = '';
statsBox.innerHTML = '';
setStatus('Ожидает запуска', '');
}
function renderTree(root, childrenMap, pageInfo, firstParent, alsoLinkedFrom) {
treeBox.innerHTML = '';
treeBox.appendChild(makeEl('div', 'scmc-scan-box-title', 'Дерево ссылок'));
function makeNode(title, path) {
var key = titleKey(title);
var info = pageInfo[key];
var node = makeEl('div', 'scmc-scan-node');
var line = makeEl('div', 'scmc-scan-node-line');
var link = document.createElement('a');
link.href = titleToUrl(title);
link.textContent = title;
line.appendChild(link);
if (info) {
line.appendChild(makeEl('span', 'scmc-scan-depth', 'ур. ' + info.depth));
if (info.requestedTitle && titleKey(info.requestedTitle) !== titleKey(info.finalTitle)) {
line.appendChild(makeEl('span', 'scmc-scan-redirect-mini', info.requestedTitle + ' → ' + info.finalTitle));
}
if (info.exists === false) {
line.appendChild(makeEl('span', 'scmc-scan-missing', 'нет страницы'));
}
}
node.appendChild(line);
if (path[key]) {
node.appendChild(makeEl('div', 'scmc-scan-loop', '↳ уже встречалась выше'));
return node;
}
var extraLinks = alsoLinkedFrom[key] || [];
if (extraLinks.length) {
node.appendChild(makeEl('div', 'scmc-scan-also', 'Ещё ссылки из: ' + extraLinks.join(', ')));
}
var nextPath = Object.assign({}, path);
nextPath[key] = true;
var children = childrenMap[key] || [];
if (children.length) {
var childrenWrap = makeEl('div', 'scmc-scan-children');
children.forEach(function (childTitle) {
var childKey = titleKey(childTitle);
if (firstParent[childKey] && titleKey(firstParent[childKey]) !== key) {
var refNode = makeEl('div', 'scmc-scan-node');
var refLine = makeEl('div', 'scmc-scan-node-line scmc-scan-node-ref');
var refLink = document.createElement('a');
refLink.href = titleToUrl(childTitle);
refLink.textContent = childTitle;
refLine.appendChild(refLink);
refLine.appendChild(makeEl('span', 'scmc-scan-ref', 'уже найдено в: ' + firstParent[childKey]));
refNode.appendChild(refLine);
childrenWrap.appendChild(refNode);
return;
}
childrenWrap.appendChild(makeNode(childTitle, nextPath));
});
node.appendChild(childrenWrap);
}
return node;
}
treeBox.appendChild(makeNode(root, {}));
}
function renderList(pageInfo) {
listBox.innerHTML = '';
listBox.appendChild(makeEl('div', 'scmc-scan-box-title', 'Все найденные страницы'));
var list = makeEl('ol', 'scmc-scan-page-list');
var pages = Object.keys(pageInfo)
.map(function (key) {
return pageInfo[key];
})
.sort(function (a, b) {
if (a.depth !== b.depth) return a.depth - b.depth;
return a.finalTitle.localeCompare(b.finalTitle, 'ru');
});
pages.forEach(function (info) {
var li = document.createElement('li');
var link = document.createElement('a');
link.href = titleToUrl(info.finalTitle);
link.textContent = info.finalTitle;
li.appendChild(link);
li.appendChild(makeEl('span', 'scmc-scan-depth', 'ур. ' + info.depth));
if (info.requestedTitle && titleKey(info.requestedTitle) !== titleKey(info.finalTitle)) {
li.appendChild(makeEl('span', 'scmc-scan-redirect-mini', 'найдено как: ' + info.requestedTitle));
}
if (info.exists === false) {
li.appendChild(makeEl('span', 'scmc-scan-missing', 'нет страницы'));
}
list.appendChild(li);
});
listBox.appendChild(list);
}
function renderRedirects(redirectsFound) {
redirectBox.innerHTML = '';
redirectBox.appendChild(makeEl('div', 'scmc-scan-box-title', 'Редиректы'));
var keys = Object.keys(redirectsFound).sort(function (a, b) {
return a.localeCompare(b, 'ru');
});
if (!keys.length) {
redirectBox.appendChild(makeEl('div', 'scmc-scan-empty', 'Редиректы не найдены.'));
return;
}
var list = makeEl('ol', 'scmc-scan-page-list');
keys.forEach(function (from) {
var to = redirectsFound[from];
var li = document.createElement('li');
var fromLink = document.createElement('a');
fromLink.href = titleToUrl(from);
fromLink.textContent = from;
var toLink = document.createElement('a');
toLink.href = titleToUrl(to);
toLink.textContent = to;
li.appendChild(fromLink);
li.appendChild(document.createTextNode(' → '));
li.appendChild(toLink);
list.appendChild(li);
});
redirectBox.appendChild(list);
}
function scan() {
clearResults();
var api = getApi();
var queue = [{
requestedTitle: rootTitle,
depth: 0,
parent: null
}];
var scanned = {};
var queued = {};
var pageInfo = {};
var childrenMap = {};
var firstParent = {};
var alsoLinkedFrom = {};
var redirectsFound = {};
var stoppedByLimit = false;
queued[titleKey(rootTitle)] = true;
scanBtn.disabled = true;
clearBtn.disabled = true;
setStatus('Сканирование...', 'scanning');
function addAlsoLinked(childTitle, parentTitle) {
var childKey = titleKey(childTitle);
if (!alsoLinkedFrom[childKey]) {
alsoLinkedFrom[childKey] = [];
}
if (alsoLinkedFrom[childKey].indexOf(parentTitle) === -1) {
alsoLinkedFrom[childKey].push(parentTitle);
}
}
function step() {
if (!queue.length) {
finish();
return;
}
if (Object.keys(scanned).length >= maxPages) {
stoppedByLimit = true;
finish();
return;
}
var item = queue.shift();
var requestedTitle = normalizeTitle(item.requestedTitle);
var requestedKey = titleKey(requestedTitle);
if (scanned[requestedKey]) {
step();
return;
}
scanned[requestedKey] = true;
setStatus('Сканирую: ' + requestedTitle + ' · уровень ' + item.depth, 'scanning');
getPageLinks(api, requestedTitle).then(function (data) {
data.redirects.forEach(function (redirect) {
if (redirect.from && redirect.to) {
redirectsFound[redirect.from] = redirect.to;
}
});
var finalTitle = normalizeTitle(data.finalTitle || requestedTitle);
var finalKey = titleKey(finalTitle);
if (!pageInfo[finalKey]) {
pageInfo[finalKey] = {
requestedTitle: requestedTitle,
finalTitle: finalTitle,
depth: item.depth,
exists: data.exists
};
} else if (item.depth < pageInfo[finalKey].depth) {
pageInfo[finalKey].depth = item.depth;
}
if (item.parent && !firstParent[finalKey]) {
firstParent[finalKey] = item.parent;
}
childrenMap[finalKey] = data.links;
data.links.forEach(function (linkTitle) {
var linkKey = titleKey(linkTitle);
if (!firstParent[linkKey]) {
firstParent[linkKey] = finalTitle;
} else if (titleKey(firstParent[linkKey]) !== finalKey) {
addAlsoLinked(linkTitle, finalTitle);
}
if (!pageInfo[linkKey]) {
pageInfo[linkKey] = {
requestedTitle: linkTitle,
finalTitle: linkTitle,
depth: item.depth + 1,
exists: true
};
} else if (item.depth + 1 < pageInfo[linkKey].depth) {
pageInfo[linkKey].depth = item.depth + 1;
}
if (item.depth < maxDepth && !queued[linkKey] && !isExcludedTitle(linkTitle)) {
queued[linkKey] = true;
queue.push({
requestedTitle: linkTitle,
depth: item.depth + 1,
parent: finalTitle
});
}
});
statsBox.textContent =
'Просканировано: ' + Object.keys(scanned).length +
' · В очереди: ' + queue.length +
' · Найдено страниц: ' + Object.keys(pageInfo).length +
' · Редиректов: ' + Object.keys(redirectsFound).length;
setTimeout(step, 120);
}).catch(function (error) {
console.error(error);
setStatus('Ошибка при сканировании: ' + requestedTitle, 'error');
scanBtn.disabled = false;
clearBtn.disabled = false;
});
}
function finish() {
var rootFinal = rootTitle;
Object.keys(pageInfo).some(function (key) {
var info = pageInfo[key];
if (titleKey(info.requestedTitle) === titleKey(rootTitle)) {
rootFinal = info.finalTitle;
return true;
}
return false;
});
renderTree(rootFinal, childrenMap, pageInfo, firstParent, alsoLinkedFrom);
renderList(pageInfo);
renderRedirects(redirectsFound);
var finalText =
'Готово. Найдено страниц: ' + Object.keys(pageInfo).length +
'. Просканировано: ' + Object.keys(scanned).length +
'. Редиректов: ' + Object.keys(redirectsFound).length + '.';
if (stoppedByLimit) {
finalText += ' Остановлено по лимиту страниц.';
}
statsBox.textContent = finalText;
setStatus('Готово', 'done');
scanBtn.disabled = false;
clearBtn.disabled = false;
}
step();
}
scanBtn.addEventListener('click', scan);
clearBtn.addEventListener('click', clearResults);
}
buildScanner(scannerRoot);
})();