모듈:노멀가이각주

이 모듈에 대한 설명문서는 모듈:노멀가이각주/설명문서에서 만들 수 있습니다

-- 노멀가이각주 2026-08-18 v7.4.1
local p = {}

local function trim(value)
	if value == nil then
		return ''
	end

	return mw.text.trim(tostring(value))
end

local function getArgs(frame)
	local parent = frame:getParent()

	if parent then
		return parent.args
	end

	return frame.args
end

local function hash(value)
	return mw.hash.hashValue('md5', tostring(value or '')):sub(1, 16)
end

local function makeLink(target, text)
	return '[[' .. target .. '|' .. text .. ']]'
end

local function isBooleanFlag(value)
	value = mw.ustring.lower(trim(value))

	return value == '1'
		or value == 'yes'
		or value == 'true'
		or value == '예'
		or value == '사용'
end

local function isReuseWord(value)
	value = mw.ustring.lower(trim(value))

	return value == '재사용'
		or value == 'reuse'
		or value == '같은각주'
		or value == '반복'
end

local function errorMessage(message)
	local node = mw.html.create('span')
		:addClass('error')
		:wikitext('노멀가이각주 오류: ' .. message)

	return tostring(node)
end

local function getDefinitionData(args)
	local arg1 = trim(args[1])
	local arg2 = trim(args[2])
	local label = trim(args['표시'])

	if label == '' then
		label = trim(args.label)
	end

	if label == '' then
		label = trim(args['이름표'])
	end

	local content = trim(args['내용'])

	if content == '' then
		content = trim(args.content)
	end

	if arg2 ~= '' then
		if label == '' then
			label = arg1
		end

		if content == '' then
			content = arg2
		end
	elseif content == '' then
		content = arg1
	elseif label == '' and arg1 ~= '' then
		label = arg1
	end

	return label, content
end

local function getReuseData(args, reuseValue)
	local arg1 = trim(args[1])
	local arg2 = trim(args[2])
	local arg3 = trim(args[3])
	local label = trim(args['표시'])

	if label == '' then
		label = trim(args.label)
	end

	if label == '' then
		label = trim(args['이름표'])
	end

	if label == '' and arg1 ~= '' then
		label = arg1
	end

	if label == '' and not isBooleanFlag(reuseValue) then
		label = reuseValue
	end

	local popupContent = trim(args['팝업'])

	if popupContent == '' then
		popupContent = trim(args['내용'])
	end

	if popupContent == '' then
		popupContent = trim(args.content)
	end

	-- {{노멀가이각주|A|재사용|원문}}에서는 세 번째 인수가
	-- 재사용 팝업에 표시할 원문이다.
	if popupContent == '' and arg3 ~= '' then
		popupContent = arg3
	elseif popupContent == '' and arg2 ~= '' and not isReuseWord(arg2) then
		popupContent = arg2
	end

	return label, popupContent
end

function p.note(frame)
	local args = getArgs(frame)
	local reuseValue = trim(args['재사용'])
	local reuse = reuseValue ~= '' or isReuseWord(args[2])
	local label
	local content

	if reuse then
		label, content = getReuseData(args, reuseValue)
	else
		label, content = getDefinitionData(args)
	end

	if not reuse and content == '' then
		return errorMessage('각주 내용이 입력되지 않았습니다.')
	end

	local referenceName = trim(args['이름'])

	if reuse and referenceName == '' then
		if isBooleanFlag(reuseValue) then
			referenceName = label
		else
			referenceName = reuseValue
		end
	end

	-- 표시 이름이 있는 각주는 같은 이름끼리 자동으로 묶는다.
	if referenceName == '' and label ~= '' then
		referenceName = label
	end

	if reuse and referenceName == '' then
		return errorMessage('재사용할 각주의 이름이 없습니다.')
	end

	local title = mw.title.getCurrentTitle().prefixedText
	local key = trim(args['키'])

	if referenceName ~= '' and key ~= '' then
		referenceName = referenceName .. '|key=' .. key
	end

	local noteHash

	if referenceName ~= '' then
		noteHash = hash('named:' .. referenceName)
	else
		noteHash = hash(table.concat({
			'note',
			title,
			content,
			key
		}, ':'))
	end

	local bodyId = 'ngfn-body-' .. noteHash
	local entryId = 'ngfn-entry-' .. noteHash
	-- 모든 각주에 안전한 Cite 이름을 붙여 본문 표시가 언제나
	-- Cite의 실제 목록 링크와 복귀 링크를 사용하게 한다.
	local citeName

	if referenceName ~= '' then
		citeName = 'ngfn-' .. hash(referenceName)
	else
		citeName = 'ngfn-' .. noteHash
	end

	local referenceContent = ''

	if not reuse then
		local entry = mw.html.create('span')
			:addClass('ngfn-list-entry')

		-- Cite가 만드는 목록 번호와 무관한 고유 목적지다.
		-- 팝업 링크는 이 요소를 직접 가리키므로 몇 번째 각주인지
		-- Lua에서 추측할 필요가 없다.
		entry:tag('span')
			:addClass('ngfn-list-target')
			:attr('id', entryId)
			:attr('tabindex', '-1')

		local listLabel = entry:tag('span')
			:addClass('ngfn-list-label')

		if label ~= '' then
			listLabel
				:addClass('ngfn-list-label-custom')
				:wikitext(makeLink('#' .. bodyId, '[' .. label .. ']'))
		else
			listLabel
				:addClass('ngfn-list-label-auto')
				:wikitext(makeLink('#' .. bodyId, '​'))
		end

		entry:tag('span')
			:addClass('ngfn-list-body')
			:wikitext(content)

		referenceContent = tostring(entry)
	end

	local reference

	if reuse then
		-- frame:extensionTag에 nil을 넣으면 빈 <ref></ref>가 되어 오류가 날 수 있다.
		-- 이름은 MD5 기반 ASCII 문자열이므로 안전하게 self-closing ref를 만든다.
		reference = frame:preprocess('<ref name="' .. citeName .. '" />')
	else
		local refArgs = {
			name = citeName
		}

		reference = frame:extensionTag('ref', referenceContent, refArgs)
	end

	local root = mw.html.create('span')
		:addClass('ngfn')
		:addClass(reuse and 'ngfn-reuse' or 'ngfn-definition')
		:attr('data-ngfn-version', '2026-08-18-v7.4.1')

	if not reuse then
		root:attr('id', bodyId)
	end

	if label ~= '' then
		root:addClass('ngfn-custom')
	end

	-- 팝업은 각주 표시를 기준으로 항상 가운데 정렬한다.
	-- 예전에 넣은 |정렬=오른쪽 같은 인수가 남아 있어도 무시한다.
	root:addClass('ngfn-align-center')

	root:wikitext(reference)

	if label ~= '' then
		root:tag('span')
			:addClass('ngfn-custom-marker')
			:wikitext('[' .. label .. ']')
	end

	-- 모바일에서는 실제 각주 링크와 미리보기 버튼을 분리한다.
	root:tag('span')
		:addClass('ngfn-mobile-open')
		:attr('tabindex', '0')
		:attr('role', 'button')
		:attr('aria-label', '각주 미리보기')
		:attr('title', '각주 미리보기')
		:wikitext('ⓘ')

	local popup = root:tag('span')
		:addClass('ngfn-popup')
		:attr('tabindex', '0')
		:attr('role', 'dialog')

	local popupLine = popup:tag('span')
		:addClass('ngfn-popup-line')

	local popupLinkText

	if label ~= '' then
		popupLinkText = '[' .. label .. ']'
	else
		-- 실제 번호는 Cite가 렌더링한 뒤 CSS가 sup.reference의 ID에서 읽는다.
		popupLinkText = '<span class="ngfn-popup-label-auto">[각주]</span>'
	end

	-- MediaWiki는 생짜 <a> 출력을 문자로 표시할 수 있으므로
	-- 내부 링크 문법으로 안전한 fragment 링크를 생성한다.
	popupLine:tag('span')
		:addClass('ngfn-popup-jump')
		:wikitext(makeLink('#' .. entryId, popupLinkText))

	local popupContent = content

	if reuse and popupContent == '' then
		popupContent = '동일한 각주입니다. 각주 표시를 눌러 목록에서 확인할 수 있습니다.'
	end

	popupLine:tag('span')
		:addClass('ngfn-popup-content')
		:wikitext(' ' .. popupContent)

	-- 모바일 팝업 하단의 전용 닫기 버튼.
	-- CSS의 :has()가 이 버튼의 포커스/터치를 감지해 팝업을 닫는다.
	popup:tag('span')
		:addClass('ngfn-mobile-close')
		:addClass('mobile-close-btn')
		:attr('tabindex', '0')
		:attr('role', 'button')
		:attr('aria-label', '각주 팝업 닫기')
		:attr('title', '각주 팝업 닫기')
		:css('display', 'none')
		:wikitext('[닫기]')

	root:tag('span')
		:addClass('ngfn-dimmer')
		:attr('tabindex', '0')
		:attr('role', 'button')
		:attr('aria-label', '각주 닫기')
		:attr('title', '각주 닫기')

	return tostring(root)
end

-- 실제로 실행 중인 모듈과 스타일을 문서에서 바로 확인한다.
-- 사용: {{#invoke:노멀가이각주|version}}
function p.version()
	local node = mw.html.create('span')
		:addClass('ngfn-version-check')
		:wikitext('Lua v7.4.1 / CSS ')

	node:tag('span')
		:addClass('ngfn-version-css-fallback')
		:wikitext('미적용')

	return tostring(node)
end

function p.list(frame)
	local references = frame:extensionTag('references', '')
	local container = mw.html.create('div')
		:addClass('ngfn-list')
		:attr('id', 'ngfn-list')
		:wikitext(references)

	return tostring(container)
end

p.main = p.note

return p