|
|
| 1번째 줄: |
1번째 줄: |
| /* ===== core-js.txt ===== */
| |
| /* =========================
| |
| MediaWiki:Gadget-core.js
| |
| ========================= */
| |
|
| |
|
| (function () {
| |
| 'use strict';
| |
|
| |
| document.documentElement.classList.add('pvz-desktop-booting');
| |
|
| |
| var PVZ_SEARCH_TIMER = null;
| |
| var PVZ_SEARCH_REQUEST_ID = 0;
| |
| var PVZ_SELECTED_SUGGESTION = -1;
| |
| var PVZ_SUGGESTIONS = [];
| |
| var PVZ_LAST_SEARCH_QUERY = '';
| |
|
| |
| function pvzReady(callback) {
| |
| function run() {
| |
| if (window.mw && mw.loader && mw.loader.using) {
| |
| mw.loader.using('mediawiki.util').then(callback, callback);
| |
| } else {
| |
| callback();
| |
| }
| |
| }
| |
|
| |
| if (document.readyState === 'loading') {
| |
| document.addEventListener('DOMContentLoaded', run, { once: true });
| |
| } else {
| |
| run();
| |
| }
| |
| }
| |
|
| |
| function pvzArray(list) {
| |
| return Array.prototype.slice.call(list || []);
| |
| }
| |
|
| |
| function pvzClosest(target, selector) {
| |
| if (!target) return null;
| |
| if (target.nodeType !== 1) target = target.parentElement;
| |
| if (!target || typeof target.closest !== 'function') return null;
| |
| return target.closest(selector);
| |
| }
| |
|
| |
| function pvzText(node) {
| |
| return node ? (node.textContent || '').replace(/\s+/g, ' ').trim() : '';
| |
| }
| |
|
| |
| function pvzRemoveById(id) {
| |
| var el = document.getElementById(id);
| |
| if (el && el.parentNode) el.parentNode.removeChild(el);
| |
| }
| |
|
| |
| function pvzGetUrl(title) {
| |
| if (window.mw && mw.util && typeof mw.util.getUrl === 'function') {
| |
| return mw.util.getUrl(title);
| |
| }
| |
|
| |
| return '/wiki/' + encodeURIComponent(String(title || '')).replace(/%20/g, '_');
| |
| }
| |
|
| |
| function pvzGetApiUrl() {
| |
| if (window.mw && mw.util && typeof mw.util.wikiScript === 'function') {
| |
| return mw.util.wikiScript('api');
| |
| }
| |
|
| |
| if (window.mw && mw.config && mw.config.get('wgScriptPath')) {
| |
| return mw.config.get('wgScriptPath') + '/api.php';
| |
| }
| |
|
| |
| return '/w/api.php';
| |
| }
| |
|
| |
| function pvzGetPageName() {
| |
| if (window.mw && mw.config && mw.config.get('wgPageName')) {
| |
| return mw.config.get('wgPageName');
| |
| }
| |
|
| |
| return (document.title || '').replace(/\s*-\s*.*$/, '').replace(/\s+/g, '_');
| |
| }
| |
|
| |
| function pvzGetTitleText() {
| |
| var heading = document.getElementById('firstHeading') ||
| |
| document.querySelector('.mw-first-heading, .firstHeading');
| |
|
| |
| if (heading) {
| |
| return pvzText(heading);
| |
| }
| |
|
| |
| if (window.mw && mw.config && mw.config.get('wgTitle')) {
| |
| return mw.config.get('wgTitle');
| |
| }
| |
|
| |
| return (document.title || '').replace(/\s*-\s*.*$/, '');
| |
| }
| |
|
| |
| function pvzGetTalkPageName() {
| |
| var pageName = pvzGetPageName();
| |
| var ns;
| |
| var title;
| |
| var namespaces;
| |
| var talkNamespace;
| |
|
| |
| if (window.mw && mw.config) {
| |
| ns = mw.config.get('wgNamespaceNumber');
| |
| title = mw.config.get('wgTitle');
| |
| namespaces = mw.config.get('wgFormattedNamespaces');
| |
|
| |
| if (ns === -1 || ns % 2 === 1) return pageName;
| |
|
| |
| talkNamespace = namespaces && namespaces[ns + 1] ? namespaces[ns + 1] : 'Talk';
| |
|
| |
| if (ns === 0) {
| |
| talkNamespace = namespaces && namespaces[1] ? namespaces[1] : 'Talk';
| |
| }
| |
|
| |
| return talkNamespace + ':' + title;
| |
| }
| |
|
| |
| return 'Talk:' + pageName;
| |
| }
| |
|
| |
| function pvzGetSearchUrl(query) {
| |
| var value = (query || '').replace(/\s+/g, ' ').trim();
| |
| if (!value) return pvzGetUrl('Special:Search');
| |
| return pvzGetUrl('Special:Search') + '?search=' + encodeURIComponent(value) + '&fulltext=1';
| |
| }
| |
|
| |
| function pvzSyncClasses() {
| |
| var root = document.documentElement;
| |
| var body = document.body;
| |
| var ua = navigator.userAgent || '';
| |
| var screenW = window.screen && window.screen.width ? window.screen.width : 9999;
| |
| var screenH = window.screen && window.screen.height ? window.screen.height : 9999;
| |
| var hasTouch = !!(navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0);
| |
| var nativeMobile = !!(body && body.classList && body.classList.contains('mw-mf'));
| |
| var isVector = !!(body && body.classList && (
| |
| body.classList.contains('skin-vector') ||
| |
| body.classList.contains('skin-vector-2022')
| |
| ));
| |
| var touchDevice = !!(
| |
| /Android|iPhone|iPad|iPod|Mobile|Windows Phone/i.test(ua) ||
| |
| (hasTouch && Math.min(screenW, screenH) <= 1400)
| |
| );
| |
|
| |
| root.classList.toggle('pvz-skin-vector', isVector);
| |
| root.classList.toggle('pvz-native-mobile', nativeMobile);
| |
| root.classList.toggle('pvz-native-desktop', !nativeMobile);
| |
| root.classList.toggle('pvz-touch-device', touchDevice);
| |
| root.classList.toggle('pvz-desktop-view-on-touch', touchDevice && !nativeMobile);
| |
|
| |
| if (body) {
| |
| body.classList.toggle('pvz-vector-ready', isVector);
| |
| }
| |
| }
| |
|
| |
|
| |
| function pvzCreateIcon(iconClass) {
| |
| var span = document.createElement('span');
| |
| span.className = 'pvz-icon ' + iconClass;
| |
| span.setAttribute('aria-hidden', 'true');
| |
| return span;
| |
| }
| |
|
| |
| function pvzSetIconOnly(el, iconClass) {
| |
| el.textContent = '';
| |
| el.appendChild(pvzCreateIcon(iconClass));
| |
| }
| |
|
| |
| function pvzAppendIconText(parent, iconClass, label) {
| |
| var text = document.createElement('span');
| |
| parent.appendChild(pvzCreateIcon(iconClass));
| |
| text.textContent = label;
| |
| parent.appendChild(text);
| |
| }
| |
|
| |
| function pvzSetBoardButtonContent(parent, iconClass, label) {
| |
| var text = document.createElement('span');
| |
|
| |
| parent.textContent = '';
| |
|
| |
| if (iconClass) {
| |
| parent.appendChild(pvzCreateIcon(iconClass));
| |
| }
| |
|
| |
| text.className = 'pvz-board-btn-text';
| |
| text.textContent = label;
| |
| parent.appendChild(text);
| |
| }
| |
|
| |
| function pvzPortletLink(ids) {
| |
| var i;
| |
| var link;
| |
|
| |
| for (i = 0; i < ids.length; i += 1) {
| |
| link = document.querySelector('#' + ids[i] + ' a');
| |
| if (link && link.href) return link;
| |
| }
| |
|
| |
| return null;
| |
| }
| |
|
| |
| function pvzCloneAction(ids, label, className, iconClass) {
| |
| var source = pvzPortletLink(ids);
| |
| var link;
| |
| var action;
| |
| var ns;
| |
| var displayLabel;
| |
|
| |
| if (!source) return null;
| |
|
| |
| displayLabel = label || pvzText(source);
| |
|
| |
| link = document.createElement('a');
| |
| link.className = className || 'pvz-board-btn';
| |
| link.href = source.href;
| |
| pvzSetBoardButtonContent(link, iconClass, displayLabel);
| |
|
| |
| if (source.getAttribute('title')) {
| |
| link.title = source.getAttribute('title');
| |
| }
| |
|
| |
| if (source.classList.contains('new') || source.closest('.new')) {
| |
| link.classList.add('new');
| |
| }
| |
|
| |
| if (source.closest('.selected') || source.closest('.mw-list-item.selected')) {
| |
| link.classList.add('is-current');
| |
| }
| |
|
| |
| if (window.mw && mw.config) {
| |
| action = mw.config.get('wgAction') || 'view';
| |
| ns = mw.config.get('wgNamespaceNumber');
| |
|
| |
| if (
| |
| action === 'view' &&
| |
| ids.some(function (id) { return id === 'ca-view' || id === 'ca-nstab-main' || id === 'ca-nstab-project'; }) &&
| |
| !(typeof ns === 'number' && ns % 2 === 1)
| |
| ) {
| |
| link.classList.add('is-current');
| |
| }
| |
|
| |
| if (
| |
| typeof ns === 'number' &&
| |
| ns % 2 === 1 &&
| |
| ids.some(function (id) { return id === 'ca-talk'; })
| |
| ) {
| |
| link.classList.add('is-current');
| |
| }
| |
|
| |
| if (
| |
| (action === 'edit' || action === 'submit' || action === 'visualeditor') &&
| |
| ids.some(function (id) { return id === 'ca-edit' || id === 'ca-ve-edit' || id === 'ca-viewsource'; })
| |
| ) {
| |
| link.classList.add('is-current');
| |
| }
| |
|
| |
| if (
| |
| action === 'history' &&
| |
| ids.some(function (id) { return id === 'ca-history'; })
| |
| ) {
| |
| link.classList.add('is-current');
| |
| }
| |
| }
| |
|
| |
| return link;
| |
| }
| |
|
| |
| function pvzAppendIf(parent, child) {
| |
| if (child) parent.appendChild(child);
| |
| }
| |
|
| |
| function pvzCreateBoardHead() {
| |
| var head = document.createElement('div');
| |
| var left = document.createElement('div');
| |
| var title = document.createElement('div');
| |
| var tabs = document.createElement('div');
| |
| var actions = document.createElement('div');
| |
| var moreMap;
| |
| var moreLinks;
| |
|
| |
| head.className = 'pvz-board-head';
| |
| left.className = 'pvz-board-head-left';
| |
| title.className = 'pvz-board-title-ribbon';
| |
| tabs.className = 'pvz-board-tabs';
| |
| actions.className = 'pvz-board-actions';
| |
|
| |
| title.textContent = pvzGetTitleText();
| |
|
| |
| var viewLink = pvzCloneAction(['ca-view', 'ca-nstab-main', 'ca-nstab-project'], '문서', null, 'pvz-icon-special');
| |
| var talkLink = pvzCloneAction(['ca-talk'], '토론', null, 'pvz-icon-talk');
| |
| if (viewLink) viewLink.classList.add('is-tab');
| |
| if (talkLink) talkLink.classList.add('is-tab');
| |
|
| |
| pvzAppendIf(tabs, viewLink);
| |
| pvzAppendIf(tabs, talkLink);
| |
|
| |
| pvzAppendIf(actions, pvzCloneAction(['ca-edit', 'ca-ve-edit', 'ca-viewsource'], '편집', null, 'pvz-icon-tools'));
| |
| pvzAppendIf(actions, pvzCloneAction(['ca-history'], '역사', null, 'pvz-icon-recent'));
| |
| pvzAppendIf(actions, pvzCloneAction(['ca-watch'], '즐겨찾기에 추가', null, 'pvz-icon-star'));
| |
| pvzAppendIf(actions, pvzCloneAction(['ca-unwatch'], '즐겨찾기 해제', null, 'pvz-icon-star'));
| |
|
| |
| moreMap = [
| |
| { ids: ['ca-move'], label: '이동', icon: 'pvz-icon-special' },
| |
| { ids: ['ca-purge'], label: '새로고침', icon: 'pvz-icon-recent' },
| |
| { ids: ['ca-delete'], label: '삭제', icon: 'pvz-icon-tools' },
| |
| { ids: ['ca-protect'], label: '보호', icon: 'pvz-icon-permissions' },
| |
| { ids: ['ca-unprotect'], label: '보호 해제', icon: 'pvz-icon-permissions' }
| |
| ];
| |
|
| |
| moreLinks = moreMap.map(function (item) {
| |
| return pvzCloneAction(item.ids, item.label, null, item.icon);
| |
| }).filter(Boolean);
| |
|
| |
| if (moreLinks.length) {
| |
| var details = document.createElement('details');
| |
| var summary = document.createElement('summary');
| |
| var menu = document.createElement('div');
| |
| var moreHasCurrent = moreLinks.some(function (link) {
| |
| return link && link.classList && link.classList.contains('is-current');
| |
| });
| |
| details.className = 'pvz-board-more';
| |
| pvzSetBoardButtonContent(summary, 'pvz-icon-plus', '더보기');
| |
| if (moreHasCurrent) summary.classList.add('is-current');
| |
| menu.className = 'pvz-board-more-menu';
| |
| moreLinks.forEach(function (link) { menu.appendChild(link); });
| |
| details.appendChild(summary);
| |
| details.appendChild(menu);
| |
| actions.appendChild(details);
| |
| }
| |
|
| |
| left.appendChild(title);
| |
| left.appendChild(tabs);
| |
| head.appendChild(left);
| |
| head.appendChild(actions);
| |
| return head;
| |
| }
| |
|
| |
| function pvzGetActiveSearchInput() {
| |
| return document.getElementById('pvz-desktop-search-input') ||
| |
| document.getElementById('pvz-mobile-search-input');
| |
| }
| |
|
| |
| function pvzCreateSearchPanel() {
| |
| var old = document.getElementById('pvz-search-suggestions');
| |
| var panel;
| |
|
| |
| if (old) return old;
| |
|
| |
| panel = document.createElement('div');
| |
| panel.id = 'pvz-search-suggestions';
| |
| panel.className = 'pvz-search-suggestions';
| |
| panel.setAttribute('aria-label', '검색어 추천');
| |
|
| |
| document.body.appendChild(panel);
| |
| return panel;
| |
| }
| |
|
| |
| function pvzCloseSuggestions() {
| |
| document.documentElement.classList.remove('pvz-suggestions-open');
| |
| PVZ_SELECTED_SUGGESTION = -1;
| |
| }
| |
|
| |
| function pvzPositionSearchPanel() {
| |
| var panel = document.getElementById('pvz-search-suggestions');
| |
| var input = pvzGetActiveSearchInput();
| |
| var rect;
| |
| var left;
| |
| var top;
| |
| var width;
| |
|
| |
| if (!panel || !input || typeof input.getBoundingClientRect !== 'function') return;
| |
|
| |
| rect = input.getBoundingClientRect();
| |
| left = Math.max(8, rect.left);
| |
| top = rect.bottom + 6;
| |
| width = Math.min(rect.width, window.innerWidth - 16);
| |
|
| |
| panel.style.setProperty('left', left + 'px', 'important');
| |
| panel.style.setProperty('top', top + 'px', 'important');
| |
| panel.style.setProperty('width', width + 'px', 'important');
| |
| }
| |
|
| |
| function pvzOpenSuggestions() {
| |
| pvzPositionSearchPanel();
| |
| if (PVZ_SUGGESTIONS.length) {
| |
| document.documentElement.classList.add('pvz-suggestions-open');
| |
| }
| |
| }
| |
|
| |
| function pvzEscapeRegExp(text) {
| |
| return String(text || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
| |
| }
| |
|
| |
| function pvzAppendHighlightedTitle(container, text, query) {
| |
| var value = text || '';
| |
| var keyword = (query || '').replace(/\s+/g, ' ').trim();
| |
| var regex;
| |
| var lastIndex = 0;
| |
| var match;
| |
|
| |
| container.textContent = '';
| |
|
| |
| if (!keyword) {
| |
| container.textContent = value;
| |
| return;
| |
| }
| |
|
| |
| regex = new RegExp(pvzEscapeRegExp(keyword), 'ig');
| |
|
| |
| while ((match = regex.exec(value)) !== null) {
| |
| if (match.index > lastIndex) {
| |
| var normal = document.createElement('span');
| |
| normal.className = 'pvz-search-suggestion-normal';
| |
| normal.textContent = value.slice(lastIndex, match.index);
| |
| container.appendChild(normal);
| |
| }
| |
|
| |
| var strong = document.createElement('strong');
| |
| strong.className = 'pvz-search-suggestion-match';
| |
| strong.textContent = value.slice(match.index, match.index + match[0].length);
| |
| container.appendChild(strong);
| |
|
| |
| lastIndex = match.index + match[0].length;
| |
| }
| |
|
| |
| if (lastIndex === 0) {
| |
| container.textContent = value;
| |
| return;
| |
| }
| |
|
| |
| if (lastIndex < value.length) {
| |
| var tail = document.createElement('span');
| |
| tail.className = 'pvz-search-suggestion-normal';
| |
| tail.textContent = value.slice(lastIndex);
| |
| container.appendChild(tail);
| |
| }
| |
| }
| |
|
| |
| function pvzRenderSearchSuggestions(items) {
| |
| var panel = pvzCreateSearchPanel();
| |
| var list = document.createElement('ul');
| |
|
| |
| panel.innerHTML = '';
| |
| PVZ_SELECTED_SUGGESTION = -1;
| |
|
| |
| if (!items.length) {
| |
| pvzCloseSuggestions();
| |
| return;
| |
| }
| |
|
| |
| list.className = 'pvz-search-suggestions-list';
| |
|
| |
| items.forEach(function (item, index) {
| |
| var li = document.createElement('li');
| |
| var a = document.createElement('a');
| |
| var thumb = document.createElement('span');
| |
| var title = document.createElement('span');
| |
|
| |
| li.className = 'pvz-search-suggestion';
| |
| li.setAttribute('data-pvz-suggestion-index', String(index));
| |
|
| |
| a.className = 'pvz-search-suggestion-link';
| |
| a.href = item.href;
| |
|
| |
| thumb.className = 'pvz-search-suggestion-thumb';
| |
|
| |
| if (item.thumbnail) {
| |
| var img = document.createElement('img');
| |
| thumb.classList.add('has-image');
| |
| img.src = item.thumbnail;
| |
| img.alt = '';
| |
| img.loading = 'lazy';
| |
| thumb.appendChild(img);
| |
| } else {
| |
| thumb.classList.add('is-empty');
| |
| }
| |
|
| |
| title.className = 'pvz-search-suggestion-title';
| |
| pvzAppendHighlightedTitle(title, item.title, PVZ_LAST_SEARCH_QUERY);
| |
|
| |
| a.appendChild(thumb);
| |
| a.appendChild(title);
| |
| a.addEventListener('click', pvzCloseSuggestions);
| |
|
| |
| li.appendChild(a);
| |
| list.appendChild(li);
| |
| });
| |
|
| |
| panel.appendChild(list);
| |
| pvzOpenSuggestions();
| |
| }
| |
|
| |
| function pvzFetchSearchSuggestions(query) {
| |
| var value = (query || '').replace(/\s+/g, ' ').trim();
| |
| var requestId;
| |
| var url;
| |
|
| |
| if (!value || !window.fetch) {
| |
| PVZ_LAST_SEARCH_QUERY = '';
| |
| PVZ_SUGGESTIONS = [];
| |
| pvzRenderSearchSuggestions([]);
| |
| pvzCloseSuggestions();
| |
| return;
| |
| }
| |
|
| |
| PVZ_LAST_SEARCH_QUERY = value;
| |
| requestId = ++PVZ_SEARCH_REQUEST_ID;
| |
|
| |
| url =
| |
| pvzGetApiUrl() +
| |
| '?action=query' +
| |
| '&format=json' +
| |
| '&formatversion=2' +
| |
| '&generator=prefixsearch' +
| |
| '&gpsnamespace=0' +
| |
| '&gpslimit=8' +
| |
| '&gpssearch=' + encodeURIComponent(value) +
| |
| '&prop=pageimages' +
| |
| '&piprop=thumbnail' +
| |
| '&pithumbsize=72' +
| |
| '&pilimit=8' +
| |
| '&redirects=1';
| |
|
| |
| fetch(url, { credentials: 'same-origin' })
| |
| .then(function (res) { return res.json(); })
| |
| .then(function (data) {
| |
| var pages;
| |
|
| |
| if (requestId !== PVZ_SEARCH_REQUEST_ID) return;
| |
|
| |
| pages = data && data.query && Array.isArray(data.query.pages)
| |
| ? data.query.pages
| |
| : [];
| |
|
| |
| pages.sort(function (a, b) {
| |
| return (a.index || 9999) - (b.index || 9999);
| |
| });
| |
|
| |
| PVZ_SUGGESTIONS = pages.map(function (page) {
| |
| return {
| |
| title: page.title,
| |
| href: pvzGetUrl(page.title),
| |
| thumbnail: page.thumbnail && page.thumbnail.source ? page.thumbnail.source : ''
| |
| };
| |
| });
| |
|
| |
| pvzRenderSearchSuggestions(PVZ_SUGGESTIONS);
| |
| })
| |
| .catch(function () {
| |
| if (requestId === PVZ_SEARCH_REQUEST_ID) {
| |
| PVZ_SUGGESTIONS = [];
| |
| pvzRenderSearchSuggestions([]);
| |
| pvzCloseSuggestions();
| |
| }
| |
| });
| |
| }
| |
|
| |
| function pvzScheduleSearchSuggestions(query) {
| |
| window.clearTimeout(PVZ_SEARCH_TIMER);
| |
| PVZ_SEARCH_TIMER = window.setTimeout(function () {
| |
| pvzFetchSearchSuggestions(query);
| |
| }, 140);
| |
| }
| |
|
| |
| function pvzUpdateSelectedSuggestion() {
| |
| document.querySelectorAll('.pvz-search-suggestion').forEach(function (li) {
| |
| var index = parseInt(li.getAttribute('data-pvz-suggestion-index'), 10);
| |
| li.classList.toggle('is-selected', index === PVZ_SELECTED_SUGGESTION);
| |
| });
| |
| }
| |
|
| |
| function pvzHandleSearchKeydown(event) {
| |
| var input = pvzGetActiveSearchInput();
| |
| var item;
| |
|
| |
| if (event.key === 'ArrowDown') {
| |
| if (!PVZ_SUGGESTIONS.length) return;
| |
| event.preventDefault();
| |
| PVZ_SELECTED_SUGGESTION += 1;
| |
| if (PVZ_SELECTED_SUGGESTION >= PVZ_SUGGESTIONS.length) PVZ_SELECTED_SUGGESTION = 0;
| |
| pvzUpdateSelectedSuggestion();
| |
| return;
| |
| }
| |
|
| |
| if (event.key === 'ArrowUp') {
| |
| if (!PVZ_SUGGESTIONS.length) return;
| |
| event.preventDefault();
| |
| PVZ_SELECTED_SUGGESTION -= 1;
| |
| if (PVZ_SELECTED_SUGGESTION < 0) PVZ_SELECTED_SUGGESTION = PVZ_SUGGESTIONS.length - 1;
| |
| pvzUpdateSelectedSuggestion();
| |
| return;
| |
| }
| |
|
| |
| if (event.key === 'Enter') {
| |
| if (PVZ_SELECTED_SUGGESTION >= 0 && PVZ_SUGGESTIONS[PVZ_SELECTED_SUGGESTION]) {
| |
| event.preventDefault();
| |
| item = PVZ_SUGGESTIONS[PVZ_SELECTED_SUGGESTION];
| |
| window.location.href = item.href;
| |
| return;
| |
| }
| |
|
| |
| if (input && input.value.replace(/\s+/g, ' ').trim()) {
| |
| event.preventDefault();
| |
| window.location.href = pvzGetSearchUrl(input.value);
| |
| }
| |
|
| |
| return;
| |
| }
| |
|
| |
| if (event.key === 'Escape') {
| |
| event.preventDefault();
| |
| pvzCloseSuggestions();
| |
| }
| |
| }
| |
|
| |
| function pvzGetLocal(key, fallback) {
| |
| try {
| |
| return localStorage.getItem(key) || fallback;
| |
| } catch (e) {
| |
| return fallback;
| |
| }
| |
| }
| |
|
| |
| function pvzSetLocal(key, value) {
| |
| try {
| |
| localStorage.setItem(key, value);
| |
| } catch (e) {}
| |
| }
| |
|
| |
| function pvzSetFontSize(value) {
| |
| if (value !== 'small' && value !== 'medium' && value !== 'large') value = 'small';
| |
| pvzSetLocal('pvz-font-size', value);
| |
| document.documentElement.classList.remove('pvz-font-small', 'pvz-font-medium', 'pvz-font-large');
| |
| document.documentElement.classList.add('pvz-font-' + value);
| |
| pvzUpdateChoices();
| |
| }
| |
|
| |
| function pvzSetTheme(value) {
| |
| if (value !== 'auto' && value !== 'light' && value !== 'dark') value = 'auto';
| |
| pvzSetLocal('pvz-mobile-theme', value);
| |
| document.documentElement.classList.remove('pvz-theme-auto', 'pvz-theme-light', 'pvz-theme-dark');
| |
| document.documentElement.classList.add('pvz-theme-' + value);
| |
| pvzUpdateChoices();
| |
| }
| |
|
| |
| function pvzSetWidth(value) {
| |
| if (value !== 'normal' && value !== 'wide') value = 'normal';
| |
| pvzSetLocal('pvz-mobile-width', value);
| |
| document.documentElement.classList.remove('pvz-width-normal', 'pvz-width-wide', 'pvz-width-full');
| |
| document.documentElement.classList.add('pvz-width-' + value);
| |
| pvzUpdateChoices();
| |
| }
| |
|
| |
| function pvzSetAutoHide(value) {
| |
| if (value !== 'on' && value !== 'off') value = 'off';
| |
| pvzSetLocal('pvz-mobile-autohide', value);
| |
| document.documentElement.classList.remove('pvz-auto-hide-on', 'pvz-auto-hide-off');
| |
| document.documentElement.classList.add('pvz-auto-hide-' + value);
| |
| pvzUpdateChoices();
| |
| }
| |
|
| |
| function pvzSetPerformance(value) {
| |
| if (value !== 'on' && value !== 'off') value = 'off';
| |
| pvzSetLocal('pvz-mobile-performance', value);
| |
| document.documentElement.classList.remove('pvz-performance-on', 'pvz-performance-off');
| |
| document.documentElement.classList.add('pvz-performance-' + value);
| |
| pvzUpdateChoices();
| |
| }
| |
|
| |
| function pvzApplyPreferences() {
| |
| pvzSetFontSize(pvzGetLocal('pvz-font-size', 'small'));
| |
| pvzSetTheme(pvzGetLocal('pvz-mobile-theme', 'auto'));
| |
| pvzSetWidth(pvzGetLocal('pvz-mobile-width', 'normal'));
| |
| pvzSetAutoHide(pvzGetLocal('pvz-mobile-autohide', 'off'));
| |
| pvzSetPerformance(pvzGetLocal('pvz-mobile-performance', 'off'));
| |
| }
| |
|
| |
| function pvzUpdateChoices() {
| |
| var values = {
| |
| font: pvzGetLocal('pvz-font-size', 'small'),
| |
| theme: pvzGetLocal('pvz-mobile-theme', 'auto'),
| |
| width: pvzGetLocal('pvz-mobile-width', 'normal'),
| |
| autohide: pvzGetLocal('pvz-mobile-autohide', 'off'),
| |
| performance: pvzGetLocal('pvz-mobile-performance', 'off')
| |
| };
| |
|
| |
| document.querySelectorAll('.pvz-mobile-choice').forEach(function (button) {
| |
| var kind = button.getAttribute('data-pvz-setting-kind');
| |
| var value = button.getAttribute('data-pvz-setting-value');
| |
| button.classList.toggle('is-selected', values[kind] === value);
| |
| });
| |
| }
| |
|
| |
| function pvzCreateMobileLink(iconClass, label, href) {
| |
| var li = document.createElement('li');
| |
| var a = document.createElement('a');
| |
|
| |
| a.href = href;
| |
| pvzAppendIconText(a, iconClass, label);
| |
|
| |
| li.appendChild(a);
| |
| return li;
| |
| }
| |
|
| |
| function pvzCreateMobileLinkList(items) {
| |
| var ul = document.createElement('ul');
| |
|
| |
| ul.className = 'pvz-mobile-link-list';
| |
|
| |
| items.forEach(function (item) {
| |
| ul.appendChild(pvzCreateMobileLink(item.icon, item.label, item.href));
| |
| });
| |
|
| |
| return ul;
| |
| }
| |
|
| |
| function pvzCreateSheetSection(title, content) {
| |
| var section = document.createElement('section');
| |
| var h = document.createElement('div');
| |
|
| |
| section.className = 'pvz-mobile-sheet-section';
| |
| h.className = 'pvz-mobile-sheet-section-title';
| |
| h.textContent = title;
| |
|
| |
| section.appendChild(h);
| |
| section.appendChild(content);
| |
|
| |
| return section;
| |
| }
| |
|
| |
| function pvzCreateBrowsePanel() {
| |
| var panel = document.createElement('nav');
| |
|
| |
| panel.id = 'pvz-mobile-panel-browse';
| |
| panel.className = 'pvz-mobile-sheet pvz-mobile-sheet-left';
| |
| panel.setAttribute('aria-label', '둘러보기');
| |
|
| |
| panel.appendChild(pvzCreateSheetSection('둘러보기', pvzCreateMobileLinkList([
| |
| { icon: 'pvz-icon-home', label: '대문', href: pvzGetUrl('대문') },
| |
| { icon: 'pvz-icon-recent', label: '최근 바뀜', href: pvzGetUrl('Special:RecentChanges') },
| |
| { icon: 'pvz-icon-random', label: '임의 문서로', href: pvzGetUrl('Special:Random') },
| |
| { icon: 'pvz-icon-help', label: '미디어위키 도움말', href: pvzGetUrl('Help:Contents') },
| |
| { icon: 'pvz-icon-special', label: '특수 문서 목록', href: pvzGetUrl('Special:SpecialPages') },
| |
| { icon: 'pvz-icon-upload', label: '파일 올리기', href: pvzGetUrl('Special:Upload') }
| |
| ])));
| |
|
| |
| panel.appendChild(pvzCreateSheetSection('관리', pvzCreateMobileLinkList([
| |
| { icon: 'pvz-icon-settings', label: '이 위키의 핵심 설정을 관리하기', href: pvzGetUrl('Special:ManageWiki/settings') },
| |
| { icon: 'pvz-icon-extensions', label: '이 위키의 확장 기능을 관리하기', href: pvzGetUrl('Special:ManageWiki/extensions') },
| |
| { icon: 'pvz-icon-namespace', label: '이 위키의 이름공간 관리', href: pvzGetUrl('Special:ManageWiki/namespaces') },
| |
| { icon: 'pvz-icon-permissions', label: '이 위키의 권한 관리', href: pvzGetUrl('Special:ManageWiki/permissions') },
| |
| { icon: 'pvz-icon-plus', label: '이 위키의 추가 설정을 관리하기', href: pvzGetUrl('Special:ManageWiki/settings') },
| |
| { icon: 'pvz-icon-backup', label: '이 위키의 백업을 관리', href: pvzGetUrl('Special:DataDump') }
| |
| ])));
| |
|
| |
| return panel;
| |
| }
| |
|
| |
| function pvzCreateChoice(label, kind, value, extraClass) {
| |
| var button = document.createElement('button');
| |
|
| |
| button.type = 'button';
| |
| button.className = 'pvz-mobile-choice' + (extraClass ? ' ' + extraClass : '');
| |
| button.setAttribute('data-pvz-setting-kind', kind);
| |
| button.setAttribute('data-pvz-setting-value', value);
| |
| button.textContent = label;
| |
|
| |
| button.addEventListener('click', function (event) {
| |
| event.preventDefault();
| |
|
| |
| if (kind === 'font') pvzSetFontSize(value);
| |
| if (kind === 'theme') pvzSetTheme(value);
| |
| if (kind === 'width') pvzSetWidth(value);
| |
| if (kind === 'autohide') pvzSetAutoHide(value);
| |
| if (kind === 'performance') pvzSetPerformance(value);
| |
| });
| |
|
| |
| return button;
| |
| }
| |
|
| |
| function pvzCreateChoiceGroup(title, kind, choices, gridClass) {
| |
| var wrap = document.createElement('div');
| |
| var h = document.createElement('div');
| |
| var grid = document.createElement('div');
| |
|
| |
| wrap.className = 'pvz-mobile-setting-group';
| |
| h.className = 'pvz-mobile-setting-title';
| |
| h.textContent = title;
| |
| grid.className = 'pvz-mobile-choice-grid' + (gridClass ? ' ' + gridClass : '');
| |
|
| |
| choices.forEach(function (choice) {
| |
| grid.appendChild(pvzCreateChoice(choice.label, kind, choice.value, choice.extraClass));
| |
| });
| |
|
| |
| wrap.appendChild(h);
| |
| wrap.appendChild(grid);
| |
|
| |
| return wrap;
| |
| }
| |
|
| |
| function pvzCreateSettingsPanel() {
| |
| var panel = document.createElement('section');
| |
|
| |
| panel.id = 'pvz-mobile-panel-settings';
| |
| panel.className = 'pvz-mobile-sheet pvz-mobile-sheet-right';
| |
| panel.setAttribute('aria-label', '화면 설정');
| |
|
| |
| panel.appendChild(pvzCreateChoiceGroup('글자 크기', 'font', [
| |
| { label: '작게', value: 'small' },
| |
| { label: '보통', value: 'medium' },
| |
| { label: '크게', value: 'large' }
| |
| ], 'three'));
| |
|
| |
| panel.appendChild(pvzCreateChoiceGroup('색상 테마', 'theme', [
| |
| { label: '자동', value: 'auto' },
| |
| { label: '라이트', value: 'light' },
| |
| { label: '다크', value: 'dark', extraClass: 'dark' }
| |
| ], 'three'));
| |
|
| |
| panel.appendChild(pvzCreateChoiceGroup('페이지 너비', 'width', [
| |
| { label: '기본', value: 'normal' },
| |
| { label: '넓게', value: 'wide' }
| |
| ], ''));
| |
|
| |
| panel.appendChild(pvzCreateChoiceGroup('자동으로 메뉴 숨김', 'autohide', [
| |
| { label: '끄기', value: 'off' },
| |
| { label: '켜기', value: 'on' }
| |
| ], ''));
| |
|
| |
| panel.appendChild(pvzCreateChoiceGroup('Performance mode', 'performance', [
| |
| { label: 'Off', value: 'off' },
| |
| { label: 'On', value: 'on' }
| |
| ], ''));
| |
|
| |
| return panel;
| |
| }
| |
|
| |
| function pvzFormatUserGroup(group) {
| |
| var map = {
| |
| bureaucrat: '사무관',
| |
| sysop: '관리자',
| |
| administrator: '관리자',
| |
| interfaceadmin: '인터페이스 관리자',
| |
| 'interface-admin': '인터페이스 관리자',
| |
| bot: '봇',
| |
| autoconfirmed: '자동 인증된 사용자',
| |
| user: '사용자'
| |
| };
| |
|
| |
| return map[group] || group;
| |
| }
| |
|
| |
| function pvzFormatDate(value) {
| |
| var date;
| |
| var days = ['일', '월', '화', '수', '목', '금', '토'];
| |
|
| |
| if (!value) return '확인 불가';
| |
|
| |
| date = new Date(value);
| |
| if (isNaN(date.getTime())) return '확인 불가';
| |
|
| |
| return date.getFullYear() + '년 ' + (date.getMonth() + 1) + '월 ' +
| |
| date.getDate() + '일 (' + days[date.getDay()] + ')';
| |
| }
| |
|
| |
| function pvzCreateAccountPanel() {
| |
| var panel = document.createElement('section');
| |
| var username = window.mw && mw.config ? mw.config.get('wgUserName') : null;
| |
| var card = document.createElement('div');
| |
| var name = document.createElement('div');
| |
| var groups = document.createElement('div');
| |
| var stats = document.createElement('div');
| |
|
| |
| panel.id = 'pvz-mobile-panel-account';
| |
| panel.className = 'pvz-mobile-sheet pvz-mobile-sheet-right';
| |
| panel.setAttribute('aria-label', '계정');
| |
|
| |
| card.className = 'pvz-mobile-account-card';
| |
|
| |
| name.id = 'pvz-mobile-account-name';
| |
| name.className = 'pvz-mobile-account-name';
| |
| name.textContent = username || '로그인하지 않음';
| |
|
| |
| groups.id = 'pvz-mobile-account-groups';
| |
| groups.className = 'pvz-mobile-account-groups';
| |
| groups.textContent = username ? '계정 정보를 불러오는 중...' : '비로그인 사용자';
| |
|
| |
| stats.className = 'pvz-mobile-account-stats';
| |
| stats.innerHTML =
| |
| '<span>편집</span><span>가입일</span>' +
| |
| '<strong id="pvz-mobile-account-edits">-</strong>' +
| |
| '<strong id="pvz-mobile-account-registration">-</strong>';
| |
|
| |
| card.appendChild(name);
| |
| card.appendChild(groups);
| |
| card.appendChild(stats);
| |
| panel.appendChild(card);
| |
|
| |
| if (!username) {
| |
| panel.appendChild(pvzCreateSheetSection('계정', pvzCreateMobileLinkList([
| |
| { icon: 'pvz-icon-login', label: '로그인', href: pvzGetUrl('Special:UserLogin') },
| |
| { icon: 'pvz-icon-plus', label: '회원 가입', href: pvzGetUrl('Special:CreateAccount') }
| |
| ])));
| |
|
| |
| return panel;
| |
| }
| |
|
| |
| panel.appendChild(pvzCreateMobileLinkList([
| |
| { icon: 'pvz-icon-talk', label: '토론', href: pvzGetUrl('User talk:' + username) },
| |
| { icon: 'pvz-icon-star', label: '즐겨찾기 문서', href: pvzGetUrl('Special:Watchlist') },
| |
| { icon: 'pvz-icon-recent', label: '기여', href: pvzGetUrl('Special:Contributions/' + username) }
| |
| ]));
| |
|
| |
| var logout = document.createElement('a');
| |
| logout.className = 'pvz-mobile-account-logout';
| |
| logout.href = pvzGetUrl('Special:UserLogout');
| |
| pvzAppendIconText(logout, 'pvz-icon-login', '로그아웃');
| |
| panel.appendChild(logout);
| |
|
| |
| return panel;
| |
| }
| |
|
| |
| function pvzLoadAccountInfo() {
| |
| var username = window.mw && mw.config ? mw.config.get('wgUserName') : null;
| |
| var url;
| |
|
| |
| if (!username || !window.fetch) return;
| |
|
| |
| url =
| |
| pvzGetApiUrl() +
| |
| '?action=query' +
| |
| '&format=json' +
| |
| '&list=users' +
| |
| '&usprop=groups%7Ceditcount%7Cregistration' +
| |
| '&ususers=' + encodeURIComponent(username);
| |
|
| |
| fetch(url, { credentials: 'same-origin' })
| |
| .then(function (res) { return res.json(); })
| |
| .then(function (data) {
| |
| var user = data && data.query && data.query.users && data.query.users[0];
| |
| var groupBox = document.getElementById('pvz-mobile-account-groups');
| |
| var editBox = document.getElementById('pvz-mobile-account-edits');
| |
| var regBox = document.getElementById('pvz-mobile-account-registration');
| |
|
| |
| if (!user) return;
| |
|
| |
| if (groupBox) {
| |
| groupBox.textContent = (user.groups || [])
| |
| .filter(function (group) {
| |
| return group !== '*' && group !== 'user' && group !== 'autoconfirmed';
| |
| })
| |
| .map(pvzFormatUserGroup)
| |
| .join(' ') || '일반 사용자';
| |
| }
| |
|
| |
| if (editBox) {
| |
| editBox.textContent = typeof user.editcount === 'number' ? String(user.editcount) : '-';
| |
| }
| |
|
| |
| if (regBox) {
| |
| regBox.textContent = pvzFormatDate(user.registration);
| |
| }
| |
| })
| |
| .catch(function () {});
| |
| }
| |
|
| |
| function pvzCreatePanels() {
| |
| var backdrop;
| |
|
| |
| pvzRemoveById('pvz-mobile-panel-backdrop');
| |
| pvzRemoveById('pvz-mobile-panel-browse');
| |
| pvzRemoveById('pvz-mobile-panel-settings');
| |
| pvzRemoveById('pvz-mobile-panel-account');
| |
|
| |
| backdrop = document.createElement('div');
| |
| backdrop.id = 'pvz-mobile-panel-backdrop';
| |
| backdrop.className = 'pvz-mobile-panel-backdrop';
| |
|
| |
| document.body.appendChild(backdrop);
| |
| document.body.appendChild(pvzCreateBrowsePanel());
| |
| document.body.appendChild(pvzCreateSettingsPanel());
| |
| document.body.appendChild(pvzCreateAccountPanel());
| |
|
| |
| pvzUpdateChoices();
| |
| }
| |
|
| |
| function pvzClosePanels() {
| |
| document.documentElement.classList.remove('pvz-mobile-panel-open');
| |
|
| |
| document.querySelectorAll('.pvz-mobile-sheet').forEach(function (panel) {
| |
| panel.classList.remove('is-open');
| |
| });
| |
|
| |
| document.querySelectorAll('.pvz-mobile-nav-button').forEach(function (button) {
| |
| button.classList.remove('is-active');
| |
| });
| |
| }
| |
|
| |
| function pvzOpenPanel(name) {
| |
| var panel = document.getElementById('pvz-mobile-panel-' + name);
| |
|
| |
| if (!panel) return;
| |
|
| |
| pvzCloseSuggestions();
| |
| pvzClosePanels();
| |
|
| |
| document.documentElement.classList.add('pvz-mobile-panel-open');
| |
| panel.classList.add('is-open');
| |
|
| |
| document.querySelectorAll('.pvz-mobile-nav-button').forEach(function (button) {
| |
| button.classList.toggle('is-active', button.getAttribute('data-pvz-mobile-panel') === name);
| |
| });
| |
|
| |
| if (name === 'account') pvzLoadAccountInfo();
| |
| pvzUpdateChoices();
| |
| }
| |
|
| |
| function pvzTogglePanel(name) {
| |
| var panel = document.getElementById('pvz-mobile-panel-' + name);
| |
|
| |
| if (panel && panel.classList.contains('is-open')) {
| |
| pvzClosePanels();
| |
| return;
| |
| }
| |
|
| |
| pvzOpenPanel(name);
| |
| }
| |
|
| |
| function pvzCleanupOldBars() {
| |
| document.querySelectorAll(
| |
| '#pvz-appbar, .pvz-appbar, #pvz-pc-accountbar, .pvz-pc-accountbar, ' +
| |
| '#pvz-pc-side-settings-button, .pvz-pc-side-settings-button, .pvz-restored-topbar, ' +
| |
| '#pvz-mobile-topbar, .pvz-mobile-topbar, .pvz-timeless-custom-topbar, ' +
| |
| '.pvz-timeless-custom-search-form, .pvz-timeless-custom-search-input'
| |
| ).forEach(function (el) {
| |
| if (el && el.parentNode) el.parentNode.removeChild(el);
| |
| });
| |
| }
| |
|
| |
|
| |
| function pvzGroupTopbarTabs() {
| |
| var bar = document.getElementById('pvz-mobile-topbar');
| |
| var strip;
| |
| var buttons;
| |
|
| |
| if (!bar) return;
| |
|
| |
| strip = bar.querySelector('.pvz-topbar-tab-strip');
| |
|
| |
| if (!strip) {
| |
| strip = document.createElement('div');
| |
| strip.className = 'pvz-topbar-tab-strip';
| |
| strip.setAttribute('aria-label', '상단 도구');
| |
| }
| |
|
| |
| buttons = [
| |
| bar.querySelector('.pvz-mobile-nav-button[data-pvz-mobile-panel="browse"]'),
| |
| bar.querySelector('.pvz-mobile-nav-button[data-pvz-mobile-panel="settings"]'),
| |
| bar.querySelector('.pvz-mobile-nav-button[data-pvz-mobile-panel="account"]')
| |
| ].filter(Boolean);
| |
|
| |
| if (!buttons.length) return;
| |
|
| |
| if (strip.parentNode !== bar) {
| |
| bar.insertBefore(strip, bar.firstChild);
| |
| }
| |
|
| |
| buttons.forEach(function (button) {
| |
| if (button.parentNode !== strip) {
| |
| strip.appendChild(button);
| |
| }
| |
| });
| |
| }
| |
|
| |
| function pvzEnsureTopbar() {
| |
| var old = document.getElementById('pvz-mobile-topbar');
| |
| var bar;
| |
| var menuButton;
| |
| var searchForm;
| |
| var searchInput;
| |
| var alertsWrap;
| |
| var alertsButton;
| |
| var alertsPopover;
| |
| var alertsHeader;
| |
| var alertsBody;
| |
| var alertsFooter;
| |
| var allAlerts;
| |
| var preferencesLink;
| |
| var accountButton;
| |
| var settingsButton;
| |
|
| |
| if (!document.body) return;
| |
|
| |
| document.documentElement.classList.add('pvz-modern-ui');
| |
|
| |
| pvzCleanupOldBars();
| |
|
| |
| /* 데스크톱은 pvz-desktop-utilitybar만 사용한다. 예전 pvz-mobile-topbar 검색바는 만들지 않는다. */
| |
| if (window.matchMedia && window.matchMedia('(min-width: 721px)').matches) {
| |
| if (old && old.parentNode) old.parentNode.removeChild(old);
| |
| return;
| |
| }
| |
|
| |
| /* 모바일 폭에서만 기존 상단바가 검색만 남거나 버튼이 빠지는 문제를 막기 위해 매번 재생성한다. */
| |
| if (old && old.parentNode) {
| |
| old.parentNode.removeChild(old);
| |
| }
| |
|
| |
| bar = document.createElement('header');
| |
| bar.id = 'pvz-mobile-topbar';
| |
| bar.className = 'pvz-mobile-topbar';
| |
| bar.setAttribute('data-pvz-topbar', 'true');
| |
| bar.setAttribute('aria-label', '식좀위키 상단 바');
| |
| bar.style.setProperty('position', 'absolute', 'important');
| |
| bar.style.setProperty('top', '0', 'important');
| |
| bar.style.setProperty('right', '0', 'important');
| |
| bar.style.setProperty('left', 'auto', 'important');
| |
| bar.style.setProperty('transform', 'none', 'important');
| |
|
| |
| menuButton = document.createElement('button');
| |
| menuButton.type = 'button';
| |
| menuButton.className = 'pvz-mobile-nav-button';
| |
| menuButton.title = '둘러보기';
| |
| menuButton.setAttribute('aria-label', '둘러보기');
| |
| menuButton.setAttribute('data-pvz-mobile-panel', 'browse');
| |
| pvzSetIconOnly(menuButton, 'pvz-icon-menu');
| |
|
| |
| searchForm = document.createElement('form');
| |
| searchForm.className = 'pvz-mobile-search-form';
| |
| searchForm.setAttribute('role', 'search');
| |
|
| |
| searchInput = document.createElement('input');
| |
| searchInput.id = 'pvz-mobile-search-input';
| |
| searchInput.className = 'pvz-mobile-search-input';
| |
| searchInput.type = 'search';
| |
| searchInput.placeholder = '문서 검색';
| |
| searchInput.autocomplete = 'off';
| |
|
| |
| searchInput.addEventListener('input', function () {
| |
| pvzScheduleSearchSuggestions(searchInput.value);
| |
| });
| |
|
| |
| searchInput.addEventListener('focus', function () {
| |
| document.documentElement.classList.add('pvz-mobile-search-focused');
| |
| pvzPositionSearchPanel();
| |
| pvzScheduleSearchSuggestions(searchInput.value);
| |
| });
| |
|
| |
| searchInput.addEventListener('blur', function () {
| |
| window.setTimeout(function () {
| |
| document.documentElement.classList.remove('pvz-mobile-search-focused');
| |
| }, 200);
| |
| });
| |
|
| |
| searchInput.addEventListener('keydown', pvzHandleSearchKeydown);
| |
|
| |
| searchForm.addEventListener('submit', function (event) {
| |
| event.preventDefault();
| |
| if (searchInput.value.replace(/\s+/g, ' ').trim()) {
| |
| window.location.href = pvzGetSearchUrl(searchInput.value);
| |
| }
| |
| });
| |
|
| |
| searchForm.appendChild(searchInput);
| |
|
| |
| alertsWrap = document.createElement('div');
| |
| alertsWrap.id = 'pvz-alerts-wrap';
| |
| alertsWrap.className = 'pvz-alerts-wrap';
| |
|
| |
| alertsButton = document.createElement('button');
| |
| alertsButton.type = 'button';
| |
| alertsButton.id = 'pvz-alerts-button';
| |
| alertsButton.className = 'pvz-mobile-nav-button';
| |
| alertsButton.title = '알림';
| |
| alertsButton.setAttribute('aria-label', '알림');
| |
| pvzSetIconOnly(alertsButton, 'pvz-icon-bell');
| |
|
| |
| alertsPopover = document.createElement('div');
| |
| alertsPopover.id = 'pvz-alerts-popover';
| |
| alertsPopover.className = 'pvz-alerts-popover';
| |
|
| |
| alertsHeader = document.createElement('div');
| |
| alertsHeader.className = 'pvz-alerts-popover-header';
| |
| alertsHeader.appendChild(pvzCreateIcon('pvz-icon-bell'));
| |
|
| |
| var headerText = document.createElement('span');
| |
| headerText.textContent = '알림';
| |
| alertsHeader.appendChild(headerText);
| |
|
| |
| alertsBody = document.createElement('div');
| |
| alertsBody.className = 'pvz-alerts-popover-body';
| |
| alertsBody.textContent = '새 알림이 없습니다.';
| |
|
| |
| alertsFooter = document.createElement('div');
| |
| alertsFooter.className = 'pvz-alerts-popover-footer';
| |
|
| |
| allAlerts = document.createElement('a');
| |
| allAlerts.href = pvzGetUrl('Special:Notifications');
| |
| allAlerts.textContent = '전체 알림';
| |
|
| |
| preferencesLink = document.createElement('a');
| |
| preferencesLink.href = pvzGetUrl('Special:Preferences');
| |
| preferencesLink.textContent = '환경설정';
| |
|
| |
| alertsFooter.appendChild(allAlerts);
| |
| alertsFooter.appendChild(preferencesLink);
| |
|
| |
| alertsPopover.appendChild(alertsHeader);
| |
| alertsPopover.appendChild(alertsBody);
| |
| alertsPopover.appendChild(alertsFooter);
| |
|
| |
| alertsWrap.appendChild(alertsButton);
| |
| alertsWrap.appendChild(alertsPopover);
| |
|
| |
| accountButton = document.createElement('button');
| |
| accountButton.type = 'button';
| |
| accountButton.className = 'pvz-mobile-nav-button';
| |
| accountButton.title = '계정';
| |
| accountButton.setAttribute('aria-label', '계정');
| |
| accountButton.setAttribute('data-pvz-mobile-panel', 'account');
| |
| pvzSetIconOnly(accountButton, 'pvz-icon-account');
| |
|
| |
| settingsButton = document.createElement('button');
| |
| settingsButton.type = 'button';
| |
| settingsButton.className = 'pvz-mobile-nav-button';
| |
| settingsButton.title = '화면 설정';
| |
| settingsButton.setAttribute('aria-label', '화면 설정');
| |
| settingsButton.setAttribute('data-pvz-mobile-panel', 'settings');
| |
| pvzSetIconOnly(settingsButton, 'pvz-icon-settings');
| |
|
| |
| if (window.matchMedia && window.matchMedia('(max-width: 720px)').matches) {
| |
| bar.appendChild(menuButton);
| |
| bar.appendChild(searchForm);
| |
| bar.appendChild(alertsWrap);
| |
| bar.appendChild(settingsButton);
| |
| bar.appendChild(accountButton);
| |
| } else {
| |
| bar.appendChild(searchForm);
| |
| bar.appendChild(alertsWrap);
| |
| bar.appendChild(accountButton);
| |
| }
| |
|
| |
| document.body.insertBefore(bar, document.body.firstChild);
| |
|
| |
| pvzCreateSearchPanel();
| |
| if (!document.getElementById('pvz-mobile-panel-browse')) pvzCreatePanels();
| |
| pvzPositionSearchPanel();
| |
| }
| |
|
| |
| function pvzEnsureDesktopUtilityBar() {
| |
| var old = document.getElementById('pvz-desktop-utilitybar');
| |
| var bar;
| |
| var searchForm;
| |
| var searchInput;
| |
| var alertsWrap;
| |
| var alertsButton;
| |
| var alertsPopover;
| |
| var alertsHeader;
| |
| var alertsBody;
| |
| var alertsFooter;
| |
| var allAlerts;
| |
| var preferencesLink;
| |
| var accountButton;
| |
|
| |
| if (!document.body) return;
| |
|
| |
| /* 모바일에서는 기존 모바일 상단바를 사용한다. */
| |
| if (window.matchMedia && window.matchMedia('(max-width: 720px)').matches) {
| |
| if (old && old.parentNode) old.parentNode.removeChild(old);
| |
| return;
| |
| }
| |
|
| |
| if (old) {
| |
| if (
| |
| old.querySelector('#pvz-desktop-search-input') &&
| |
| old.querySelector('#pvz-desktop-alerts-button') &&
| |
| old.querySelector('#pvz-desktop-account-button')
| |
| ) {
| |
| return;
| |
| }
| |
|
| |
| if (old.parentNode) old.parentNode.removeChild(old);
| |
| }
| |
|
| |
| bar = document.createElement('div');
| |
| bar.id = 'pvz-desktop-utilitybar';
| |
| bar.className = 'pvz-desktop-utilitybar';
| |
| bar.setAttribute('aria-label', '식좀위키 데스크톱 유틸리티 바');
| |
|
| |
| searchForm = document.createElement('form');
| |
| searchForm.className = 'pvz-desktop-search-form';
| |
| searchForm.setAttribute('role', 'search');
| |
|
| |
| searchInput = document.createElement('input');
| |
| searchInput.id = 'pvz-desktop-search-input';
| |
| searchInput.className = 'pvz-desktop-search-input';
| |
| searchInput.type = 'search';
| |
| searchInput.placeholder = '문서 검색';
| |
| searchInput.autocomplete = 'off';
| |
|
| |
| searchInput.addEventListener('input', function () {
| |
| pvzScheduleSearchSuggestions(searchInput.value);
| |
| });
| |
|
| |
| searchInput.addEventListener('focus', function () {
| |
| document.documentElement.classList.add('pvz-mobile-search-focused');
| |
| pvzPositionSearchPanel();
| |
| pvzScheduleSearchSuggestions(searchInput.value);
| |
| });
| |
|
| |
| searchInput.addEventListener('blur', function () {
| |
| window.setTimeout(function () {
| |
| document.documentElement.classList.remove('pvz-mobile-search-focused');
| |
| }, 200);
| |
| });
| |
|
| |
| searchInput.addEventListener('keydown', pvzHandleSearchKeydown);
| |
|
| |
| searchForm.addEventListener('submit', function (event) {
| |
| event.preventDefault();
| |
| if (searchInput.value.replace(/\s+/g, ' ').trim()) {
| |
| window.location.href = pvzGetSearchUrl(searchInput.value);
| |
| }
| |
| });
| |
|
| |
| searchForm.appendChild(searchInput);
| |
|
| |
| alertsWrap = document.createElement('div');
| |
| alertsWrap.id = 'pvz-desktop-alerts-wrap';
| |
| alertsWrap.className = 'pvz-desktop-alerts-wrap';
| |
|
| |
| alertsButton = document.createElement('button');
| |
| alertsButton.type = 'button';
| |
| alertsButton.id = 'pvz-desktop-alerts-button';
| |
| alertsButton.className = 'pvz-desktop-util-button';
| |
| alertsButton.title = '알림';
| |
| alertsButton.setAttribute('aria-label', '알림');
| |
| pvzSetIconOnly(alertsButton, 'pvz-icon-bell');
| |
|
| |
| alertsPopover = document.createElement('div');
| |
| alertsPopover.id = 'pvz-desktop-alerts-popover';
| |
| alertsPopover.className = 'pvz-desktop-alerts-popover';
| |
|
| |
| alertsHeader = document.createElement('div');
| |
| alertsHeader.className = 'pvz-desktop-alerts-popover-header';
| |
| alertsHeader.textContent = '알림';
| |
|
| |
| alertsBody = document.createElement('div');
| |
| alertsBody.className = 'pvz-desktop-alerts-popover-body';
| |
| alertsBody.textContent = '새 알림이 없습니다.';
| |
|
| |
| alertsFooter = document.createElement('div');
| |
| alertsFooter.className = 'pvz-desktop-alerts-popover-footer';
| |
|
| |
| allAlerts = document.createElement('a');
| |
| allAlerts.href = pvzGetUrl('Special:Notifications');
| |
| allAlerts.textContent = '전체 알림';
| |
|
| |
| preferencesLink = document.createElement('a');
| |
| preferencesLink.href = pvzGetUrl('Special:Preferences');
| |
| preferencesLink.textContent = '환경설정';
| |
|
| |
| alertsFooter.appendChild(allAlerts);
| |
| alertsFooter.appendChild(preferencesLink);
| |
|
| |
| alertsPopover.appendChild(alertsHeader);
| |
| alertsPopover.appendChild(alertsBody);
| |
| alertsPopover.appendChild(alertsFooter);
| |
|
| |
| alertsWrap.appendChild(alertsButton);
| |
| alertsWrap.appendChild(alertsPopover);
| |
|
| |
| alertsButton.addEventListener('click', function (event) {
| |
| event.preventDefault();
| |
| event.stopPropagation();
| |
| alertsWrap.classList.toggle('is-open');
| |
| });
| |
|
| |
| accountButton = document.createElement('button');
| |
| accountButton.type = 'button';
| |
| accountButton.id = 'pvz-desktop-account-button';
| |
| accountButton.className = 'pvz-desktop-util-button pvz-mobile-nav-button';
| |
| accountButton.title = '계정';
| |
| accountButton.setAttribute('aria-label', '계정');
| |
| accountButton.setAttribute('data-pvz-mobile-panel', 'account');
| |
| pvzSetIconOnly(accountButton, 'pvz-icon-account');
| |
|
| |
| /* 왼쪽부터 검색바, 알림, 계정. 따라서 오른쪽 끝 기준은 계정 → 알림 → 검색바. */
| |
| bar.appendChild(searchForm);
| |
| bar.appendChild(alertsWrap);
| |
| bar.appendChild(accountButton);
| |
|
| |
| document.body.insertBefore(bar, document.body.firstChild);
| |
|
| |
| pvzCreateSearchPanel();
| |
| if (!document.getElementById('pvz-mobile-panel-account')) pvzCreatePanels();
| |
| pvzPositionSearchPanel();
| |
| }
| |
|
| |
| function pvzEnsureLogo() {
| |
| var old = document.getElementById('pvz-page-logo-link');
| |
| var topbar = document.getElementById('pvz-mobile-topbar');
| |
| var logo;
| |
| var img;
| |
| if (old || !document.body) return;
| |
| logo = document.createElement('a');
| |
| logo.id = 'pvz-page-logo-link';
| |
| logo.className = 'pvz-page-logo-link';
| |
| logo.href = pvzGetUrl('식물 vs 좀비 위키');
| |
| logo.title = '식물 vs 좀비 위키';
| |
| logo.setAttribute('aria-label', '식물 vs 좀비 위키로 이동');
| |
| img = document.createElement('img');
| |
| img.src = '/wiki/Special:Redirect/file/PvZKR_Wiki_Logo2.png';
| |
| img.alt = 'Plants vs. Zombies 위키';
| |
| img.loading = 'eager';
| |
| logo.appendChild(img);
| |
| if (topbar && topbar.parentNode === document.body && topbar.nextSibling) document.body.insertBefore(logo, topbar.nextSibling);
| |
| else if (topbar && topbar.parentNode === document.body) document.body.appendChild(logo);
| |
| else document.body.insertBefore(logo, document.body.firstChild);
| |
| }
| |
|
| |
| function pvzCreateSideLink(label, href) {
| |
| var a = document.createElement('a');
| |
| a.className = 'pvz-board-side-link';
| |
| a.href = href;
| |
| a.textContent = label;
| |
| return a;
| |
| }
| |
|
| |
| function pvzCreateSideSection(title, items) {
| |
| var section = document.createElement('section');
| |
| var h = document.createElement('div');
| |
| var paper = document.createElement('div');
| |
| section.className = 'pvz-board-side-section pvz-side-section-' + String(title || '').replace(/\s+/g, '-');
| |
| h.className = 'pvz-board-side-title';
| |
| h.textContent = title;
| |
| paper.className = 'pvz-board-side-paper';
| |
| items.forEach(function (item) { paper.appendChild(pvzCreateSideLink(item.label, item.href)); });
| |
| section.appendChild(h);
| |
| section.appendChild(paper);
| |
| return section;
| |
| }
| |
|
| |
| function pvzCreateSideNav() {
| |
| var nav = document.createElement('aside');
| |
| var pageName = pvzGetPageName();
| |
| var printableHref = window.location.href.split('#')[0] + (window.location.href.indexOf('?') === -1 ? '?printable=yes' : '&printable=yes');
| |
|
| |
| nav.className = 'pvz-board-side-nav';
| |
| nav.setAttribute('aria-label', '위키 보조창');
| |
|
| |
| nav.appendChild(pvzCreateSideSection('둘러보기', [
| |
| { label: '최근 바뀜', href: pvzGetUrl('Special:RecentChanges') },
| |
| { label: '임의 문서', href: pvzGetUrl('Special:Random') },
| |
| { label: '분류', href: pvzGetUrl('Special:Categories') },
| |
| { label: '미디어위키 도움말', href: pvzGetUrl('Help:Contents') }
| |
| ]));
| |
|
| |
| nav.appendChild(pvzCreateSideSection('문서 도구', [
| |
| { label: '새 문서', href: pvzGetUrl('Special:NewPages') },
| |
| { label: '파일 올리기', href: pvzGetUrl('Special:Upload') },
| |
| { label: '여기로 링크하는 문서', href: pvzGetUrl('Special:WhatLinksHere/' + pageName) },
| |
| { label: '인쇄용 판', href: printableHref },
| |
| { label: '문서 정보', href: pvzGetUrl(pageName) + '?action=info' }
| |
| ]));
| |
|
| |
| nav.appendChild(pvzCreateSideSection('위키 관리', [
| |
| { label: '특수 문서', href: pvzGetUrl('Special:SpecialPages') },
| |
| { label: '핵심 설정 관리', href: pvzGetUrl('Special:ManageWiki/settings') },
| |
| { label: '확장 기능 관리', href: pvzGetUrl('Special:ManageWiki/extensions') },
| |
| { label: '이름공간 관리', href: pvzGetUrl('Special:ManageWiki/namespaces') },
| |
| { label: '백업 관리', href: pvzGetUrl('Special:DataDump') }
| |
| ]));
| |
|
| |
| return nav;
| |
| }
| |
|
| |
| function pvzCreateRelatedPanel() {
| |
| var panel = document.createElement('aside');
| |
| var title = document.createElement('div');
| |
| var list = document.createElement('div');
| |
| panel.id = 'pvz-board-related-panel';
| |
| panel.className = 'pvz-board-related-panel';
| |
| panel.setAttribute('aria-label', '관련 문서');
| |
| title.className = 'pvz-board-related-title';
| |
| title.textContent = '관련 문서';
| |
| list.id = 'pvz-board-related-list';
| |
| list.className = 'pvz-board-related-list';
| |
| panel.appendChild(title);
| |
| panel.appendChild(list);
| |
| return panel;
| |
| }
| |
|
| |
| function pvzCreateRelatedCard(titleText, href, thumb) {
| |
| var link = document.createElement('a');
| |
| var media = document.createElement('div');
| |
| var titleBar = document.createElement('div');
| |
| var title = document.createElement('span');
| |
| link.className = 'pvz-related-card';
| |
| link.href = href;
| |
| media.className = 'pvz-related-card-media';
| |
| if (thumb) {
| |
| var img = document.createElement('img');
| |
| img.src = thumb;
| |
| img.alt = '';
| |
| img.loading = 'lazy';
| |
| media.appendChild(img);
| |
| }
| |
| titleBar.className = 'pvz-related-card-titlebar';
| |
| title.className = 'pvz-related-card-title';
| |
| title.textContent = titleText || '문서 이름';
| |
| titleBar.appendChild(title);
| |
| link.appendChild(media);
| |
| link.appendChild(titleBar);
| |
| return link;
| |
| }
| |
|
| |
| function pvzCollectRelatedTitles(limit) {
| |
| var current = pvzGetPageName().replace(/_/g, ' ');
| |
| var contentText = document.getElementById('mw-content-text');
| |
| var links = contentText ? contentText.querySelectorAll('a[href]') : [];
| |
| var seen = {};
| |
| var results = [];
| |
| pvzArray(links).forEach(function (link) {
| |
| var href = link.getAttribute('href') || '';
| |
| var title = '';
| |
| if (!href || results.length >= limit) return;
| |
| if (href.indexOf('#') === 0) return;
| |
| if (href.indexOf('/wiki/') === 0) {
| |
| title = decodeURIComponent(href.replace(/^\/wiki\//, '').split('#')[0].split('?')[0]).replace(/_/g, ' ');
| |
| } else if (link.dataset && link.dataset.title) {
| |
| title = String(link.dataset.title).replace(/_/g, ' ');
| |
| }
| |
| if (!title || title === current) return;
| |
| if (/^(Special|File|파일|Category|분류|Template|틀|Help|User|사용자|Talk|토론):/i.test(title)) return;
| |
| if (seen[title]) return;
| |
| seen[title] = true;
| |
| results.push(title);
| |
| });
| |
| return results.slice(0, limit);
| |
| }
| |
|
| |
| function pvzRenderRelatedCards(items) {
| |
| var list = document.getElementById('pvz-board-related-list');
| |
| if (!list) return;
| |
| list.innerHTML = '';
| |
| if (!items || !items.length) items = [{ title: '대문', href: pvzGetUrl('대문'), thumb: '' }];
| |
| items.forEach(function (item) { list.appendChild(pvzCreateRelatedCard(item.title, item.href, item.thumb)); });
| |
| }
| |
|
| |
| function pvzLoadRelatedDocs() {
| |
| var titles = pvzCollectRelatedTitles(12);
| |
| var url;
| |
| pvzRenderRelatedCards(titles.map(function (title) { return { title: title, href: pvzGetUrl(title), thumb: '' }; }));
| |
| if (!titles.length || !window.fetch) return;
| |
| url = pvzGetApiUrl() + '?action=query&format=json&formatversion=2&prop=pageimages&piprop=thumbnail&pithumbsize=260&titles=' + encodeURIComponent(titles.join('|'));
| |
| fetch(url, { credentials: 'same-origin' })
| |
| .then(function (res) { return res.json(); })
| |
| .then(function (data) {
| |
| var pages = data && data.query && Array.isArray(data.query.pages) ? data.query.pages : [];
| |
| var map = {};
| |
| pages.forEach(function (page) { if (page && page.title) map[page.title] = page.thumbnail && page.thumbnail.source ? page.thumbnail.source : ''; });
| |
| pvzRenderRelatedCards(titles.map(function (title) { return { title: title, href: pvzGetUrl(title), thumb: map[title] || '' }; }));
| |
| })
| |
| .catch(function () {});
| |
| }
| |
|
| |
| function pvzSetRelatedOpen(open) {
| |
| document.documentElement.classList.toggle('pvz-related-open', !!open);
| |
| if (document.body) document.body.classList.toggle('pvz-related-open', !!open);
| |
| var button = document.getElementById('pvz-side-toggle');
| |
| if (button) {
| |
| button.setAttribute('aria-expanded', open ? 'true' : 'false');
| |
| button.setAttribute('aria-label', open ? '관련 문서 닫기' : '관련 문서 열기');
| |
| button.setAttribute('title', open ? '관련 문서 닫기' : '관련 문서 열기');
| |
| button.innerHTML = '';
| |
| button.appendChild(pvzCreateIcon(open ? 'pvz-icon-chevron-left' : 'pvz-icon-chevron-right'));
| |
| }
| |
| }
| |
|
| |
| function pvzEnsureSideToggle() {
| |
| var button = document.getElementById('pvz-side-toggle');
| |
| if (button || !document.body) return;
| |
| button = document.createElement('button');
| |
| button.type = 'button';
| |
| button.id = 'pvz-side-toggle';
| |
| button.setAttribute('aria-expanded', 'false');
| |
| button.setAttribute('aria-label', '관련 문서 열기');
| |
| button.setAttribute('title', '관련 문서 열기');
| |
| button.appendChild(pvzCreateIcon('pvz-icon-chevron-right'));
| |
| document.body.appendChild(button);
| |
| }
| |
|
| |
| function pvzSetAlertsOpen(open) {
| |
| var wrap = document.getElementById('pvz-alerts-wrap');
| |
| if (wrap) wrap.classList.toggle('is-open', !!open);
| |
| }
| |
|
| |
| function pvzToggleAlertsOpen() {
| |
| var wrap = document.getElementById('pvz-alerts-wrap');
| |
| if (!wrap) return;
| |
| pvzSetAlertsOpen(!wrap.classList.contains('is-open'));
| |
| }
| |
|
| |
| function pvzHeadingText(heading) {
| |
| var clone;
| |
|
| |
| if (!heading) return '';
| |
|
| |
| clone = heading.cloneNode(true);
| |
| clone.querySelectorAll('.mw-editsection, .mw-headline-number, .toctogglecheckbox').forEach(function (el) {
| |
| el.remove();
| |
| });
| |
|
| |
| return pvzText(clone);
| |
| }
| |
|
| |
| function pvzSafeAnchor(text, index) {
| |
| var id = String(text || '')
| |
| .trim()
| |
| .replace(/\s+/g, '-')
| |
| .replace(/[^\w가-힣ぁ-んァ-ン一-龥-]/g, '');
| |
|
| |
| if (!id) id = 'section-' + index;
| |
|
| |
| return 'pvz-section-' + id + '-' + index;
| |
| }
| |
|
| |
| function pvzHeadingId(heading, text, index) {
| |
| var headline = heading.querySelector ? heading.querySelector('.mw-headline') : null;
| |
| var id;
| |
|
| |
| if (headline && headline.id) return headline.id;
| |
| if (heading.id) return heading.id;
| |
|
| |
| id = pvzSafeAnchor(text, index);
| |
| heading.id = id;
| |
| return id;
| |
| }
| |
|
| |
| function pvzSkipHeading(heading) {
| |
| var text = pvzHeadingText(heading);
| |
|
| |
| if (!text) return true;
| |
|
| |
| if (
| |
| heading.closest('.pvz-custom-toc-wrap') ||
| |
| heading.closest('.toc') ||
| |
| heading.closest('#toc') ||
| |
| heading.closest('table') ||
| |
| heading.closest('.infobox') ||
| |
| heading.closest('.navbox') ||
| |
| heading.closest('.metadata') ||
| |
| heading.closest('.catlinks') ||
| |
| heading.closest('.mw-references-wrap') ||
| |
| heading.closest('.reference')
| |
| ) {
| |
| return true;
| |
| }
| |
|
| |
| return text === '목차' || text === 'Contents' || text === '각주' ||
| |
| text === 'References' || text === '분류';
| |
| }
| |
|
| |
| function pvzIsTextParagraph(el) {
| |
| if (!el || el.tagName !== 'P') return false;
| |
| return (el.textContent || '').replace(/\s+/g, ' ').trim().length > 0;
| |
| }
| |
|
| |
| function pvzCreateCustomToc() {
| |
| var contentText = document.getElementById('mw-content-text');
| |
| var contentRoot = contentText && (
| |
| contentText.querySelector('.mw-parser-output') || contentText
| |
| );
| |
| var existingWrap;
| |
| var nativeTocs;
| |
| var headings;
| |
| var leadParagraph;
| |
| var wrap;
| |
| var toc;
| |
| var head;
| |
| var title;
| |
| var toggle;
| |
| var list;
| |
| var counters = [0, 0, 0, 0, 0];
| |
|
| |
| function getDepth(heading) {
| |
| var level = parseInt(heading.tagName.replace('H', ''), 10);
| |
| var depth = level - 1;
| |
|
| |
| if (depth < 1) depth = 1;
| |
| if (depth > 5) depth = 5;
| |
|
| |
| return depth;
| |
| }
| |
|
| |
| function getNumber(depth) {
| |
| var i;
| |
| var parts = [];
| |
|
| |
| counters[depth - 1] += 1;
| |
|
| |
| for (i = depth; i < counters.length; i += 1) {
| |
| counters[i] = 0;
| |
| }
| |
|
| |
| for (i = 0; i < depth; i += 1) {
| |
| if (!counters[i]) counters[i] = 1;
| |
| parts.push(counters[i]);
| |
| }
| |
|
| |
| return parts.join('.');
| |
| }
| |
|
| |
| function isExcludedLeadContainer(element) {
| |
| if (!element || !element.matches) return false;
| |
|
| |
| return element.matches(
| |
| 'table, figure, .thumb, .tright, .tleft, .floatright, .floatleft, .floatnone, ' +
| |
| '.infobox, .pvz-cp-infobox, .pvz-character-infobox, .character-infobox, ' +
| |
| '.navbox, .metadata, .catlinks, .mw-references-wrap, ' +
| |
| '.pvz-message, .pvz-custom-toc-wrap, #toc, .toc'
| |
| );
| |
| }
| |
|
| |
| function isLeadParagraph(element) {
| |
| var clone;
| |
| var text;
| |
|
| |
| /*
| |
| 첫 문단은 .mw-parser-output의 직접 자식인 <p>만 인정한다.
| |
| 틀이나 정보상자 내부의 <p>를 첫 문장으로 오인하지 않는다.
| |
| */
| |
| if (!element || element.parentNode !== contentRoot || element.tagName !== 'P') {
| |
| return false;
| |
| }
| |
|
| |
| if (isExcludedLeadContainer(element)) return false;
| |
|
| |
| /*
| |
| TemplateStyles/ResourceLoader가 넣은 style 요소나
| |
| CSS 전용 문단은 실제 본문 문단으로 취급하지 않는다.
| |
| */
| |
| if (
| |
| element.querySelector(
| |
| 'style, script, template, noscript, ' +
| |
| 'link[rel="mw-deduplicated-inline-style"], ' +
| |
| '[data-mw-deduplicate]'
| |
| )
| |
| ) {
| |
| return false;
| |
| }
| |
|
| |
| clone = element.cloneNode(true);
| |
|
| |
| clone.querySelectorAll(
| |
| 'figure, .thumb, .mw-file-description, .mw-default-size, ' +
| |
| '.mw-image-border, .reference, sup, .mw-editsection, ' +
| |
| 'style, script, template, noscript, link, [data-mw-deduplicate]'
| |
| ).forEach(function (node) {
| |
| node.remove();
| |
| });
| |
|
| |
| text = (clone.textContent || '').replace(/\s+/g, ' ').trim();
| |
|
| |
| /*
| |
| 일부 파서 구조에서는 CSS가 순수 텍스트 노드로 남을 수 있으므로
| |
| 대표적인 CSS 시작 패턴도 추가로 제외한다.
| |
| */
| |
| if (
| |
| /^(?:\.mw-parser-output\b|@media\b|@supports\b|@layer\b|:root\b)/.test(text) &&
| |
| /[{};]/.test(text)
| |
| ) {
| |
| return false;
| |
| }
| |
|
| |
| return text.length > 0;
| |
| }
| |
|
| |
| function findLeadParagraph() {
| |
| var children;
| |
| var i;
| |
| var child;
| |
|
| |
| if (!contentRoot) return null;
| |
|
| |
| children = pvzArray(contentRoot.children);
| |
|
| |
| for (i = 0; i < children.length; i += 1) {
| |
| child = children[i];
| |
|
| |
| if (isExcludedLeadContainer(child)) continue;
| |
|
| |
| if (isLeadParagraph(child)) {
| |
| return child;
| |
| }
| |
| }
| |
|
| |
| return null;
| |
| }
| |
|
| |
| function placeTocAfterLead(tocWrap, paragraph) {
| |
| if (
| |
| !tocWrap ||
| |
| !paragraph ||
| |
| paragraph.parentNode !== contentRoot
| |
| ) {
| |
| return false;
| |
| }
| |
|
| |
| if (
| |
| tocWrap.parentNode === contentRoot &&
| |
| paragraph.nextSibling === tocWrap
| |
| ) {
| |
| return true;
| |
| }
| |
|
| |
| contentRoot.insertBefore(tocWrap, paragraph.nextSibling);
| |
|
| |
| return true;
| |
| }
| |
|
| |
| if (!contentRoot || contentRoot.querySelector('.pvz-no-custom-toc')) return;
| |
|
| |
| leadParagraph = findLeadParagraph();
| |
|
| |
| /*
| |
| 실제 첫 본문 문단이 확인되기 전에는 목차를 임의 위치에 생성하지 않는다.
| |
| 틀:작성중이나 캐릭터 정보상자 내부 문단은 첫 문단 후보에서 제외한다.
| |
| */
| |
| if (!leadParagraph) return;
| |
|
| |
| existingWrap = contentRoot.querySelector('.pvz-custom-toc-wrap');
| |
|
| |
| if (existingWrap) {
| |
| placeTocAfterLead(existingWrap, leadParagraph);
| |
| return;
| |
| }
| |
|
| |
| headings = pvzArray(
| |
| contentRoot.querySelectorAll('h2, h3, h4, h5, h6')
| |
| ).filter(function (heading) {
| |
| return !pvzSkipHeading(heading);
| |
| });
| |
|
| |
| if (headings.length < 2) return;
| |
|
| |
| nativeTocs = pvzArray(
| |
| contentRoot.querySelectorAll('#toc, .toc')
| |
| ).filter(function (node) {
| |
| return node && !(
| |
| node.classList &&
| |
| node.classList.contains('pvz-custom-toc')
| |
| );
| |
| });
| |
|
| |
| nativeTocs.forEach(function (old) {
| |
| if (old && old.parentNode) {
| |
| old.parentNode.removeChild(old);
| |
| }
| |
| });
| |
|
| |
| wrap = document.createElement('div');
| |
| wrap.className = 'pvz-custom-toc-wrap';
| |
|
| |
| toc = document.createElement('nav');
| |
| toc.className = 'pvz-custom-toc';
| |
| toc.setAttribute('aria-label', '목차');
| |
|
| |
| head = document.createElement('div');
| |
| head.className = 'pvz-custom-toc-head';
| |
|
| |
| title = document.createElement('div');
| |
| title.className = 'pvz-custom-toc-title';
| |
| title.textContent = '목차';
| |
|
| |
| toggle = document.createElement('button');
| |
| toggle.type = 'button';
| |
| toggle.className = 'pvz-custom-toc-toggle';
| |
| toggle.setAttribute('aria-expanded', 'true');
| |
| toggle.textContent = '접기';
| |
|
| |
| toggle.addEventListener('click', function (event) {
| |
| var collapsed;
| |
|
| |
| event.preventDefault();
| |
|
| |
| collapsed = !toc.classList.contains('is-collapsed');
| |
| toc.classList.toggle('is-collapsed', collapsed);
| |
| toggle.setAttribute(
| |
| 'aria-expanded',
| |
| collapsed ? 'false' : 'true'
| |
| );
| |
| toggle.textContent = collapsed ? '펼치기' : '접기';
| |
| });
| |
|
| |
| head.appendChild(title);
| |
| head.appendChild(toggle);
| |
|
| |
| list = document.createElement('ol');
| |
| list.className = 'pvz-custom-toc-list';
| |
|
| |
| headings.forEach(function (heading, index) {
| |
| var depth = getDepth(heading);
| |
| var level = depth + 1;
| |
| var text = pvzHeadingText(heading);
| |
| var anchor = pvzHeadingId(heading, text, index + 1);
| |
| var number = getNumber(depth);
| |
| var item = document.createElement('li');
| |
| var link = document.createElement('a');
| |
| var numberSpan = document.createElement('span');
| |
| var textSpan = document.createElement('span');
| |
|
| |
| item.className =
| |
| 'pvz-custom-toc-item pvz-custom-toc-level-' +
| |
| level +
| |
| ' pvz-custom-toc-depth-' +
| |
| depth;
| |
|
| |
| link.href = '#' + anchor;
| |
|
| |
| numberSpan.className = 'pvz-custom-toc-number';
| |
| numberSpan.textContent = number;
| |
|
| |
| textSpan.className = 'pvz-custom-toc-text';
| |
| textSpan.textContent = text;
| |
|
| |
| link.appendChild(numberSpan);
| |
| link.appendChild(textSpan);
| |
| item.appendChild(link);
| |
| list.appendChild(item);
| |
| });
| |
|
| |
| toc.appendChild(head);
| |
| toc.appendChild(list);
| |
| wrap.appendChild(toc);
| |
|
| |
| placeTocAfterLead(wrap, leadParagraph);
| |
| }
| |
|
| |
| function pvzEnsureRelatedPanel() {
| |
| var panel = document.getElementById('pvz-board-related-panel') ||
| |
| document.querySelector('.pvz-board-related-panel');
| |
|
| |
| if (!document.body) return panel;
| |
|
| |
| if (!panel) {
| |
| panel = pvzCreateRelatedPanel();
| |
| }
| |
|
| |
| /* 관련 문서 창은 왼쪽 레일 안에 두지 않고 body 직속으로 둔다.
| |
| 이렇게 해야 blur 막이나 본문창보다 항상 위에 올라올 수 있다. */
| |
| if (panel.parentNode !== document.body) {
| |
| document.body.appendChild(panel);
| |
| }
| |
|
| |
| return panel;
| |
| }
| |
|
| |
| function pvzBuildBoard() {
| |
| var contentText = document.getElementById('mw-content-text');
| |
| var boardRoot;
| |
| var frameOuter;
| |
| var frameThird;
| |
| var frameSecond;
| |
| var frameInner;
| |
| var boardBody;
| |
| var paper;
| |
| var paperMain;
| |
| var paperMeta;
| |
| var sideNav;
| |
| var relatedPanel;
| |
| var leftRail;
| |
| var logo;
| |
| var catlinks;
| |
| var firstChild;
| |
| if (!contentText || !document.body) return;
| |
| pvzEnsureSideToggle();
| |
| if (document.body.classList.contains('pvz-board-ready')) {
| |
| logo = document.getElementById('pvz-page-logo-link');
| |
| boardRoot = document.getElementById('bodyContent') || document.querySelector('#content') || contentText.parentElement;
| |
| if (boardRoot && boardRoot.classList.contains('pvz-board-root')) {
| |
| leftRail = boardRoot.querySelector('.pvz-board-left-rail');
| |
| sideNav = boardRoot.querySelector('.pvz-board-side-nav');
| |
| relatedPanel = pvzEnsureRelatedPanel();
| |
| if (!leftRail) { leftRail = document.createElement('div'); leftRail.className = 'pvz-board-left-rail'; boardRoot.insertBefore(leftRail, boardRoot.firstChild); }
| |
| if (logo && logo.parentNode !== leftRail) leftRail.appendChild(logo);
| |
| if (!sideNav) leftRail.appendChild(pvzCreateSideNav());
| |
| relatedPanel = pvzEnsureRelatedPanel();
| |
| }
| |
| pvzLoadRelatedDocs();
| |
| pvzCreateCustomToc();
| |
| return;
| |
| }
| |
| boardRoot = document.getElementById('bodyContent') || document.querySelector('#content') || contentText.parentElement;
| |
| if (!boardRoot) return;
| |
| boardRoot.classList.add('pvz-board-root');
| |
| logo = document.getElementById('pvz-page-logo-link');
| |
| if (logo && logo.parentNode !== boardRoot) boardRoot.insertBefore(logo, boardRoot.firstChild);
| |
| frameOuter = document.createElement('div'); frameOuter.className = 'pvz-board-frame-outer-real';
| |
| frameThird = document.createElement('div'); frameThird.className = 'pvz-board-frame-third-real';
| |
| frameSecond = document.createElement('div'); frameSecond.className = 'pvz-board-frame-second-real';
| |
| frameInner = document.createElement('div'); frameInner.className = 'pvz-board-frame-inner-real';
| |
| boardBody = document.createElement('div'); boardBody.className = 'pvz-board-body';
| |
| paper = document.createElement('div'); paper.className = 'pvz-board-paper';
| |
| paperMain = document.createElement('div'); paperMain.className = 'pvz-board-paper-main';
| |
| paperMeta = document.createElement('div'); paperMeta.className = 'pvz-board-paper-meta';
| |
| sideNav = pvzCreateSideNav(); relatedPanel = pvzEnsureRelatedPanel();
| |
| leftRail = document.createElement('div'); leftRail.className = 'pvz-board-left-rail';
| |
| if (logo) leftRail.appendChild(logo);
| |
| leftRail.appendChild(sideNav);
| |
| firstChild = boardRoot.firstChild;
| |
| boardRoot.insertBefore(leftRail, firstChild || null);
| |
| boardRoot.insertBefore(frameOuter, leftRail.nextSibling || null);
| |
| paperMain.appendChild(contentText);
| |
| catlinks = document.getElementById('catlinks') || document.querySelector('.catlinks');
| |
| if (catlinks && !paperMeta.contains(catlinks)) paperMeta.appendChild(catlinks);
| |
| paper.appendChild(paperMain); paper.appendChild(paperMeta);
| |
| boardBody.appendChild(pvzCreateBoardHead()); boardBody.appendChild(paper);
| |
| frameInner.appendChild(boardBody); frameSecond.appendChild(frameInner); frameThird.appendChild(frameSecond); frameOuter.appendChild(frameThird);
| |
| document.body.classList.add('pvz-board-ready');
| |
| document.documentElement.classList.add('pvz-desktop-booted');
| |
| pvzLoadRelatedDocs();
| |
| pvzCreateCustomToc();
| |
| }
| |
|
| |
| function pvzMarkBoardActionActive(target) {
| |
| var button = pvzClosest(target, '.pvz-board-btn, .pvz-board-more > summary');
| |
| if (!button) return;
| |
| document.querySelectorAll('.pvz-board-btn.is-clicked, .pvz-board-more > summary.is-clicked').forEach(function (el) {
| |
| el.classList.remove('is-clicked');
| |
| });
| |
| button.classList.add('is-clicked');
| |
| }
| |
|
| |
| function pvzBindEvents() {
| |
| if (window.PVZ_VECTOR_EVENTS_BOUND) return;
| |
| window.PVZ_VECTOR_EVENTS_BOUND = true;
| |
|
| |
| document.addEventListener('click', function (event) {
| |
| var panelButton = pvzClosest(event.target, '.pvz-mobile-nav-button[data-pvz-mobile-panel], .pvz-desktop-util-button[data-pvz-mobile-panel]');
| |
| var panel = pvzClosest(event.target, '.pvz-mobile-sheet');
| |
| var panelBackdrop = pvzClosest(event.target, '#pvz-mobile-panel-backdrop');
| |
| var suggestionsPanel = pvzClosest(event.target, '#pvz-search-suggestions');
| |
| var searchInput = pvzClosest(event.target, '#pvz-mobile-search-input');
| |
| var alertsButton = pvzClosest(event.target, '#pvz-alerts-button');
| |
| var alertsWrap = pvzClosest(event.target, '#pvz-alerts-wrap');
| |
| var desktopAlertsWrap = pvzClosest(event.target, '#pvz-desktop-alerts-wrap');
| |
| var sideToggle = pvzClosest(event.target, '#pvz-side-toggle');
| |
| var boardAction = pvzClosest(event.target, '.pvz-board-btn, .pvz-board-more > summary');
| |
| var name;
| |
|
| |
| if (sideToggle) {
| |
| event.preventDefault();
| |
| event.stopPropagation();
| |
| pvzSetRelatedOpen(!document.body.classList.contains('pvz-related-open'));
| |
| return;
| |
| }
| |
|
| |
| if (boardAction) {
| |
| pvzMarkBoardActionActive(boardAction);
| |
| }
| |
|
| |
| if (alertsButton) {
| |
| event.preventDefault();
| |
| event.stopPropagation();
| |
| pvzToggleAlertsOpen();
| |
| return;
| |
| }
| |
|
| |
| if (panelButton) {
| |
| event.preventDefault();
| |
| event.stopPropagation();
| |
| name = panelButton.getAttribute('data-pvz-mobile-panel');
| |
| pvzTogglePanel(name);
| |
| return;
| |
| }
| |
|
| |
| if (panelBackdrop) {
| |
| event.preventDefault();
| |
| pvzClosePanels();
| |
| return;
| |
| }
| |
|
| |
| if (!suggestionsPanel && !searchInput) pvzCloseSuggestions();
| |
| if (!alertsWrap) pvzSetAlertsOpen(false);
| |
| if (!desktopAlertsWrap) {
| |
| var desktopAlerts = document.getElementById('pvz-desktop-alerts-wrap');
| |
| if (desktopAlerts) desktopAlerts.classList.remove('is-open');
| |
| }
| |
| if (document.body.classList.contains('pvz-related-open') && !pvzClosest(event.target, '.pvz-board-related-panel') && !sideToggle) {
| |
| pvzSetRelatedOpen(false);
| |
| }
| |
| if (!panel && !panelButton && !panelBackdrop) pvzClosePanels();
| |
| }, true);
| |
|
| |
| document.addEventListener('keydown', function (event) {
| |
| if (event.key === 'Escape') {
| |
| pvzCloseSuggestions();
| |
| pvzClosePanels();
| |
| pvzSetAlertsOpen(false);
| |
| var desktopAlerts = document.getElementById('pvz-desktop-alerts-wrap');
| |
| if (desktopAlerts) desktopAlerts.classList.remove('is-open');
| |
| pvzSetRelatedOpen(false);
| |
| }
| |
| });
| |
|
| |
| window.addEventListener('resize', function () {
| |
| pvzSyncClasses();
| |
| pvzPositionSearchPanel();
| |
| pvzEnsureTopbar();
| |
| pvzEnsureDesktopUtilityBar();
| |
| pvzLoadRelatedDocs();
| |
| });
| |
|
| |
| window.addEventListener('scroll', function () {
| |
| pvzPositionSearchPanel();
| |
| }, true);
| |
| }
| |
|
| |
| function pvzInit() {
| |
| pvzSyncClasses();
| |
| pvzApplyPreferences();
| |
| pvzEnsureTopbar();
| |
| pvzEnsureDesktopUtilityBar();
| |
| pvzEnsureLogo();
| |
| pvzBuildBoard();
| |
| pvzBindEvents();
| |
|
| |
| window.setTimeout(function () {
| |
| pvzSyncClasses();
| |
| pvzEnsureTopbar();
| |
| pvzEnsureDesktopUtilityBar();
| |
| pvzEnsureLogo();
| |
| pvzCreateCustomToc();
| |
| }, 120);
| |
|
| |
| window.setTimeout(function () {
| |
| pvzSyncClasses();
| |
| pvzEnsureTopbar();
| |
| pvzEnsureDesktopUtilityBar();
| |
| pvzEnsureLogo();
| |
| pvzCreateCustomToc();
| |
| }, 600);
| |
|
| |
| window.setTimeout(function () {
| |
| pvzSyncClasses();
| |
| pvzEnsureTopbar();
| |
| pvzEnsureDesktopUtilityBar();
| |
| pvzEnsureLogo();
| |
| pvzCreateCustomToc();
| |
| }, 1400);
| |
| }
| |
|
| |
| if (window.mw && mw.hook) {
| |
| mw.hook('wikipage.content').add(function () {
| |
| pvzInit();
| |
| });
| |
| }
| |
|
| |
| pvzReady(pvzInit);
| |
| }());
| |
|
| |
| /* ===== mobile-js.txt ===== */
| |
| /* =========================
| |
| MediaWiki:Gadget-mobile.js
| |
| Timeless 모바일 알림·리본·본문 프레임 재정리 v125
| |
| - v124 기준
| |
| - 검색바 1280px 이상 판정을 visualViewport가 아닌 레이아웃 viewport 기준으로 변경
| |
| - 아이패드 가로 화면 핀치 확대 시 검색바가 100% 폭으로 풀리는 문제 수정
| |
| - 검색바 왼쪽 돋보기 아이콘 제거
| |
| - 검색 문구를 '위키에서 검색하세요'로 변경
| |
| - 검색 제안에서 입력 문자열과 일치하는 부분만 굵게 강조
| |
| - MediaWiki API 기반 검색 제안과 문서 대표 이미지 표시 유지
| |
| - 한글 IME 입력·키보드 탐색·바깥 클릭 닫기 대응 유지
| |
| - 화면 회전·리사이즈·본문 폭 변화 시 검색바 폭 자동 재계산
| |
| - 사용자 환경 기반 초기 테마 적용 유지
| |
| - Timeless 상단 패널 absolute 문서 좌표 방식 유지
| |
| - 모바일 목차 .mf-section-0 대응 유지
| |
| - footer 아래 실제 여백 감지 기능 유지
| |
| ========================= */
| |
|
| |
| (function () {
| |
| 'use strict';
| |
|
| |
| document.documentElement.classList.add('pvz-timeless-mobile-booting');
| |
|
| |
|
| |
| function pvzArray(list) {
| |
| return Array.prototype.slice.call(list || []);
| |
| }
| |
|
| |
| function pvzIsTimeless() {
| |
| var skinName = '';
| |
|
| |
| if (window.mw && mw.config && typeof mw.config.get === 'function') {
| |
| skinName = mw.config.get('skin') || mw.config.get('wgDefaultSkin') || '';
| |
| }
| |
|
| |
| return skinName === 'timeless' ||
| |
| (document.body && document.body.classList && document.body.classList.contains('skin-timeless'));
| |
| }
| |
|
| |
| function pvzMarkReady() {
| |
| document.documentElement.classList.add('pvz-timeless-mobile-ready');
| |
| document.documentElement.classList.add('pvz-timeless-mobile-booted');
| |
| if (document.body) {
| |
| document.body.classList.add('pvz-timeless-mobile-ready');
| |
| }
| |
| }
| |
|
| |
| function pvzSkipHeading(heading) {
| |
| if (!heading) return true;
| |
| if (heading.closest && heading.closest('.pvz-custom-toc, .toc, #toc, .navbox, .metadata, .catlinks, .mw-references-wrap')) return true;
| |
| if (heading.classList && heading.classList.contains('pvz-custom-toc-title')) return true;
| |
| return !(heading.textContent || '').replace(/\s+/g, ' ').trim();
| |
| }
| |
|
| |
| function pvzHeadingText(heading) {
| |
| var clone = heading.cloneNode(true);
| |
| var text;
| |
|
| |
| clone.querySelectorAll('.mw-editsection, .mw-headline-anchor, .reference, sup').forEach(function (node) {
| |
| node.remove();
| |
| });
| |
|
| |
| text = (clone.textContent || '').replace(/\s+/g, ' ').trim();
| |
| return text || '문단';
| |
| }
| |
|
| |
| function pvzHeadingId(heading, text, index) {
| |
| var headline = heading.querySelector && heading.querySelector('.mw-headline');
| |
| var anchor = headline || heading;
| |
| var id = anchor.id;
| |
|
| |
| if (!id) {
| |
| id = 'pvz-heading-' + index + '-' + encodeURIComponent(text).replace(/%/g, '');
| |
| anchor.id = id;
| |
| }
| |
|
| |
| return id;
| |
| }
| |
|
| |
|
| |
|
| |
|
| |
| function pvzNormalizeText(text) {
| |
| return (text || '').replace(/\s+/g, ' ').trim();
| |
| }
| |
|
| |
| function pvzLinkKey(link) {
| |
| var href = link && link.getAttribute ? link.getAttribute('href') || '' : '';
| |
| var text = pvzNormalizeText(link ? link.textContent : '');
| |
|
| |
| return href + '|' + text;
| |
| }
| |
|
| |
| function pvzFindExistingLink(config) {
| |
| var links = pvzArray(document.querySelectorAll('a[href]'));
| |
| var idPatterns = config.ids || [];
| |
| var textPatterns = config.text || [];
| |
| var hrefPatterns = config.href || [];
| |
| var i;
| |
| var link;
| |
| var href;
| |
| var text;
| |
| var id;
| |
|
| |
| for (i = 0; i < links.length; i += 1) {
| |
| link = links[i];
| |
| id = link.id || (link.parentNode && link.parentNode.id) || '';
| |
|
| |
| if (id && idPatterns.some(function (pattern) {
| |
| return pattern.test(id);
| |
| })) {
| |
| return link;
| |
| }
| |
| }
| |
|
| |
| for (i = 0; i < links.length; i += 1) {
| |
| link = links[i];
| |
| href = link.getAttribute('href') || '';
| |
|
| |
| if (href && hrefPatterns.some(function (pattern) {
| |
| return pattern.test(href);
| |
| })) {
| |
| return link;
| |
| }
| |
| }
| |
|
| |
| for (i = 0; i < links.length; i += 1) {
| |
| link = links[i];
| |
| text = pvzNormalizeText(link.textContent);
| |
|
| |
| if (text && textPatterns.some(function (pattern) {
| |
| return pattern.test(text);
| |
| })) {
| |
| return link;
| |
| }
| |
| }
| |
|
| |
| return null;
| |
| }
| |
|
| |
| function pvzMakeFallbackUrl(type) {
| |
| var pageName = '';
| |
| var title = '';
| |
|
| |
| if (window.mw && mw.config && typeof mw.config.get === 'function') {
| |
| pageName = mw.config.get('wgPageName') || '';
| |
| title = mw.config.get('wgTitle') || pageName || '';
| |
| }
| |
|
| |
| if (!window.mw || !mw.util || typeof mw.util.getUrl !== 'function') {
| |
| return '';
| |
| }
| |
|
| |
| if (type === 'upload') return mw.util.getUrl('Special:Upload');
| |
| if (type === 'shorturl') return mw.util.getUrl('Special:UrlShortener');
| |
| if (type === 'whatlinkshere') return mw.util.getUrl('Special:WhatLinksHere/' + pageName);
| |
| if (type === 'recentchangeslinked') return mw.util.getUrl('Special:RecentChangesLinked/' + pageName);
| |
| if (type === 'printable') return mw.util.getUrl(pageName, { printable: 'yes' });
| |
| if (type === 'permalink') {
| |
| if (mw.config.get('wgRevisionId')) {
| |
| return mw.util.getUrl(pageName, { oldid: mw.config.get('wgRevisionId') });
| |
| }
| |
| return '';
| |
| }
| |
| if (type === 'info') return mw.util.getUrl(pageName, { action: 'info' });
| |
| if (type === 'history') return mw.util.getUrl(pageName, { action: 'history' });
| |
|
| |
| /* 권한성 문서 도구는 기본 링크가 실제로 존재할 때만 노출한다. */
| |
| if (type === 'move') return '';
| |
| if (type === 'delete') return '';
| |
| if (type === 'protect') return '';
| |
|
| |
| return title ? mw.util.getUrl(title) : '';
| |
| }
| |
|
| |
| function pvzToolEntry(label, type, matchConfig) {
| |
| var found = pvzFindExistingLink(matchConfig || {});
| |
| var href = found ? found.getAttribute('href') : pvzMakeFallbackUrl(type);
| |
|
| |
| if (!href) return null;
| |
|
| |
| return {
| |
| label: label,
| |
| href: href,
| |
| source: found || null
| |
| };
| |
| }
| |
|
| |
| function pvzMakeSpecialUrl(specialName) {
| |
| if (window.mw && mw.util && typeof mw.util.getUrl === 'function') {
| |
| return mw.util.getUrl('Special:' + specialName);
| |
| }
| |
|
| |
| return '/wiki/Special:' + specialName;
| |
| }
| |
|
| |
| function pvzMakePageUrl(pageName) {
| |
| if (window.mw && mw.util && typeof mw.util.getUrl === 'function') {
| |
| return mw.util.getUrl(pageName);
| |
| }
| |
|
| |
| return '/wiki/' + String(pageName || '').replace(/\s/g, '_');
| |
| }
| |
|
| |
| function pvzPageName() {
| |
| if (window.mw && mw.config && typeof mw.config.get === 'function') {
| |
| return mw.config.get('wgPageName') || '';
| |
| }
| |
|
| |
| return '';
| |
| }
| |
|
| |
| function pvzMainPageName() {
| |
| if (window.mw && mw.config && typeof mw.config.get === 'function') {
| |
| return mw.config.get('wgMainPageTitle') || '식물 vs 좀비 위키';
| |
| }
| |
|
| |
| return '식물 vs 좀비 위키';
| |
| }
| |
|
| |
| function pvzToolEntryWithFallback(label, type, matchConfig, fallbackHref) {
| |
| var found = pvzFindExistingLink(matchConfig || {});
| |
| var href = found ? found.getAttribute('href') : fallbackHref;
| |
|
| |
| if (!href) return null;
| |
|
| |
| return {
| |
| label: label,
| |
| href: href,
| |
| source: found || null
| |
| };
| |
| }
| |
|
| |
| function pvzToolEntryFromNativeOnly(label, matchConfig) {
| |
| var found = pvzFindExistingLink(matchConfig || {});
| |
|
| |
| if (!found) return null;
| |
|
| |
| return {
| |
| label: label,
| |
| href: found.getAttribute('href'),
| |
| source: found
| |
| };
| |
| }
| |
|
| |
| function pvzCreateToolsSvgIcon() {
| |
| var span = document.createElement('span');
| |
| var svgNS = 'http://www.w3.org/2000/svg';
| |
| var svg = document.createElementNS(svgNS, 'svg');
| |
| var path = document.createElementNS(svgNS, 'path');
| |
|
| |
| span.className = 'pvz-custom-mobile-tools-icon';
| |
| span.setAttribute('aria-hidden', 'true');
| |
|
| |
| svg.setAttribute('viewBox', '0 0 24 24');
| |
| svg.setAttribute('focusable', 'false');
| |
|
| |
| path.setAttribute('d', 'M21.2 18.6 13.9 11.3c.7-1.9.3-4.1-1.2-5.6-1.6-1.6-4-2-6-1.1l3.5 3.5-2.1 2.1-3.6-3.5c-.8 2-.4 4.4 1.2 6 1.5 1.5 3.7 1.9 5.6 1.2l7.3 7.3c.3.3.8.3 1.1 0l1.5-1.5c.3-.3.3-.8 0-1.1Z');
| |
|
| |
| svg.appendChild(path);
| |
| span.appendChild(svg);
| |
|
| |
| return span;
| |
| }
| |
|
| |
| function pvzCreateToolsToggle(shell) {
| |
| var button = document.createElement('button');
| |
| var text = document.createElement('span');
| |
|
| |
| button.type = 'button';
| |
| button.className = 'pvz-custom-mobile-tools-toggle';
| |
| button.setAttribute('aria-controls', 'pvz-custom-mobile-tools-list');
| |
| button.setAttribute('aria-expanded', 'false');
| |
| button.title = '도구';
| |
|
| |
| text.className = 'pvz-custom-mobile-tools-toggle-text';
| |
| text.textContent = '도구';
| |
|
| |
| button.appendChild(pvzCreateToolsSvgIcon());
| |
| button.appendChild(text);
| |
|
| |
| button.addEventListener('click', function (event) {
| |
| var collapsed;
| |
|
| |
| event.preventDefault();
| |
|
| |
| collapsed = !shell.classList.contains('is-collapsed');
| |
| shell.classList.toggle('is-collapsed', collapsed);
| |
| shell.dataset.pvzManualToggle = '1';
| |
| button.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
| |
| });
| |
|
| |
| return button;
| |
| }
| |
|
| |
| function pvzApplyToolsCompactState(shell) {
| |
| var button;
| |
| var isCompact;
| |
|
| |
| if (!shell) return;
| |
|
| |
| button = shell.querySelector('.pvz-custom-mobile-tools-toggle');
| |
| isCompact = !!(window.matchMedia && window.matchMedia('(max-width: 760px)').matches);
| |
|
| |
| /* 모바일에서도 보조창은 기본적으로 보이게 둔다.
| |
| 사용자가 직접 도구 버튼을 눌렀을 때만 접힌 상태를 유지한다. */
| |
| if (!isCompact || shell.dataset.pvzManualToggle !== '1') {
| |
| shell.classList.remove('is-collapsed');
| |
| }
| |
|
| |
| if (button) {
| |
| button.setAttribute('aria-expanded', shell.classList.contains('is-collapsed') ? 'false' : 'true');
| |
| }
| |
| }
| |
|
| |
| function pvzFindLogoBlockInside(anchor) {
| |
| var candidates;
| |
| var i;
| |
| var node;
| |
|
| |
| if (!anchor || !anchor.querySelectorAll) return null;
| |
|
| |
| candidates = pvzArray(anchor.querySelectorAll(
| |
| '#p-logo, .mw-logo, .mw-wiki-logo, #mw-wiki-logo, ' +
| |
| 'a.mw-wiki-logo, a[title*="식물 vs 좀비 위키"], a[aria-label*="식물 vs 좀비 위키"], ' +
| |
| 'a[href*="식물_vs_좀비_위키"], a[href*="Main_Page"]'
| |
| ));
| |
|
| |
| for (i = 0; i < candidates.length; i += 1) {
| |
| node = candidates[i];
| |
|
| |
| while (node && node.parentNode && node.parentNode !== anchor) {
| |
| if (
| |
| node.matches &&
| |
| node.matches('#p-logo, .mw-logo, .mw-wiki-logo, #mw-wiki-logo, .mw-portlet, nav, header, div, a')
| |
| ) {
| |
| if (node.parentNode === anchor) return node;
| |
| }
| |
| node = node.parentNode;
| |
| }
| |
|
| |
| if (node && node.parentNode === anchor) return node;
| |
| }
| |
|
| |
| return null;
| |
| }
| |
|
| |
| function pvzInsertToolsBelowLogo(anchor, shell) {
| |
| var logoBlock;
| |
|
| |
| if (!anchor || !shell) return;
| |
|
| |
| logoBlock = pvzFindLogoBlockInside(anchor);
| |
|
| |
| if (logoBlock && logoBlock.parentNode === anchor) {
| |
| if (logoBlock.nextSibling) {
| |
| anchor.insertBefore(shell, logoBlock.nextSibling);
| |
| } else {
| |
| anchor.appendChild(shell);
| |
| }
| |
| return;
| |
| }
| |
|
| |
| anchor.appendChild(shell);
| |
| }
| |
|
| |
|
| |
| function pvzCreateToolsSection(title, entries, sectionKey) {
| |
| var section;
| |
| var heading;
| |
| var list;
| |
|
| |
| entries = (entries || []).filter(Boolean);
| |
| if (!entries.length) return null;
| |
|
| |
| section = document.createElement('section');
| |
| section.className = 'pvz-custom-mobile-tools-section';
| |
| if (sectionKey) section.setAttribute('data-pvz-tools-section', sectionKey);
| |
|
| |
| heading = document.createElement('h2');
| |
| heading.className = 'pvz-custom-mobile-tools-heading';
| |
| heading.textContent = title;
| |
|
| |
| list = document.createElement('ul');
| |
| list.className = 'pvz-custom-mobile-tools-list';
| |
|
| |
| entries.forEach(function (entry) {
| |
| var item = document.createElement('li');
| |
| var link = document.createElement('a');
| |
| var text;
| |
|
| |
| item.className = 'pvz-custom-mobile-tools-item';
| |
|
| |
| link.href = entry.href || '#';
| |
| if (entry.className) link.className = entry.className;
| |
| if (entry.themeMode) {
| |
| link.setAttribute('data-pvz-timeless-theme-mode', entry.themeMode);
| |
| link.setAttribute('role', 'button');
| |
| }
| |
|
| |
| if (entry.icon) {
| |
| link.appendChild(pvzCreateInlineSvgIcon(entry.icon));
| |
| text = document.createElement('span');
| |
| text.textContent = entry.label;
| |
| link.appendChild(text);
| |
| } else {
| |
| link.textContent = entry.label;
| |
| }
| |
|
| |
| if (entry.source) {
| |
| if (entry.source.title) link.title = entry.source.title;
| |
| if (entry.source.target) link.target = entry.source.target;
| |
| }
| |
|
| |
| item.appendChild(link);
| |
| list.appendChild(item);
| |
| });
| |
|
| |
| section.appendChild(heading);
| |
| section.appendChild(list);
| |
|
| |
| return section;
| |
| }
| |
|
| |
| function pvzFindTimelessToolsAnchor() {
| |
| var selectors = [
| |
| '#mw-site-navigation',
| |
| '#mw-related-navigation',
| |
| '#mw-content',
| |
| '#content',
| |
| '#bodyContent',
| |
| '.mw-body'
| |
| ];
| |
| var i;
| |
| var node;
| |
|
| |
| for (i = 0; i < selectors.length; i += 1) {
| |
| node = document.querySelector('body.skin-timeless ' + selectors[i]) ||
| |
| document.querySelector(selectors[i]);
| |
|
| |
| if (node) return node;
| |
| }
| |
|
| |
| return document.body;
| |
| }
| |
|
| |
| function pvzHideNativeTimelessToolPortlets() {
| |
| var candidates = pvzArray(document.querySelectorAll(
| |
| 'body.skin-timeless #mw-site-navigation .mw-portlet, ' +
| |
| 'body.skin-timeless #mw-related-navigation .mw-portlet, ' +
| |
| 'body.skin-timeless #mw-site-navigation [id^="p-"], ' +
| |
| 'body.skin-timeless #mw-related-navigation [id^="p-"], ' +
| |
| 'body.skin-timeless nav[id^="p-"], ' +
| |
| 'body.skin-timeless div[id^="p-"], ' +
| |
| 'body.skin-timeless #catlinks, ' +
| |
| 'body.skin-timeless .catlinks'
| |
| ));
| |
|
| |
| candidates.forEach(function (node) {
| |
| var text;
| |
| var id;
| |
|
| |
| if (!node || !node.classList) return;
| |
| if (node.classList.contains('pvz-custom-mobile-tools') || node.classList.contains('pvz-custom-mobile-tools-shell') || node.classList.contains('pvz-timeless-wide-rail')) return;
| |
| if (node.id === 'p-logo') return;
| |
| if (node.closest && node.closest('.pvz-custom-mobile-tools-shell')) return;
| |
|
| |
| text = pvzNormalizeText(node.textContent);
| |
| id = node.id || '';
| |
|
| |
| if (
| |
| /p-(navigation|namespaces|views|variants|personal|tb|wiki-tools|wikitools|page-tools|actions|cactions|more|tb-page|tb-more|managewiki|management|admin)/i.test(id) ||
| |
| /둘러보기|Navigation|관리|Manage|위키 도구|Wiki tools|Site tools|문서 도구|Page tools|Document tools|더 보기|More/.test(text) ||
| |
| /대문|최근 바뀜|임의 문서|미디어위키 도움말|특수 문서 목록|Main page|Recent changes|Random page|Help|Special pages/.test(text) ||
| |
| /이 위키의 핵심 설정|이 위키의 확장 기능|이 위키의 이름공간|이 위키의 권한|이 위키의 추가 설정|이 위키의 백업/.test(text) ||
| |
| (/파일 올리기|Upload file/.test(text) && /축약된 URL|Short URL|Get shortened URL/.test(text)) ||
| |
| (/삭제|Delete/.test(text) && /이동|Move/.test(text) && /보호|Protect/.test(text)) ||
| |
| (/여기를 가리키는 문서|What links here/.test(text) && /문서 정보|Page information/.test(text))
| |
| ) {
| |
| node.classList.add('pvz-native-tools-hidden');
| |
| }
| |
| });
| |
|
| |
| pvzArray(document.querySelectorAll(
| |
| 'body.skin-timeless #mw-site-navigation, ' +
| |
| 'body.skin-timeless #mw-related-navigation'
| |
| )).forEach(function (container) {
| |
| var hasVisibleNonCustom = false;
| |
|
| |
| pvzArray(container.children).forEach(function (child) {
| |
| if (!child || !child.classList) return;
| |
| if (child.classList.contains('pvz-custom-mobile-tools-shell') || child.classList.contains('pvz-timeless-wide-rail')) return;
| |
| if (child.id === 'p-logo' || child.matches('.mw-logo, .mw-wiki-logo, #mw-wiki-logo, header')) return;
| |
|
| |
| if (!child.classList.contains('pvz-native-tools-hidden')) {
| |
| if ((child.textContent || '').replace(/\s+/g, '').length > 0) {
| |
| hasVisibleNonCustom = true;
| |
| }
| |
| }
| |
| });
| |
|
| |
| if (!hasVisibleNonCustom) {
| |
| container.classList.add('pvz-native-tools-container-cleaned');
| |
| }
| |
| });
| |
| }
| |
|
| |
|
| |
|
| |
| function pvzRemoveTimelessColorStripes() {
| |
| pvzArray(document.querySelectorAll(
| |
| 'body.skin-timeless #mw-header-hack, ' +
| |
| 'body.skin-timeless .color-bar, ' +
| |
| 'body.skin-timeless .header-color-bar, ' +
| |
| 'body.skin-timeless .mw-header-color-bar, ' +
| |
| 'body.skin-timeless #mw-header-hack .color-left, ' +
| |
| 'body.skin-timeless #mw-header-hack .color-middle, ' +
| |
| 'body.skin-timeless #mw-header-hack .color-right, ' +
| |
| 'body.skin-timeless #mw-header-container .color-left, ' +
| |
| 'body.skin-timeless #mw-header-container .color-middle, ' +
| |
| 'body.skin-timeless #mw-header-container .color-right, ' +
| |
| 'body.skin-timeless #mw-content-container .color-left, ' +
| |
| 'body.skin-timeless #mw-content-container .color-middle, ' +
| |
| 'body.skin-timeless #mw-content-container .color-right, ' +
| |
| 'body.skin-timeless [class~="color-left"], ' +
| |
| 'body.skin-timeless [class~="color-middle"], ' +
| |
| 'body.skin-timeless [class~="color-right"], ' +
| |
| 'body.skin-timeless [class*="color-left"], ' +
| |
| 'body.skin-timeless [class*="color-middle"], ' +
| |
| 'body.skin-timeless [class*="color-right"]'
| |
| )).forEach(function (node) {
| |
| if (node && node.parentNode) node.parentNode.removeChild(node);
| |
| });
| |
| }
| |
|
| |
| function pvzCategoryEntriesForCustomBox() {
| |
| var entries = [];
| |
| var seen = {};
| |
|
| |
| pvzArray(document.querySelectorAll('#catlinks a[href], .catlinks a[href]')).forEach(function (link) {
| |
| var label = pvzNormalizeText(link.textContent);
| |
| var href = link.getAttribute('href');
| |
| var key;
| |
|
| |
| if (!label || !href) return;
| |
| if (/^(분류|Categories?|Category)$/i.test(label)) return;
| |
|
| |
| key = href + '|' + label;
| |
| if (seen[key]) return;
| |
|
| |
| seen[key] = true;
| |
| entries.push({
| |
| label: label,
| |
| href: href,
| |
| source: link
| |
| });
| |
| });
| |
|
| |
| return entries;
| |
| }
| |
|
| |
|
| |
| /* =========================
| |
| 커스텀 검색바 시작
| |
| - 기존 Timeless/MediaWiki 헤더 검색창 대신 독립 검색 UI를 사용한다.
| |
| - 실제 검색은 Special:Search로 제출한다.
| |
| - 검색 제안은 MediaWiki API opensearch를 사용한다.
| |
| - 1280px 이상에서는 실제 #mw-content의 위치와 폭을 그대로 따라간다.
| |
| ========================= */
| |
|
| |
| var pvzCustomSearchInputTimer = null;
| |
| var pvzCustomSearchRequestId = 0;
| |
| var pvzCustomSearchActiveIndex = -1;
| |
| var pvzCustomSearchComposing = false;
| |
| var pvzCustomSearchEventsBound = false;
| |
| var pvzCustomSearchViewportBound = false;
| |
| var pvzCustomSearchResizeObserver = null;
| |
| var pvzCustomSearchWidthFrame = 0;
| |
| var pvzSearchThumbnailCache = Object.create(null);
| |
|
| |
| function pvzCreateSearchSvgIcon() {
| |
| var span = document.createElement('span');
| |
| var svgNS = 'http://www.w3.org/2000/svg';
| |
| var svg = document.createElementNS(svgNS, 'svg');
| |
| var circle = document.createElementNS(svgNS, 'circle');
| |
| var line = document.createElementNS(svgNS, 'path');
| |
|
| |
| span.className = 'pvz-custom-search-icon';
| |
| span.setAttribute('aria-hidden', 'true');
| |
|
| |
| svg.setAttribute('viewBox', '0 0 24 24');
| |
| svg.setAttribute('focusable', 'false');
| |
|
| |
| circle.setAttribute('cx', '10.8');
| |
| circle.setAttribute('cy', '10.8');
| |
| circle.setAttribute('r', '6.6');
| |
| circle.setAttribute('fill', 'none');
| |
| circle.setAttribute('stroke', 'currentColor');
| |
| circle.setAttribute('stroke-width', '2.2');
| |
|
| |
| line.setAttribute('d', 'M15.7 15.7 21 21');
| |
| line.setAttribute('fill', 'none');
| |
| line.setAttribute('stroke', 'currentColor');
| |
| line.setAttribute('stroke-width', '2.2');
| |
| line.setAttribute('stroke-linecap', 'round');
| |
|
| |
| svg.appendChild(circle);
| |
| svg.appendChild(line);
| |
| span.appendChild(svg);
| |
|
| |
| return span;
| |
| }
| |
|
| |
| function pvzCustomSearchElements() {
| |
| return {
| |
| root: document.getElementById('pvz-custom-searchbox'),
| |
| form: document.getElementById('pvz-custom-searchbox-form'),
| |
| input: document.getElementById('pvz-custom-searchbox-input'),
| |
| suggestions: document.getElementById('pvz-custom-searchbox-suggestions')
| |
| };
| |
| }
| |
|
| |
| function pvzClearCustomSearchSuggestions() {
| |
| var elements = pvzCustomSearchElements();
| |
|
| |
| pvzCustomSearchActiveIndex = -1;
| |
|
| |
| if (elements.suggestions) {
| |
| elements.suggestions.textContent = '';
| |
| elements.suggestions.hidden = true;
| |
| elements.suggestions.classList.remove('is-open');
| |
| }
| |
|
| |
| if (elements.input) {
| |
| elements.input.setAttribute('aria-expanded', 'false');
| |
| elements.input.removeAttribute('aria-activedescendant');
| |
| }
| |
|
| |
| if (document.body) {
| |
| document.body.classList.remove('pvz-custom-search-suggestions-open');
| |
| }
| |
| }
| |
|
| |
| function pvzCustomSearchSuggestionLinks() {
| |
| var elements = pvzCustomSearchElements();
| |
|
| |
| if (!elements.suggestions) return [];
| |
|
| |
| return pvzArray(
| |
| elements.suggestions.querySelectorAll('.pvz-custom-search-suggestion')
| |
| );
| |
| }
| |
|
| |
| function pvzSetCustomSearchActiveIndex(index) {
| |
| var elements = pvzCustomSearchElements();
| |
| var links = pvzCustomSearchSuggestionLinks();
| |
|
| |
| if (!links.length || !elements.input) {
| |
| pvzCustomSearchActiveIndex = -1;
| |
| return;
| |
| }
| |
|
| |
| if (index < 0) index = links.length - 1;
| |
| if (index >= links.length) index = 0;
| |
|
| |
| pvzCustomSearchActiveIndex = index;
| |
|
| |
| links.forEach(function (link, currentIndex) {
| |
| var active = currentIndex === index;
| |
|
| |
| link.classList.toggle('is-active', active);
| |
| link.setAttribute('aria-selected', active ? 'true' : 'false');
| |
| });
| |
|
| |
| elements.input.setAttribute(
| |
| 'aria-activedescendant',
| |
| links[index].id
| |
| );
| |
|
| |
| if (typeof links[index].scrollIntoView === 'function') {
| |
| links[index].scrollIntoView({
| |
| block: 'nearest'
| |
| });
| |
| }
| |
| }
| |
|
| |
| function pvzLoadSearchThumbnail(title) {
| |
| if (!title) return Promise.resolve('');
| |
|
| |
| if (Object.prototype.hasOwnProperty.call(pvzSearchThumbnailCache, title)) {
| |
| return pvzSearchThumbnailCache[title];
| |
| }
| |
|
| |
| if (!window.mw || !mw.loader) {
| |
| return Promise.resolve('');
| |
| }
| |
|
| |
| pvzSearchThumbnailCache[title] = mw.loader.using('mediawiki.api').then(function () {
| |
| var api = new mw.Api();
| |
|
| |
| return api.get({
| |
| action: 'query',
| |
| prop: 'pageimages',
| |
| piprop: 'thumbnail',
| |
| pithumbsize: 80,
| |
| titles: title,
| |
| formatversion: 2
| |
| });
| |
| }).then(function (data) {
| |
| var pages = data && data.query && data.query.pages;
| |
| var page = pages && pages[0];
| |
|
| |
| return page && page.thumbnail && page.thumbnail.source ?
| |
| page.thumbnail.source :
| |
| '';
| |
| }).catch(function () {
| |
| return '';
| |
| });
| |
|
| |
| return pvzSearchThumbnailCache[title];
| |
| }
| |
|
| |
| function pvzAppendHighlightedSearchText(container, text, query) {
| |
| var source = String(text || '');
| |
| var needle = String(query || '');
| |
| var sourceLower;
| |
| var needleLower;
| |
| var cursor = 0;
| |
| var index;
| |
| var strong;
| |
|
| |
| container.textContent = '';
| |
|
| |
| if (!needle) {
| |
| container.textContent = source;
| |
| return;
| |
| }
| |
|
| |
| sourceLower = source.toLocaleLowerCase();
| |
| needleLower = needle.toLocaleLowerCase();
| |
|
| |
| while (cursor < source.length) {
| |
| index = sourceLower.indexOf(needleLower, cursor);
| |
|
| |
| if (index === -1) {
| |
| container.appendChild(
| |
| document.createTextNode(source.slice(cursor))
| |
| );
| |
| break;
| |
| }
| |
|
| |
| if (index > cursor) {
| |
| container.appendChild(
| |
| document.createTextNode(source.slice(cursor, index))
| |
| );
| |
| }
| |
|
| |
| strong = document.createElement('strong');
| |
| strong.className = 'pvz-custom-search-suggestion-match';
| |
| strong.textContent = source.slice(index, index + needle.length);
| |
| container.appendChild(strong);
| |
|
| |
| cursor = index + needle.length;
| |
| }
| |
|
| |
| if (!source.length) {
| |
| container.textContent = '';
| |
| }
| |
| }
| |
|
| |
| function pvzRenderCustomSearchSuggestions(query, data, requestId) {
| |
| var elements = pvzCustomSearchElements();
| |
| var titles;
| |
| var urls;
| |
|
| |
| if (
| |
| requestId !== pvzCustomSearchRequestId ||
| |
| !elements.suggestions ||
| |
| !elements.input
| |
| ) {
| |
| return;
| |
| }
| |
|
| |
| if (pvzNormalizeText(elements.input.value) !== query) return;
| |
|
| |
| titles = Array.isArray(data && data[1]) ? data[1] : [];
| |
| urls = Array.isArray(data && data[3]) ? data[3] : [];
| |
|
| |
| elements.suggestions.textContent = '';
| |
| pvzCustomSearchActiveIndex = -1;
| |
|
| |
| if (!titles.length) {
| |
| pvzClearCustomSearchSuggestions();
| |
| return;
| |
| }
| |
|
| |
| titles.slice(0, 7).forEach(function (title, index) {
| |
| var link = document.createElement('a');
| |
| var thumb = document.createElement('span');
| |
| var label = document.createElement('span');
| |
| var href = urls[index] || pvzMakePageUrl(title);
| |
|
| |
| link.id = 'pvz-custom-search-option-' + index;
| |
| link.className = 'pvz-custom-search-suggestion';
| |
| link.href = href;
| |
| link.setAttribute('role', 'option');
| |
| link.setAttribute('aria-selected', 'false');
| |
|
| |
| thumb.className = 'pvz-custom-search-suggestion-thumb';
| |
| thumb.setAttribute('aria-hidden', 'true');
| |
|
| |
| label.className = 'pvz-custom-search-suggestion-label';
| |
| pvzAppendHighlightedSearchText(label, title, query);
| |
|
| |
| link.appendChild(thumb);
| |
| link.appendChild(label);
| |
| elements.suggestions.appendChild(link);
| |
|
| |
| pvzLoadSearchThumbnail(title).then(function (source) {
| |
| var image;
| |
|
| |
| if (!source || !thumb.isConnected) return;
| |
|
| |
| image = document.createElement('img');
| |
| image.src = source;
| |
| image.alt = '';
| |
| image.loading = 'lazy';
| |
| image.decoding = 'async';
| |
|
| |
| thumb.appendChild(image);
| |
| thumb.classList.add('has-image');
| |
| });
| |
| });
| |
|
| |
| elements.suggestions.hidden = false;
| |
| elements.suggestions.classList.add('is-open');
| |
| elements.input.setAttribute('aria-expanded', 'true');
| |
|
| |
| if (document.body) {
| |
| document.body.classList.add('pvz-custom-search-suggestions-open');
| |
| }
| |
| }
| |
|
| |
| function pvzRequestCustomSearchSuggestions(query) {
| |
| var requestId;
| |
|
| |
| query = pvzNormalizeText(query);
| |
|
| |
| if (query.length < 1) {
| |
| pvzClearCustomSearchSuggestions();
| |
| return;
| |
| }
| |
|
| |
| if (!window.mw || !mw.loader) {
| |
| pvzClearCustomSearchSuggestions();
| |
| return;
| |
| }
| |
|
| |
| requestId = ++pvzCustomSearchRequestId;
| |
|
| |
| mw.loader.using('mediawiki.api').then(function () {
| |
| var api = new mw.Api();
| |
|
| |
| return api.get({
| |
| action: 'opensearch',
| |
| search: query,
| |
| limit: 7,
| |
| namespace: 0,
| |
| redirects: 'resolve',
| |
| format: 'json'
| |
| });
| |
| }).then(function (data) {
| |
| pvzRenderCustomSearchSuggestions(query, data, requestId);
| |
| }).catch(function () {
| |
| if (requestId === pvzCustomSearchRequestId) {
| |
| pvzClearCustomSearchSuggestions();
| |
| }
| |
| });
| |
| }
| |
|
| |
| function pvzQueueCustomSearchSuggestions() {
| |
| var elements = pvzCustomSearchElements();
| |
|
| |
| window.clearTimeout(pvzCustomSearchInputTimer);
| |
|
| |
| if (!elements.input || pvzCustomSearchComposing) return;
| |
|
| |
| pvzCustomSearchInputTimer = window.setTimeout(function () {
| |
| pvzRequestCustomSearchSuggestions(elements.input.value);
| |
| }, 170);
| |
| }
| |
|
| |
| function pvzFindCustomSearchMainContent() {
| |
| return (
| |
| document.querySelector('body.skin-timeless #mw-content') ||
| |
| document.querySelector('body.skin-timeless #content') ||
| |
| document.querySelector('body.skin-timeless .mw-body')
| |
| );
| |
| }
| |
|
| |
| function pvzSyncCustomSearchWidth() {
| |
| var elements = pvzCustomSearchElements();
| |
| var header = document.getElementById('mw-header-container');
| |
| var content;
| |
| var headerRect;
| |
| var contentRect;
| |
| var layoutViewportWidth = Math.round(
| |
| document.documentElement.clientWidth ||
| |
| window.innerWidth ||
| |
| 0
| |
| );
| |
|
| |
| if (!elements.root || !header) return;
| |
|
| |
| elements.root.classList.remove('is-main-width');
| |
| elements.root.style.removeProperty('--pvz-custom-search-left');
| |
| elements.root.style.removeProperty('--pvz-custom-search-width');
| |
|
| |
| if (layoutViewportWidth < 1280) return;
| |
|
| |
| content = pvzFindCustomSearchMainContent();
| |
| if (!content || typeof content.getBoundingClientRect !== 'function') return;
| |
|
| |
| headerRect = header.getBoundingClientRect();
| |
| contentRect = content.getBoundingClientRect();
| |
|
| |
| if (contentRect.width <= 0) return;
| |
|
| |
| elements.root.style.setProperty(
| |
| '--pvz-custom-search-left',
| |
| Math.round(contentRect.left - headerRect.left) + 'px'
| |
| );
| |
|
| |
| elements.root.style.setProperty(
| |
| '--pvz-custom-search-width',
| |
| Math.round(contentRect.width) + 'px'
| |
| );
| |
|
| |
| elements.root.classList.add('is-main-width');
| |
| }
| |
|
| |
| function pvzQueueCustomSearchWidthSync() {
| |
| window.cancelAnimationFrame(pvzCustomSearchWidthFrame);
| |
|
| |
| pvzCustomSearchWidthFrame = window.requestAnimationFrame(function () {
| |
| pvzSyncCustomSearchWidth();
| |
| });
| |
| }
| |
|
| |
| function pvzBindCustomSearchViewportSync() {
| |
| var content;
| |
|
| |
| if (pvzCustomSearchViewportBound) {
| |
| pvzQueueCustomSearchWidthSync();
| |
| return;
| |
| }
| |
|
| |
| pvzCustomSearchViewportBound = true;
| |
|
| |
| window.addEventListener('resize', pvzQueueCustomSearchWidthSync, {
| |
| passive: true
| |
| });
| |
|
| |
| if (
| |
| window.screen &&
| |
| window.screen.orientation &&
| |
| typeof window.screen.orientation.addEventListener === 'function'
| |
| ) {
| |
| window.screen.orientation.addEventListener(
| |
| 'change',
| |
| pvzQueueCustomSearchWidthSync
| |
| );
| |
| }
| |
|
| |
| if (
| |
| window.visualViewport &&
| |
| typeof window.visualViewport.addEventListener === 'function'
| |
| ) {
| |
| window.visualViewport.addEventListener(
| |
| 'resize',
| |
| pvzQueueCustomSearchWidthSync,
| |
| { passive: true }
| |
| );
| |
| }
| |
|
| |
| content = pvzFindCustomSearchMainContent();
| |
|
| |
| if (window.ResizeObserver && content) {
| |
| pvzCustomSearchResizeObserver = new ResizeObserver(function () {
| |
| pvzQueueCustomSearchWidthSync();
| |
| });
| |
|
| |
| pvzCustomSearchResizeObserver.observe(content);
| |
| }
| |
| }
| |
|
| |
| function pvzBindCustomSearchEvents() {
| |
| var elements = pvzCustomSearchElements();
| |
|
| |
| if (
| |
| pvzCustomSearchEventsBound ||
| |
| !elements.form ||
| |
| !elements.input ||
| |
| !elements.suggestions
| |
| ) {
| |
| return;
| |
| }
| |
|
| |
| pvzCustomSearchEventsBound = true;
| |
|
| |
| elements.input.addEventListener('compositionstart', function () {
| |
| pvzCustomSearchComposing = true;
| |
| });
| |
|
| |
| elements.input.addEventListener('compositionend', function () {
| |
| pvzCustomSearchComposing = false;
| |
| pvzQueueCustomSearchSuggestions();
| |
| });
| |
|
| |
| elements.input.addEventListener('input', function () {
| |
| if (!pvzCustomSearchComposing) {
| |
| pvzQueueCustomSearchSuggestions();
| |
| }
| |
| });
| |
|
| |
| elements.input.addEventListener('focus', function () {
| |
| if (pvzNormalizeText(elements.input.value)) {
| |
| pvzQueueCustomSearchSuggestions();
| |
| }
| |
| });
| |
|
| |
| elements.input.addEventListener('keydown', function (event) {
| |
| var links = pvzCustomSearchSuggestionLinks();
| |
|
| |
| if (event.key === 'ArrowDown' && links.length) {
| |
| event.preventDefault();
| |
| pvzSetCustomSearchActiveIndex(pvzCustomSearchActiveIndex + 1);
| |
| return;
| |
| }
| |
|
| |
| if (event.key === 'ArrowUp' && links.length) {
| |
| event.preventDefault();
| |
| pvzSetCustomSearchActiveIndex(pvzCustomSearchActiveIndex - 1);
| |
| return;
| |
| }
| |
|
| |
| if (
| |
| event.key === 'Enter' &&
| |
| pvzCustomSearchActiveIndex >= 0 &&
| |
| links[pvzCustomSearchActiveIndex]
| |
| ) {
| |
| event.preventDefault();
| |
| window.location.href = links[pvzCustomSearchActiveIndex].href;
| |
| return;
| |
| }
| |
|
| |
| if (event.key === 'Escape') {
| |
| pvzClearCustomSearchSuggestions();
| |
| elements.input.blur();
| |
| }
| |
| });
| |
|
| |
| elements.form.addEventListener('submit', function (event) {
| |
| if (!pvzNormalizeText(elements.input.value)) {
| |
| event.preventDefault();
| |
| elements.input.focus();
| |
| }
| |
| });
| |
|
| |
| elements.suggestions.addEventListener('pointerdown', function (event) {
| |
| var link = event.target && event.target.closest ?
| |
| event.target.closest('.pvz-custom-search-suggestion') :
| |
| null;
| |
|
| |
| if (!link) return;
| |
|
| |
| link.classList.add('is-pressed');
| |
|
| |
| window.setTimeout(function () {
| |
| if (link.classList) link.classList.remove('is-pressed');
| |
| }, 140);
| |
| });
| |
|
| |
| document.addEventListener('pointerdown', function (event) {
| |
| var root = document.getElementById('pvz-custom-searchbox');
| |
|
| |
| if (!root || root.contains(event.target)) return;
| |
|
| |
| pvzClearCustomSearchSuggestions();
| |
| }, { passive: true });
| |
| }
| |
|
| |
| function pvzBuildCustomSearch() {
| |
| var header = document.getElementById('mw-header-container');
| |
| var existing = document.getElementById('pvz-custom-searchbox');
| |
| var root;
| |
| var form;
| |
| var field;
| |
| var input;
| |
| var button;
| |
| var suggestions;
| |
|
| |
| if (!header) return null;
| |
|
| |
| if (existing) {
| |
| pvzSyncCustomSearchWidth();
| |
| return existing;
| |
| }
| |
|
| |
| root = document.createElement('div');
| |
| root.id = 'pvz-custom-searchbox';
| |
| root.className = 'pvz-custom-searchbox';
| |
| root.setAttribute('role', 'search');
| |
|
| |
| form = document.createElement('form');
| |
| form.id = 'pvz-custom-searchbox-form';
| |
| form.className = 'pvz-custom-searchbox-form';
| |
| form.method = 'get';
| |
| form.action = pvzMakeSpecialUrl('Search');
| |
|
| |
| field = document.createElement('div');
| |
| field.className = 'pvz-custom-searchbox-field';
| |
|
| |
| input = document.createElement('input');
| |
| input.id = 'pvz-custom-searchbox-input';
| |
| input.className = 'pvz-custom-searchbox-input';
| |
| input.type = 'search';
| |
| input.name = 'search';
| |
| input.placeholder = '위키에서 검색하세요';
| |
| input.autocomplete = 'off';
| |
| input.spellcheck = false;
| |
| input.setAttribute('autocapitalize', 'off');
| |
| input.setAttribute('enterkeyhint', 'search');
| |
| input.setAttribute('aria-label', '위키에서 검색하세요');
| |
| input.setAttribute('aria-autocomplete', 'list');
| |
| input.setAttribute('aria-controls', 'pvz-custom-searchbox-suggestions');
| |
| input.setAttribute('aria-expanded', 'false');
| |
|
| |
| button = document.createElement('button');
| |
| button.className = 'pvz-custom-searchbox-submit';
| |
| button.type = 'submit';
| |
| button.setAttribute('aria-label', '검색');
| |
| button.appendChild(pvzCreateSearchSvgIcon());
| |
|
| |
| suggestions = document.createElement('div');
| |
| suggestions.id = 'pvz-custom-searchbox-suggestions';
| |
| suggestions.className = 'pvz-custom-searchbox-suggestions';
| |
| suggestions.setAttribute('role', 'listbox');
| |
| suggestions.setAttribute('aria-label', '검색 제안');
| |
| suggestions.hidden = true;
| |
|
| |
| field.appendChild(input);
| |
| field.appendChild(button);
| |
| form.appendChild(field);
| |
| root.appendChild(form);
| |
| root.appendChild(suggestions);
| |
| header.appendChild(root);
| |
|
| |
| if (document.body) {
| |
| document.body.classList.add('pvz-custom-search-ready');
| |
| }
| |
|
| |
| pvzBindCustomSearchEvents();
| |
| pvzBindCustomSearchViewportSync();
| |
| pvzQueueCustomSearchWidthSync();
| |
|
| |
| window.setTimeout(pvzQueueCustomSearchWidthSync, 120);
| |
| window.setTimeout(pvzQueueCustomSearchWidthSync, 420);
| |
|
| |
| return root;
| |
| }
| |
|
| |
| /* 커스텀 검색바 끝 */
| |
|
| |
|
| |
| function pvzCreateInlineSvgIcon(kind) {
| |
| var span = document.createElement('span');
| |
| var svgNS = 'http://www.w3.org/2000/svg';
| |
| var svg = document.createElementNS(svgNS, 'svg');
| |
| var path = document.createElementNS(svgNS, 'path');
| |
| var paths = {
| |
| menu: 'M3 6h18v2.4H3V6Zm0 5h18v2.4H3V11Zm0 5h18v2.4H3V16Z',
| |
| tools: 'M21.2 18.6 13.9 11.3c.7-1.9.3-4.1-1.2-5.6-1.6-1.6-4-2-6-1.1l3.5 3.5-2.1 2.1-3.6-3.5c-.8 2-.4 4.4 1.2 6 1.5 1.5 3.7 1.9 5.6 1.2l7.3 7.3c.3.3.8.3 1.1 0l1.5-1.5c.3-.3.3-.8 0-1.1Z',
| |
| document: 'M6 2h9l5 5v15H6V2Zm8 1.8V8h4.2L14 3.8ZM8.5 12h7v1.8h-7V12Zm0 4h7v1.8h-7V16Z',
| |
| view: 'M6 2h9l5 5v15H6V2Zm8 1.8V8h4.2L14 3.8ZM8.5 12h7v1.8h-7V12Zm0 4h7v1.8h-7V16Z',
| |
| talk: 'M4 4h16v12H9.2L4 20V4Zm3 3v2h10V7H7Zm0 4v2h7v-2H7Z',
| |
| edit: 'M4 17.2V21h3.8L18.9 9.9l-3.8-3.8L4 17.2Zm16.2-9.6-1.8 1.8-3.8-3.8 1.8-1.8a1.2 1.2 0 0 1 1.7 0l2.1 2.1a1.2 1.2 0 0 1 0 1.7Z',
| |
| history: 'M12 4a8 8 0 1 1-7.4 5H2l4-4 4 4H6.8A5.9 5.9 0 1 0 12 6V4Zm1 4v4l3 2-1.1 1.7-3.4-2.2A1 1 0 0 1 11 12V8h2Z',
| |
| watch: 'm12 2 2.9 6.2 6.6 1-4.8 4.7 1.2 6.7-5.9-3.2-5.9 3.2 1.2-6.7-4.8-4.8 6.6-.9L12 2Z',
| |
| more: 'M6 10a2 2 0 1 1 0 4 2 2 0 0 1 0-4Zm6 0a2 2 0 1 1 0 4 2 2 0 0 1 0-4Zm6 0a2 2 0 1 1 0 4 2 2 0 0 1 0-4Z',
| |
| alerts: 'M12 2a5 5 0 0 0-5 5v2.1c0 1.1-.4 2.2-1.1 3L4.3 14a1 1 0 0 0 .7 1.7h14a1 1 0 0 0 .7-1.7l-1.6-1.9A4.8 4.8 0 0 1 17 9.1V7a5 5 0 0 0-5-5Zm0 20a3 3 0 0 0 2.8-2H9.2A3 3 0 0 0 12 22Z',
| |
| account: 'M12 12a5 5 0 1 0 0-10 5 5 0 0 0 0 10Zm0 2c-5.4 0-9 2.7-9 6v1h18v-1c0-3.3-3.6-6-9-6Z',
| |
| sun: 'M12 4V1h2v3h-2Zm0 19v-3h2v3h-2ZM4.2 5.6 2.1 3.5 3.5 2.1l2.1 2.1-1.4 1.4Zm16.3 16.3-2.1-2.1 1.4-1.4 2.1 2.1-1.4 1.4ZM1 13v-2h3v2H1Zm19 0v-2h3v2h-3ZM3.5 21.9l-1.4-1.4 2.1-2.1 1.4 1.4-2.1 2.1ZM19.8 5.6l-1.4-1.4 2.1-2.1 1.4 1.4-2.1 2.1ZM13 7a5 5 0 1 1 0 10 5 5 0 0 1 0-10Z',
| |
| moon: 'M21 14.8A8.5 8.5 0 0 1 9.2 3a7 7 0 1 0 8.8 8.8A8.4 8.4 0 0 1 21 14.8Z'
| |
| };
| |
|
| |
| span.className = 'pvz-timeless-action-icon pvz-timeless-action-icon-' + kind;
| |
| span.setAttribute('aria-hidden', 'true');
| |
| svg.setAttribute('viewBox', '0 0 24 24');
| |
| svg.setAttribute('focusable', 'false');
| |
| path.setAttribute('d', paths[kind] || paths.menu);
| |
| svg.appendChild(path);
| |
| span.appendChild(svg);
| |
| return span;
| |
| }
| |
|
| |
| function pvzClampNumber(value, min, max) {
| |
| if (max < min) return min;
| |
| return Math.max(min, Math.min(value, max));
| |
| }
| |
|
| |
| function pvzFindTimelessPanelButton(name) {
| |
| var selector = '.pvz-timeless-action-button[data-pvz-timeless-panel="' + name + '"], ' +
| |
| '.pvz-timeless-page-action[data-pvz-timeless-panel="' + name + '"]';
| |
| var buttons = pvzArray(document.querySelectorAll(selector));
| |
| var i;
| |
| var rect;
| |
|
| |
| for (i = 0; i < buttons.length; i += 1) {
| |
| if (!buttons[i] || typeof buttons[i].getBoundingClientRect !== 'function') continue;
| |
| rect = buttons[i].getBoundingClientRect();
| |
| if (rect.width > 0 && rect.height > 0) return buttons[i];
| |
| }
| |
|
| |
| return buttons[0] || null;
| |
| }
| |
|
| |
| function pvzGetTimelessViewport() {
| |
| var vv = window.visualViewport;
| |
| var width = window.innerWidth || document.documentElement.clientWidth || 0;
| |
| var height = window.innerHeight || document.documentElement.clientHeight || 0;
| |
|
| |
| if (!vv || typeof vv.width !== 'number' || typeof vv.height !== 'number') {
| |
| return {
| |
| left: 0,
| |
| top: 0,
| |
| width: width,
| |
| height: height,
| |
| centerX: width / 2
| |
| };
| |
| }
| |
|
| |
| return {
| |
| left: typeof vv.offsetLeft === 'number' ? vv.offsetLeft : 0,
| |
| top: typeof vv.offsetTop === 'number' ? vv.offsetTop : 0,
| |
| width: vv.width || width,
| |
| height: vv.height || height,
| |
| centerX: (typeof vv.offsetLeft === 'number' ? vv.offsetLeft : 0) + ((vv.width || width) / 2)
| |
| };
| |
| }
| |
|
| |
| var pvzTimelessViewportWidth = 0;
| |
| var pvzTimelessViewportHeight = 0;
| |
| var pvzTimelessPanelRefreshTimers = [];
| |
| var pvzTimelessPanelCloseTimers = new WeakMap();
| |
|
| |
| function pvzResetTimelessPanelGeometry(panel) {
| |
| if (!panel || panel.nodeType !== 1) return;
| |
|
| |
| panel.removeAttribute('data-pvz-panel-fixed-at-open');
| |
| panel.removeAttribute('data-pvz-panel-document-locked');
| |
| panel.removeAttribute('data-pvz-panel-document-top');
| |
| panel.removeAttribute('data-pvz-panel-document-left');
| |
| panel.removeAttribute('data-pvz-panel-document-width');
| |
|
| |
| [
| |
| 'position',
| |
| 'top',
| |
| 'left',
| |
| 'right',
| |
| 'width',
| |
| 'max-width',
| |
| 'max-height',
| |
| 'overflow',
| |
| 'transform',
| |
| 'transform-origin',
| |
| 'z-index'
| |
| ].forEach(function (property) {
| |
| panel.style.removeProperty(property);
| |
| });
| |
| }
| |
|
| |
| function pvzClearTimelessPanelCloseTimer(panel) {
| |
| var timer;
| |
|
| |
| if (!panel) return;
| |
|
| |
| timer = pvzTimelessPanelCloseTimers.get(panel);
| |
| if (!timer) return;
| |
|
| |
| window.clearTimeout(timer);
| |
| pvzTimelessPanelCloseTimers.delete(panel);
| |
| }
| |
|
| |
| function pvzScheduleTimelessPanelGeometryCleanup(panel) {
| |
| var timer;
| |
|
| |
| if (!panel) return;
| |
|
| |
| pvzClearTimelessPanelCloseTimer(panel);
| |
|
| |
| timer = window.setTimeout(function () {
| |
| pvzTimelessPanelCloseTimers.delete(panel);
| |
|
| |
| if (!panel.classList.contains('is-open')) {
| |
| pvzResetTimelessPanelGeometry(panel);
| |
| }
| |
| }, 240);
| |
|
| |
| pvzTimelessPanelCloseTimers.set(panel, timer);
| |
| }
| |
|
| |
| function pvzPositionTimelessPanel(name) {
| |
| var panel = document.getElementById('pvz-timeless-panel-' + name);
| |
| var button = pvzFindTimelessPanelButton(name);
| |
| var viewport;
| |
| var buttonRect;
| |
| var panelRect;
| |
| var panelWidth;
| |
| var center;
| |
| var minLeft;
| |
| var maxLeft;
| |
| var left;
| |
| var top;
| |
| var scrollX;
| |
| var scrollY;
| |
|
| |
| if (!panel || !button || typeof button.getBoundingClientRect !== 'function') return;
| |
|
| |
| viewport = pvzGetTimelessViewport();
| |
| buttonRect = button.getBoundingClientRect();
| |
| panelRect = panel.getBoundingClientRect();
| |
|
| |
| panelWidth = panelRect.width || 236;
| |
| panelWidth = Math.min(panelWidth, Math.max(0, viewport.width - 16));
| |
|
| |
| center = buttonRect.left + (buttonRect.width / 2);
| |
| minLeft = viewport.left + 8;
| |
| maxLeft = viewport.left + viewport.width - panelWidth - 8;
| |
| left = pvzClampNumber(center - (panelWidth / 2), minLeft, maxLeft);
| |
|
| |
| top = viewport.top + buttonRect.bottom + 6;
| |
|
| |
| scrollX = window.pageXOffset || document.documentElement.scrollLeft || 0;
| |
| scrollY = window.pageYOffset || document.documentElement.scrollTop || 0;
| |
|
| |
| panel.style.setProperty('position', 'absolute', 'important');
| |
| panel.style.setProperty('top', Math.round(top + scrollY) + 'px', 'important');
| |
| panel.style.setProperty('left', Math.round(left + scrollX) + 'px', 'important');
| |
| panel.style.setProperty('right', 'auto', 'important');
| |
| panel.style.setProperty('max-width', 'calc(100vw - 12px)', 'important');
| |
| panel.style.setProperty('max-height', 'none', 'important');
| |
| panel.style.setProperty('overflow', 'visible', 'important');
| |
| panel.style.setProperty('transform', 'none', 'important');
| |
| }
| |
|
| |
| function pvzRepositionOpenTimelessPanel() {
| |
| var open = document.querySelector('.pvz-timeless-sheet.is-open');
| |
| var name;
| |
|
| |
| if (!open || !open.id) return;
| |
|
| |
| name = open.id.replace(/^pvz-timeless-panel-/, '');
| |
| if (name) pvzPositionTimelessPanel(name);
| |
| }
| |
|
| |
| function pvzQueueTimelessPanelViewportRefresh() {
| |
| pvzTimelessPanelRefreshTimers.forEach(function (timer) {
| |
| window.clearTimeout(timer);
| |
| });
| |
|
| |
| pvzTimelessPanelRefreshTimers = [];
| |
|
| |
| window.requestAnimationFrame(function () {
| |
| pvzRepositionOpenTimelessPanel();
| |
| });
| |
|
| |
| [140, 420].forEach(function (delay) {
| |
| pvzTimelessPanelRefreshTimers.push(
| |
| window.setTimeout(function () {
| |
| pvzRepositionOpenTimelessPanel();
| |
| }, delay)
| |
| );
| |
| });
| |
| }
| |
|
| |
| function pvzHandleTimelessViewportResize() {
| |
| var viewport = pvzGetTimelessViewport();
| |
| var width = Math.round(viewport.width || window.innerWidth || 0);
| |
| var height = Math.round(viewport.height || window.innerHeight || 0);
| |
| var widthChanged;
| |
| var heightChanged;
| |
| var orientationChanged;
| |
|
| |
| if (!pvzTimelessViewportWidth || !pvzTimelessViewportHeight) {
| |
| pvzTimelessViewportWidth = width;
| |
| pvzTimelessViewportHeight = height;
| |
| return;
| |
| }
| |
|
| |
| widthChanged = Math.abs(width - pvzTimelessViewportWidth);
| |
| heightChanged = Math.abs(height - pvzTimelessViewportHeight);
| |
| orientationChanged =
| |
| (width > height) !== (pvzTimelessViewportWidth > pvzTimelessViewportHeight);
| |
|
| |
| pvzTimelessViewportWidth = width;
| |
| pvzTimelessViewportHeight = height;
| |
|
| |
| if (orientationChanged || widthChanged >= 80 || heightChanged >= 120) {
| |
| pvzQueueTimelessPanelViewportRefresh();
| |
| }
| |
| }
| |
|
| |
| function pvzCloseTimelessPanels() {
| |
| document.documentElement.classList.remove('pvz-timeless-panel-open');
| |
| if (document.body) document.body.classList.remove('pvz-timeless-panel-open');
| |
|
| |
| pvzArray(document.querySelectorAll('.pvz-timeless-sheet')).forEach(function (panel) {
| |
| panel.classList.remove('is-open');
| |
| pvzScheduleTimelessPanelGeometryCleanup(panel);
| |
| });
| |
|
| |
| pvzArray(document.querySelectorAll('.pvz-timeless-action-button, .pvz-timeless-page-action[data-pvz-timeless-panel]')).forEach(function (button) {
| |
| button.classList.remove('is-active');
| |
| button.setAttribute('aria-expanded', 'false');
| |
| });
| |
| }
| |
|
| |
| function pvzOpenTimelessPanel(name) {
| |
| var panel = document.getElementById('pvz-timeless-panel-' + name);
| |
|
| |
| if (!panel) return;
| |
|
| |
| if (name === 'alerts') pvzRefreshTimelessAlertsPanel();
| |
|
| |
| pvzCloseTimelessPanels();
| |
| pvzClearTimelessPanelCloseTimer(panel);
| |
| pvzResetTimelessPanelGeometry(panel);
| |
| pvzPositionTimelessPanel(name);
| |
|
| |
| document.documentElement.classList.add('pvz-timeless-panel-open');
| |
| if (document.body) document.body.classList.add('pvz-timeless-panel-open');
| |
|
| |
| panel.classList.add('is-open');
| |
|
| |
| pvzArray(document.querySelectorAll('.pvz-timeless-action-button, .pvz-timeless-page-action[data-pvz-timeless-panel]')).forEach(function (button) {
| |
| var active = button.getAttribute('data-pvz-timeless-panel') === name;
| |
| button.classList.toggle('is-active', active);
| |
| button.setAttribute('aria-expanded', active ? 'true' : 'false');
| |
| });
| |
| }
| |
|
| |
| function pvzToggleTimelessPanel(name) {
| |
| var panel = document.getElementById('pvz-timeless-panel-' + name);
| |
|
| |
| if (panel && panel.classList.contains('is-open')) {
| |
| pvzCloseTimelessPanels();
| |
| return;
| |
| }
| |
|
| |
| pvzOpenTimelessPanel(name);
| |
| }
| |
|
| |
| function pvzCreateTimelessActionButton(name, label, iconKind) {
| |
| var button = document.createElement('button');
| |
|
| |
| button.type = 'button';
| |
| button.className = 'pvz-timeless-action-button pvz-timeless-action-' + name;
| |
| button.setAttribute('data-pvz-timeless-panel', name);
| |
| button.setAttribute('aria-label', label);
| |
| button.setAttribute('aria-expanded', 'false');
| |
| button.title = label;
| |
| button.appendChild(pvzCreateInlineSvgIcon(iconKind));
| |
| return button;
| |
| }
| |
|
| |
|
| |
| function pvzGetTalkPageUrl() {
| |
| var pageName = pvzPageName();
| |
| var title = '';
| |
|
| |
| if (window.mw && mw.config && typeof mw.config.get === 'function') {
| |
| title = mw.config.get('wgTitle') || pageName;
| |
| }
| |
|
| |
| if (!pageName) return pvzMakePageUrl('Talk:' + title);
| |
|
| |
| if (/^토론:|^Talk:/i.test(pageName)) return pvzMakePageUrl(pageName);
| |
| if (pageName.indexOf(':') === -1) return pvzMakePageUrl('토론:' + title);
| |
|
| |
| return pvzMakePageUrl('토론:' + title);
| |
| }
| |
|
| |
| function pvzIsCurrentPageAction(kind) {
| |
| var action = 'view';
| |
| var ns = null;
| |
|
| |
| if (window.mw && mw.config && typeof mw.config.get === 'function') {
| |
| action = mw.config.get('wgAction') || 'view';
| |
| ns = mw.config.get('wgNamespaceNumber');
| |
| }
| |
|
| |
| if (kind === 'view') {
| |
| return action === 'view' && !(typeof ns === 'number' && ns % 2 === 1);
| |
| }
| |
|
| |
| if (kind === 'talk') {
| |
| return typeof ns === 'number' && ns % 2 === 1;
| |
| }
| |
|
| |
| if (kind === 'edit') {
| |
| return action === 'edit' || action === 'submit' || action === 'visualeditor';
| |
| }
| |
|
| |
| if (kind === 'history') {
| |
| return action === 'history';
| |
| }
| |
|
| |
| return false;
| |
| }
| |
|
| |
| function pvzCreatePageActionElement(kind, label, href, extraClass) {
| |
| var el;
| |
|
| |
| if (href) {
| |
| el = document.createElement('a');
| |
| el.href = href;
| |
| } else {
| |
| el = document.createElement('button');
| |
| el.type = 'button';
| |
| }
| |
|
| |
| el.className = 'pvz-timeless-page-action pvz-timeless-page-action-' + (extraClass || kind);
| |
|
| |
| if (pvzIsCurrentPageAction(kind)) {
| |
| el.classList.add('is-current');
| |
| el.setAttribute('aria-current', 'page');
| |
| }
| |
|
| |
| el.setAttribute('aria-label', label);
| |
| el.title = label;
| |
| el.appendChild(pvzCreateInlineSvgIcon(kind));
| |
| el.querySelector('.pvz-timeless-action-icon').classList.add('pvz-timeless-page-action-icon');
| |
|
| |
| return el;
| |
| }
| |
|
| |
| function pvzCreatePagePanelButton(kind, label, panelName, extraClass) {
| |
| var button = pvzCreatePageActionElement(kind, label, '', extraClass);
| |
|
| |
| button.setAttribute('data-pvz-timeless-panel', panelName);
| |
| button.setAttribute('aria-expanded', 'false');
| |
|
| |
| return button;
| |
| }
| |
|
| |
| function pvzPageActionUrl(type) {
| |
| var found;
| |
| var pageName = pvzPageName();
| |
|
| |
| if (type === 'view') {
| |
| found = pvzFindExistingLink({
| |
| ids: [/ca-view/i, /ca-nstab-main/i, /ca-nstab-project/i],
| |
| text: [/^문서$/, /^Read$/i, /^Page$/i]
| |
| });
| |
| return found ? found.getAttribute('href') : pvzMakePageUrl(pageName);
| |
| }
| |
|
| |
| if (type === 'talk') {
| |
| found = pvzFindExistingLink({
| |
| ids: [/ca-talk/i, /ca-talkpage/i],
| |
| text: [/^토론$/, /^Discussion$/i, /^Talk$/i]
| |
| });
| |
| return found ? found.getAttribute('href') : pvzGetTalkPageUrl();
| |
| }
| |
|
| |
| if (type === 'edit') {
| |
| found = pvzFindExistingLink({
| |
| ids: [/ca-edit/i, /ca-ve-edit/i, /ca-viewsource/i],
| |
| href: [/action=(edit|submit)/i, /veaction=edit/i],
| |
| text: [/^편집$/, /^원본 편집$/, /^Edit$/i, /^View source$/i]
| |
| });
| |
| return found ? found.getAttribute('href') : (pageName ? pvzMakePageUrl(pageName) + '?action=edit' : '');
| |
| }
| |
|
| |
| if (type === 'history') {
| |
| found = pvzFindExistingLink({
| |
| ids: [/ca-history/i],
| |
| href: [/action=history/i],
| |
| text: [/^역사$/, /^문서 역사$/, /^View history$/i, /^History$/i]
| |
| });
| |
| return found ? found.getAttribute('href') : (pageName ? pvzMakePageUrl(pageName) + '?action=history' : '');
| |
| }
| |
|
| |
| if (type === 'watch') {
| |
| found = pvzFindExistingLink({
| |
| ids: [/ca-watch/i, /ca-unwatch/i, /ca-watchlist/i],
| |
| href: [/action=(watch|unwatch)/i, /Special:(Watchlist|주시문서)/i, /특수:(주시문서|Watchlist)/i],
| |
| text: [/주시문서/, /즐겨찾기/, /^Watch$/i, /^Unwatch$/i]
| |
| });
| |
| return found ? found.getAttribute('href') : (pageName ? pvzMakePageUrl(pageName) + '?action=watch' : pvzMakeSpecialUrl('Watchlist'));
| |
| }
| |
|
| |
| return '';
| |
| }
| |
|
| |
| function pvzEnsureTimelessPageActions() {
| |
| var old = document.getElementById('pvz-timeless-page-actions');
| |
| var heading = document.getElementById('firstHeading') ||
| |
| document.querySelector('.mw-first-heading, .firstHeading');
| |
| var wrap = document.createElement('nav');
| |
| var left = document.createElement('div');
| |
| var right = document.createElement('div');
| |
| var watchUrl = pvzPageActionUrl('watch');
| |
|
| |
| if (!heading || !heading.parentNode) return;
| |
|
| |
| if (old && old.parentNode) old.parentNode.removeChild(old);
| |
|
| |
| wrap.id = 'pvz-timeless-page-actions';
| |
| wrap.className = 'pvz-timeless-page-actions';
| |
| wrap.setAttribute('aria-label', '문서 작업');
| |
|
| |
| left.className = 'pvz-timeless-page-actions-left';
| |
| right.className = 'pvz-timeless-page-actions-right';
| |
|
| |
| left.appendChild(pvzCreatePageActionElement('view', '문서', pvzPageActionUrl('view'), 'view'));
| |
| left.appendChild(pvzCreatePageActionElement('talk', '토론', pvzPageActionUrl('talk'), 'talk'));
| |
|
| |
| right.appendChild(pvzCreatePageActionElement('edit', '편집', pvzPageActionUrl('edit'), 'edit'));
| |
| right.appendChild(pvzCreatePageActionElement('history', '역사', pvzPageActionUrl('history'), 'history'));
| |
|
| |
| if (watchUrl) {
| |
| right.appendChild(pvzCreatePageActionElement('watch', '즐겨찾기', watchUrl, 'watch'));
| |
| }
| |
|
| |
| right.appendChild(pvzCreatePagePanelButton('more', '더보기', 'more', 'more'));
| |
|
| |
| wrap.appendChild(left);
| |
| wrap.appendChild(right);
| |
|
| |
| if (heading.nextSibling) {
| |
| heading.parentNode.insertBefore(wrap, heading.nextSibling);
| |
| } else {
| |
| heading.parentNode.appendChild(wrap);
| |
| }
| |
| }
| |
|
| |
| function pvzEnsureTimelessActionbar() {
| |
| var bar = document.getElementById('pvz-timeless-mobile-actions');
| |
| var host = document.querySelector('body.skin-timeless #mw-header-container') ||
| |
| document.getElementById('mw-header-container') ||
| |
| document.querySelector('body.skin-timeless #mw-header') ||
| |
| document.body;
| |
|
| |
| if (!host) return bar;
| |
|
| |
| if (!bar) {
| |
| bar = document.createElement('div');
| |
| bar.id = 'pvz-timeless-mobile-actions';
| |
| bar.className = 'pvz-timeless-mobile-actions';
| |
| bar.setAttribute('aria-label', '식좀위키 모바일 빠른 메뉴');
| |
| }
| |
|
| |
| bar.innerHTML = '';
| |
| bar.appendChild(pvzCreateTimelessActionButton('browse', '둘러보기', 'menu'));
| |
| bar.appendChild(pvzCreateTimelessActionButton('settings', '위키 도구', 'tools'));
| |
| bar.appendChild(pvzCreateTimelessActionButton('document', '문서 도구', 'document'));
| |
| bar.appendChild(pvzCreateTimelessActionButton('alerts', '알림', 'alerts'));
| |
| bar.appendChild(pvzCreateTimelessActionButton('account', '계정', 'account'));
| |
|
| |
| if (bar.parentNode !== host) {
| |
| host.appendChild(bar);
| |
| }
| |
|
| |
| var title = document.getElementById('pvz-timeless-mobile-title');
| |
| if (!title) {
| |
| title = document.createElement('a');
| |
| title.id = 'pvz-timeless-mobile-title';
| |
| title.href = pvzMakePageUrl(pvzMainPageName());
| |
| title.textContent = '식물 vs 좀비 위키';
| |
| title.setAttribute('aria-label', '식물 vs 좀비 위키 대문으로 이동');
| |
| }
| |
| if (title.parentNode !== host) {
| |
| host.appendChild(title);
| |
| }
| |
|
| |
| return bar;
| |
| }
| |
|
| |
| function pvzCreateTimelessPanel(name, label, sections) {
| |
| var old = document.getElementById('pvz-timeless-panel-' + name);
| |
| var panel = document.createElement('section');
| |
| var body = document.createElement('div');
| |
|
| |
| if (old && old.parentNode) old.parentNode.removeChild(old);
| |
|
| |
| panel.id = 'pvz-timeless-panel-' + name;
| |
| panel.className = 'pvz-timeless-sheet pvz-timeless-sheet-' + name;
| |
| panel.setAttribute('aria-label', label);
| |
|
| |
| body.className = 'pvz-timeless-sheet-body';
| |
| (sections || []).filter(Boolean).forEach(function (section) {
| |
| body.appendChild(section);
| |
| });
| |
|
| |
| panel.appendChild(body);
| |
| document.body.appendChild(panel);
| |
| }
| |
|
| |
| function pvzEnsureTimelessPanelBackdrop() {
| |
| var old = document.getElementById('pvz-timeless-panel-backdrop');
| |
| if (old) return old;
| |
|
| |
| old = document.createElement('div');
| |
| old.id = 'pvz-timeless-panel-backdrop';
| |
| old.className = 'pvz-timeless-panel-backdrop';
| |
| document.body.appendChild(old);
| |
| return old;
| |
| }
| |
|
| |
| function pvzStoredColorMode() {
| |
| try {
| |
| return localStorage.getItem('pvz-timeless-color-mode') || '';
| |
| } catch (e) {
| |
| return '';
| |
| }
| |
| }
| |
|
| |
| function pvzCurrentColorMode() {
| |
| var stored = pvzStoredColorMode();
| |
| var root = document.documentElement;
| |
|
| |
| if (stored === 'light' || stored === 'dark') return stored;
| |
|
| |
| if (root && root.classList && (
| |
| root.classList.contains('theme-dark') ||
| |
| root.classList.contains('dark') ||
| |
| root.classList.contains('client-dark-mode') ||
| |
| root.classList.contains('mw-theme-night') ||
| |
| root.classList.contains('skin-theme-clientpref-night')
| |
| )) {
| |
| return 'dark';
| |
| }
| |
|
| |
| if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
| |
| return 'dark';
| |
| }
| |
|
| |
| return 'light';
| |
| }
| |
|
| |
| function pvzApplyTimelessColorMode(mode, persist) {
| |
| var root = document.documentElement;
| |
| var body = document.body;
| |
| var add;
| |
| var remove;
| |
|
| |
| if (mode !== 'light' && mode !== 'dark') mode = 'light';
| |
| if (typeof persist === 'undefined') persist = true;
| |
|
| |
| if (persist) {
| |
| try {
| |
| localStorage.setItem('pvz-timeless-color-mode', mode);
| |
| } catch (e) {}
| |
| }
| |
|
| |
| add = mode === 'dark'
| |
| ? ['theme-dark', 'dark', 'client-dark-mode', 'mw-theme-night', 'skin-theme-clientpref-night']
| |
| : ['theme-light', 'light', 'client-light-mode', 'skin-theme-clientpref-day'];
| |
|
| |
| remove = mode === 'dark'
| |
| ? ['theme-light', 'light', 'client-light-mode', 'skin-theme-clientpref-day']
| |
| : ['theme-dark', 'dark', 'client-dark-mode', 'mw-theme-night', 'skin-theme-clientpref-night'];
| |
|
| |
| [root, body].forEach(function (node) {
| |
| if (!node || !node.classList) return;
| |
| remove.forEach(function (name) { node.classList.remove(name); });
| |
| add.forEach(function (name) { node.classList.add(name); });
| |
| });
| |
| }
| |
|
| |
| var pvzSystemThemeMedia = null;
| |
| var pvzSystemThemeListenerBound = false;
| |
|
| |
| function pvzHasStoredColorMode() {
| |
| var stored = pvzStoredColorMode();
| |
| return stored === 'light' || stored === 'dark';
| |
| }
| |
|
| |
| function pvzApplyInitialTimelessColorMode() {
| |
| var stored = pvzStoredColorMode();
| |
| var mode = pvzCurrentColorMode();
| |
|
| |
| pvzApplyTimelessColorMode(mode, stored === 'light' || stored === 'dark');
| |
| }
| |
|
| |
| function pvzBindSystemThemeListener() {
| |
| if (
| |
| pvzSystemThemeListenerBound ||
| |
| !window.matchMedia
| |
| ) {
| |
| return;
| |
| }
| |
|
| |
| pvzSystemThemeMedia = window.matchMedia('(prefers-color-scheme: dark)');
| |
| pvzSystemThemeListenerBound = true;
| |
|
| |
| function handleSystemThemeChange(event) {
| |
| if (pvzHasStoredColorMode()) return;
| |
|
| |
| pvzApplyTimelessColorMode(event.matches ? 'dark' : 'light', false);
| |
|
| |
| window.setTimeout(function () {
| |
| if (typeof pvzBuildCustomMobileTools === 'function') {
| |
| pvzBuildCustomMobileTools();
| |
| }
| |
| }, 0);
| |
| }
| |
|
| |
| if (typeof pvzSystemThemeMedia.addEventListener === 'function') {
| |
| pvzSystemThemeMedia.addEventListener('change', handleSystemThemeChange);
| |
| } else if (typeof pvzSystemThemeMedia.addListener === 'function') {
| |
| pvzSystemThemeMedia.addListener(handleSystemThemeChange);
| |
| }
| |
| }
| |
|
| |
| function pvzThemeToggleEntry() {
| |
| var current = pvzCurrentColorMode();
| |
|
| |
| if (current === 'dark') {
| |
| return {
| |
| label: '라이트 모드',
| |
| href: '#',
| |
| icon: 'sun',
| |
| className: 'pvz-timeless-theme-toggle',
| |
| themeMode: 'light'
| |
| };
| |
| }
| |
|
| |
| return {
| |
| label: '다크 모드',
| |
| href: '#',
| |
| icon: 'moon',
| |
| className: 'pvz-timeless-theme-toggle',
| |
| themeMode: 'dark'
| |
| };
| |
| }
| |
|
| |
| /* =========================
| |
| 계정 패널 사용자 정보 시작
| |
| - 현재 로그인 사용자의 명시적 직책, 편집 횟수, 가입일을 Action API에서 불러온다.
| |
| ========================= */
| |
|
| |
| var pvzAccountInfoPromise = null;
| |
|
| |
| function pvzAccountRoleLabel(group) {
| |
| var labels = {
| |
| 'bureaucrat': '사무관',
| |
| 'interface-admin': '인터페이스 관리자',
| |
| 'sysop': '관리자',
| |
| 'patroller': '순찰자',
| |
| 'rollbacker': '되돌리기 사용자',
| |
| 'autopatrolled': '자동 순찰 면제 사용자',
| |
| 'bot': '봇',
| |
| 'confirmed': '인증된 사용자',
| |
| 'checkuser': '검사관',
| |
| 'suppress': '기록보호자',
| |
| 'oversight': '기록보호자',
| |
| 'steward': '스튜어드',
| |
| 'translator': '번역자',
| |
| 'translationadmin': '번역 관리자'
| |
| };
| |
|
| |
| return labels[group] || group;
| |
| }
| |
|
| |
| function pvzAccountExplicitRoles(userinfo) {
| |
| var memberships = userinfo && userinfo.groupmemberships;
| |
| var groups = userinfo && userinfo.groups;
| |
| var names = [];
| |
|
| |
| if (Array.isArray(memberships) && memberships.length) {
| |
| memberships.forEach(function (membership) {
| |
| if (membership && membership.group) names.push(membership.group);
| |
| });
| |
| } else if (Array.isArray(groups)) {
| |
| groups.forEach(function (group) {
| |
| if (group && group !== '*' && group !== 'user' && group !== 'autoconfirmed') {
| |
| names.push(group);
| |
| }
| |
| });
| |
| }
| |
|
| |
| names = names.filter(function (group, index, list) {
| |
| return group && list.indexOf(group) === index;
| |
| });
| |
|
| |
| if (!names.length) return '일반 사용자';
| |
|
| |
| return names.map(pvzAccountRoleLabel).join(' · ');
| |
| }
| |
|
| |
| function pvzFormatAccountRegistrationDate(value) {
| |
| var date;
| |
| var formatter;
| |
| var parts;
| |
| var data = {};
| |
|
| |
| if (!value) return '확인할 수 없음';
| |
|
| |
| date = new Date(value);
| |
| if (isNaN(date.getTime())) return '확인할 수 없음';
| |
|
| |
| try {
| |
| formatter = new Intl.DateTimeFormat('ko-KR', {
| |
| timeZone: 'Asia/Seoul',
| |
| year: 'numeric',
| |
| month: 'numeric',
| |
| day: 'numeric',
| |
| weekday: 'short'
| |
| });
| |
|
| |
| parts = formatter.formatToParts(date);
| |
| parts.forEach(function (part) {
| |
| if (part.type !== 'literal') data[part.type] = part.value;
| |
| });
| |
|
| |
| return (
| |
| data.year + '년 ' +
| |
| data.month + '월 ' +
| |
| data.day + '일 (' +
| |
| data.weekday + ')'
| |
| );
| |
| } catch (e) {
| |
| return date.getFullYear() + '년 ' +
| |
| (date.getMonth() + 1) + '월 ' +
| |
| date.getDate() + '일';
| |
| }
| |
| }
| |
|
| |
| function pvzLoadAccountInfo() {
| |
| if (pvzAccountInfoPromise) return pvzAccountInfoPromise;
| |
|
| |
| if (!window.mw || !mw.loader || !mw.config || !mw.config.get('wgUserName')) {
| |
| return Promise.resolve(null);
| |
| }
| |
|
| |
| pvzAccountInfoPromise = mw.loader.using('mediawiki.api').then(function () {
| |
| var api = new mw.Api();
| |
|
| |
| return api.get({
| |
| action: 'query',
| |
| meta: 'userinfo',
| |
| uiprop: 'groups|groupmemberships|editcount|registrationdate',
| |
| formatversion: 2
| |
| });
| |
| }).then(function (data) {
| |
| return data && data.query ? data.query.userinfo : null;
| |
| }).catch(function () {
| |
| return null;
| |
| });
| |
|
| |
| return pvzAccountInfoPromise;
| |
| }
| |
|
| |
| function pvzCreateAccountInfoRow(label, value) {
| |
| var row = document.createElement('div');
| |
| var key = document.createElement('span');
| |
| var text = document.createElement('span');
| |
|
| |
| row.className = 'pvz-timeless-account-info-row';
| |
|
| |
| key.className = 'pvz-timeless-account-info-label';
| |
| key.textContent = label;
| |
|
| |
| text.className = 'pvz-timeless-account-info-value';
| |
| text.textContent = value;
| |
|
| |
| row.appendChild(key);
| |
| row.appendChild(text);
| |
|
| |
| return row;
| |
| }
| |
|
| |
| function pvzCreateAccountInfoBlock(username) {
| |
| var box = document.createElement('div');
| |
|
| |
| box.className = 'pvz-timeless-account-info';
| |
| box.setAttribute('aria-label', username + ' 계정 정보');
| |
|
| |
| box.appendChild(pvzCreateAccountInfoRow('직책', '불러오는 중…'));
| |
| box.appendChild(pvzCreateAccountInfoRow('편집', '불러오는 중…'));
| |
| box.appendChild(pvzCreateAccountInfoRow('가입일', '불러오는 중…'));
| |
|
| |
| pvzLoadAccountInfo().then(function (userinfo) {
| |
| var rows;
| |
| var editcount;
| |
|
| |
| if (!box.isConnected || !userinfo) return;
| |
|
| |
| rows = box.querySelectorAll('.pvz-timeless-account-info-value');
| |
| editcount = Number(userinfo.editcount);
| |
|
| |
| if (rows[0]) rows[0].textContent = pvzAccountExplicitRoles(userinfo);
| |
| if (rows[1]) {
| |
| rows[1].textContent = isNaN(editcount) ?
| |
| '확인할 수 없음' :
| |
| editcount.toLocaleString('ko-KR') + '회';
| |
| }
| |
| if (rows[2]) rows[2].textContent = pvzFormatAccountRegistrationDate(userinfo.registrationdate);
| |
| });
| |
|
| |
| return box;
| |
| }
| |
|
| |
| /* 계정 패널 사용자 정보 끝 */
| |
|
| |
|
| |
| function pvzAccountSection() {
| |
| var username = window.mw && mw.config && typeof mw.config.get === 'function' ? mw.config.get('wgUserName') : null;
| |
| var entries;
| |
| var section;
| |
| var list;
| |
|
| |
| if (username) {
| |
| entries = [
| |
| { label: username, href: pvzMakePageUrl('User:' + username) },
| |
| { label: '사용자 토론', href: pvzMakePageUrl('User talk:' + username) },
| |
| { label: '주시문서 목록', href: pvzMakeSpecialUrl('Watchlist') },
| |
| { label: '기여', href: pvzMakeSpecialUrl('Contributions/' + username) },
| |
| { label: '환경설정', href: pvzMakeSpecialUrl('Preferences') },
| |
| { label: '로그아웃', href: pvzMakeSpecialUrl('UserLogout') },
| |
| pvzThemeToggleEntry()
| |
| ];
| |
| } else {
| |
| entries = [
| |
| { label: '로그인', href: pvzMakeSpecialUrl('UserLogin') },
| |
| { label: '회원 가입', href: pvzMakeSpecialUrl('CreateAccount') },
| |
| pvzThemeToggleEntry()
| |
| ];
| |
| }
| |
|
| |
| section = pvzCreateToolsSection('계정', entries, 'account');
| |
|
| |
| if (section && username) {
| |
| list = section.querySelector('.pvz-custom-mobile-tools-list');
| |
| section.insertBefore(pvzCreateAccountInfoBlock(username), list || null);
| |
| }
| |
|
| |
| return section;
| |
| }
| |
|
| |
| function pvzNativeNotificationCount() {
| |
| var nodes = pvzArray(document.querySelectorAll(
| |
| '.mw-echo-notifications-badge, .mw-echo-notification-badge, ' +
| |
| '#pt-notifications-alert .mw-echo-notifications-badge, ' +
| |
| '#pt-notifications-notice .mw-echo-notifications-badge, ' +
| |
| '#pt-notifications-alert [data-counter-num], ' +
| |
| '#pt-notifications-notice [data-counter-num]'
| |
| ));
| |
| var count = 0;
| |
|
| |
| nodes.forEach(function (node) {
| |
| var raw = '';
| |
| var value;
| |
|
| |
| if (!node) return;
| |
| raw = node.getAttribute('data-counter-num') || node.getAttribute('data-counter-text') || node.getAttribute('aria-label') || node.textContent || '';
| |
| value = parseInt(String(raw).replace(/[^0-9]/g, ''), 10);
| |
| if (!isNaN(value)) count += value;
| |
| });
| |
|
| |
| return count;
| |
| }
| |
|
| |
| function pvzCollectNotificationEntries() {
| |
| var entries = [];
| |
| var seen = {};
| |
| var selectors = [
| |
| '.mw-echo-ui-notificationItemWidget a[href]',
| |
| '.mw-echo-notification a[href]',
| |
| '.mw-echo-notifications a[href]',
| |
| '.mw-notification a[href]',
| |
| '.mw-notifications a[href]',
| |
| '#pt-notifications-alert a[href]',
| |
| '#pt-notifications-notice a[href]'
| |
| ];
| |
|
| |
| pvzArray(document.querySelectorAll(selectors.join(', '))).forEach(function (link) {
| |
| var label = pvzNormalizeText(link.textContent || link.getAttribute('title') || link.getAttribute('aria-label') || '');
| |
| var href = link.getAttribute('href');
| |
| var key;
| |
|
| |
| if (!href) return;
| |
| if (!label || /^(알림|공지|Notifications?|Alerts?|Notices?)$/i.test(label)) return;
| |
|
| |
| key = href + '|' + label;
| |
| if (seen[key]) return;
| |
|
| |
| seen[key] = true;
| |
| entries.push({
| |
| label: label,
| |
| href: href,
| |
| source: link
| |
| });
| |
| });
| |
|
| |
| return entries.slice(0, 8);
| |
| }
| |
|
| |
| function pvzCreateAlertEmptySection() {
| |
| var section = document.createElement('section');
| |
| var heading = document.createElement('h2');
| |
| var list = document.createElement('ul');
| |
| var item = document.createElement('li');
| |
| var empty = document.createElement('span');
| |
|
| |
| section.className = 'pvz-custom-mobile-tools-section pvz-timeless-alert-section';
| |
| section.setAttribute('data-pvz-tools-section', 'alerts');
| |
|
| |
| heading.className = 'pvz-custom-mobile-tools-heading';
| |
| heading.textContent = '알림';
| |
|
| |
| list.className = 'pvz-custom-mobile-tools-list';
| |
| item.className = 'pvz-custom-mobile-tools-item';
| |
| empty.className = 'pvz-timeless-alert-empty';
| |
| empty.textContent = '새 알림이 없습니다.';
| |
|
| |
| item.appendChild(empty);
| |
| list.appendChild(item);
| |
| section.appendChild(heading);
| |
| section.appendChild(list);
| |
|
| |
| return section;
| |
| }
| |
|
| |
| function pvzAlertsSection() {
| |
| var entries = pvzCollectNotificationEntries();
| |
| var count = pvzNativeNotificationCount();
| |
|
| |
| if (!entries.length && count < 1) {
| |
| return pvzCreateAlertEmptySection();
| |
| }
| |
|
| |
| if (!entries.length && count > 0) {
| |
| entries.push({ label: '알림 확인하기', href: pvzMakeSpecialUrl('Notifications') });
| |
| }
| |
|
| |
| entries.push({ label: '전체 알림', href: pvzMakeSpecialUrl('Notifications') });
| |
| entries.push({ label: '환경설정', href: pvzMakeSpecialUrl('Preferences') });
| |
|
| |
| return pvzCreateToolsSection('알림', entries, 'alerts');
| |
| }
| |
|
| |
| function pvzRefreshTimelessAlertsPanel() {
| |
| var panel = document.getElementById('pvz-timeless-panel-alerts');
| |
| var body = panel && panel.querySelector('.pvz-timeless-sheet-body');
| |
|
| |
| if (!body) return;
| |
|
| |
| body.innerHTML = '';
| |
| body.appendChild(pvzAlertsSection());
| |
| }
| |
|
| |
| function pvzBuildTimelessWideRail(side, sections, includeLogo) {
| |
| var rail = document.createElement('aside');
| |
| var logo;
| |
| var img;
| |
|
| |
| rail.className = 'pvz-timeless-wide-rail pvz-timeless-wide-rail-' + side;
| |
| rail.setAttribute('aria-label', side === 'left' ? '왼쪽 보조창' : '오른쪽 보조창');
| |
|
| |
| if (includeLogo) {
| |
| logo = document.createElement('a');
| |
| logo.className = 'pvz-timeless-wide-logo';
| |
| logo.href = pvzMakePageUrl(pvzMainPageName());
| |
| logo.title = '식물 vs 좀비 위키';
| |
| logo.setAttribute('aria-label', '식물 vs 좀비 위키로 이동');
| |
|
| |
| img = document.createElement('img');
| |
| img.src = '/wiki/Special:Redirect/file/PvZKR_Wiki_Logo2.png';
| |
| img.alt = 'Plants vs. Zombies 위키';
| |
| img.loading = 'eager';
| |
|
| |
| logo.appendChild(img);
| |
| rail.appendChild(logo);
| |
| }
| |
|
| |
| (sections || []).filter(Boolean).forEach(function (section) {
| |
| rail.appendChild(section);
| |
| });
| |
|
| |
| return rail;
| |
| }
| |
|
| |
| function pvzBuildTimelessWideTools(navEntries, wikiEntries, moreEntries, manageEntries, categoryEntries) {
| |
| var leftHost = document.querySelector('body.skin-timeless #mw-site-navigation') || document.getElementById('mw-site-navigation');
| |
| var rightHost = document.querySelector('body.skin-timeless #mw-related-navigation') || document.getElementById('mw-related-navigation');
| |
| var leftRail;
| |
| var rightRail;
| |
|
| |
| pvzArray(document.querySelectorAll('.pvz-timeless-wide-rail')).forEach(function (node) {
| |
| if (node && node.parentNode) node.parentNode.removeChild(node);
| |
| });
| |
|
| |
| if (!leftHost && !rightHost) return;
| |
|
| |
| leftRail = pvzBuildTimelessWideRail('left', [
| |
| pvzCreateToolsSection('둘러보기', navEntries, 'navigation'),
| |
| pvzCreateToolsSection('위키 도구', wikiEntries, 'wiki')
| |
| ], true);
| |
|
| |
| rightRail = pvzBuildTimelessWideRail('right', [
| |
| pvzCreateToolsSection('문서 도구', moreEntries, 'document'),
| |
| pvzCreateToolsSection('분류', categoryEntries, 'category'),
| |
| pvzCreateToolsSection('위키 관리', manageEntries, 'management')
| |
| ], false);
| |
|
| |
| if (leftHost) leftHost.appendChild(leftRail);
| |
| else document.body.insertBefore(leftRail, document.body.firstChild);
| |
|
| |
| if (rightHost) rightHost.appendChild(rightRail);
| |
| else document.body.appendChild(rightRail);
| |
| }
| |
|
| |
| function pvzBuildMobileCategoryBox(categorySection) {
| |
| var old = document.querySelector('.pvz-mobile-category-box');
| |
| var box;
| |
| var content;
| |
|
| |
| if (old && old.parentNode) old.parentNode.removeChild(old);
| |
| if (!categorySection) return;
| |
|
| |
| box = document.createElement('div');
| |
| box.className = 'pvz-mobile-category-box';
| |
| box.appendChild(categorySection);
| |
|
| |
| content = document.getElementById('mw-content-text') || document.querySelector('#bodyContent, .mw-body-content, #mw-content, #content');
| |
| if (content && content.parentNode) {
| |
| if (content.nextSibling) content.parentNode.insertBefore(box, content.nextSibling);
| |
| else content.parentNode.appendChild(box);
| |
| } else {
| |
| document.body.appendChild(box);
| |
| }
| |
| }
| |
|
| |
| function pvzBindTimelessPanelEvents() {
| |
| if (window.PVZ_TIMELESS_PANEL_EVENTS_BOUND) return;
| |
| window.PVZ_TIMELESS_PANEL_EVENTS_BOUND = true;
| |
|
| |
| document.addEventListener('click', function (event) {
| |
| var themeToggle = event.target && event.target.closest ? event.target.closest('[data-pvz-timeless-theme-mode]') : null;
| |
| var button = event.target && event.target.closest ? event.target.closest('.pvz-timeless-action-button[data-pvz-timeless-panel], .pvz-timeless-page-action[data-pvz-timeless-panel]') : null;
| |
| var panel = event.target && event.target.closest ? event.target.closest('.pvz-timeless-sheet') : null;
| |
| var backdrop = event.target && event.target.closest ? event.target.closest('#pvz-timeless-panel-backdrop') : null;
| |
|
| |
| if (themeToggle) {
| |
| event.preventDefault();
| |
| event.stopPropagation();
| |
| pvzApplyTimelessColorMode(themeToggle.getAttribute('data-pvz-timeless-theme-mode'));
| |
| pvzCloseTimelessPanels();
| |
| window.setTimeout(pvzBuildCustomMobileTools, 0);
| |
| return;
| |
| }
| |
|
| |
| if (button) {
| |
| event.preventDefault();
| |
| event.stopPropagation();
| |
| pvzToggleTimelessPanel(button.getAttribute('data-pvz-timeless-panel'));
| |
| return;
| |
| }
| |
|
| |
| if (backdrop) {
| |
| event.preventDefault();
| |
| pvzCloseTimelessPanels();
| |
| return;
| |
| }
| |
|
| |
| if (!panel) pvzCloseTimelessPanels();
| |
| }, true);
| |
|
| |
| document.addEventListener('keydown', function (event) {
| |
| if (event.key === 'Escape') pvzCloseTimelessPanels();
| |
| });
| |
|
| |
| pvzHandleTimelessViewportResize();
| |
|
| |
| window.addEventListener('resize', function () {
| |
| pvzHandleTimelessViewportResize();
| |
| }, { passive: true });
| |
|
| |
| if (
| |
| window.screen &&
| |
| window.screen.orientation &&
| |
| typeof window.screen.orientation.addEventListener === 'function'
| |
| ) {
| |
| window.screen.orientation.addEventListener('change', function () {
| |
| pvzQueueTimelessPanelViewportRefresh();
| |
| });
| |
| } else {
| |
| window.addEventListener('orientationchange', function () {
| |
| pvzQueueTimelessPanelViewportRefresh();
| |
| }, { passive: true });
| |
| }
| |
|
| |
| if (window.visualViewport && typeof window.visualViewport.addEventListener === 'function') {
| |
| window.visualViewport.addEventListener('resize', function () {
| |
| pvzHandleTimelessViewportResize();
| |
| }, { passive: true });
| |
|
| |
| /* visualViewport.scroll은 등록하지 않는다.
| |
| 주소창 접힘/스크롤만으로 패널 좌표를 계속 재계산하지 않는다. */
| |
| }
| |
| }
| |
|
| |
| function pvzBuildCustomMobileTools() {
| |
| var oldShell = document.querySelector('.pvz-custom-mobile-tools-shell, .pvz-custom-mobile-tools');
| |
| var oldCategory = document.querySelector('.pvz-mobile-category-box');
| |
| var navSection;
| |
| var manageSection;
| |
| var wikiSection;
| |
| var moreSection;
| |
| var documentSection;
| |
| var alertsSection;
| |
| var categorySection;
| |
| var createdKeys = {};
| |
| var navEntries;
| |
| var manageEntries;
| |
| var wikiEntries;
| |
| var moreEntries;
| |
| var categoryEntries;
| |
|
| |
| function unique(entry) {
| |
| var key;
| |
|
| |
| if (!entry) return null;
| |
|
| |
| key = entry.href + '|' + entry.label;
| |
| if (createdKeys[key]) return null;
| |
|
| |
| createdKeys[key] = true;
| |
| return entry;
| |
| }
| |
|
| |
| if (!pvzIsTimeless() || !document.body) return;
| |
|
| |
| document.body.classList.remove('pvz-custom-mobile-tools-ready');
| |
|
| |
| if (oldShell && oldShell.parentNode) oldShell.parentNode.removeChild(oldShell);
| |
| if (oldCategory && oldCategory.parentNode) oldCategory.parentNode.removeChild(oldCategory);
| |
| pvzArray(document.querySelectorAll('.pvz-timeless-wide-rail')).forEach(function (node) {
| |
| if (node && node.parentNode) node.parentNode.removeChild(node);
| |
| });
| |
|
| |
|
| |
| navEntries = [
| |
| unique(pvzToolEntryWithFallback('대문', 'mainpage', {
| |
| ids: [/n-mainpage/i, /n-main-page/i],
| |
| href: [/\/wiki\/(%EC%8B%9D%EB%AC%BC_vs_%EC%A2%80%EB%B9%84_%EC%9C%84%ED%82%A4|식물_vs_좀비_위키|Main_Page)/i],
| |
| text: [/^대문$/, /^Main page$/i]
| |
| }, pvzMakePageUrl(pvzMainPageName()))),
| |
| unique(pvzToolEntryWithFallback('최근 바뀜', 'recentchanges', {
| |
| ids: [/n-recentchanges/i],
| |
| href: [/Special:(RecentChanges|최근바뀜)/i, /특수:(최근바뀜|RecentChanges)/i],
| |
| text: [/^최근 바뀜$/, /^Recent changes$/i]
| |
| }, pvzMakeSpecialUrl('RecentChanges'))),
| |
| unique(pvzToolEntryWithFallback('미디어위키 도움말', 'help', {
| |
| ids: [/n-help/i],
| |
| href: [/mediawiki\.org.*Help/i, /Help:Contents/i],
| |
| text: [/미디어위키 도움말/, /^도움말$/, /MediaWiki help/i, /^Help$/i]
| |
| }, 'https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents')),
| |
| unique(pvzToolEntryWithFallback('특수 문서 목록', 'specialpages', {
| |
| ids: [/n-specialpages/i, /t-specialpages/i],
| |
| href: [/Special:(SpecialPages|특수문서)/i, /특수:(특수문서|SpecialPages)/i],
| |
| text: [/특수 문서 목록/, /Special pages/i]
| |
| }, pvzMakeSpecialUrl('SpecialPages'))),
| |
| unique(pvzToolEntryWithFallback('무작위 문서', 'random', {
| |
| ids: [/n-randompage/i, /n-random/i],
| |
| href: [/Special:(Random|RandomPage|임의문서)/i, /특수:(임의문서|Random|RandomPage)/i],
| |
| text: [/임의 문서/, /무작위 문서/, /Random page/i]
| |
| }, pvzMakeSpecialUrl('Random')))
| |
| ].filter(Boolean);
| |
|
| |
| moreEntries = [
| |
| unique(pvzToolEntry('여기를 가리키는 문서', 'whatlinkshere', {
| |
| ids: [/t-whatlinkshere/i],
| |
| href: [/Special:(WhatLinksHere|가리키는문서)/i, /특수:(가리키는문서|WhatLinksHere)/i],
| |
| text: [/여기를 가리키는 문서/, /What links here/i]
| |
| })),
| |
| unique(pvzToolEntry('가리키는 글의 최근 바뀜', 'recentchangeslinked', {
| |
| ids: [/t-recentchangeslinked/i],
| |
| href: [/Special:(RecentChangesLinked|가리키는글의최근바뀜)/i, /특수:(가리키는글의최근바뀜|RecentChangesLinked)/i],
| |
| text: [/가리키는 글의 최근 바뀜/, /Related changes/i]
| |
| })),
| |
| unique(pvzToolEntry('고유 링크', 'permalink', {
| |
| ids: [/t-permalink/i],
| |
| href: [/oldid=/i],
| |
| text: [/고유 링크/, /Permanent link/i]
| |
| })),
| |
| unique(pvzToolEntry('문서 정보', 'info', {
| |
| ids: [/t-info/i, /ca-info/i],
| |
| href: [/action=info/i],
| |
| text: [/문서 정보/, /Page information/i]
| |
| })),
| |
| unique(pvzToolEntry('문서 기록', 'history', {
| |
| ids: [/ca-history/i],
| |
| href: [/action=history/i],
| |
| text: [/문서 역사/, /문서 기록/, /^역사$/, /^History$/i, /^View history$/i]
| |
| }))
| |
| ].filter(Boolean);
| |
|
| |
| wikiEntries = [
| |
| unique(pvzToolEntry('파일 올리기', 'upload', {
| |
| ids: [/t-upload/i, /pt-upload/i],
| |
| href: [/Special:(Upload|올리기)/i, /특수:(올리기|Upload)/i],
| |
| text: [/^파일 올리기$/, /^Upload file$/i]
| |
| })),
| |
| unique(pvzToolEntry('축약된 URL 얻기', 'shorturl', {
| |
| ids: [/t-urlshortener/i, /t-shorturl/i],
| |
| href: [/Special:(UrlShortener|ShortUrl|URLShortener)/i, /특수:(UrlShortener|ShortUrl|축약)/i],
| |
| text: [/축약된 URL/, /Short URL/i, /Get shortened URL/i]
| |
| }))
| |
| ].filter(Boolean);
| |
|
| |
| manageEntries = [
| |
| unique(pvzToolEntryWithFallback('특수 문서', 'specialpages', {
| |
| href: [/Special:(SpecialPages|특수문서)/i, /특수:(SpecialPages|특수문서)/i],
| |
| text: [/^특수 문서$/, /^Special pages$/i]
| |
| }, pvzMakeSpecialUrl('SpecialPages'))),
| |
|
| |
| unique(pvzToolEntryWithFallback('핵심 설정 관리', 'managewiki-core', {
| |
| href: [/Special:ManageWiki(?:\/core)?(?:[/?#]|$)/i, /특수:ManageWiki(?:\/core)?(?:[/?#]|$)/i],
| |
| text: [/이 위키의 핵심 설정을 관리하기/, /Manage this wiki's core settings/i]
| |
| }, pvzMakeSpecialUrl('ManageWiki/core'))),
| |
|
| |
| unique(pvzToolEntryWithFallback('확장 기능 관리', 'managewiki-extensions', {
| |
| href: [/Special:ManageWiki\/extensions/i, /특수:ManageWiki\/extensions/i],
| |
| text: [/이 위키의 확장 기능을 관리하기/, /Manage this wiki's extensions/i]
| |
| }, pvzMakeSpecialUrl('ManageWiki/extensions'))),
| |
|
| |
| unique(pvzToolEntryWithFallback('권한 관리', 'managewiki-permissions', {
| |
| href: [/Special:ManageWiki\/permissions/i, /특수:ManageWiki\/permissions/i],
| |
| text: [/이 위키의 권한 관리/, /Manage this wiki's permissions/i]
| |
| }, pvzMakeSpecialUrl('ManageWiki/permissions'))),
| |
|
| |
| unique(pvzToolEntryWithFallback('이름공간 관리', 'managewiki-namespaces', {
| |
| href: [/Special:ManageWiki\/namespaces/i, /특수:ManageWiki\/namespaces/i],
| |
| text: [/이 위키의 이름공간 관리/, /Manage this wiki's namespaces/i]
| |
| }, pvzMakeSpecialUrl('ManageWiki/namespaces'))),
| |
|
| |
| unique(pvzToolEntryWithFallback('백업 관리', 'datadump', {
| |
| href: [/Special:(DataDump|ManageWiki\/backup|ManageWiki\/backups)/i, /특수:(DataDump|ManageWiki\/backup|ManageWiki\/backups)/i],
| |
| text: [/이 위키의 백업을 관리/, /Manage this wiki's backups/i, /^DataDump$/i]
| |
| }, pvzMakeSpecialUrl('DataDump')))
| |
| ].filter(Boolean);
| |
|
| |
| categoryEntries = pvzCategoryEntriesForCustomBox().map(unique).filter(Boolean);
| |
|
| |
| navSection = pvzCreateToolsSection('둘러보기', navEntries, 'navigation');
| |
| moreSection = pvzCreateToolsSection('더 보기', moreEntries, 'more');
| |
| documentSection = pvzCreateToolsSection('문서 도구', moreEntries, 'document');
| |
| wikiSection = pvzCreateToolsSection('위키 도구', wikiEntries, 'wiki');
| |
| manageSection = pvzCreateToolsSection('위키 관리', manageEntries, 'management');
| |
| alertsSection = pvzAlertsSection();
| |
| categorySection = pvzCreateToolsSection('분류', categoryEntries, 'category');
| |
|
| |
| pvzEnsureTimelessPageActions();
| |
| pvzEnsureTimelessActionbar();
| |
| pvzEnsureTimelessPanelBackdrop();
| |
| pvzCreateTimelessPanel('browse', '둘러보기', [navSection]);
| |
| pvzCreateTimelessPanel('settings', '위키 도구', [wikiSection, manageSection]);
| |
| pvzCreateTimelessPanel('document', '문서 도구', [documentSection]);
| |
| pvzCreateTimelessPanel('more', '더 보기', [moreSection]);
| |
| pvzCreateTimelessPanel('alerts', '알림', [alertsSection]);
| |
| pvzCreateTimelessPanel('account', '계정', [pvzAccountSection()]);
| |
| pvzBuildMobileCategoryBox(categorySection);
| |
| pvzBuildTimelessWideTools(navEntries, wikiEntries, moreEntries, manageEntries, categoryEntries);
| |
| pvzBindTimelessPanelEvents();
| |
|
| |
| document.body.classList.add('pvz-custom-mobile-tools-ready');
| |
| document.body.classList.add('pvz-timeless-actions-ready');
| |
|
| |
| pvzHideNativeTimelessToolPortlets();
| |
| pvzRemoveTimelessColorStripes();
| |
| }
| |
|
| |
|
| |
| function pvzStabilizeCustomToc() {
| |
| var toc = document.querySelector('.pvz-custom-toc');
| |
| var list = toc && toc.querySelector('.pvz-custom-toc-list');
| |
|
| |
| if (!toc || !list || toc.classList.contains('is-collapsed')) return;
| |
|
| |
| toc.classList.add('pvz-toc-reflowing');
| |
|
| |
| window.requestAnimationFrame(function () {
| |
| var width;
| |
|
| |
| toc.classList.remove('pvz-toc-reflowing');
| |
|
| |
| /*
| |
| 첫 렌더링 때 Timeless/폰트 로딩/float 계산이 겹치면
| |
| 목차 항목의 max-content 폭이 너무 좁게 잡히는 경우가 있다.
| |
| offsetWidth를 한 번 읽고 클래스를 붙여 강제로 레이아웃을 재계산한다.
| |
| */
| |
| width = toc.offsetWidth;
| |
| toc.style.setProperty('--pvz-toc-measured-width', width + 'px');
| |
| toc.classList.add('pvz-toc-layout-ready');
| |
| });
| |
| }
| |
|
| |
| function pvzQueueTocStabilize() {
| |
| pvzStabilizeCustomToc();
| |
| window.setTimeout(pvzStabilizeCustomToc, 80);
| |
| window.setTimeout(pvzStabilizeCustomToc, 250);
| |
| window.setTimeout(pvzStabilizeCustomToc, 700);
| |
|
| |
| if (document.fonts && document.fonts.ready && typeof document.fonts.ready.then === 'function') {
| |
| document.fonts.ready.then(function () {
| |
| pvzStabilizeCustomToc();
| |
| });
| |
| }
| |
| }
| |
|
| |
| function pvzCreateCustomToc() {
| |
| var contentText = document.getElementById('mw-content-text');
| |
| var parserOutput = contentText && (
| |
| contentText.querySelector('.mw-parser-output') || contentText
| |
| );
| |
| var leadContainer = parserOutput && (
| |
| parserOutput.querySelector(':scope > .mf-section-0') ||
| |
| parserOutput.querySelector(':scope > section.mf-section-0') ||
| |
| parserOutput
| |
| );
| |
| var existingWrap;
| |
| var nativeTocs;
| |
| var headings;
| |
| var leadParagraph;
| |
| var wrap;
| |
| var toc;
| |
| var head;
| |
| var title;
| |
| var toggle;
| |
| var list;
| |
| var counters = [0, 0, 0, 0, 0];
| |
|
| |
| function getDepth(heading) {
| |
| var level = parseInt(heading.tagName.replace('H', ''), 10);
| |
| var depth = level - 1;
| |
|
| |
| if (depth < 1) depth = 1;
| |
| if (depth > 5) depth = 5;
| |
|
| |
| return depth;
| |
| }
| |
|
| |
| function getNumber(depth) {
| |
| var i;
| |
| var parts = [];
| |
|
| |
| counters[depth - 1] += 1;
| |
|
| |
| for (i = depth; i < counters.length; i += 1) {
| |
| counters[i] = 0;
| |
| }
| |
|
| |
| for (i = 0; i < depth; i += 1) {
| |
| if (!counters[i]) counters[i] = 1;
| |
| parts.push(counters[i]);
| |
| }
| |
|
| |
| return parts.join('.');
| |
| }
| |
|
| |
| function isExcludedLeadContainer(element) {
| |
| if (!element || !element.matches) return false;
| |
|
| |
| return element.matches(
| |
| 'table, figure, .thumb, .tright, .tleft, .floatright, .floatleft, .floatnone, ' +
| |
| '.infobox, .pvz-cp-infobox, .pvz-character-infobox, .character-infobox, ' +
| |
| '.navbox, .metadata, .catlinks, .mw-references-wrap, ' +
| |
| '.pvz-message, .pvz-custom-toc-wrap, #toc, .toc'
| |
| );
| |
| }
| |
|
| |
| function isLeadParagraph(element) {
| |
| var clone;
| |
| var text;
| |
|
| |
| if (
| |
| !element ||
| |
| element.parentNode !== leadContainer ||
| |
| element.tagName !== 'P'
| |
| ) {
| |
| return false;
| |
| }
| |
|
| |
| if (isExcludedLeadContainer(element)) return false;
| |
|
| |
| if (
| |
| element.querySelector(
| |
| 'style, script, template, noscript, ' +
| |
| 'link[rel="mw-deduplicated-inline-style"], ' +
| |
| '[data-mw-deduplicate]'
| |
| )
| |
| ) {
| |
| return false;
| |
| }
| |
|
| |
| clone = element.cloneNode(true);
| |
|
| |
| clone.querySelectorAll(
| |
| 'figure, .thumb, .mw-file-description, .mw-default-size, ' +
| |
| '.mw-image-border, .reference, sup, .mw-editsection, ' +
| |
| 'style, script, template, noscript, link, [data-mw-deduplicate]'
| |
| ).forEach(function (node) {
| |
| node.remove();
| |
| });
| |
|
| |
| text = (clone.textContent || '').replace(/\s+/g, ' ').trim();
| |
|
| |
| if (!text) return false;
| |
|
| |
| if (
| |
| /^(?:\.mw-parser-output\b|@media\b|@supports\b|@layer\b|:root\b)/.test(text) &&
| |
| /[{};]/.test(text)
| |
| ) {
| |
| return false;
| |
| }
| |
|
| |
| return true;
| |
| }
| |
|
| |
| function findLeadParagraph() {
| |
| var children;
| |
| var i;
| |
| var child;
| |
|
| |
| if (!leadContainer) return null;
| |
|
| |
| children = pvzArray(leadContainer.children);
| |
|
| |
| for (i = 0; i < children.length; i += 1) {
| |
| child = children[i];
| |
|
| |
| if (isExcludedLeadContainer(child)) continue;
| |
|
| |
| if (isLeadParagraph(child)) {
| |
| return child;
| |
| }
| |
| }
| |
|
| |
| return null;
| |
| }
| |
|
| |
| function placeTocAfterLead(tocWrap, paragraph) {
| |
| if (
| |
| !tocWrap ||
| |
| !paragraph ||
| |
| paragraph.parentNode !== leadContainer
| |
| ) {
| |
| return false;
| |
| }
| |
|
| |
| if (
| |
| tocWrap.parentNode === leadContainer &&
| |
| paragraph.nextSibling === tocWrap
| |
| ) {
| |
| return true;
| |
| }
| |
|
| |
| leadContainer.insertBefore(tocWrap, paragraph.nextSibling);
| |
|
| |
| return true;
| |
| }
| |
|
| |
| if (
| |
| !parserOutput ||
| |
| !leadContainer ||
| |
| parserOutput.querySelector('.pvz-no-custom-toc')
| |
| ) {
| |
| return;
| |
| }
| |
|
| |
| leadParagraph = findLeadParagraph();
| |
|
| |
| if (!leadParagraph) return;
| |
|
| |
| existingWrap = parserOutput.querySelector('.pvz-custom-toc-wrap');
| |
|
| |
| if (existingWrap) {
| |
| placeTocAfterLead(existingWrap, leadParagraph);
| |
| pvzQueueTocStabilize();
| |
| return;
| |
| }
| |
|
| |
| headings = pvzArray(
| |
| parserOutput.querySelectorAll('h2, h3, h4, h5, h6')
| |
| ).filter(function (heading) {
| |
| return !pvzSkipHeading(heading);
| |
| });
| |
|
| |
| if (headings.length < 2) return;
| |
|
| |
| nativeTocs = pvzArray(
| |
| parserOutput.querySelectorAll('#toc, .toc')
| |
| ).filter(function (node) {
| |
| return node && !(
| |
| node.classList &&
| |
| node.classList.contains('pvz-custom-toc')
| |
| );
| |
| });
| |
|
| |
| nativeTocs.forEach(function (old) {
| |
| if (old && old.parentNode) {
| |
| old.parentNode.removeChild(old);
| |
| }
| |
| });
| |
|
| |
| wrap = document.createElement('div');
| |
| wrap.className = 'pvz-custom-toc-wrap';
| |
|
| |
| toc = document.createElement('nav');
| |
| toc.className = 'pvz-custom-toc';
| |
| toc.setAttribute('aria-label', '목차');
| |
|
| |
| head = document.createElement('div');
| |
| head.className = 'pvz-custom-toc-head';
| |
|
| |
| title = document.createElement('div');
| |
| title.className = 'pvz-custom-toc-title';
| |
| title.textContent = '목차';
| |
|
| |
| toggle = document.createElement('button');
| |
| toggle.type = 'button';
| |
| toggle.className = 'pvz-custom-toc-toggle';
| |
| toggle.setAttribute('aria-expanded', 'true');
| |
| toggle.textContent = '접기';
| |
|
| |
| toggle.addEventListener('click', function (event) {
| |
| var collapsed;
| |
|
| |
| event.preventDefault();
| |
|
| |
| collapsed = !toc.classList.contains('is-collapsed');
| |
| toc.classList.toggle('is-collapsed', collapsed);
| |
| toggle.setAttribute(
| |
| 'aria-expanded',
| |
| collapsed ? 'false' : 'true'
| |
| );
| |
| toggle.textContent = collapsed ? '펼치기' : '접기';
| |
| });
| |
|
| |
| head.appendChild(title);
| |
| head.appendChild(toggle);
| |
|
| |
| list = document.createElement('ol');
| |
| list.className = 'pvz-custom-toc-list';
| |
|
| |
| headings.forEach(function (heading, index) {
| |
| var depth = getDepth(heading);
| |
| var level = depth + 1;
| |
| var text = pvzHeadingText(heading);
| |
| var anchor = pvzHeadingId(heading, text, index + 1);
| |
| var number = getNumber(depth);
| |
| var item = document.createElement('li');
| |
| var link = document.createElement('a');
| |
| var numberSpan = document.createElement('span');
| |
| var textSpan = document.createElement('span');
| |
|
| |
| item.className =
| |
| 'pvz-custom-toc-item pvz-custom-toc-level-' +
| |
| level +
| |
| ' pvz-custom-toc-depth-' +
| |
| depth;
| |
|
| |
| link.href = '#' + anchor;
| |
|
| |
| numberSpan.className = 'pvz-custom-toc-number';
| |
| numberSpan.textContent = number;
| |
|
| |
| textSpan.className = 'pvz-custom-toc-text';
| |
| textSpan.textContent = text;
| |
|
| |
| link.appendChild(numberSpan);
| |
| link.appendChild(textSpan);
| |
| item.appendChild(link);
| |
| list.appendChild(item);
| |
| });
| |
|
| |
| toc.appendChild(head);
| |
| toc.appendChild(list);
| |
| wrap.appendChild(toc);
| |
|
| |
| placeTocAfterLead(wrap, leadParagraph);
| |
| pvzQueueTocStabilize();
| |
| }
| |
|
| |
|
| |
|
| |
|
| |
| var pvzFooterGapTimer = null;
| |
|
| |
| function pvzGetFooterElement() {
| |
| return document.getElementById('mw-footer-container') ||
| |
| document.getElementById('footer') ||
| |
| document.querySelector('.mw-footer') ||
| |
| document.querySelector('footer');
| |
| }
| |
|
| |
| function pvzUpdateFooterBackdropGapClass() {
| |
| var footer = pvzGetFooterElement();
| |
| var html = document.documentElement;
| |
| var body = document.body;
| |
| var rect;
| |
| var viewportHeight;
| |
| var docHeight;
| |
| var hasGap;
| |
|
| |
| if (!html || !body || !footer || !footer.getBoundingClientRect) return;
| |
|
| |
| html.classList.remove('pvz-footer-backdrop-gap-needed');
| |
| body.classList.remove('pvz-footer-backdrop-gap-needed');
| |
|
| |
| rect = footer.getBoundingClientRect();
| |
| viewportHeight = window.visualViewport && window.visualViewport.height ?
| |
| window.visualViewport.height :
| |
| window.innerHeight;
| |
|
| |
| docHeight = Math.max(
| |
| body.scrollHeight || 0,
| |
| body.offsetHeight || 0,
| |
| html.clientHeight || 0,
| |
| html.scrollHeight || 0,
| |
| html.offsetHeight || 0
| |
| );
| |
|
| |
| /* footer container의 실제 바닥이 viewport 아래까지 닿지 못할 때만 켠다.
| |
| 3px 여유값은 확대/축소 반올림 오차 방지용이다. */
| |
| hasGap = rect.bottom < viewportHeight - 3 && docHeight <= viewportHeight + 6;
| |
|
| |
| if (hasGap) {
| |
| html.classList.add('pvz-footer-backdrop-gap-needed');
| |
| body.classList.add('pvz-footer-backdrop-gap-needed');
| |
| }
| |
| }
| |
|
| |
| function pvzQueueFooterBackdropGapCheck() {
| |
| window.clearTimeout(pvzFooterGapTimer);
| |
| pvzFooterGapTimer = window.setTimeout(pvzUpdateFooterBackdropGapClass, 80);
| |
| }
| |
|
| |
| function pvzStartFooterBackdropGapWatcher() {
| |
| pvzUpdateFooterBackdropGapClass();
| |
|
| |
| window.setTimeout(pvzUpdateFooterBackdropGapClass, 120);
| |
| window.setTimeout(pvzUpdateFooterBackdropGapClass, 400);
| |
| window.setTimeout(pvzUpdateFooterBackdropGapClass, 1000);
| |
|
| |
| window.addEventListener('resize', pvzQueueFooterBackdropGapCheck, { passive: true });
| |
| window.addEventListener('orientationchange', pvzQueueFooterBackdropGapCheck, { passive: true });
| |
| window.addEventListener('load', pvzQueueFooterBackdropGapCheck, { passive: true });
| |
|
| |
| if (window.visualViewport) {
| |
| window.visualViewport.addEventListener('resize', pvzQueueFooterBackdropGapCheck, { passive: true });
| |
| window.visualViewport.addEventListener('scroll', pvzQueueFooterBackdropGapCheck, { passive: true });
| |
| }
| |
|
| |
| if (window.MutationObserver && document.body) {
| |
| var observer = new MutationObserver(pvzQueueFooterBackdropGapCheck);
| |
| observer.observe(document.body, {
| |
| childList: true,
| |
| subtree: true,
| |
| attributes: true,
| |
| attributeFilter: ['class', 'style']
| |
| });
| |
| }
| |
| }
| |
|
| |
|
| |
| function pvzInit() {
| |
| if (!pvzIsTimeless()) return;
| |
|
| |
| pvzApplyInitialTimelessColorMode();
| |
| pvzBindSystemThemeListener();
| |
| pvzBuildCustomSearch();
| |
| pvzMarkReady();
| |
|
| |
| pvzRemoveTimelessColorStripes();
| |
| pvzStartFooterBackdropGapWatcher();
| |
| pvzCreateCustomToc();
| |
| pvzBuildCustomMobileTools();
| |
|
| |
| window.setTimeout(pvzBuildCustomSearch, 120);
| |
| window.setTimeout(pvzBuildCustomMobileTools, 300);
| |
| window.setTimeout(pvzBuildCustomSearch, 600);
| |
| window.setTimeout(pvzBuildCustomMobileTools, 1000);
| |
| }
| |
|
| |
| if (document.readyState === 'loading') {
| |
| document.addEventListener('DOMContentLoaded', pvzInit);
| |
| } else {
| |
| pvzInit();
| |
| }
| |
|
| |
| if (window.mw && mw.hook) {
| |
| mw.hook('wikipage.content').add(function () {
| |
| pvzInit();
| |
| });
| |
| }
| |
|
| |
| if (window.addEventListener) {
| |
| window.addEventListener('resize', function () {
| |
| var shell = document.querySelector('.pvz-custom-mobile-tools-shell');
| |
| pvzApplyToolsCompactState(shell);
| |
| });
| |
| }
| |
|
| |
|
| |
| /* =========================
| |
| v94 쿠키 안내문 portal
| |
| - CSS z-index만으로 footer 뒤에서 빠져나오지 못하는 경우를 해결한다.
| |
| - 쿠키 안내문 DOM을 footer 밖, body 직속으로 옮겨 최상위 fixed 레이어로 표시한다.
| |
| ========================= */
| |
|
| |
| function pvzLooksLikeCookieWarning(el) {
| |
| var text;
| |
| var idClass;
| |
|
| |
| if (!el || el.nodeType !== 1) return false;
| |
|
| |
| text = (el.textContent || '').replace(/\s+/g, ' ').trim();
| |
| idClass = ((el.id || '') + ' ' + (el.className || '')).toLowerCase();
| |
|
| |
| if (text.indexOf('쿠키는 저희의 서비스 전달에 도움을 줍니다') !== -1) return true;
| |
| if (text.indexOf('저희의 쿠키 사용에 동의') !== -1) return true;
| |
| if (text.indexOf('쿠키') !== -1 && text.indexOf('서비스') !== -1 && text.length > 20) return true;
| |
|
| |
| if (idClass.indexOf('cookiewarning') !== -1) return true;
| |
| if (idClass.indexOf('cookie-warning') !== -1) return true;
| |
| if (idClass.indexOf('cookiewarning') !== -1) return true;
| |
| if (idClass.indexOf('mw-cookie') !== -1) return true;
| |
| if (idClass.indexOf('ext-cookie') !== -1) return true;
| |
|
| |
| return false;
| |
| }
| |
|
| |
| function pvzFindCookieWarningElement() {
| |
| var selectors = [
| |
| '#mw-cookiewarning-container',
| |
| '.mw-cookiewarning-container',
| |
| '#mw-cookiewarning',
| |
| '.mw-cookiewarning',
| |
| '.mw-cookie-warning',
| |
| '#cookieWarning',
| |
| '.cookieWarning',
| |
| '.cookie-warning',
| |
| '.ext-cookie-warning',
| |
| '.ext-cookie-warning-container',
| |
| '[id*="cookiewarning" i]',
| |
| '[class*="cookiewarning" i]',
| |
| '[id*="cookie-warning" i]',
| |
| '[class*="cookie-warning" i]',
| |
| '[id*="cookieWarning" i]',
| |
| '[class*="cookieWarning" i]',
| |
| '[id*="mw-cookie" i]',
| |
| '[class*="mw-cookie" i]',
| |
| '[id*="ext-cookie" i]',
| |
| '[class*="ext-cookie" i]'
| |
| ];
| |
| var candidates = [];
| |
| var found;
| |
| var all;
| |
| var i;
| |
| var el;
| |
| var parent;
| |
|
| |
| selectors.forEach(function (selector) {
| |
| try {
| |
| pvzArray(document.querySelectorAll(selector)).forEach(function (node) {
| |
| if (candidates.indexOf(node) === -1) candidates.push(node);
| |
| });
| |
| } catch (e) {}
| |
| });
| |
|
| |
| for (i = 0; i < candidates.length; i += 1) {
| |
| if (pvzLooksLikeCookieWarning(candidates[i])) return candidates[i];
| |
| }
| |
|
| |
| all = document.body ? document.body.querySelectorAll('div, aside, section, p, span') : [];
| |
| for (i = 0; i < all.length; i += 1) {
| |
| el = all[i];
| |
| if (pvzLooksLikeCookieWarning(el)) {
| |
| parent = el.closest('#mw-cookiewarning-container, .mw-cookiewarning-container, #mw-cookiewarning, .mw-cookiewarning, .mw-cookie-warning, #cookieWarning, .cookieWarning, .cookie-warning, .ext-cookie-warning, .ext-cookie-warning-container, div, aside, section') || el;
| |
| return parent;
| |
| }
| |
| }
| |
|
| |
| return null;
| |
| }
| |
|
| |
| function pvzPortalCookieWarning() {
| |
| var el = pvzFindCookieWarningElement();
| |
|
| |
| if (!el || !document.body) return;
| |
|
| |
| el.classList.add('pvz-cookie-warning-portal');
| |
|
| |
| if (el.parentNode !== document.body) {
| |
| document.body.appendChild(el);
| |
| }
| |
| }
| |
|
| |
| function pvzInitCookieWarningPortal() {
| |
| var observer;
| |
|
| |
| pvzPortalCookieWarning();
| |
|
| |
| window.setTimeout(pvzPortalCookieWarning, 250);
| |
| window.setTimeout(pvzPortalCookieWarning, 1000);
| |
| window.setTimeout(pvzPortalCookieWarning, 2500);
| |
|
| |
| if (window.MutationObserver && document.body) {
| |
| observer = new MutationObserver(function () {
| |
| pvzPortalCookieWarning();
| |
| });
| |
|
| |
| observer.observe(document.body, {
| |
| childList: true,
| |
| subtree: true
| |
| });
| |
| }
| |
| }
| |
|
| |
| pvzInitCookieWarningPortal();
| |
|
| |
|
| |
| /* =========================
| |
| 액션바 계열 패널 문서 좌표 고정
| |
| - Timeless 상단 sheet는 v122의 전용 위치 처리에서 직접 absolute 좌표를 계산한다.
| |
| - 아래 document-lock은 그 외 액션바/드롭다운 패널에만 사용한다.
| |
| ========================= */
| |
|
| |
| function pvzGetActionAndTopbarPanels() {
| |
| var selectors = [
| |
| '.pvz-timeless-panel.is-open',
| |
| '.pvz-mobile-panel.is-open',
| |
| '.pvz-timeless-menu.is-open',
| |
| '.pvz-timeless-dropdown.is-open',
| |
| '.pvz-actionbar-panel.is-open',
| |
| '.pvz-action-panel.is-open',
| |
| '.pvz-topbar-panel.is-open',
| |
| '.pvz-topbar-menu.is-open',
| |
| '.pvz-timeless-panel[aria-hidden="false"]',
| |
| '.pvz-mobile-panel[aria-hidden="false"]',
| |
| '.pvz-timeless-menu[aria-hidden="false"]',
| |
| '.pvz-timeless-dropdown[aria-hidden="false"]',
| |
| '[data-pvz-panel-open="true"]',
| |
| '[data-pvz-sheet-open="true"]',
| |
| '[data-pvz-menu-open="true"]'
| |
| ];
| |
|
| |
| var panels = [];
| |
|
| |
| selectors.forEach(function (selector) {
| |
| try {
| |
| pvzArray(document.querySelectorAll(selector)).forEach(function (panel) {
| |
| if (panels.indexOf(panel) === -1) panels.push(panel);
| |
| });
| |
| } catch (e) {}
| |
| });
| |
|
| |
| return panels;
| |
| }
| |
|
| |
| function pvzDocumentLockPanel(panel) {
| |
| var rect;
| |
| var scrollX;
| |
| var scrollY;
| |
| var docTop;
| |
| var docLeft;
| |
| var width;
| |
|
| |
| if (!panel || panel.nodeType !== 1) return;
| |
|
| |
| rect = panel.getBoundingClientRect();
| |
| scrollX = window.pageXOffset || document.documentElement.scrollLeft || 0;
| |
| scrollY = window.pageYOffset || document.documentElement.scrollTop || 0;
| |
|
| |
| docTop = rect.top + scrollY;
| |
| docLeft = rect.left + scrollX;
| |
| width = rect.width || panel.offsetWidth || 0;
| |
|
| |
| panel.dataset.pvzPanelDocumentLocked = 'true';
| |
| panel.dataset.pvzPanelDocumentTop = String(docTop);
| |
| panel.dataset.pvzPanelDocumentLeft = String(docLeft);
| |
| panel.dataset.pvzPanelDocumentWidth = String(width);
| |
|
| |
| panel.style.setProperty('position', 'absolute', 'important');
| |
| panel.style.setProperty('top', docTop + 'px', 'important');
| |
| panel.style.setProperty('left', docLeft + 'px', 'important');
| |
| panel.style.setProperty('right', 'auto', 'important');
| |
| panel.style.setProperty('width', width + 'px', 'important');
| |
| panel.style.setProperty('max-width', 'calc(100vw - 12px)', 'important');
| |
| panel.style.setProperty('transform', 'none', 'important');
| |
| panel.style.setProperty('z-index', '2147483000', 'important');
| |
| }
| |
|
| |
| function pvzRestoreDocumentLockedPanel(panel) {
| |
| var top;
| |
| var left;
| |
| var width;
| |
|
| |
| if (!panel || panel.nodeType !== 1) return;
| |
| if (panel.dataset.pvzPanelDocumentLocked !== 'true') return;
| |
|
| |
| top = panel.dataset.pvzPanelDocumentTop;
| |
| left = panel.dataset.pvzPanelDocumentLeft;
| |
| width = panel.dataset.pvzPanelDocumentWidth;
| |
|
| |
| panel.style.setProperty('position', 'absolute', 'important');
| |
| if (top) panel.style.setProperty('top', top + 'px', 'important');
| |
| if (left) panel.style.setProperty('left', left + 'px', 'important');
| |
| if (width) panel.style.setProperty('width', width + 'px', 'important');
| |
| panel.style.setProperty('right', 'auto', 'important');
| |
| panel.style.setProperty('transform', 'none', 'important');
| |
| }
| |
|
| |
| function pvzDocumentLockOpenPanels() {
| |
| pvzGetActionAndTopbarPanels().forEach(function (panel) {
| |
| if (panel.dataset.pvzPanelDocumentLocked === 'true') {
| |
| pvzRestoreDocumentLockedPanel(panel);
| |
| return;
| |
| }
| |
|
| |
| pvzDocumentLockPanel(panel);
| |
| });
| |
| }
| |
|
| |
| function pvzRecalculateDocumentLockedPanels() {
| |
| pvzGetActionAndTopbarPanels().forEach(function (panel) {
| |
| panel.removeAttribute('data-pvz-panel-document-locked');
| |
| panel.removeAttribute('data-pvz-panel-document-top');
| |
| panel.removeAttribute('data-pvz-panel-document-left');
| |
| panel.removeAttribute('data-pvz-panel-document-width');
| |
|
| |
| panel.style.removeProperty('position');
| |
| panel.style.removeProperty('top');
| |
| panel.style.removeProperty('left');
| |
| panel.style.removeProperty('right');
| |
| panel.style.removeProperty('width');
| |
| panel.style.removeProperty('max-width');
| |
| panel.style.removeProperty('transform');
| |
|
| |
| window.setTimeout(function () {
| |
| pvzDocumentLockPanel(panel);
| |
| }, 0);
| |
| });
| |
| }
| |
|
| |
| function pvzInitPanelDocumentLock() {
| |
| var observer;
| |
|
| |
| pvzDocumentLockOpenPanels();
| |
|
| |
| if (window.MutationObserver && document.body) {
| |
| observer = new MutationObserver(function () {
| |
| pvzDocumentLockOpenPanels();
| |
| });
| |
|
| |
| observer.observe(document.body, {
| |
| attributes: true,
| |
| childList: true,
| |
| subtree: true,
| |
| attributeFilter: ['class', 'style', 'aria-hidden', 'data-pvz-panel-open', 'data-pvz-sheet-open', 'data-pvz-menu-open']
| |
| });
| |
| }
| |
|
| |
| /* 스크롤 중에는 좌표를 새로 계산하지 않는다.
| |
| 기존 코드가 fixed/top 값을 다시 넣는 경우만 문서 좌표 absolute 값으로 되돌린다. */
| |
| window.addEventListener('scroll', function () {
| |
| window.requestAnimationFrame(function () {
| |
| pvzGetActionAndTopbarPanels().forEach(pvzRestoreDocumentLockedPanel);
| |
| });
| |
| }, { passive: true });
| |
|
| |
| window.addEventListener('resize', function () {
| |
| window.clearTimeout(pvzInitPanelDocumentLock._resizeTimer);
| |
| pvzInitPanelDocumentLock._resizeTimer = window.setTimeout(
| |
| pvzRecalculateDocumentLockedPanels,
| |
| 260
| |
| );
| |
| }, { passive: true });
| |
|
| |
| function pvzQueueDocumentLockedPanelRecalculation() {
| |
| window.setTimeout(pvzRecalculateDocumentLockedPanels, 520);
| |
| }
| |
|
| |
| if (
| |
| window.screen &&
| |
| window.screen.orientation &&
| |
| typeof window.screen.orientation.addEventListener === 'function'
| |
| ) {
| |
| window.screen.orientation.addEventListener(
| |
| 'change',
| |
| pvzQueueDocumentLockedPanelRecalculation
| |
| );
| |
| } else {
| |
| window.addEventListener(
| |
| 'orientationchange',
| |
| pvzQueueDocumentLockedPanelRecalculation,
| |
| { passive: true }
| |
| );
| |
| }
| |
| }
| |
|
| |
| pvzInitPanelDocumentLock();
| |
|
| |
| }());
| |