diff --git a/WebContent/js/djb/apispec/app.js b/WebContent/js/djb/apispec/app.js new file mode 100644 index 0000000..28dbd77 --- /dev/null +++ b/WebContent/js/djb/apispec/app.js @@ -0,0 +1,1729 @@ +/* DJB OpenAPI Editor POC — app.js + * vanilla JS, 단일 상태 + 함수형 렌더 + */ +(function () { + 'use strict'; + + // ============================================================ + // 1. 상태 + // ============================================================ + const STEPS = [ + { id: 1, title: '기본정보', hint: 'API 의 메타 정보(설명, 연락처, 라이선스, 태그) 를 입력합니다.' }, + { id: 2, title: '엔드포인트', hint: '환경별 서버 URL 과 오퍼레이션 메타정보를 작성합니다.' }, + { id: 3, title: '파라미터/스키마', hint: '요청 파라미터, 본문, 응답 스키마를 정의합니다.' }, + { id: 4, title: '인증/보안', hint: 'API 인증 방식과 적용 범위를 설정합니다.' }, + { id: 5, title: '예제/응답', hint: '실제 요청/응답 예제와 헤더를 입력합니다.' }, + { id: 6, title: '포탈 게시 정보', hint: '개발자포탈 공개 설정(공개 여부/권한/법인/응답유형)을 지정합니다.' } + ]; + + const state = { + step: 1, + showPreview: true, + previewTab: 'swagger', // 'swagger' | 'yaml-ro' | 'yaml-edit' + data: deepClone(window.SAMPLE_DATA), + step3Section: 'body', // 'params' | 'body' | 'response' | 'components' + responseStatusTab: '200', + components: [], // 재사용 스키마(POC: 빈 상태) + }; + + // ============================================================ + // 2. 유틸 + // ============================================================ + function deepClone(o) { return JSON.parse(JSON.stringify(o)); } + function $(sel, ctx) { return (ctx || document).querySelector(sel); } + function $$(sel, ctx) { return Array.from((ctx || document).querySelectorAll(sel)); } + function el(tag, attrs, children) { + const e = document.createElement(tag); + if (attrs) for (const k in attrs) { + if (k === 'class') e.className = attrs[k]; + else if (k === 'html') e.innerHTML = attrs[k]; + else if (k.startsWith('on') && typeof attrs[k] === 'function') e.addEventListener(k.slice(2), attrs[k]); + else e.setAttribute(k, attrs[k]); + } + if (children) (Array.isArray(children) ? children : [children]).forEach(c => { + if (c == null) return; + e.appendChild(typeof c === 'string' ? document.createTextNode(c) : c); + }); + return e; + } + function debounce(fn, ms) { let t; return function () { clearTimeout(t); t = setTimeout(() => fn.apply(this, arguments), ms); }; } + function toast(msg, kind) { + const t = $('#toast'); + t.textContent = msg; + t.className = 'fixed bottom-5 left-1/2 -translate-x-1/2 px-4 py-2 rounded text-white text-sm shadow-lg z-50 ' + + (kind === 'error' ? 'bg-red-600' : kind === 'warn' ? 'bg-amber-600' : 'bg-slate-900'); + t.classList.remove('hidden'); + setTimeout(() => t.classList.add('hidden'), 2400); + } + function escapeHtml(s) { + return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); + } + // CrossScriptingFilter 가 엔티티(( 등)로 치환한 값 복원. input value 로만 사용 → 안전, 멱등. + function decodeEntities(s) { if (s == null || s === '') return s; var t = document.createElement('textarea'); t.innerHTML = String(s); return t.value; } + + // 잠금 필드 표준 렌더 + function lockedInput(value, opts) { + opts = opts || {}; + const cls = 'w-full px-3 py-1.5 pr-8 text-sm rounded border border-slate-200 bg-slate-100 text-slate-600 cursor-not-allowed'; + return ` +
${escapeHtml(hint)}
` : ''}${escapeHtml(opts.hint)}
` : ''}${escapeHtml(op.path.value)}
+ 🔒 게이트웨이에서 자동 동기화
+ | 위치 | +이름 | +타입 | +필수 | +format | +maxLen | +설명 | +예제 | ++ |
|---|
| 필드명 | +타입 | +필수 | +format | +maxLen | +제약 | +설명 | +예제 | ++ |
|---|
${escapeHtml(s.key)}
+ ${escapeHtml(s.type)}
+ 🔒 GW
+ | name | type | description | example | |
|---|---|---|---|---|
| ${input(h.name, `data-resp-header="${i}.name"`)} | +${selectInput(h.type, ['string', 'integer', 'number', 'boolean'], `data-resp-header="${i}.type"`)} | +${input(h.description, `data-resp-header="${i}.description"`)} | +${input(h.example, `data-resp-header="${i}.example"`)} | ++ |
h?l[c][f]=s+1:n.charAt(c-1)===i.charAt(f-1)?l[c][f]=l[c-1][f-1]:l[c][f]=Math.min(l[c-1][f-1]+1,Math.min(l[c][f-1]+1,l[c-1][f]+1)),l[c][f]{u();function Xg(r,e){var t=r.type,i=r.value,n,s;return e&&(s=e(r))!==void 0?s:t==="word"||t==="space"?i:t==="string"?(n=r.quote||"",n+i+(r.unclosed?"":n)):t==="comment"?"/*"+i+(r.unclosed?"":"*/"):t==="div"?(r.before||"")+i+(r.after||""):Array.isArray(r.nodes)?(n=Zg(r.nodes,e),t!=="function"?n:i+"("+(r.before||"")+n+(r.after||"")+(r.unclosed?"":")")):i}function Zg(r,e){var t,i;if(Array.isArray(r)){for(t="",i=r.length-1;~i;i-=1)t=Xg(r[i],e)+t;return t}return Xg(r,e)}Jg.exports=Zg});var ry=x((lq,ty)=>{u();var Cs="-".charCodeAt(0),_s="+".charCodeAt(0),Fl=".".charCodeAt(0),j2="e".charCodeAt(0),z2="E".charCodeAt(0);function U2(r){var e=r.charCodeAt(0),t;if(e===_s||e===Cs){if(t=r.charCodeAt(1),t>=48&&t<=57)return!0;var i=r.charCodeAt(2);return t===Fl&&i>=48&&i<=57}return e===Fl?(t=r.charCodeAt(1),t>=48&&t<=57):e>=48&&e<=57}ty.exports=function(r){var e=0,t=r.length,i,n,s;if(t===0||!U2(r))return!1;for(i=r.charCodeAt(e),(i===_s||i===Cs)&&e++;e{u();function Gy(r,e){var t=r.type,i=r.value,n,s;return e&&(s=e(r))!==void 0?s:t==="word"||t==="space"?i:t==="string"?(n=r.quote||"",n+i+(r.unclosed?"":n)):t==="comment"?"/*"+i+(r.unclosed?"":"*/"):t==="div"?(r.before||"")+i+(r.after||""):Array.isArray(r.nodes)?(n=Qy(r.nodes,e),t!=="function"?n:i+"("+(r.before||"")+n+(r.after||"")+(r.unclosed?"":")")):i}function Qy(r,e){var t,i;if(Array.isArray(r)){for(t="",i=r.length-1;~i;i-=1)t=Gy(r[i],e)+t;return t}return Gy(r,e)}Yy.exports=Qy});var Zy=x((o$,Xy)=>{u();var $s="-".charCodeAt(0),Ls="+".charCodeAt(0),su=".".charCodeAt(0),vO="e".charCodeAt(0),xO="E".charCodeAt(0);function kO(r){var e=r.charCodeAt(0),t;if(e===Ls||e===$s){if(t=r.charCodeAt(1),t>=48&&t<=57)return!0;var i=r.charCodeAt(2);return t===su&&i>=48&&i<=57}return e===su?(t=r.charCodeAt(1),t>=48&&t<=57):e>=48&&e<=57}Xy.exports=function(r){var e=0,t=r.length,i,n,s;if(t===0||!kO(r))return!1;for(i=r.charCodeAt(e),(i===Ls||i===$s)&&e++;e