fix: 비주얼 매퍼 JS 문법 오류 수정

- iframe 스크립트를 template 태그 방식으로 변경 (EJS 백틱 이스케이프 충돌 해결)
- Mixed Content 치환을 단순 문자열 replace로 변경
- regex 이스케이프 문제 수정
This commit is contained in:
chpark
2026-03-30 00:00:08 +09:00
parent 51496b976e
commit fd64b19d3a
+70 -65
View File
@@ -109,6 +109,66 @@
</div>
</div>
<script id="mapper-inject-script" type="text/template">
(function(){
var highlight = null;
var overlay = document.createElement("div");
overlay.id = "__mapper_overlay__";
overlay.style.cssText = "position:fixed;pointer-events:none;border:3px solid #6366f1;background:rgba(99,102,241,.12);z-index:999999;transition:all .1s;display:none;border-radius:4px";
document.body.appendChild(overlay);
var label = document.createElement("div");
label.style.cssText = "position:fixed;z-index:999999;background:#6366f1;color:#fff;font-size:11px;padding:2px 8px;border-radius:4px;pointer-events:none;display:none;font-family:monospace";
document.body.appendChild(label);
document.addEventListener("mousemove", function(e) {
var el = e.target;
if (el.id === "__mapper_overlay__" || el === label) return;
highlight = el;
var r = el.getBoundingClientRect();
overlay.style.display = "block";
overlay.style.left = r.left + "px";
overlay.style.top = r.top + "px";
overlay.style.width = r.width + "px";
overlay.style.height = r.height + "px";
label.style.display = "block";
label.style.left = r.left + "px";
label.style.top = Math.max(0, r.top - 22) + "px";
label.textContent = getSelector(el);
});
document.addEventListener("click", function(e) {
e.preventDefault();
e.stopPropagation();
if (!highlight) return;
var sel = getSelector(highlight);
var text = (highlight.textContent || "").trim().substring(0, 80);
var tag = highlight.tagName.toLowerCase();
var href = highlight.getAttribute("href") || "";
window.parent.postMessage({type:"element-selected", selector:sel, text:text, tag:tag, href:href}, "*");
}, true);
function getSelector(el) {
if (el.id && el.id.indexOf("__") !== 0) return "#" + el.id;
var path = [];
while (el && el.nodeType === 1) {
var s = el.tagName.toLowerCase();
if (el.id && el.id.indexOf("__") !== 0) { path.unshift("#" + el.id); break; }
if (el.className && typeof el.className === "string") {
var cls = el.className.trim().split(" ").filter(function(c){ return c && c.indexOf("__") !== 0 && c.length < 40; }).slice(0, 2);
if (cls.length) s += "." + cls.join(".");
}
var sib = el.parentNode ? Array.from(el.parentNode.children).filter(function(c){ return c.tagName === el.tagName; }) : [];
if (sib.length > 1) { s += ":nth-child(" + (Array.from(el.parentNode.children).indexOf(el) + 1) + ")"; }
path.unshift(s);
el = el.parentNode;
if (path.length > 4) break;
}
return path.join(" > ");
}
})();
</script>
<script>
var adsenseList = (__INIT__ || {}).adsense || [];
var dataType = null;
@@ -150,13 +210,15 @@ async function fetchPage() {
var baseTag = '<base href="' + baseUrl.origin + '/">';
html = html.replace(/<head([^>]*)>/i, '<head$1>' + baseTag);
// Mixed Content 방지: http → https 변환 (리소스 URL만)
html = html.replace(/(src|href|action)=(["'])http:\/\//gi, '$1=$2https://');
html = html.replace(/url\((['"]?)http:\/\//gi, 'url($1https://');
// Mixed Content 방지: http → https 변환
while (html.indexOf('src="http://') !== -1) html = html.replace('src="http://', 'src="https://');
while (html.indexOf("src='http://") !== -1) html = html.replace("src='http://", "src='https://");
while (html.indexOf('href="http://') !== -1) html = html.replace('href="http://', 'href="https://');
while (html.indexOf("href='http://") !== -1) html = html.replace("href='http://", "href='https://");
// iframe에 매퍼 스크립트 주입
var mapperScript = getMapperScript();
html = html.replace(/<\\/body>/i, mapperScript + '</body>');
html = html.replace(new RegExp('</' + 'body>', 'i'), mapperScript + '</' + 'body>');
frame.srcdoc = html;
pageLoaded = true;
@@ -169,67 +231,10 @@ async function fetchPage() {
btn.disabled = false; btn.textContent = '페이지 가져오기';
}
// === iframe 내부에 주입할 스크립트 ===
// === iframe 내부에 주입할 스크립트 (template 태그에서 읽기) ===
function getMapperScript() {
return '<scr' + 'ipt>' +
'(function(){' +
' var highlight = null;' +
' var overlay = document.createElement("div");' +
' overlay.id = "__mapper_overlay__";' +
' overlay.style.cssText = "position:fixed;pointer-events:none;border:3px solid #6366f1;background:rgba(99,102,241,.12);z-index:999999;transition:all .1s;display:none;border-radius:4px";' +
' document.body.appendChild(overlay);' +
'' +
' var label = document.createElement("div");' +
' label.style.cssText = "position:fixed;z-index:999999;background:#6366f1;color:#fff;font-size:11px;padding:2px 8px;border-radius:4px;pointer-events:none;display:none;font-family:monospace";' +
' document.body.appendChild(label);' +
'' +
' document.addEventListener("mousemove", function(e) {' +
' var el = e.target;' +
' if (el.id === "__mapper_overlay__" || el === label) return;' +
' highlight = el;' +
' var r = el.getBoundingClientRect();' +
' overlay.style.display = "block";' +
' overlay.style.left = r.left + "px";' +
' overlay.style.top = r.top + "px";' +
' overlay.style.width = r.width + "px";' +
' overlay.style.height = r.height + "px";' +
' label.style.display = "block";' +
' label.style.left = r.left + "px";' +
' label.style.top = Math.max(0, r.top - 22) + "px";' +
' label.textContent = getSelector(el);' +
' });' +
'' +
' document.addEventListener("click", function(e) {' +
' e.preventDefault();' +
' e.stopPropagation();' +
' if (!highlight) return;' +
' var sel = getSelector(highlight);' +
' var text = (highlight.textContent || "").trim().substring(0, 80);' +
' var tag = highlight.tagName.toLowerCase();' +
' var href = highlight.getAttribute("href") || "";' +
' window.parent.postMessage({type:"element-selected", selector:sel, text:text, tag:tag, href:href, outerHTML: highlight.outerHTML.substring(0,300)}, "*");' +
' }, true);' +
'' +
' function getSelector(el) {' +
' if (el.id && !/^__/.test(el.id)) return "#" + el.id;' +
' var path = [];' +
' while (el && el.nodeType === 1) {' +
' var sel = el.tagName.toLowerCase();' +
' if (el.id && !/^__/.test(el.id)) { path.unshift("#" + el.id); break; }' +
' if (el.className && typeof el.className === "string") {' +
' var cls = el.className.trim().split(/\\s+/).filter(function(c){return c && !/^__/.test(c) && c.length < 40}).slice(0, 2);' +
' if (cls.length) sel += "." + cls.join(".");' +
' }' +
' var sib = el.parentNode ? Array.from(el.parentNode.children).filter(function(c){return c.tagName===el.tagName}) : [];' +
' if (sib.length > 1) { sel += ":nth-child(" + (Array.from(el.parentNode.children).indexOf(el) + 1) + ")"; }' +
' path.unshift(sel);' +
' el = el.parentNode;' +
' if (path.length > 4) break;' +
' }' +
' return path.join(" > ");' +
' }' +
'})();' +
'</scr' + 'ipt>';
var code = document.getElementById('mapper-inject-script').textContent;
return '<scr' + 'ipt>' + code + '</scr' + 'ipt>';
}
// === 데이터 타입 선택 ===
@@ -315,7 +320,7 @@ function updateJson() {
// 컨테이너 기준 상대 셀렉터로 변환
var sel = mappings[f].selector;
if (containerSelector && sel.indexOf(containerSelector) === 0) {
sel = sel.substring(containerSelector.length).replace(/^\\s*>\\s*/, '');
sel = sel.substring(containerSelector.length).replace(/^\s*>\s*/, '');
}
rules.fields[f] = { selector: sel || mappings[f].selector, type: mappings[f].type || 'text' };
if (mappings[f].attr) rules.fields[f].attr = mappings[f].attr;