diff --git a/installer-rp/renderer.js b/installer-rp/renderer.js index c166acb..8d705ca 100644 --- a/installer-rp/renderer.js +++ b/installer-rp/renderer.js @@ -124,7 +124,7 @@ function renderStep1() { nextBtn.addEventListener('click', function () { if (!state.selectedKey) return api.selectPack(state.selectedKey).then(function () { - renderStep2() + renderAgreement() }).catch(function (err) { alert(err.message || tt('common.selectFailed')) }) @@ -140,6 +140,170 @@ function renderStep1() { }) } +// 약관 동의 페이지: 1단계 직후, 2단계 설치 진입 전에 노출. +// rp 인스톨러는 리소스팩·설치기 두 약관만 확인·동의하면 된다. +function renderAgreement() { + setActiveStep(1) + clearPage() + var KINDS = [ + { id: 'resourcepack', tab: tt('agreement.tabResourcepack') }, + { id: 'installer-rp', tab: tt('agreement.tabInstaller') } + ] + var section = document.createElement('section') + section.className = 'page' + section.innerHTML = + '

' + escapeHtml(tt('agreement.heading')) + '

' + + '

' + escapeHtml(tt('agreement.intro')) + '

' + + '
' + + KINDS.map(function (k, i) { + return '' + }).join('') + + '
' + + '
' + escapeHtml(tt('agreement.loading')) + '
' + + '' + + '
' + + '
' + + ' ' + + ' ' + + '
' + pageHost.appendChild(section) + + var body = section.querySelector('#agBody') + var tabs = section.querySelectorAll('[data-ag]') + var nextBtn = section.querySelector('#next') + var accept = section.querySelector('#agAccept') + var msg = section.querySelector('#agMsg') + + // 본문 캐시. 탭 전환 시 재요청하지 않음. + var cache = {} + + function showKind(kind) { + if (cache[kind]) { body.innerHTML = cache[kind]; return } + body.textContent = tt('agreement.loading') + api.getTerm(kind).then(function (res) { + if (!res.ok) { + body.innerHTML = '

' + escapeHtml(tt('agreement.loadFailed', { message: res.message || '' })) + '

' + return + } + var html = renderTermsMarkdown(res.content || '') + cache[kind] = html + body.innerHTML = html + }).catch(function (err) { + body.innerHTML = '

' + escapeHtml(tt('agreement.loadFailed', { message: err.message })) + '

' + }) + } + + tabs.forEach(function (b) { + b.addEventListener('click', function () { + tabs.forEach(function (x) { x.classList.remove('active') }) + b.classList.add('active') + showKind(b.getAttribute('data-ag')) + }) + }) + + accept.addEventListener('change', function () { + nextBtn.disabled = !accept.checked + if (accept.checked) msg.textContent = '' + }) + + nextBtn.addEventListener('click', function () { + if (!accept.checked) { + msg.textContent = tt('agreement.agreeRequired') + msg.classList.add('error') + return + } + renderStep2() + }) + section.querySelector('#back').addEventListener('click', renderStep1) + + showKind(KINDS[0].id) +} + +// 인스톨러용 미니 markdown 렌더러. 사이트 termsEditor 와 같은 규칙을 처리한다. +function renderTermsMarkdown(src) { + function escHtml(s) { + return s.replace(/&/g, '&').replace(//g, '>') + } + function inline(s) { + s = escHtml(s) + s = s.replace(/`([^`]+)`/g, '$1') + s = s.replace(/\*\*([^*]+)\*\*/g, '$1') + s = s.replace(/(^|\W)\*([^*\n]+)\*(?=\W|$)/g, '$1$2') + s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1') + s = s.replace(/(^|[\s(])(https?:\/\/[^\s)]+)/g, function (m, p, u) { + return p + '' + u + '' + }) + return s + } + var lines = src.replace(/\r\n/g, '\n').split('\n') + var out = [] + var i = 0 + var stack = null + function closeList() { if (stack) { out.push(''); stack = null } } + while (i < lines.length) { + var line = lines[i] + var fence = /^```(\w*)\s*$/.exec(line) + if (fence) { + closeList() + var code = []; i += 1 + while (i < lines.length && !/^```\s*$/.test(lines[i])) { code.push(lines[i]); i += 1 } + if (i < lines.length) i += 1 + out.push('
' + escHtml(code.join('\n')) + '
') + continue + } + var togStart = /^:::toggle\s+(.+)$/.exec(line) + if (togStart) { + closeList() + var summary = togStart[1]; var body2 = []; i += 1 + while (i < lines.length && !/^:::\s*$/.test(lines[i])) { body2.push(lines[i]); i += 1 } + if (i < lines.length) i += 1 + out.push('
' + inline(summary) + '' + renderTermsMarkdown(body2.join('\n')) + '
') + continue + } + var h = /^(#{1,6})\s+(.*)$/.exec(line) + if (h) { + closeList() + out.push('' + inline(h[2]) + '') + i += 1; continue + } + if (/^---+\s*$/.test(line)) { closeList(); out.push('
'); i += 1; continue } + if (/^>\s?/.test(line)) { + closeList() + var q = [] + while (i < lines.length && /^>\s?/.test(lines[i])) { q.push(lines[i].replace(/^>\s?/, '')); i += 1 } + out.push('
' + renderTermsMarkdown(q.join('\n')) + '
') + continue + } + var ol = /^\s*\d+\.\s+(.*)$/.exec(line) + if (ol) { + if (stack !== 'ol') { closeList(); out.push('
    '); stack = 'ol' } + out.push('
  1. ' + inline(ol[1]) + '
  2. '); i += 1; continue + } + var ul = /^\s*[-*]\s+(.*)$/.exec(line) + if (ul) { + if (stack !== 'ul') { closeList(); out.push('