편집 요약 없음 |
편집 요약 없음 |
||
| 38번째 줄: | 38번째 줄: | ||
end | end | ||
end | end | ||
-- 이미지를 감싸는 위키텍스트 생성 | |||
if #images == 0 then | if #images == 0 then | ||
return '[[파일:NoImage.png|200px]]' | return '[[파일:NoImage.png|200px]]' | ||
| 62번째 줄: | 63번째 줄: | ||
local jp = trim(args['이름_일어'] or '') | local jp = trim(args['이름_일어'] or '') | ||
local en = trim(args['이름_영어'] or '') | local en = trim(args['이름_영어'] or '') | ||
local parts = {} | local parts = {} | ||
-- HTML 코드를 Lua에서 직접 생성하지 않고, 위키텍스트 문자열로 반환 | |||
if kr ~= '' then table.insert(parts, string.format('<div class="prf-name-kr">%s</div>', mw.text.escape(kr))) end | if kr ~= '' then table.insert(parts, string.format('<div class="prf-name-kr">%s</div>', mw.text.escape(kr))) end | ||
if jp ~= '' or en ~= '' then | if jp ~= '' or en ~= '' then | ||
local second = '' | local second = '' | ||
| 75번째 줄: | 77번째 줄: | ||
table.insert(parts, string.format('<div class="prf-name-sub">%s</div>', second)) | table.insert(parts, string.format('<div class="prf-name-sub">%s</div>', second)) | ||
end | end | ||
return table.concat(parts, '\n') | return table.concat(parts, '\n') | ||
end | end | ||
| 82번째 줄: | 83번째 줄: | ||
local function row(label, value) | local function row(label, value) | ||
if not value or value == '' then return '' end | if not value or value == '' then return '' end | ||
-- 이 함수는 HTML 문자열을 반환 | |||
return string.format('<div class="prf-row"><strong>%s</strong> %s</div>', mw.text.escape(label), mw.text.escape(value)) | return string.format('<div class="prf-row"><strong>%s</strong> %s</div>', mw.text.escape(label), mw.text.escape(value)) | ||
end | end | ||
| 89번째 줄: | 91번째 줄: | ||
local nv = trim(args['나이_VN']) | local nv = trim(args['나이_VN']) | ||
local na = trim(args['나이_애니']) | local na = trim(args['나이_애니']) | ||
-- 이 함수는 HTML 문자열을 반환 | |||
if nv ~= '' and na ~= '' then | if nv ~= '' and na ~= '' then | ||
return string.format('<div class="prf-row"><strong>나이</strong> VN %s / 애니 %s</div>', mw.text.escape(nv), mw.text.escape(na)) | return string.format('<div class="prf-row"><strong>나이</strong> VN %s / 애니 %s</div>', mw.text.escape(nv), mw.text.escape(na)) | ||
| 104번째 줄: | 108번째 줄: | ||
local parent = frame:getParent() or frame | local parent = frame:getParent() or frame | ||
local args = parent.args or {} | local args = parent.args or {} | ||
-- frame이 유효하지 않으면 에러 방지 | |||
if not frame.preprocess then | |||
return "스크립트 오류: Frame 객체가 유효하지 않습니다." | |||
end | |||
for k, v in pairs(args) do args[k] = trim(v) end | for k, v in pairs(args) do args[k] = trim(v) end | ||
local theme = args['색상'] and args['색상'] ~= '' and args['색상'] or '#4cc9ff' | local theme = args['색상'] and args['색상'] ~= '' and args['색상'] or '#4cc9ff' | ||
local image_block = build_image_block(args) | local image_block = build_image_block(args) | ||
local name_block = build_name_block(args) | local name_block = build_name_block(args) | ||
-- 기본 정보 | -- 기본 정보 | ||
local basic = '' | local basic = '' | ||
| 120번째 줄: | 129번째 줄: | ||
basic = basic .. row('머리색', args['머리색']) | basic = basic .. row('머리색', args['머리색']) | ||
basic = basic .. row('눈색', args['눈색']) | basic = basic .. row('눈색', args['눈색']) | ||
-- FC 프로필 | -- FC 프로필 | ||
local fc = '' | local fc = '' | ||
| 134번째 줄: | 143번째 줄: | ||
fc = fc .. tstr | fc = fc .. tstr | ||
end | end | ||
-- 개인정보 (학교는 개인정보 안에) | -- 개인정보 (학교는 개인정보 안에) | ||
local personal = '' | local personal = '' | ||
| 141번째 줄: | 150번째 줄: | ||
personal = personal .. row('취미', args['취미']) | personal = personal .. row('취미', args['취미']) | ||
personal = personal .. row('좋아하는 음식', args['좋아하는음식']) | personal = personal .. row('좋아하는 음식', args['좋아하는음식']) | ||
-- 성우 | -- 성우 | ||
local voice = '' | local voice = '' | ||
voice = voice .. row('일본어', args['성우_JP']) | voice = voice .. row('일본어', args['성우_JP']) | ||
voice = voice .. row('영어', args['성우_EN']) | voice = voice .. row('영어', args['성우_EN']) | ||
-- 데뷔 | -- 데뷔 | ||
local debut = '' | local debut = '' | ||
| 152번째 줄: | 161번째 줄: | ||
debut = debut .. row('게임(VN)', args['게임_데뷔']) | debut = debut .. row('게임(VN)', args['게임_데뷔']) | ||
debut = debut .. row('만화', args['만화_데뷔']) | debut = debut .. row('만화', args['만화_데뷔']) | ||
-- HTML 구성 | -- HTML 구성 | ||
local html = mw.html.create() | local html = mw.html.create() | ||
html:tag('div'):addClass('prf-card'):cssText('display:flex;gap:14px;border-radius:10px;padding:12px;align-items:flex-start;'):done() | html:tag('div'):addClass('prf-card'):cssText('display:flex;gap:14px;border-radius:10px;padding:12px;align-items:flex-start;'):done() | ||
-- 좌측 이미지 | -- 좌측 이미지 (frame:preprocess 사용으로 wikitext 에러 해결) | ||
local left = mw.html.create('div'):addClass('prf-left'):cssText('flex:0 0 36%;min-width:200px;') | local left = mw.html.create('div'):addClass('prf-left'):cssText('flex:0 0 36%;min-width:200px;') | ||
left: | left:node(frame:preprocess(image_block)) | ||
html:node(left) | html:node(left) | ||
-- 우측 | -- 우측 (모든 wikitext는 frame:preprocess로 처리) | ||
local right = mw.html.create('div'):addClass('prf-right'):cssText('flex:1 1 auto;') | local right = mw.html.create('div'):addClass('prf-right'):cssText('flex:1 1 auto;') | ||
-- 이름 블록 | |||
right:tag('div'):addClass('prf-name'):node(frame:preprocess(name_block)):done() | |||
if basic ~= '' then right: | -- 섹션별 정보 | ||
if fc ~= '' then right: | if basic ~= '' then | ||
if personal ~= '' then right: | right:tag('div'):addClass('prf-section'):tag('h4'):text('기본 정보'):done() | ||
if voice ~= '' then right: | right:node(frame:preprocess(basic)) | ||
if debut ~= '' then right: | end | ||
if fc ~= '' then | |||
right:tag('div'):addClass('prf-section'):tag('h4'):text('FC 프로필'):done() | |||
right:node(frame:preprocess(fc)) | |||
end | |||
if personal ~= '' then | |||
right:tag('div'):addClass('prf-section'):tag('h4'):text('개인 정보'):done() | |||
right:node(frame:preprocess(personal)) | |||
end | |||
if voice ~= '' then | |||
right:tag('div'):addClass('prf-section'):tag('h4'):text('성우'):done() | |||
right:node(frame:preprocess(voice)) | |||
end | |||
if debut ~= '' then | |||
right:tag('div'):addClass('prf-section'):tag('h4'):text('데뷔'):done() | |||
right:node(frame:preprocess(debut)) | |||
end | |||
html:node(right) | html:node(right) | ||
-- 인라인 CSS (반응형 포함) - 테마 색상 적용 | -- 인라인 CSS (반응형 포함) - 테마 색상 적용 | ||
local style = string.format([[ | local style = string.format([[<style>.prf-card { border:2px solid %s; background:#ffffff; }.prf-name-kr { font-size:1.25em; font-weight:700; color:%s; margin-bottom:4px; }.prf-name-sub { font-size:0.95em; color:#444; margin-bottom:8px; }.prf-row { margin:4px 0; }.prf-section h4 { margin:8px 0 6px 0; color:%s; font-size:0.95em; }.sig-list { margin:4px 0 0 18px; padding:0; }.tabber { margin-bottom:8px; }@media screen and (max-width:768px) { .prf-card { flex-direction:column; } .prf-left { width:100%%; min-width:0; }}</style>]], theme, theme, theme) | ||
<style> | |||
.prf-card { border:2px solid %s; background:#ffffff; } | -- 최종 출력: 스타일 + HTML 객체 | ||
.prf-name-kr { font-size:1.25em; font-weight:700; color:%s; margin-bottom:4px; } | |||
.prf-name-sub { font-size:0.95em; color:#444; margin-bottom:8px; } | |||
.prf-row { margin:4px 0; } | |||
.prf-section h4 { margin:8px 0 6px 0; color:%s; font-size:0.95em; } | |||
.sig-list { margin:4px 0 0 18px; padding:0; } | |||
.tabber { margin-bottom:8px; } | |||
@media screen and (max-width:768px) { | |||
} | |||
</style> | |||
]], theme, theme, theme) | |||
return style .. tostring(html) | return style .. tostring(html) | ||
end | end | ||
return p | return p | ||
2025년 12월 5일 (금) 02:53 판
이 모듈에 대한 설명문서는 모듈:푸른 저편의 포리듬/ 캐릭터 프로필/설명문서에서 만들 수 있습니다
local p = {}
local mw = mw
local tt = mw.text
-- 안전 문자열 트림
local function trim(s)
if not s then return '' end
return tt.trim(tostring(s))
end
-- 시그니처 기술 수집 (시그니처기술1..10)
local function gather_techs(args)
local techs = {}
for i = 1, 10 do
local k = '시그니처기술' .. i
if args[k] and args[k] ~= '' then
table.insert(techs, trim(args[k]))
end
end
return techs
end
-- 이미지 블록 (VN 먼저, 애니 다음). 파일명은 그대로 사용.
local function build_image_block(args)
local images = {}
if args['이미지_VN'] and args['이미지_VN'] ~= '' then
table.insert(images, { title = "비주얼 노벨", file = trim(args['이미지_VN']) })
end
if args['이미지_애니'] and args['이미지_애니'] ~= '' then
table.insert(images, { title = "애니메이션", file = trim(args['이미지_애니']) })
end
if args['이미지_추가'] and args['이미지_추가'] ~= '' then
for img in string.gmatch(args['이미지_추가'], '([^,]+)') do
img = trim(img)
if img ~= '' then
table.insert(images, { title = "추가", file = img })
end
end
end
-- 이미지를 감싸는 위키텍스트 생성
if #images == 0 then
return '[[파일:NoImage.png|200px]]'
elseif #images == 1 then
return string.format('[[파일:%s|200px]]', mw.text.encode(images[1].file))
else
local t = {}
table.insert(t, '<tabber>')
for i, im in ipairs(images) do
local title = mw.text.escape(im.title)
local file = mw.text.encode(im.file)
table.insert(t, string.format('%s= [[파일:%s|200px]]', title, file))
if i < #images then table.insert(t, '|-|') end
end
table.insert(t, '</tabber>')
return table.concat(t, '\n')
end
end
-- 이름 블록: 한글(첫줄) / 일어 → 영어(둘째줄, 일어 우선)
local function build_name_block(args)
local kr = trim(args['이름'] or '')
local jp = trim(args['이름_일어'] or '')
local en = trim(args['이름_영어'] or '')
local parts = {}
-- HTML 코드를 Lua에서 직접 생성하지 않고, 위키텍스트 문자열로 반환
if kr ~= '' then table.insert(parts, string.format('<div class="prf-name-kr">%s</div>', mw.text.escape(kr))) end
if jp ~= '' or en ~= '' then
local second = ''
if jp ~= '' and en ~= '' then
second = mw.text.escape(jp) .. ' / ' .. mw.text.escape(en)
else
second = mw.text.escape((jp ~= '' and jp) or en)
end
table.insert(parts, string.format('<div class="prf-name-sub">%s</div>', second))
end
return table.concat(parts, '\n')
end
-- 라벨+값 행 생성 (값 없으면 빈 문자열)
local function row(label, value)
if not value or value == '' then return '' end
-- 이 함수는 HTML 문자열을 반환
return string.format('<div class="prf-row"><strong>%s</strong> %s</div>', mw.text.escape(label), mw.text.escape(value))
end
-- 나이 출력 규칙: VN 먼저, 애니 다음
local function build_age_row(args)
local nv = trim(args['나이_VN'])
local na = trim(args['나이_애니'])
-- 이 함수는 HTML 문자열을 반환
if nv ~= '' and na ~= '' then
return string.format('<div class="prf-row"><strong>나이</strong> VN %s / 애니 %s</div>', mw.text.escape(nv), mw.text.escape(na))
elseif nv ~= '' then
return string.format('<div class="prf-row"><strong>나이</strong> VN %s</div>', mw.text.escape(nv))
elseif na ~= '' then
return string.format('<div class="prf-row"><strong>나이</strong> 애니 %s</div>', mw.text.escape(na))
else
return ''
end
end
-- 메인 생성 함수 (ASCII 식별자 사용: "main")
function p.main(frame)
local parent = frame:getParent() or frame
local args = parent.args or {}
-- frame이 유효하지 않으면 에러 방지
if not frame.preprocess then
return "스크립트 오류: Frame 객체가 유효하지 않습니다."
end
for k, v in pairs(args) do args[k] = trim(v) end
local theme = args['색상'] and args['색상'] ~= '' and args['색상'] or '#4cc9ff'
local image_block = build_image_block(args)
local name_block = build_name_block(args)
-- 기본 정보
local basic = ''
basic = basic .. row('성별', args['성별'])
basic = basic .. build_age_row(args)
basic = basic .. row('생일', args['생일'])
basic = basic .. row('신장', args['신장'])
basic = basic .. row('체중', args['체중'])
basic = basic .. row('머리색', args['머리색'])
basic = basic .. row('눈색', args['눈색'])
-- FC 프로필
local fc = ''
fc = fc .. row('포지션', args['포지션'])
fc = fc .. row('소속 팀', args['소속_팀'])
local techs = gather_techs(args)
if #techs > 0 then
local tstr = '<div class="prf-row"><strong>시그니처 기술</strong><ol class="sig-list">'
for _, tname in ipairs(techs) do
tstr = tstr .. string.format('<li>%s</li>', mw.text.escape(tname))
end
tstr = tstr .. '</ol></div>'
fc = fc .. tstr
end
-- 개인정보 (학교는 개인정보 안에)
local personal = ''
personal = personal .. row('학교', args['학교'])
personal = personal .. row('거주지', args['거주지'])
personal = personal .. row('취미', args['취미'])
personal = personal .. row('좋아하는 음식', args['좋아하는음식'])
-- 성우
local voice = ''
voice = voice .. row('일본어', args['성우_JP'])
voice = voice .. row('영어', args['성우_EN'])
-- 데뷔
local debut = ''
debut = debut .. row('애니메이션', args['애니_데뷔'])
debut = debut .. row('게임(VN)', args['게임_데뷔'])
debut = debut .. row('만화', args['만화_데뷔'])
-- HTML 구성
local html = mw.html.create()
html:tag('div'):addClass('prf-card'):cssText('display:flex;gap:14px;border-radius:10px;padding:12px;align-items:flex-start;'):done()
-- 좌측 이미지 (frame:preprocess 사용으로 wikitext 에러 해결)
local left = mw.html.create('div'):addClass('prf-left'):cssText('flex:0 0 36%;min-width:200px;')
left:node(frame:preprocess(image_block))
html:node(left)
-- 우측 (모든 wikitext는 frame:preprocess로 처리)
local right = mw.html.create('div'):addClass('prf-right'):cssText('flex:1 1 auto;')
-- 이름 블록
right:tag('div'):addClass('prf-name'):node(frame:preprocess(name_block)):done()
-- 섹션별 정보
if basic ~= '' then
right:tag('div'):addClass('prf-section'):tag('h4'):text('기본 정보'):done()
right:node(frame:preprocess(basic))
end
if fc ~= '' then
right:tag('div'):addClass('prf-section'):tag('h4'):text('FC 프로필'):done()
right:node(frame:preprocess(fc))
end
if personal ~= '' then
right:tag('div'):addClass('prf-section'):tag('h4'):text('개인 정보'):done()
right:node(frame:preprocess(personal))
end
if voice ~= '' then
right:tag('div'):addClass('prf-section'):tag('h4'):text('성우'):done()
right:node(frame:preprocess(voice))
end
if debut ~= '' then
right:tag('div'):addClass('prf-section'):tag('h4'):text('데뷔'):done()
right:node(frame:preprocess(debut))
end
html:node(right)
-- 인라인 CSS (반응형 포함) - 테마 색상 적용
local style = string.format([[<style>.prf-card { border:2px solid %s; background:#ffffff; }.prf-name-kr { font-size:1.25em; font-weight:700; color:%s; margin-bottom:4px; }.prf-name-sub { font-size:0.95em; color:#444; margin-bottom:8px; }.prf-row { margin:4px 0; }.prf-section h4 { margin:8px 0 6px 0; color:%s; font-size:0.95em; }.sig-list { margin:4px 0 0 18px; padding:0; }.tabber { margin-bottom:8px; }@media screen and (max-width:768px) { .prf-card { flex-direction:column; } .prf-left { width:100%%; min-width:0; }}</style>]], theme, theme, theme)
-- 최종 출력: 스타일 + HTML 객체
return style .. tostring(html)
end
return p