사용자:Hsl0/연구소/5.js
보이기
참고: 설정을 저장한 후에 바뀐 점을 확인하기 위해서는 브라우저의 캐시를 새로 고쳐야 합니다.
- 파이어폭스 / 사파리: Shift 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5 또는 Ctrl-R을 입력 (Mac에서는 ⌘-R)
- 구글 크롬: Ctrl-Shift-R키를 입력 (Mac에서는 ⌘-Shift-R)
- 엣지: Ctrl 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5를 입력.
(() => {
const body = document.body;
const title = document.getElementById('firstHeading');
const root = document.querySelector(
'#mw-content-text > .mw-parser-output'
);
const toolList = document.querySelector(
'#p-cactions ul, #p-tb ul'
);
if (!title || !root || !toolList) return;
if (document.getElementById('ca-lg-immersive')) return;
if (
root.classList.contains('game-layout') ||
document.getElementById('lg-menu-button')
) {
console.warn('새로고침 후 기존 JS 대신 실행하세요.');
return;
}
let active = false;
let cardState = null;
let controls = null;
const toolItem = document.createElement('li');
toolItem.id = 'ca-lg-immersive';
const modeLink = document.createElement('a');
modeLink.href = '#';
modeLink.textContent = '몰입 모드 켜기';
toolItem.append(modeLink);
toolList.append(toolItem);
modeLink.addEventListener('click', event => {
event.preventDefault();
body.classList.add('lg-immersive');
});
function visibleLinks(element) {
return [...element.querySelectorAll('a[href]')].filter(link =>
link.getClientRects().length > 0 &&
link.textContent.replace(/[\s\u200b]/g, '') !== ''
);
}
function isArrow(link) {
return [...link.querySelectorAll('span')].some(
span => span.textContent.trim() === '→'
);
}
function isAction(element) {
if (element.matches('ul')) {
const items = [...element.children];
return items.length > 0 && items.every(item =>
item.matches('li') &&
!item.querySelector(
'ul, ol, table, input, button, select, textarea'
) &&
visibleLinks(item).length === 1
);
}
if (element.matches('p')) {
const links = visibleLinks(element);
return links.length === 1 &&
isArrow(links[0]) &&
element.textContent.trim().length < 160 &&
!element.querySelector(
'img, input, button, select, textarea'
);
}
return false;
}
function createCards() {
const blocks = [...root.children].filter(element =>
!element.matches('style, link, script') &&
getComputedStyle(element).display !== 'none' &&
(
element.textContent.replace(/[\s\u200b]/g, '') !== '' ||
element.querySelector(
'img, input, canvas, iframe, video'
)
)
);
const actionBlocks = [];
for (let index = blocks.length - 1; index >= 0; index--) {
if (!isAction(blocks[index])) break;
actionBlocks.unshift(blocks[index]);
}
if (!actionBlocks.length) return;
const originalNodes = [...root.childNodes];
const context = document.createElement('div');
context.className = 'game-card game-context';
const actions = document.createElement('div');
actions.className = 'game-card game-actions';
for (const node of originalNodes) {
(actionBlocks.includes(node) ? actions : context).append(node);
}
const markedLinks = [];
for (const link of actions.querySelectorAll('a[href]')) {
if (isArrow(link) && !link.classList.contains('game-button')) {
link.classList.add('game-button');
markedLinks.push(link);
}
}
root.append(context, actions);
root.classList.add('game-layout');
cardState = { originalNodes, context, actions, markedLinks };
}
function restoreCards() {
if (!cardState) return;
const { originalNodes, context, actions, markedLinks } = cardState;
for (const link of markedLinks) {
link.classList.remove('game-button');
}
root.append(...originalNodes);
context.remove();
actions.remove();
root.classList.remove('game-layout');
cardState = null;
}
const icons = {
menu: '<path d="M5 7h14M5 12h14M5 17h14"/>',
settings: `
<circle cx="12" cy="12" r="7"/>
<circle cx="12" cy="12" r="3"/>
<path d="
M12 2v3 M12 19v3
M2 12h3 M19 12h3
M5 5l2 2 M17 17l2 2
M5 19l2-2 M17 7l2-2
"/>
`,
info: `
<circle cx="12" cy="12" r="9"/>
<path d="M12 11v6M12 7v.1"/>
`
};
function makeButton(icon, label) {
const button = document.createElement('button');
button.type = 'button';
button.className = 'lg-icon-button';
button.title = label;
button.setAttribute('aria-label', label);
button.innerHTML = `
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.7"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>${icons[icon]}</svg>
`;
return button;
}
function createControls() {
const menuButton = makeButton('menu', '리버티게임 메뉴');
menuButton.id = 'lg-menu-button';
menuButton.classList.add('lg-menu-button', 'lg-controls');
const infoButton = makeButton('info', '게임 정보 및 문서 도구');
// 각 버튼은 몰입 모드 클래스만 삭제합니다.
menuButton.addEventListener('click', () => {
body.classList.remove('lg-immersive');
});
infoButton.addEventListener('click', () => {
body.classList.remove('lg-immersive');
});
const settingsButton = makeButton('settings', '설정 (시각적 시안)');
settingsButton.disabled = true;
const tools = document.createElement('div');
tools.className = 'lg-tools lg-controls';
tools.append(settingsButton, infoButton);
title.before(menuButton);
title.after(tools);
controls = { menuButton, tools };
}
function removeControls() {
if (!controls) return;
controls.menuButton.remove();
controls.tools.remove();
controls = null;
}
function syncMode() {
const enabled = body.classList.contains('lg-immersive');
if (enabled === active) return;
active = enabled;
if (enabled) {
createCards();
createControls();
} else {
removeControls();
restoreCards();
}
}
new MutationObserver(syncMode).observe(body, {
attributes: true,
attributeFilter: ['class']
});
syncMode();
})();