diff --git a/src/EnbedDecoratiion.ts b/src/EnbedDecoratiion.ts index 98af29a..8373473 100644 --- a/src/EnbedDecoratiion.ts +++ b/src/EnbedDecoratiion.ts @@ -52,12 +52,13 @@ class StatefulDecorationSet { linkEl.addClass("markdown-rendered"); linkEl.addClass("external-link"); div.appendChild(linkEl); - LinkThumbnailWidgetParams(token.value).then(params => { - if (params) { - linkEl.innerHTML = params; - linkEl.addEventListener("click", (e) => e.stopPropagation()); - } - }); + const params = await LinkThumbnailWidgetParams(token.value); + if (params) { + linkEl.innerHTML = params; + linkEl.addEventListener("click", (e) => e.stopPropagation()); + } else if (params === null) { + linkEl.innerHTML = token.value; + } deco = this.decoCache[token.value] = Decoration.replace({widget: new ogLinkWidget(div), block: true}); } decorations.push(deco.range(token.from, token.to)); diff --git a/src/LinkThumbnailWidgetParams.ts b/src/LinkThumbnailWidgetParams.ts index 8c850a6..55f09fb 100644 --- a/src/LinkThumbnailWidgetParams.ts +++ b/src/LinkThumbnailWidgetParams.ts @@ -13,132 +13,116 @@ const OGDATACHACHE = "ogDataCache" // url 정규식 const urlRegex = new RegExp("^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$"); -// 로컬데이터 접근 -function loadLocalOgData(url: string) { +// 저장하기 전에 img 데이터를 url-> blob -> base64로 변환 후 저장 +async function getImgFile(imgUrl: string) { + const imgFormat = ["jpg", "jpeg", "png", "bmp", "tif", "gif", "svg"]; + try { + let imgType = ""; + imgFormat.forEach((format) => { + if (imgUrl.includes(format)) { + imgType = format; + } + }); + + const options: RequestUrlParam = { + url: imgUrl, + contentType: `image/${imgType}` + } + + const file = await requestUrl(options); + const fileArrayBuffer = file.arrayBuffer; + + // 방법 2) ArrayBuffer 자체를 base64로 변환 + const uint8 = new Uint8Array(fileArrayBuffer); + const base64String = btoa(uint8.reduce((data, byte)=> { + return data + String.fromCharCode(byte); + }, '')); + if (imgType.includes("svg")) imgType += "+xml"; + return `data:image/${imgType};charset=utf-8;base64,` + base64String; + + } catch (error) { + console.log(error); + return ""; + } +} + +async function getOgData(url: string) { + // 로컬데이터 접근 const data = localStorage.getItem(OGDATACHACHE + url); if (data) { const ogData: ogData = JSON.parse(data); return ogData; - } -} - -async function saveLocalOgData(url:string, ogData: ogData) { - const imgUrl = ogData.ogImage; - if (imgUrl !== "") { - try { - const lastDot = imgUrl.lastIndexOf("."); - let imgType = imgUrl.substring(lastDot + 1, imgUrl.length).toLowerCase(); + } else { + const urlArr = urlRegex.exec(url); + if (urlArr) { + try { + const response = await requestUrl(url); + if (response && response.headers && response.headers['content-type'] && !response.headers['content-type']?.includes('text/')) { + throw new Error('Page must return a header content-type with text/'); + } - const options: RequestUrlParam = { - url: imgUrl, - method: "POST", - contentType: `image/${imgType}` - } + // 인코딩 문제 해결 + const bodyArrayBuffer = response.arrayBuffer; + const contentType = response.headers["content-type"].toLowerCase().trim(); + const charset = contentType.substring(contentType.indexOf("charset=") + 8, contentType.length); + let body; + if (charset === "utf-8") { + body = Buffer.from(bodyArrayBuffer).toString('utf-8'); + } else { + body = decode(Buffer.from(bodyArrayBuffer), charset); + } + + const parser = new DOMParser(); + const document = parser.parseFromString(body, 'text/html'); + + const ogTitle = document.querySelector("meta[property='og:title']")?.getAttribute("content") || document.querySelector("title")?.textContent || ""; + const ogDescription = document.querySelector("meta[property='og:description']")?.getAttribute("content") || ""; + let ogImage = ""; + let imgUrl = document.querySelector("meta[property='og:image']")?.getAttribute("content") || ""; + if (imgUrl !== "") { + const baseUrl = url.replace(urlArr[4], ""); + if (!imgUrl.startsWith("http")) { + imgUrl = baseUrl + (imgUrl.startsWith("/"))? "" : "/" + imgUrl; + } - const file = await requestUrl(options); - const fileArrayBuffer = file.arrayBuffer; - // 방법 1) blob 변환 - // const fileBlob = new Blob([fileArrayBuffer], { type: `image/${imgType}`}); - - // // 파일 리더 생성 - // const reader = new FileReader(); - // reader.readAsDataURL(fileBlob); - // let base64String = ""; - // reader.onloadend = () => { - // const base64 = reader.result; - // if (typeof base64 === "string") base64String = base64; - // }; - - // 방법 2) ArrayBuffer 자체를 base64로 변환 - const uint8 = new Uint8Array(fileArrayBuffer); - const base64String = btoa(uint8.reduce((data, byte)=> { - return data + String.fromCharCode(byte); - }, '')); - if (imgType.includes("svg")) imgType += "+xml"; - ogData.ogImage = `data:image/${imgType};charset=utf-8;base64,` + base64String; - - } catch (error) { - console.log(error); - } - } - - // 저장하기 전에 img 데이터를 url-> blob -> base64로 변환 후 저장 - localStorage.setItem(OGDATACHACHE + url, JSON.stringify(ogData)); -} - - -async function getOgData(url: string) { - const urlArr = urlRegex.exec(url); - if (urlArr) { - try { - const options: RequestUrlParam = { - url: url, - method: "POST", + ogImage = await getImgFile(imgUrl); + } + + const ogImageAlt = document.querySelector("meta[property='og:image:alt']")?.getAttribute("content") || ""; + const ogUrl = document.querySelector("meta[property='og:url']")?.getAttribute("content") || url; + + const data: ogData = { + "ogTitle": ogTitle, + "ogDescription": ogDescription, + "ogImage": ogImage, + "ogImageAlt": ogImageAlt, + "ogUrl": ogUrl + } + + localStorage.setItem(OGDATACHACHE + url, JSON.stringify(data)); + return data; + } catch (error) { + console.error(error); + return null; } - const response = await requestUrl(options); - if (response && response.headers && response.headers['content-type'] && !response.headers['content-type']?.includes('text/')) { - throw new Error('Page must return a header content-type with text/'); - } - - // 인코딩 문제 해결 - const bodyArrayBuffer = response.arrayBuffer; - const contentType = response.headers["content-type"].toLowerCase().trim(); - const charset = contentType.substring(contentType.indexOf("charset=") + 8, contentType.length); - let body; - if (charset === "utf-8") { - body = Buffer.from(bodyArrayBuffer).toString('utf-8'); - } else { - body = decode(Buffer.from(bodyArrayBuffer), charset); - } - - const parser = new DOMParser(); - const document = parser.parseFromString(body, 'text/html'); - - const ogTitle = document.querySelector("meta[property='og:title']")?.getAttribute("content") || document.querySelector("title")?.textContent || ""; - const ogDescription = document.querySelector("meta[property='og:description']")?.getAttribute("content") || ""; - let ogImage = document.querySelector("meta[property='og:image']")?.getAttribute("content") || ""; - if (ogImage.startsWith("/")) { - const baseUrl = url.replace(urlArr[4], ""); - ogImage = baseUrl + ogImage; - } - const ogImageAlt = document.querySelector("meta[property='og:image:alt']")?.getAttribute("content") || ""; - const ogUrl = document.querySelector("meta[property='og:url']")?.getAttribute("content") || url; - - const data: ogData = { - "ogTitle": ogTitle, - "ogDescription": ogDescription, - "ogImage": ogImage, - "ogImageAlt": ogImageAlt, - "ogUrl": ogUrl - } - - await saveLocalOgData(url, data); - return data; - } catch (error) { - console.error(error); - return null; } } } -function renderOgData(data:ogData) { - return ` -
${data?.ogUrl}
-