firstCommit
This commit is contained in:
commit
1be3c1cd98
|
|
@ -0,0 +1,250 @@
|
|||
// background.js (Service Worker)
|
||||
|
||||
chrome.runtime.onInstalled.addListener(() => {
|
||||
chrome.contextMenus.create({
|
||||
id: "searchTrademark",
|
||||
title: "지재권 검색",
|
||||
contexts: ["selection"]
|
||||
});
|
||||
chrome.alarms.create("keepAlive", { periodInMinutes: 4 });
|
||||
});
|
||||
|
||||
chrome.alarms.onAlarm.addListener((alarm) => {
|
||||
if (alarm.name === "keepAlive") {
|
||||
console.log("[background.js] 서비스 워커 유지 알람 실행됨");
|
||||
}
|
||||
});
|
||||
|
||||
// 컨텍스트 메뉴 클릭 시 – 키워드 검색 후 각 결과의 출원번호로 상세 조회 진행
|
||||
chrome.contextMenus.onClicked.addListener((info, tab) => {
|
||||
const keyword = info.selectionText.trim();
|
||||
if (!keyword) return;
|
||||
const url = buildMarkInfoUrl(keyword);
|
||||
|
||||
// 1. 키워드 검색: 기존 방식대로 __NUXT_DATA__ 파싱 (재귀적 인덱스 재활용)
|
||||
fetch(url)
|
||||
.then(resp => {
|
||||
if (!resp.ok) throw new Error(`네트워크 오류: ${resp.status}`);
|
||||
return resp.text();
|
||||
})
|
||||
.then(html => {
|
||||
const match = /<script[^>]*id="__NUXT_DATA__"[^>]*>([\s\S]*?)<\/script>/i.exec(html);
|
||||
if (!match) throw new Error("__NUXT_DATA__ 태그를 찾을 수 없습니다.");
|
||||
let jsonString = match[1];
|
||||
let globalData;
|
||||
try {
|
||||
globalData = JSON.parse(jsonString);
|
||||
} catch (e) {
|
||||
throw new Error("JSON 파싱 실패: " + e.toString());
|
||||
}
|
||||
// 키워드 검색 결과 파싱 (등록정보만 추출 – 재귀적 인덱스 치환 방식)
|
||||
const keywordResults = parseSearchResults(globalData);
|
||||
if (!Array.isArray(keywordResults) || keywordResults.length === 0) {
|
||||
throw new Error("키워드 검색 결과가 없습니다.");
|
||||
}
|
||||
// 최대 10건까지만 처리
|
||||
const limitedResults = keywordResults.slice(0, 10);
|
||||
// 각 결과의 출원번호(appNum)로 상세 조회 진행
|
||||
const detailPromises = limitedResults.map(result => {
|
||||
const appNum = result.registration_info.applicationNum;
|
||||
return fetchDetailInfo(appNum)
|
||||
.then(detail => {
|
||||
result.detail = detail;
|
||||
return result;
|
||||
})
|
||||
.catch(err => {
|
||||
result.detailError = err.toString();
|
||||
return result;
|
||||
});
|
||||
});
|
||||
return Promise.all(detailPromises);
|
||||
})
|
||||
.then(allResults => {
|
||||
chrome.tabs.sendMessage(tab.id, {
|
||||
action: "showTooltip",
|
||||
detailInfo: allResults,
|
||||
keyword: keyword
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
chrome.tabs.sendMessage(tab.id, {
|
||||
action: "showTooltip",
|
||||
detailInfo: { error: err.toString() },
|
||||
keyword: keyword
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 키워드 검색 URL (기본 코드 그대로)
|
||||
function buildMarkInfoUrl(keyword) {
|
||||
const encoded = encodeURIComponent(keyword);
|
||||
return `https://markinfo.kr/search?page=1&size=20&sort=_score,desc&sort=applicationDate,desc&searchType=ST01&searchKeyword=${encoded}&statuses=APPLICATION&statuses=PUBLICATION&statuses=REGISTRATION`;
|
||||
}
|
||||
|
||||
/* ===== 1. 키워드 검색 결과 파싱 (재귀적 인덱스 재활용 방식) ===== */
|
||||
|
||||
/**
|
||||
* 키워드 검색 __NUXT_DATA__에서 등록정보(상표명, 출원번호 등)만 추출하여 결과 배열로 반환
|
||||
*/
|
||||
function parseSearchResults(globalData) {
|
||||
let results = [];
|
||||
let i = 0;
|
||||
while (i < globalData.length) {
|
||||
let item = globalData[i];
|
||||
if (isPlainObject(item) && item.hasOwnProperty("applicationNum")) {
|
||||
let regRes = extractRegistrationInfo(globalData, i);
|
||||
let registration_info = regRes.registration_info;
|
||||
i = regRes.nextIndex;
|
||||
results.push({ registration_info });
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 등록정보 추출 (키워드 검색용 – 재귀적 인덱스 재활용)
|
||||
*/
|
||||
function extractRegistrationInfo(globalData, startIndex) {
|
||||
let regMapping = null;
|
||||
let i = startIndex;
|
||||
for (; i < globalData.length; i++) {
|
||||
let item = globalData[i];
|
||||
if (isPlainObject(item) && item.hasOwnProperty("applicationNum")) {
|
||||
regMapping = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!regMapping) return { registration_info: {}, nextIndex: i };
|
||||
const registration_info = {};
|
||||
for (let key in regMapping) {
|
||||
const ref = regMapping[key];
|
||||
if (typeof ref === "number" && ref < globalData.length) {
|
||||
registration_info[key] = globalData[ref];
|
||||
} else {
|
||||
registration_info[key] = ref;
|
||||
}
|
||||
}
|
||||
return { registration_info, nextIndex: i + 1 };
|
||||
}
|
||||
|
||||
/* ===== 2. 상세 검색 (출원번호 기반) – 단일 결과용, 단순 치환 방식 ===== */
|
||||
|
||||
/**
|
||||
* 상세 조회: 주어진 출원번호(appNum)로 상세 페이지 __NUXT_DATA__를 가져와서
|
||||
* 등록정보, 권리정보, 출원인 정보를 추출합니다.
|
||||
* (대리인 정보는 제외)
|
||||
*/
|
||||
function fetchDetailInfo(appNum) {
|
||||
const detailUrl = `https://markinfo.kr/search/${appNum}`;
|
||||
return fetch(detailUrl)
|
||||
.then(resp => {
|
||||
if (!resp.ok) throw new Error(`네트워크 오류: ${resp.status}`);
|
||||
return resp.text();
|
||||
})
|
||||
.then(html => {
|
||||
const match = /<script[^>]*id="__NUXT_DATA__"[^>]*>([\s\S]*?)<\/script>/i.exec(html);
|
||||
if (!match) throw new Error("__NUXT_DATA__ 태그를 찾을 수 없습니다.");
|
||||
let jsonString = match[1];
|
||||
let detailGlobalData;
|
||||
try {
|
||||
detailGlobalData = JSON.parse(jsonString);
|
||||
} catch (e) {
|
||||
throw new Error("JSON 파싱 실패: " + e.toString());
|
||||
}
|
||||
if (!Array.isArray(detailGlobalData)) {
|
||||
throw new Error("globalData가 배열이 아님");
|
||||
}
|
||||
// 단일 결과이므로, mapping 객체 내 숫자값은 바로 치환
|
||||
const registration_info = detailExtractRegistrationInfo(detailGlobalData);
|
||||
const rights_info = detailExtractRightsInfo(detailGlobalData);
|
||||
const applicant_info = detailExtractApplicantInfo(detailGlobalData);
|
||||
return { registration_info, rights_info, applicant_info };
|
||||
});
|
||||
}
|
||||
|
||||
// 단일 상세 조회용 등록정보 추출 (숫자이면 한 번만 치환)
|
||||
function detailExtractRegistrationInfo(globalData) {
|
||||
let regMapping = null;
|
||||
for (let item of globalData) {
|
||||
if (isPlainObject(item) && item.hasOwnProperty("applicationNum")) {
|
||||
regMapping = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!regMapping) return {};
|
||||
const registration_info = {};
|
||||
for (let key in regMapping) {
|
||||
const ref = regMapping[key];
|
||||
registration_info[key] = (typeof ref === "number" && ref < globalData.length)
|
||||
? globalData[ref]
|
||||
: ref;
|
||||
}
|
||||
return registration_info;
|
||||
}
|
||||
|
||||
// 단일 상세 조회용 권리정보 추출 (숫자 치환만 진행)
|
||||
function detailExtractRightsInfo(globalData) {
|
||||
const rights_info = {};
|
||||
for (let item of globalData) {
|
||||
if (isPlainObject(item) &&
|
||||
"classificationCode" in item &&
|
||||
"asignProductName" in item &&
|
||||
"asignProductNameEn" in item &&
|
||||
"similarCodes" in item &&
|
||||
!("applicationNum" in item)) {
|
||||
let classificationCode = (typeof item.classificationCode === "number" && item.classificationCode < globalData.length)
|
||||
? globalData[item.classificationCode]
|
||||
: item.classificationCode;
|
||||
let asignProductName = (typeof item.asignProductName === "number" && item.asignProductName < globalData.length)
|
||||
? globalData[item.asignProductName]
|
||||
: item.asignProductName;
|
||||
let asignProductNameEn = (typeof item.asignProductNameEn === "number" && item.asignProductNameEn < globalData.length)
|
||||
? globalData[item.asignProductNameEn]
|
||||
: item.asignProductNameEn;
|
||||
let similarCodes = (typeof item.similarCodes === "number" && item.similarCodes < globalData.length)
|
||||
? globalData[item.similarCodes]
|
||||
: item.similarCodes;
|
||||
let designation = String(classificationCode);
|
||||
if (!rights_info[designation]) {
|
||||
rights_info[designation] = [];
|
||||
}
|
||||
rights_info[designation].push({
|
||||
asignProductName,
|
||||
asignProductNameEn,
|
||||
similarCodes
|
||||
});
|
||||
}
|
||||
}
|
||||
return rights_info;
|
||||
}
|
||||
|
||||
// 단일 상세 조회용 출원인 정보 추출 – 오직 nationalCodeName와 applicantName만 표시, 그리고 출원날짜와 권리상태도 추가
|
||||
function detailExtractApplicantInfo(globalData) {
|
||||
let mapping = {};
|
||||
for (let item of globalData) {
|
||||
if (isPlainObject(item) && item.hasOwnProperty("applicantCode")) {
|
||||
mapping["nationalCodeName"] = (typeof item["nationalCodeName"] === "number" && item["nationalCodeName"] < globalData.length)
|
||||
? globalData[item["nationalCodeName"]]
|
||||
: item["nationalCodeName"];
|
||||
mapping["applicantName"] = (typeof item["applicantName"] === "number" && item["applicantName"] < globalData.length)
|
||||
? globalData[item["applicantName"]]
|
||||
: item["applicantName"];
|
||||
// 추가: 출원날짜와 권리상태 (예: lastDisposalCodeName)
|
||||
mapping["applicationDate"] = (typeof item["applicationDate"] === "number" && item["applicationDate"] < globalData.length)
|
||||
? globalData[item["applicationDate"]]
|
||||
: item["applicationDate"];
|
||||
mapping["lastDisposalCodeName"] = (typeof item["lastDisposalCodeName"] === "number" && item["lastDisposalCodeName"] < globalData.length)
|
||||
? globalData[item["lastDisposalCodeName"]]
|
||||
: item["lastDisposalCodeName"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { mapping };
|
||||
}
|
||||
|
||||
/* ===== 유틸리티 함수 ===== */
|
||||
function isPlainObject(obj) {
|
||||
return Object.prototype.toString.call(obj) === "[object Object]";
|
||||
}
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
// content.js
|
||||
|
||||
let lastContextMenuPos = null;
|
||||
let tooltipEl = null;
|
||||
|
||||
document.addEventListener("contextmenu", (e) => {
|
||||
lastContextMenuPos = { x: e.pageX, y: e.pageY };
|
||||
});
|
||||
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
if (message.action === "showTooltip") {
|
||||
if (!tooltipEl) {
|
||||
tooltipEl = document.createElement("div");
|
||||
tooltipEl.id = "markinfo-tooltip";
|
||||
tooltipEl.style.position = "absolute";
|
||||
tooltipEl.style.zIndex = "999999";
|
||||
tooltipEl.style.background = "#fff";
|
||||
tooltipEl.style.border = "1px solid #ccc";
|
||||
tooltipEl.style.borderRadius = "8px";
|
||||
tooltipEl.style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)";
|
||||
tooltipEl.style.fontFamily = "'Roboto', sans-serif";
|
||||
tooltipEl.style.fontSize = "14px";
|
||||
tooltipEl.style.color = "#333";
|
||||
tooltipEl.style.maxWidth = "600px";
|
||||
tooltipEl.style.maxHeight = "500px";
|
||||
// 전체 툴팁 컨테이너에 flex 컬럼 레이아웃
|
||||
tooltipEl.style.display = "flex";
|
||||
tooltipEl.style.flexDirection = "column";
|
||||
|
||||
// 헤더: 항상 보이는 영역 (sticky)
|
||||
const headerDiv = document.createElement("div");
|
||||
headerDiv.id = "markinfo-tooltip-header";
|
||||
headerDiv.style.position = "sticky";
|
||||
headerDiv.style.top = "0";
|
||||
headerDiv.style.background = "#fff";
|
||||
headerDiv.style.padding = "12px 16px";
|
||||
headerDiv.style.borderBottom = "1px solid #ccc";
|
||||
headerDiv.style.display = "flex";
|
||||
headerDiv.style.justifyContent = "space-between";
|
||||
headerDiv.style.alignItems = "center";
|
||||
|
||||
// 검색 키워드 제목
|
||||
const titleElem = document.createElement("h2");
|
||||
titleElem.id = "tooltip-title";
|
||||
titleElem.style.margin = "0";
|
||||
titleElem.style.fontSize = "20px";
|
||||
titleElem.style.color = "#2c3e50";
|
||||
headerDiv.appendChild(titleElem);
|
||||
|
||||
// 내부 닫기 버튼 (헤더 내)
|
||||
const headerCloseBtn = document.createElement("button");
|
||||
headerCloseBtn.textContent = "닫기";
|
||||
headerCloseBtn.style.padding = "6px 10px";
|
||||
headerCloseBtn.style.backgroundColor = "#e74c3c";
|
||||
headerCloseBtn.style.color = "#fff";
|
||||
headerCloseBtn.style.border = "none";
|
||||
headerCloseBtn.style.borderRadius = "4px";
|
||||
headerCloseBtn.style.cursor = "pointer";
|
||||
headerCloseBtn.onclick = removeTooltip;
|
||||
headerDiv.appendChild(headerCloseBtn);
|
||||
|
||||
// 스크롤되는 본문 영역
|
||||
const bodyDiv = document.createElement("div");
|
||||
bodyDiv.id = "markinfo-tooltip-body";
|
||||
bodyDiv.style.padding = "16px";
|
||||
bodyDiv.style.overflowY = "auto";
|
||||
// 본문 영역은 전체 높이에서 헤더 높이를 뺀 만큼 사용
|
||||
bodyDiv.style.flex = "1 1 auto";
|
||||
|
||||
tooltipEl.appendChild(headerDiv);
|
||||
tooltipEl.appendChild(bodyDiv);
|
||||
document.body.appendChild(tooltipEl);
|
||||
|
||||
// 글로벌 닫기 버튼 (항상 보이는 우측 상단)
|
||||
ensureGlobalCloseButton();
|
||||
}
|
||||
// 업데이트: 헤더 제목에 검색 키워드 설정
|
||||
document.getElementById("tooltip-title").textContent = "검색 키워드: " + message.keyword;
|
||||
renderDetailInfo(message.detailInfo, message.keyword);
|
||||
if (lastContextMenuPos) {
|
||||
positionTooltip(tooltipEl, lastContextMenuPos);
|
||||
} else {
|
||||
tooltipEl.style.top = "10px";
|
||||
tooltipEl.style.left = "10px";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function positionTooltip(tooltip, pos) {
|
||||
tooltip.style.left = (pos.x + 10) + "px";
|
||||
tooltip.style.top = (pos.y + 10) + "px";
|
||||
const rect = tooltip.getBoundingClientRect();
|
||||
const docWidth = document.documentElement.clientWidth;
|
||||
const docHeight = document.documentElement.clientHeight;
|
||||
if (rect.right > docWidth) {
|
||||
tooltip.style.left = (docWidth - rect.width - 10) + "px";
|
||||
}
|
||||
if (rect.bottom > docHeight) {
|
||||
tooltip.style.top = (docHeight - rect.height - 10) + "px";
|
||||
}
|
||||
}
|
||||
|
||||
function removeTooltip() {
|
||||
if (tooltipEl && tooltipEl.parentNode) {
|
||||
tooltipEl.parentNode.removeChild(tooltipEl);
|
||||
}
|
||||
tooltipEl = null;
|
||||
// 글로벌 닫기 버튼 제거
|
||||
const globalClose = document.getElementById("tooltip-global-close");
|
||||
if (globalClose && globalClose.parentNode) {
|
||||
globalClose.parentNode.removeChild(globalClose);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureGlobalCloseButton() {
|
||||
if (!document.getElementById("tooltip-global-close")) {
|
||||
const btn = document.createElement("button");
|
||||
btn.id = "tooltip-global-close";
|
||||
btn.textContent = "닫기";
|
||||
btn.style.position = "fixed";
|
||||
btn.style.top = "20px";
|
||||
btn.style.right = "20px";
|
||||
btn.style.padding = "8px 12px";
|
||||
btn.style.backgroundColor = "#e74c3c";
|
||||
btn.style.color = "#fff";
|
||||
btn.style.border = "none";
|
||||
btn.style.borderRadius = "4px";
|
||||
btn.style.cursor = "pointer";
|
||||
btn.style.zIndex = "1000000";
|
||||
btn.onclick = removeTooltip;
|
||||
document.body.appendChild(btn);
|
||||
}
|
||||
}
|
||||
|
||||
function renderDetailInfo(results, keyword) {
|
||||
const bodyDiv = document.getElementById("markinfo-tooltip-body");
|
||||
if (!bodyDiv) return;
|
||||
let html = `<div style="line-height: 1.6;">`;
|
||||
if (results.error) {
|
||||
html += `<p style="color: red; font-weight: bold;">오류: ${results.error}</p>`;
|
||||
} else if (!Array.isArray(results) || results.length === 0) {
|
||||
html += `<p style="color: #7f8c8d;">검색 결과가 없습니다.</p>`;
|
||||
} else {
|
||||
results.forEach((result, idx) => {
|
||||
html += `<div style="margin-bottom: 24px; padding-bottom: 12px; border-bottom: 1px solid #ecf0f1;">`;
|
||||
html += `<h3 style="margin: 0 0 8px 0; font-size: 18px; color: #2980b9;">${idx + 1}번 결과</h3>`;
|
||||
// 상세 검색 결과 (출원번호 상세 조회)
|
||||
if (result.detail) {
|
||||
html += `<h4 style="margin: 12px 0 6px; font-size: 16px; color: #16a085; border-bottom: 1px solid #16a085; display: inline-block;">상세 정보</h4>`;
|
||||
const dreg = result.detail.registration_info;
|
||||
html += `<p style="margin: 6px 0;"><strong>상표명:</strong> ${dreg.trademarkName || "(상표명 없음)"}</p>`;
|
||||
html += `<p style="margin: 6px 0;"><strong>출원번호:</strong> ${dreg.applicationNum || "(출원번호 없음)"}</p>`;
|
||||
html += `<p style="margin: 6px 0;"><strong>출원날짜:</strong> ${dreg.applicationDate || "(출원날짜 없음)"}</p>`;
|
||||
html += `<p style="margin: 6px 0;"><strong>권리상태:</strong> ${dreg.lastDisposalCodeName || "(권리상태 없음)"}</p>`;
|
||||
html += `<p style="margin: 6px 0;"><strong>공고번호:</strong> ${dreg.publicationNum || "(공고번호 없음)"}</p>`;
|
||||
html += `<p style="margin: 6px 0;"><strong>등록번호:</strong> ${dreg.registerNum || "(등록번호 없음)"}</p>`;
|
||||
|
||||
html += `<h4 style="margin: 12px 0 6px; font-size: 16px; color: #8e44ad; border-bottom: 1px solid #8e44ad; display: inline-block;">권리정보</h4>`;
|
||||
if (result.detail.rights_info && Object.keys(result.detail.rights_info).length > 0) {
|
||||
for (let key in result.detail.rights_info) {
|
||||
html += `<p style="margin: 6px 0;"><strong>카테고리 코드:</strong> ${key}</p>`;
|
||||
result.detail.rights_info[key].forEach(item => {
|
||||
html += `<p style="margin-left: 20px; margin: 4px 0;">- 지정상품명: ${item.asignProductName || ""}</p>`;
|
||||
html += `<p style="margin-left: 20px; margin: 4px 0;"> 영문: ${item.asignProductNameEn || ""}</p>`;
|
||||
html += `<p style="margin-left: 20px; margin: 4px 0;"> 유사군코드: ${item.similarCodes || ""}</p>`;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
html += `<p style="margin: 6px 0; color: #7f8c8d;">(권리정보 없음)</p>`;
|
||||
}
|
||||
|
||||
html += `<h4 style="margin: 12px 0 6px; font-size: 16px; color: #e67e22; border-bottom: 1px solid #e67e22; display: inline-block;">출원인 정보</h4>`;
|
||||
if (result.detail.applicant_info && result.detail.applicant_info.mapping) {
|
||||
const mapping = result.detail.applicant_info.mapping;
|
||||
html += `<p style="margin: 6px 0;"><strong>국가명:</strong> ${mapping.nationalCodeName || "(없음)"}</p>`;
|
||||
html += `<p style="margin: 6px 0;"><strong>출원인명:</strong> ${mapping.applicantName || "(없음)"}</p>`;
|
||||
} else {
|
||||
html += `<p style="margin: 6px 0; color: #7f8c8d;">(출원인 정보 없음)</p>`;
|
||||
}
|
||||
} else if (result.detailError) {
|
||||
html += `<p style="color: red; font-weight: bold;">상세 정보 검색 오류: ${result.detailError}</p>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
});
|
||||
}
|
||||
html += `</div>`;
|
||||
bodyDiv.innerHTML = html;
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
// background.js (Service Worker)
|
||||
|
||||
// 1) 확장 프로그램 설치/업데이트 시 컨텍스트 메뉴 생성
|
||||
chrome.runtime.onInstalled.addListener(() => {
|
||||
chrome.contextMenus.create({
|
||||
id: "searchTrademark",
|
||||
title: "지재권 검색",
|
||||
contexts: ["selection"] // 텍스트를 드래그 선택한 상태에서만 메뉴 표시
|
||||
});
|
||||
|
||||
// (선택) 서비스 워커 keep-alive
|
||||
chrome.alarms.create("keepAlive", { periodInMinutes: 4 });
|
||||
});
|
||||
|
||||
chrome.alarms.onAlarm.addListener((alarm) => {
|
||||
if (alarm.name === "keepAlive") {
|
||||
console.log("[background.js] 서비스 워커 유지 알람 실행됨");
|
||||
}
|
||||
});
|
||||
|
||||
// 2) 컨텍스트 메뉴 클릭 시 처리
|
||||
chrome.contextMenus.onClicked.addListener((info, tab) => {
|
||||
if (info.menuItemId === "searchTrademark") {
|
||||
// 선택된 텍스트(드래그된 내용)
|
||||
const keyword = info.selectionText.trim();
|
||||
if (!keyword) return;
|
||||
|
||||
console.log("[background.js] 컨텍스트 메뉴 검색 키워드:", keyword);
|
||||
|
||||
// MarkInfo 검색 URL (키워드 검색 예시)
|
||||
const url = buildMarkInfoUrl(keyword);
|
||||
|
||||
// fetch로 마크인포 요청
|
||||
fetch(url)
|
||||
.then((resp) => {
|
||||
if (!resp.ok) {
|
||||
throw new Error(`네트워크 오류: ${resp.status}`);
|
||||
}
|
||||
return resp.text();
|
||||
})
|
||||
.then((html) => {
|
||||
// __NUXT_DATA__ 추출 (정규표현식 예시)
|
||||
const match = /<script[^>]*id="__NUXT_DATA__"[^>]*>([\s\S]*?)<\/script>/i.exec(html);
|
||||
if (!match) {
|
||||
throw new Error("__NUXT_DATA__ 태그를 찾을 수 없습니다.");
|
||||
}
|
||||
const jsonString = match[1];
|
||||
const globalData = JSON.parse(jsonString);
|
||||
|
||||
// 예: globalData 배열 중 첫 번째 아이템이 주요 정보라고 가정
|
||||
let firstItem = null;
|
||||
if (Array.isArray(globalData) && globalData.length > 0) {
|
||||
firstItem = globalData[0];
|
||||
}
|
||||
|
||||
// 노티피케이션에 표시할 문자열 생성
|
||||
let msg = "";
|
||||
if (firstItem && typeof firstItem === "object") {
|
||||
// 예: applicationNum, trademarkName 등 필요한 필드만 추출
|
||||
const applicationNum = firstItem.applicationNum || "(출원번호 없음)";
|
||||
const trademarkName = firstItem.trademarkName || "(상표명 없음)";
|
||||
msg = `출원번호: ${applicationNum}\n상표명: ${trademarkName}`;
|
||||
} else {
|
||||
// globalData 전체 중 일부만 표시
|
||||
msg = JSON.stringify(globalData).slice(0, 80) + "...";
|
||||
}
|
||||
|
||||
// 알림 표시
|
||||
chrome.notifications.create({
|
||||
type: "basic",
|
||||
iconUrl: "icon.png",
|
||||
title: "지재권 검색 결과",
|
||||
message: `검색 키워드: ${keyword}\n결과: ${msg}`
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 3) URL 생성 함수
|
||||
function buildMarkInfoUrl(keyword) {
|
||||
const encoded = encodeURIComponent(keyword);
|
||||
return `https://markinfo.kr/search?page=1&size=20&sort=_score,desc&sort=applicationDate,desc&searchType=ST01&searchKeyword=${encoded}&statuses=APPLICATION&statuses=PUBLICATION&statuses=REGISTRATION`;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"manifest_version": 3,
|
||||
"name": "지재권 검색 확장 (컨텍스트 메뉴)",
|
||||
"version": "1.0",
|
||||
"description": "드래그한 텍스트를 우클릭 → '지재권 검색'으로 마크인포 검색을 수행합니다.",
|
||||
"permissions": [
|
||||
"contextMenus",
|
||||
"storage",
|
||||
"notifications",
|
||||
"alarms"
|
||||
],
|
||||
"host_permissions": [
|
||||
"https://markinfo.kr/*"
|
||||
],
|
||||
"background": {
|
||||
"service_worker": "background.js"
|
||||
},
|
||||
"action": {
|
||||
"default_icon": "icon.png"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"manifest_version": 3,
|
||||
"name": "지재권 검색 확장 (컨텍스트 메뉴)",
|
||||
"version": "1.0",
|
||||
"description": "드래그한 텍스트를 우클릭 → '지재권 검색'으로 MarkInfo 검색을 수행하고 결과를 툴팁으로 표시합니다.",
|
||||
"permissions": [
|
||||
"contextMenus",
|
||||
"storage",
|
||||
"notifications",
|
||||
"alarms"
|
||||
],
|
||||
"host_permissions": [
|
||||
"https://markinfo.kr/*"
|
||||
],
|
||||
"background": {
|
||||
"service_worker": "background.js"
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["<all_urls>"],
|
||||
"js": ["content.js"],
|
||||
"run_at": "document_end"
|
||||
}
|
||||
],
|
||||
"action": {
|
||||
"default_icon": "icon.png"
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue