미디어위키:Gadget-CU.js
외관
참고: 설정을 저장한 후에 바뀐 점을 확인하기 위해서는 브라우저의 캐시를 새로 고쳐야 합니다.
- 파이어폭스 / 사파리: Shift 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5 또는 Ctrl-R을 입력 (Mac에서는 ⌘-R)
- 구글 크롬: Ctrl-Shift-R키를 입력 (Mac에서는 ⌘-Shift-R)
- 엣지: Ctrl 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5를 입력.
const MW_BASE_URL = globalThis.MW_BASE_URL || ''
async function fetchJSON(title) {
const response = await fetch(
`${MW_BASE_URL}/rest.php/v1/page/${encodeURIComponent(title)}`,
)
if (!response.ok) {
throw new Error(`Fetch failed with ${response.status}`)
}
const payload = await response.json()
payload.source = JSON.parse(payload.source)
return payload
}
async function fetchCache(title) {
if (!sessionStorage.getItem(title)) {
await navigator.locks.request(title, async (lock) => {
const payload = await fetchJSON(title)
sessionStorage.setItem(title, JSON.stringify(payload))
})
}
try {
return JSON.parse(sessionStorage.getItem(title))
} catch (e) {
sessionStorage.removeItem(title)
throw e
}
}
async function fetchData(ver, lang) {
const prefix = `CU/data/${ver}`
const promises = {
locale: `${prefix}/${lang}.json`,
items: `${prefix}/items.json`,
liquids: `${prefix}/liquids.json`,
}
return Object.fromEntries(
await Promise.all(
Object.entries(promises).map(async ([k, v]) => [k, await fetchCache(v)]),
),
)
}
const locale = {
get: (key, lang = null) => {
const targetLocale =
(lang ? locale[lang] : locale[document.documentElement.lang]) || locale.en
return targetLocale[key] || key
},
en: {
toggleRetroFont: 'Toggle Retro Font',
},
ko: {
toggleRetroFont: '레트로 글꼴 전환',
},
}
/**
* @type {Object<string, () => void>}
*/
const scripts = {
NoRetroFonts: () => {
const dataKey = 'noRetroFont'
let dataValue = localStorage.getItem(dataKey) == 'true'
const apply = () => {
delete document.documentElement.dataset[dataKey]
if (dataValue) {
document.documentElement.dataset[dataKey] = ''
}
}
// 네비게이션 요소
$('#p-navigation ul').append(
$('<li id="n-noRetroFont" class="mw-list-item">').append(
$('<a href="#">')
.text(locale.get('toggleRetroFont'))
.on('click', (e) => {
e.preventDefault()
dataValue = !dataValue
localStorage.setItem(dataKey, dataValue)
apply()
}),
),
)
apply()
},
Tooltip: () => {
const $body = $('body')
let $tooltip = null
let $target = null
/**
* @param {PointerEvent} e
* @returns
*/
async function onPointerEnter(e) {
const $currentTarget = $(this)
const id = $currentTarget.data('cui')
if (!id || $currentTarget.is($target)) {
return
}
$target = $currentTarget
const ver = $currentTarget.data('cuiVer') || '6.1'
const lang = $currentTarget.data('cuiLang') || 'EN'
const rawLiquids = $currentTarget.data('cuiLiquids')
const containedLiquids = rawLiquids
? rawLiquids.split(';').map((v) => v.split(','))
: []
const { locale, items, liquids } = await fetchData(ver, lang)
// 타겟 요소가 바뀌었으면 넘어가기
if (!$currentTarget.is($target)) {
return
}
$target = $currentTarget
const item = items.source[id]
let itemValue = item.value
let itemActuallyUsableOnLimb = item.usableOnLimb
const headTags = [
$('<p class="essential">').text(locale.source.main[id] || id),
]
const footTags = [
$('<p class="essential">').text(locale.source.main[id + 'dsc'] || ''),
]
const liquidTags = containedLiquids.map(([id, amount]) => {
const liquid = liquids.source[id]
itemValue += liquid.valuePerLiter * (amount / 1000)
if (liquid.healthUsable) {
itemActuallyUsableOnLimb = true
}
return $('<section class="cu-liquid">')
.css(
'color',
`color(srgb ${liquid.color.r} ${liquid.color.g} ${liquid.color.b})`,
)
.append(
$('<p class="essential">').text(
`${locale.source.other[liquid.localeName]} (${amount}ml)`,
),
$('<p>').text(locale.source.other[liquid.localeName + 'dsc'] || ''),
)
})
const usableTags = []
// 액체가 들어있을 수 있는 경우
if (item.capacity > 0) {
footTags.push($('<section class="cu-liquids">').append(liquidTags))
}
// 무게가 있는 경우
if (item.weight > 0) {
footTags.push(
$('<p class="essential">')
.append('<i class="cu-icon c-0"></i>')
.append(`${locale.source.other.weight}: ${item.weight || 0}u`),
)
}
// 손으로만 들 수 있는 경우
if (item.onlyHoldInHands) {
footTags.push(
$('<p style="color:#ff8787">')
.append('<i class="cu-icon c-6"></i>')
.append(locale.source.other.itemonlyinhands),
)
}
// 사용할 수 있는 경우
if (item.usable) {
usableTags.push(
$('<span style="color:#a6ffaa">')
.append('<i class="cu-icon c-6"></i>')
.append(
item.usableWithLMB
? locale.source.other.itemusablehand
: locale.source.other.itemusableinventory,
),
)
}
// 사지에 사용할 수 있는 경우
if (itemActuallyUsableOnLimb) {
usableTags.push(
$('<span style="color:#fffb91">')
.append('<i class="cu-icon c-12"></i>')
.append(locale.source.other.itemusablewound),
)
}
// 사용할 수 있는 경우
if (usableTags.length > 0) {
footTags.push(
$('<p style="color:#a3a3a3">')
.append('<i class="cu-icon c-11"></i>')
.append(locale.source.other.itemusable)
.append(
usableTags.reduce(
(p, c) => (p.length > 0 ? [...p, '/', c] : [c]),
[],
),
),
)
}
// 가치가 있는 경우
if (itemValue > 0) {
const displayItemValue = Math.round(Math.min(itemValue, 50))
footTags.push(
$('<p style="color:#f6ff73">')
.append('<i class="cu-icon c-9"></i>')
.append(`${locale.source.other.itemvalue}${displayItemValue}c`),
)
}
$tooltip = $('<div class="cu-tooltip"></div>')
.append($('<section>').append(headTags))
.append('<br>')
.append($('<section>').append(footTags))
if (e.shiftKey) {
$tooltip.attr('open', '')
}
$body.append($tooltip)
}
/**
* @param {PointerEvent} e
* @returns
*/
function onPointerLeave(e) {
if ($tooltip) {
$tooltip.remove()
$tooltip = null
$target = null
}
$target = null
}
/**
* @param {PointerEvent} e
* @returns
*/
function onPointerMove(e) {
if (!$tooltip || !$target) {
return
}
const event = e.type.includes('touch') ? e.originalEvent.touches[0] : e
const gap = parseInt($tooltip.data('cuiGap'), 10) || 14
const x = event.clientX + gap
const y = event.clientY + gap
requestAnimationFrame(() => {
if ($tooltip) {
$tooltip.css({
left: `${Math.max(0, Math.min(x, window.innerWidth - $tooltip.outerWidth(true)))}px`,
top: `${Math.max(0, Math.min(y, window.innerHeight - $tooltip.outerHeight(true)))}px`,
})
}
})
}
/**
* @param {KeyboardEvent} e
* @returns
*/
function onKey(e) {
if ($tooltip) {
$tooltip.removeAttr('open')
if (e.shiftKey) {
$tooltip.attr('open', '')
}
}
}
$('[data-cui]')
.on('pointerenter', onPointerEnter)
.on('pointerleave', onPointerLeave)
.on('pointermove', onPointerMove)
$('body').on('keyup keydown', onKey)
},
}
function setup() {
$('html').addClass('gadget-cu')
for (const [name, func] of Object.entries(scripts)) {
try {
func(name)
} catch (e) {
console.error(name, e)
}
}
}
if (globalThis.mw) {
mw.loader.using(['mediawiki.util', 'jquery'], () => {
$(() => {
if (mw.config.get('wgCategories').includes('Casualties: Unknown')) {
setup()
}
})
})
} else {
setup()
}