Skip to content

Instantly share code, notes, and snippets.

@M1nhNV
Last active October 16, 2020 08:47
Show Gist options
  • Save M1nhNV/c7413906ec98bcbe34a759d9c5e32898 to your computer and use it in GitHub Desktop.
Save M1nhNV/c7413906ec98bcbe34a759d9c5e32898 to your computer and use it in GitHub Desktop.
Open Graph
import openGraph from './open-graph'
async addOpenGraphInfoToContent (content) {
return await openGraph(content)
}
/**
* @author @phong.nhh
*/
var ready = callback => {
if (document.readyState != "loading") callback();
else document.addEventListener("DOMContentLoaded", callback);
};
ready(() => {
var allATags = document.querySelectorAll('a')
for (let i = 0; i < allATags.length; i++) {
const aTag = allATags[i];
aTag.setAttribute('id', 'aTag' + i);
aTag.setAttribute('class', 'open-graph');
const url = aTag.getAttribute('href')
getContentRaw(apiUrl + url).then(data => {
if (data && data.data) {
const info = data.data;
const isEmptyData = Object.keys(info).findIndex(key => key !== 'image' && info[key] === '') !== -1
if (!isEmptyData) {
aTag.innerHTML = `
<span class="left">
<span class="title">${info.title}</span>
<span class="description">${info.description}</span>
<span class="fa fa-link">${parseDomainName(info.url)}</span>
</span>
<img src="${info.image || thumbnailErrorUrl}" alt="${info.title}">
`
}
}
})
}
});
function parseDomainName(url) {
const urlRegex = /^(?:https?:\/\/)?(?:www\.)?([^/]+)/;
const siteNameMatch = url.match(urlRegex);
if (siteNameMatch !== null && siteNameMatch[1] !== '') {
return siteNameMatch[1];
}
return url;
}
async function getContentRaw(url) {
const options = {
headers: new Headers({
'content-type': 'application/json',
'Accept': 'application/json'
})
};
const response = await fetch(url, options);
return response.json()
}
<!--
* @author @phong.nhh
* Example for mobile web browser
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script>
var apiUrl = 'https://api.dev.k250.bla-one.net/v1/data/storage/content?path='
var thumbnailErrorUrl = 'img.jpg'
</script>
<script src="index.js"></script>
</head>
<body>
<p><a target="_blank" href="https://www.24h.com.vn/tin-tuc-trong-ngay/ben-xe-lon-nhat-nuoc-dep-nhu-san-bay-quoc-te-va-hinh-anh-sau-khai-truong-c46a1190355.html">https://www.24h.com.vn/tin-tuc-trong-ngay/ben-xe-lon-nhat-nuoc-dep-nhu-san-bay-quoc-te-va-hinh-anh-sau-khai-truong-c46a1190355.html</a></p><p><a target="_blank" href="https://nld.com.vn/thoi-su/nhieu-ky-vong-vao-dai-hoi-dang-bo-tp-hcm-nhiem-ky-2020-2025-20201013230535416.htm">https://nld.com.vn/thoi-su/nhieu-ky-vong-vao-dai-hoi-dang-bo-tp-hcm-nhiem-ky-2020-2025-20201013230535416.htm</a></p><p><a target="_blank" href="https://www.instagram.com/giant_japan/">https://www.instagram.com/giant_japan/</a></p><p><a target="_blank" href="https://www3.nhk.or.jp/news/html/20201014/k10012662481000.html?utm_int=error_contents_news-main_003">https://www3.nhk.or.jp/news/html/20201014/k10012662481000.html?utm_int=error_contents_news-main_003</a><a target="_blank" href="https://www.olympicchannel.com/ja/stories/features/detail/%E6%9C%80%E5%A4%A7%E6%99%82%E9%80%9F70km-h%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E8%87%AA%E8%BB%A2%E8%BB%8A%E3%83%88%E3%83%A9%E3%83%83%E3%82%AF%E7%AB%B6%E6%8A%80-%E5%80%8B%E4%BA%BA%E3%81%8B%E3%82%89%E3%83%81-%E3%83%A0%E3%81%AB%E3%82%88%E3%82%8B%E7%99%BD%E7%86%B1%E3%81%AE6%E7%A8%AE%E7%9B%AE%E3%82%92%E8%A7%A3%E8%AA%AC/">https://www.olympicchannel.com/ja/stories/features/detail/%E6%9C%80%E5%A4%A7%E6%99%82%E9%80%9F70km-h%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E8%87%AA%E8%BB%A2%E8%BB%8A%E3%83%88%E3%83%A9%E3%83%83%E3%82%AF%E7%AB%B6%E6%8A%80-%E5%80%8B%E4%BA%BA%E3%81%8B%E3%82%89%E3%83%81-%E3%83%A0%E3%81%AB%E3%82%88%E3%82%8B%E7%99%BD%E7%86%B1%E3%81%AE6%E7%A8%AE%E7%9B%AE%E3%82%92%E8%A7%A3%E8%AA%AC/</a></p><p><a target="_blank" href="https://www3.nhk.or.jp/news/html/20201013/k10012661771000.html?utm_int=all_side_ranking-social_004">https://www3.nhk.or.jp/news/html/20201013/k10012661771000.html?utm_int=all_side_ranking-social_004</a></p>
</body>
</html>
.open-graph {
display: flex;
width: 100%;
max-width: 600px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 7px;
cursor: pointer;
margin-bottom: 10px;
margin-top: 10px;
text-decoration: none;
box-sizing: border-box;
}
.open-graph img {
padding-left: 10px;
margin-bottom: 0;
text-align: right;
height: 80px;
}
.open-graph .left {
width: 60%;
color: #999;
flex: 1;
display: flex;
flex-direction: column;
}
.open-graph .left .title {
color: #666;
display: block;
font-weight: bold;
margin-bottom: 10px;
height: 17px;
font-size: 14px;
white-space: pre;
overflow: hidden;
text-overflow: ellipsis;
}
.open-graph .left .description {
margin-bottom: 10px;
display: block;
height: 35px;
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
}
.open-graph .left .fa {
display: block;
font-weight: bold;
color: #666;
margin-top: auto;
}
.open-graph .left .fa:before {
margin-right: 7px;
}
@media (max-width: 767px) {
.open-graph img {
height: 60px;
}
.open-graph .left .title {
font-size: 12px;
}
.open-graph .left .description {
font-size: 11px;
height: 17px;
white-space: pre;
}
.open-graph .left .fa {
font-size: 11px;
}
}
// Using Axios call api get data
// global object
const result = {}
import thumbnailErrorUrl from '@/assets/images/thumbnail-error.png'
import AxiosClient from '@/utils/axios';
export default async function openGraph (bodyString, callBack) {
const data = await detectATag(bodyString).then(
newContent => {
return newContent
}
)
return callBack(data)
}
function detectURL (string) {
const regexUrl = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/s
const match = string.match(regexUrl)
return match !== null && match[0] !== null ? match[0] : ''
}
async function detectATag (content) {
const aTagRegex = /<\s*a[^>]*>(.*?)<\s*\/\s*a>/g
const resultATag = content.match(aTagRegex)
if (resultATag === null) {
return content
}
try {
let newContent = String(content)
const mappingUrlAndATag = {}
for (const i in resultATag) {
// get urls
const url = detectURL(resultATag[i])
mappingUrlAndATag[url] = resultATag[i]
// check has result
if (result.hasOwnProperty(url)) {
newContent = makeHtml(newContent, url, mappingUrlAndATag)
continue;
}
// call api
await AxiosClient.get('data/storage/content?path='+ url).then(res => {
if (res.data && res.data.data ) {
result[url] = res.data.data
newContent = makeHtml(newContent, url, mappingUrlAndATag)
} else {
result[url] = ''
}
}).catch(e => '')
}
return newContent
} catch (e) {
return content
}
}
function makeHtml (newContent, url, mappingUrlAndATag) {
if (result[url] && result[url].title !== '' && result[url].url !== '' && result[url].description !== '' && result[url].site_name !== '') {
const html = generateHTML(url, result[url])
for (const i in mappingUrlAndATag) {
if (i === url) {
newContent = newContent.replace(mappingUrlAndATag[url], html)
}
}
}
return newContent
}
function generateHTML (url, info) {
return `<a href="${url}" target="_blank" class="open-graph">
<span class="left">
<span class="title">${info.title}</span>
<span class="description">${info.description}</span>
<span class="fa fa-link">${parseDomainName(info.url)}</span>
</span>
<img src="${ info.image === '' ? thumbnailErrorUrl : info.image }" alt="${info.title}">
</a>`
}
function parseDomainName (url) {
const urlRegex = /^(?:https?:\/\/)?(?:www\.)?([^/]+)/
const siteNameMatch = url.match(urlRegex)
if (siteNameMatch !== null && siteNameMatch[1] !== '') {
return siteNameMatch[1]
}
return url
}
// global object
const result = {}
export default async function openGraph (bodyString) {
return await detectATag(bodyString).then(
newContent => {
return newContent
}
)
}
function detectURL (string) {
const regexUrl = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g
const result = string.match(regexUrl)
const urls = []
for (const i in result) {
if (!urls.includes(result[i])) {
urls.push(result[i])
}
}
return urls
}
function checkHasResult (url) {
return result && result.hasOwnProperty(url)
}
function saveToResult (url, info) {
result[url] = info
}
async function detectATag (content) {
const aTagRegex = /<\s*a[^>]*>(.*?)<\s*\/\s*a>/g
const resultATag = content.match(aTagRegex)
if (resultATag === null) {
return content
}
try {
let newContent = String(content)
const htmlContentRaws = {}
const promiseContents = {}
const mappingUrlAndATag = {}
for (const i in resultATag) {
// get urls
const url = detectURL(resultATag[i])
mappingUrlAndATag[url] = resultATag[i]
promiseContents[url] = getContentRaw(url).then ( res => {
return res
}).catch(e => '')
}
for (const i in promiseContents) {
htmlContentRaws[i] = await promiseContents[i]
}
//get info
for (const i in htmlContentRaws) {
if (htmlContentRaws[i] !== '') {
//get og html string
const ogTags = parseOgTag(htmlContentRaws[i])
// get open graph info
const openGraphInfo = parseInfo(ogTags)
// save to result
saveToResult(i, openGraphInfo)
// generate html
const html = generateHTML(i, openGraphInfo)
for (const url in mappingUrlAndATag) {
if (url === i) {
newContent = newContent.replace(mappingUrlAndATag[i], html)
}
}
}
}
return newContent
} catch (e) {
return content
}
}
async function getContentRaw (url) {
const response = await fetch(url)
return await _getTextFromStream(response.body)
}
function parseOgTag (rawHtml) {
const re = /<meta\sproperty="og:.+\/>/gm;
return rawHtml.match(re)
}
function parseInfo (tags) {
const info = {
title: '',
site_name: '',
url: '',
image: '',
description: ''
}
for (const i in tags) {
const regContent = /<meta\sproperty="og:([a-z_]+)".+content="(.+)+"\s\/>/;
const match = tags[i].match(regContent)
if (match !== null && info.hasOwnProperty(match[1])) {
info[match[1]] = match[2]
const urlRegex = /^(?:https?:\/\/)?(?:www\.)?([^/]+)/
const siteNameMatch = info.url.match(urlRegex)
if (siteNameMatch !== null && siteNameMatch[1] !== '') {
info.site_name = siteNameMatch[1]
}
}
}
return info
}
function generateHTML (url, info) {
return `<a href="${url}" target="_blank" class="open-graph">
<span class="left">
<span class="title">${info.title}</span>
<span class="description">${info.description}</span>
<span class="fa fa-link">${info.site_name}</span>
</span>
<img src="${info.image}" alt="${info.title}">
</a>`
}
async function _getTextFromStream (readableStream) {
const reader = readableStream.getReader()
const utf8Decoder = new TextDecoder()
let nextChunk
let resultStr = ''
while (!(nextChunk = await reader.read()).done) {
const partialData = nextChunk.value
resultStr += utf8Decoder.decode(partialData)
}
return resultStr
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment