vitepress-linkcard 0.9.9 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,9 +5,8 @@ vitepress-linkcard
5
5
  <div align="center">
6
6
 
7
7
  [![NPM Version](https://img.shields.io/npm/v/vitepress-linkcard?style=flat&logo=npm&logoColor=white&label=npmjs&color=%23CB3837)](https://www.npmjs.com/package/vitepress-linkcard)
8
- ![npm bundle size](https://img.shields.io/bundlephobia/min/vitepress-linkcard)
9
- [![NPM License](https://img.shields.io/npm/l/vitepress-linkcard)
10
- ](/LICENSE)
8
+ [![NPM bundle size](https://img.shields.io/bundlephobia/min/vitepress-linkcard)](https://www.npmjs.com/package/vitepress-linkcard)
9
+ [![NPM License](https://img.shields.io/npm/l/vitepress-linkcard)](/LICENSE)
11
10
 
12
11
  [![VitePress](https://img.shields.io/badge/For_VitePress-v1.6.4-%235C73E7?logo=vitepress&logoColor=white)](https://vuejs.github.io/vitepress/v1/)
13
12
  [![Yarn](https://img.shields.io/badge/Built_with_Yarn-v4.9.2-%232C8EBB?logo=yarn&logoColor=white)](https://yarnpkg.com/)
@@ -0,0 +1,23 @@
1
+ /*!
2
+ * vitepress-linkcard v1.0.0
3
+ * (c) 2022 - 2025 luckrya
4
+ * Released under the MIT License.
5
+ */
6
+ Object.defineProperty(exports,"__esModule",{value:!0});var e=require("url"),t=require("child_process"),r=require("fs"),n=require("http"),o=require("https"),i=require("node:fs");function a(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var s,c,l=a(e),u=a(t),d=a(r),f=a(n),p=a(o),h=a(i);function g(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=Array(t);r<t;r++)n[r]=e[r];return n}function v(e,t){for(var r=0;r<t.length;r++){var n=t[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,m(n.key),n)}}function y(e,t,r){return(t=m(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function b(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function E(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{};t%2?b(Object(r),!0).forEach(function(t){y(e,t,r[t])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):b(Object(r)).forEach(function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))})}return e}function w(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var r=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=r){var n,o,i,a,s=[],c=!0,l=!1;try{if(i=(r=r.call(e)).next,0===t){if(Object(r)!==r)return;c=!1}else for(;!(c=(n=i.call(r)).done)&&(s.push(n.value),s.length!==t);c=!0);}catch(e){l=!0,o=e}finally{try{if(!c&&null!=r.return&&(a=r.return(),Object(a)!==a))return}finally{if(l)throw o}}return s}}(e,t)||function(e,t){if(e){if("string"==typeof e)return g(e,t);var r={}.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?g(e,t):void 0}}
7
+ /*!
8
+ * @luckrya/utility v0.1.0
9
+ * (c) 2022 - 2022 Y.R
10
+ * Released under the MIT License.
11
+ */(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function m(e){var t=function(e,t){if("object"!=typeof e||!e)return e;var r=e[Symbol.toPrimitive];if(void 0!==r){var n=r.call(e,t||"default");if("object"!=typeof n)return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:t+""}(c=s||(s={})).Array="[object Array]",c.Object="[object Object]",c.Function="[object Function]",c.Number="[object Number]",c.String="[object String]",c.Boolean="[object Boolean]",c.Undefined="[object Undefined]",c.Null="[object Null]",c.Error="[object Error]";var x=function(e){return Object.prototype.toString.call(e)===s.String},O=function(e){return Object.prototype.toString.call(e)===s.Function},S="undefined"!=typeof window,C=function(){return"".concat(process.cwd(),"/.linkcardrc")},k=function(){return function(e,t,r){return t&&v(e.prototype,t),r&&v(e,r),Object.defineProperty(e,"prototype",{writable:!1}),e}(function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e)},[{key:"setFile",value:function(e){var t=e,r=this.readFile();r&&(t=Object.assign(r,t)),h.default.writeFileSync(C(),JSON.stringify(t))}},{key:"readFile",value:function(){var e=h.default.readFileSync(C(),"utf-8"),t=JSON.parse(e);if(function(e){return Object.prototype.toString.call(e)===s.Object}(t))return t}},{key:"has",value:function(e){return!!this.get(e)}},{key:"get",value:function(e){var t=this.readFile();return null==t?void 0:t[e]}},{key:"set",value:function(e,t){this.setFile(y({},e,t))}}])}(),N=new k;function T(e){return'style="'.concat(function(e){return Object.entries(e).map(function(e){var t,r=w(e,2),n=r[0],o=r[1];if(n&&o)return"".concat((t=n,t.replace(/\B([A-Z])/g,"-$1").toLowerCase()),": ").concat(o,";")}).filter(Boolean).join(" ")}(e),'"')}var j=function(e){return{"-webkit-box-orient":"vertical","-webkit-line-clamp":e,display:"-webkit-box",hyphens:"auto",lineClamp:e,overflow:"hidden",overflowWrap:"anywhere",textOverflow:"ellipsis",wordBreak:"break-word"}},R=function(e,t){var r,n,o,i,a={rel:'rel="noopener noreferrer"',target:'target="'.concat(t.target,'"'),href:'href="'.concat(t.href,'"'),title:'title="'.concat(t.linkTitle,'"'),borderColor:'borderColor="'.concat(t.borderColor,'"'),bgColor:'bgColor="'.concat(t.bgColor,'"')},s=function(e){return e.replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&#039;/g,"'")},c=(r=t.borderColor||"#7d7d7dff",n=t.bgColor||"#7d7d7d00",{a:T({color:"unset !important",display:"block",width:"100%",textDecoration:"none"}),container:T({display:"flex",alignItems:"center",flexWrap:"wrap",gap:"10px",borderRadius:"12px",border:"1px solid ".concat(r),backgroundColor:n,boxSizing:"border-box",width:"100%",height:"130px"}),img:T({borderRadius:"0px 12px 12px 0px",maxWidth:"40%",height:"128px",flexShrink:0,objectFit:"contain",overflow:"hidden"}),texts:T({flex:"1 1 0%",minWidth:"0"}),title:T(E(E({},j(2)),{},{opacity:1,fontSize:"16px",lineHeight:"22px",margin:"0 16px 8px 16px",fontWeight:"bold"})),domain:T(E(E({},j(1)),{},{opacity:1,fontSize:"12px",lineHeight:"16px",margin:"8px 16px 8px 16px",textDecoration:"underline"})),description:T(E(E({},j(2)),{},{opacity:.8,fontSize:"12px",lineHeight:"16px",margin:"8px 16px 0px 16px"}))}),l=t.href||"",u=new URL(l).origin.replace(/^https?:\/\//,"").replace(/^www\./,"")||"Unknown domain",d=e.title,f=e.description;"github.com"==u?(d=(null===(o=e.title)||void 0===o?void 0:o.split(":")[0].replace("GitHub - ",""))||"No title",f=(null===(i=f)||void 0===i?void 0:i.replace(" - ".concat(d),"").replace("Contribute to ".concat(d," development by creating an account on GitHub."),""))||""):(d=e.title||"No title",f=e.description||"");return'<span style="display:block;">\n <a '.concat(a.rel," ").concat(a.target," ").concat(a.href," ").concat(a.title," ").concat(c.a,">\n <span ").concat(c.container,">\n <span ").concat(c.texts,">\n <span ").concat(c.title,">\n ").concat(s(d),"\n </span>\n <span ").concat(c.domain,">\n ").concat(s(u),"\n </span>\n <span ").concat(c.description,">\n ").concat(s(f),'\n </span>\n </span>\n <img src="').concat(null==e?void 0:e.logo,'" ').concat(c.img,"/>\n </span>\n </a>\n</span>")};function D(e){return new URL(e)}var A="https://resources.whatwg.org/logo-url.svg",L=/(<[A-Za-z]+\s*[^>]*>(.*)<\/[A-Za-z]+>)/,q=/content=["|']([^>]*)["|']/,P=/href=["|']([^>]*)["|']/,H=/(<title\s*[^>]*>(.*)<\/title>)/g,_=function(e){return new RegExp("<".concat(arguments.length>1&&void 0!==arguments[1]?arguments[1]:"meta","\\s[^>]*\\w+=['|\"]([a-zA-Z]|:|\\s)*").concat(e,"['|\"][^>]*\\/?>"))};function F(e){var t,r=e.match(_("title"));if(null!=r&&r.length){var n=r[0].match(q);n&&x(n[1])&&(t=n[1])}else{var o=e.match(H);if(null!=o&&o.length){var i=o[0].match(L);i&&x(i[2])&&(t=i[2])}}return t}function I(e){var t,r=e.match(_("description"));if(null!=r&&r.length){var n=r[0].match(q);n&&x(n[1])&&(t=n[1])}return t}function U(e){var t,r=e.match(_("image"));if(null!=r&&r.length){var n=r[0].match(q);n&&x(n[1])&&(t=n[1])}else{var o=e.match(_("icon","link"));if(null!=o&&o.length){var i=o[0].match(P);i&&x(i[1])&&(t=i[1])}}return t}function M(e,t){var r,n,o,i={title:F(e),description:I(e),logo:(r=U(e),r?D(r)?r:"".concat(null===(n=D(t))||void 0===n?void 0:n.origin).concat("/".concat(r).replace(/\/\//g,"/")):A)};return o=i,Object.values(o).filter(function(e){return x(e)}).length?i:null}function B(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var G,z={};
12
+ /**
13
+ * Wrapper for built-in http.js to emulate the browser XMLHttpRequest object.
14
+ *
15
+ * This can be used with JS designed for browsers to improve reuse of code and
16
+ * allow the use of existing libraries.
17
+ *
18
+ * Usage: include("XMLHttpRequest.js") and use XMLHttpRequest per W3C specs.
19
+ *
20
+ * @author Dan DeFelippi <dan@driverdan.com>
21
+ * @contributor David Ellis <d.f.ellis@ieee.org>
22
+ * @license MIT
23
+ */var J=function(){if(G)return z;G=1;var e=l.default,t=u.default.spawn,r=d.default;return z.XMLHttpRequest=function(){var n,o,i=this,a=f.default,s=p.default,c={},l=!1,u={"User-Agent":"node-XMLHttpRequest",Accept:"*/*"},d={},h={},g=["accept-charset","accept-encoding","access-control-request-headers","access-control-request-method","connection","content-length","content-transfer-encoding","cookie","cookie2","date","expect","host","keep-alive","origin","referer","te","trailer","transfer-encoding","upgrade","via"],v=["TRACE","TRACK","CONNECT"],y=!1,b=!1,E={};this.UNSENT=0,this.OPENED=1,this.HEADERS_RECEIVED=2,this.LOADING=3,this.DONE=4,this.readyState=this.UNSENT,this.onreadystatechange=null,this.responseText="",this.responseXML="",this.status=null,this.statusText=null,this.withCredentials=!1;this.open=function(e,t,r,n,o){if(this.abort(),b=!1,!function(e){return e&&-1===v.indexOf(e)}(e))throw new Error("SecurityError: Request method not allowed");c={method:e,url:t.toString(),async:"boolean"!=typeof r||r,user:n||null,password:o||null},w(this.OPENED)},this.setDisableHeaderCheck=function(e){l=e},this.setRequestHeader=function(e,t){if(this.readyState!==this.OPENED)throw new Error("INVALID_STATE_ERR: setRequestHeader can only be called when state is OPEN");if(function(e){return l||e&&-1===g.indexOf(e.toLowerCase())}(e)){if(y)throw new Error("INVALID_STATE_ERR: send flag is true");e=h[e.toLowerCase()]||e,h[e.toLowerCase()]=e,d[e]=d[e]?d[e]+", "+t:t}else console.warn('Refused to set unsafe header "'+e+'"')},this.getResponseHeader=function(e){return"string"==typeof e&&this.readyState>this.OPENED&&o&&o.headers&&o.headers[e.toLowerCase()]&&!b?o.headers[e.toLowerCase()]:null},this.getAllResponseHeaders=function(){if(this.readyState<this.HEADERS_RECEIVED||b)return"";var e="";for(var t in o.headers)"set-cookie"!==t&&"set-cookie2"!==t&&(e+=t+": "+o.headers[t]+"\r\n");return e.substr(0,e.length-2)},this.getRequestHeader=function(e){return"string"==typeof e&&h[e.toLowerCase()]?d[h[e.toLowerCase()]]:""},this.send=function(l){if(this.readyState!==this.OPENED)throw new Error("INVALID_STATE_ERR: connection must be opened before send() is called");if(y)throw new Error("INVALID_STATE_ERR: send has already been called");var f,p=!1,g=!1,v=e.parse(c.url);switch(v.protocol){case"https:":p=!0;case"http:":f=v.hostname;break;case"file:":g=!0;break;case void 0:case null:case"":f="localhost";break;default:throw new Error("Protocol not supported.")}if(g){if("GET"!==c.method)throw new Error("XMLHttpRequest: Only GET method is supported");if(c.async)r.readFile(v.pathname,"utf8",function(e,t){e?i.handleError(e):(i.status=200,i.responseText=t,w(i.DONE))});else try{this.responseText=r.readFileSync(v.pathname,"utf8"),this.status=200,w(i.DONE)}catch(e){this.handleError(e)}}else{var E=v.port||(p?443:80),m=v.pathname+(v.search?v.search:"");for(var x in u)h[x.toLowerCase()]||(d[x]=u[x]);if(d.Host=f,p&&443===E||80===E||(d.Host+=":"+v.port),c.user){void 0===c.password&&(c.password="");var O=new Buffer(c.user+":"+c.password);d.Authorization="Basic "+O.toString("base64")}"GET"===c.method||"HEAD"===c.method?l=null:l?(d["Content-Length"]=Buffer.isBuffer(l)?l.length:Buffer.byteLength(l),d["Content-Type"]||(d["Content-Type"]="text/plain;charset=UTF-8")):"POST"===c.method&&(d["Content-Length"]=0);var S={host:f,port:E,path:m,method:c.method,headers:d,agent:!1,withCredentials:i.withCredentials};if(b=!1,c.async){var C=p?s.request:a.request;y=!0,i.dispatchEvent("readystatechange");var k=function(e){i.handleError(e)};n=C(S,function t(r){if(301!==(o=r).statusCode&&302!==o.statusCode&&303!==o.statusCode&&307!==o.statusCode)o.setEncoding("utf8"),w(i.HEADERS_RECEIVED),i.status=o.statusCode,o.on("data",function(e){e&&(i.responseText+=e),y&&w(i.LOADING)}),o.on("end",function(){y&&(w(i.DONE),y=!1)}),o.on("error",function(e){i.handleError(e)});else{c.url=o.headers.location;var a=e.parse(c.url);f=a.hostname;var s={hostname:a.hostname,port:a.port,path:a.path,method:303===o.statusCode?"GET":c.method,headers:d,withCredentials:i.withCredentials};(n=C(s,t).on("error",k)).end()}}).on("error",k),l&&n.write(l),n.end(),i.dispatchEvent("loadstart")}else{var N=".node-xmlhttprequest-content-"+process.pid,T=".node-xmlhttprequest-sync-"+process.pid;r.writeFileSync(T,"","utf8");for(var j="var http = require('http'), https = require('https'), fs = require('fs');var doRequest = http"+(p?"s":"")+".request;var options = "+JSON.stringify(S)+";var responseText = '';var req = doRequest(options, function(response) {response.setEncoding('utf8');response.on('data', function(chunk) { responseText += chunk;});response.on('end', function() {fs.writeFileSync('"+N+"', JSON.stringify({err: null, data: {statusCode: response.statusCode, headers: response.headers, text: responseText}}), 'utf8');fs.unlinkSync('"+T+"');});response.on('error', function(error) {fs.writeFileSync('"+N+"', JSON.stringify({err: error}), 'utf8');fs.unlinkSync('"+T+"');});}).on('error', function(error) {fs.writeFileSync('"+N+"', JSON.stringify({err: error}), 'utf8');fs.unlinkSync('"+T+"');});"+(l?"req.write('"+JSON.stringify(l).slice(1,-1).replace(/'/g,"\\'")+"');":"")+"req.end();",R=t(process.argv[0],["-e",j]);r.existsSync(T););var D=JSON.parse(r.readFileSync(N,"utf8"));R.stdin.end(),r.unlinkSync(N),D.err?i.handleError(D.err):(o=D.data,i.status=D.data.statusCode,i.responseText=D.data.text,w(i.DONE))}}},this.handleError=function(e){this.status=0,this.statusText=e,this.responseText=e.stack,b=!0,w(this.DONE),this.dispatchEvent("error")},this.abort=function(){n&&(n.abort(),n=null),d=u,this.status=0,this.responseText="",this.responseXML="",b=!0,this.readyState===this.UNSENT||this.readyState===this.OPENED&&!y||this.readyState===this.DONE||(y=!1,w(this.DONE)),this.readyState=this.UNSENT,this.dispatchEvent("abort")},this.addEventListener=function(e,t){e in E||(E[e]=[]),E[e].push(t)},this.removeEventListener=function(e,t){e in E&&(E[e]=E[e].filter(function(e){return e!==t}))},this.dispatchEvent=function(e){if("function"==typeof i["on"+e]&&i["on"+e](),e in E)for(var t=0,r=E[e].length;t<r;t++)E[e][t].call(i)};var w=function(e){e!=i.LOADING&&i.readyState===e||(i.readyState=e,(c.async||i.readyState<i.OPENED||i.readyState===i.DONE)&&i.dispatchEvent("readystatechange"),i.readyState!==i.DONE||b||(i.dispatchEvent("load"),i.dispatchEvent("loadend")))}},z}(),X=B(J),V=new Map,W=S?window.XMLHttpRequest:X.XMLHttpRequest;function Z(e){if(V.has(e))return V.get(e);var t;try{var r=new W;r.open("GET",e,!1),r.setRequestHeader("Content-Type","text/html"),r.send(),200===r.status&&r.responseText&&(t=r.responseText,V.set(e,r.responseText))}catch(e){console.error("【XHR Error】:".concat(e instanceof Error?e.message:"get remote URL resource exception!"))}return t}var $=new Map;exports.generateCard=function(e,t){return new Promise(function(r){var n=Z(e);if(n){var o=M(n,e);if(o){var i={linkTitle:t.linkTitle,target:t.target||"_blank",classPrefix:t.classPrefix},a=R(o,E(E({},i),{},{href:e})),s={url:e,data:o,options:i,dom:a};$.set(e,s),r(s)}}})},exports.linkToCardPlugin=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};function r(e){var r,n=function(e){if(N.has(e))return N.get(e);var t=null,r=Z(e);return r&&(t=M(r,e))&&N.set(e,t),t}(e.url);if(n){!function(e,t){e.forEach(function(e,r){r!==t&&(e.hidden=!0)})}(e.tokens,e.i);var o={href:e.url,linkTitle:(r=e.tokens,r.map(function(e){var t=e.hidden,r=e.content;return t?r:""}).filter(Boolean).join("")),target:t.target||"_blank",classPrefix:t.classPrefix,borderColor:t.borderColor,bgColor:t.bgColor};return O(t.render)?t.render(n,o):R(n,o)}}e.renderer.renderInline=function(t,r,n){for(var o="",i=0;i<t.length;i++){var a=t[i],s=e.renderer.rules[a.type];a.hidden?o+="":O(s)?o+=s(t,i,r,n,e.renderer):o+=e.renderer.renderToken(t,i,r)}return o},e.renderer.rules.link_open=function(e,t,n,o,i){var a,s=e[t],c="a"===s.tag&&"link_open"===s.type,l=function(e){var t=new RegExp("^(".concat("@",":)([a-zA-Z0-9]+.*)")),r=null==e?void 0:e.match(t);return{isCardLink:!!r,url:null==r?void 0:r[2]}}(null===(a=s.attrs)||void 0===a||null===(a=a.filter(function(e){return e.includes("href")})[0])||void 0===a?void 0:a[1]),u=l.url,d=l.isCardLink;if(c&&d&&u){var f=r({url:u,tokens:e,i:t});if(f)return f}return i.renderToken(e,t,n)}};
@@ -0,0 +1,23 @@
1
+ /*!
2
+ * vitepress-linkcard v1.0.0
3
+ * (c) 2022 - 2025 luckrya
4
+ * Released under the MIT License.
5
+ */
6
+ import e from"url";import t from"child_process";import r from"fs";import n from"http";import o from"https";import i from"node:fs";function a(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=Array(t);r<t;r++)n[r]=e[r];return n}function s(e,t){for(var r=0;r<t.length;r++){var n=t[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,d(n.key),n)}}function c(e,t,r){return(t=d(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function l(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function u(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{};t%2?l(Object(r),!0).forEach(function(t){c(e,t,r[t])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):l(Object(r)).forEach(function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))})}return e}function f(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var r=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=r){var n,o,i,a,s=[],c=!0,l=!1;try{if(i=(r=r.call(e)).next,0===t){if(Object(r)!==r)return;c=!1}else for(;!(c=(n=i.call(r)).done)&&(s.push(n.value),s.length!==t);c=!0);}catch(e){l=!0,o=e}finally{try{if(!c&&null!=r.return&&(a=r.return(),Object(a)!==a))return}finally{if(l)throw o}}return s}}(e,t)||function(e,t){if(e){if("string"==typeof e)return a(e,t);var r={}.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?a(e,t):void 0}}
7
+ /*!
8
+ * @luckrya/utility v0.1.0
9
+ * (c) 2022 - 2022 Y.R
10
+ * Released under the MIT License.
11
+ */(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function d(e){var t=function(e,t){if("object"!=typeof e||!e)return e;var r=e[Symbol.toPrimitive];if(void 0!==r){var n=r.call(e,t||"default");if("object"!=typeof n)return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:t+""}var p,h;(h=p||(p={})).Array="[object Array]",h.Object="[object Object]",h.Function="[object Function]",h.Number="[object Number]",h.String="[object String]",h.Boolean="[object Boolean]",h.Undefined="[object Undefined]",h.Null="[object Null]",h.Error="[object Error]";var v=function(e){return Object.prototype.toString.call(e)===p.String},g=function(e){return Object.prototype.toString.call(e)===p.Function},y="undefined"!=typeof window,b=function(){return"".concat(process.cwd(),"/.linkcardrc")},m=function(){return function(e,t,r){return t&&s(e.prototype,t),r&&s(e,r),Object.defineProperty(e,"prototype",{writable:!1}),e}(function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e)},[{key:"setFile",value:function(e){var t=e,r=this.readFile();r&&(t=Object.assign(r,t)),i.writeFileSync(b(),JSON.stringify(t))}},{key:"readFile",value:function(){var e=i.readFileSync(b(),"utf-8"),t=JSON.parse(e);if(function(e){return Object.prototype.toString.call(e)===p.Object}(t))return t}},{key:"has",value:function(e){return!!this.get(e)}},{key:"get",value:function(e){var t=this.readFile();return null==t?void 0:t[e]}},{key:"set",value:function(e,t){this.setFile(c({},e,t))}}])}(),E=new m;function w(e){return'style="'.concat(function(e){return Object.entries(e).map(function(e){var t,r=f(e,2),n=r[0],o=r[1];if(n&&o)return"".concat((t=n,t.replace(/\B([A-Z])/g,"-$1").toLowerCase()),": ").concat(o,";")}).filter(Boolean).join(" ")}(e),'"')}var x=function(e){return{"-webkit-box-orient":"vertical","-webkit-line-clamp":e,display:"-webkit-box",hyphens:"auto",lineClamp:e,overflow:"hidden",overflowWrap:"anywhere",textOverflow:"ellipsis",wordBreak:"break-word"}},S=function(e,t){var r,n,o,i,a={rel:'rel="noopener noreferrer"',target:'target="'.concat(t.target,'"'),href:'href="'.concat(t.href,'"'),title:'title="'.concat(t.linkTitle,'"'),borderColor:'borderColor="'.concat(t.borderColor,'"'),bgColor:'bgColor="'.concat(t.bgColor,'"')},s=function(e){return e.replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&#039;/g,"'")},c=(r=t.borderColor||"#7d7d7dff",n=t.bgColor||"#7d7d7d00",{a:w({color:"unset !important",display:"block",width:"100%",textDecoration:"none"}),container:w({display:"flex",alignItems:"center",flexWrap:"wrap",gap:"10px",borderRadius:"12px",border:"1px solid ".concat(r),backgroundColor:n,boxSizing:"border-box",width:"100%",height:"130px"}),img:w({borderRadius:"0px 12px 12px 0px",maxWidth:"40%",height:"128px",flexShrink:0,objectFit:"contain",overflow:"hidden"}),texts:w({flex:"1 1 0%",minWidth:"0"}),title:w(u(u({},x(2)),{},{opacity:1,fontSize:"16px",lineHeight:"22px",margin:"0 16px 8px 16px",fontWeight:"bold"})),domain:w(u(u({},x(1)),{},{opacity:1,fontSize:"12px",lineHeight:"16px",margin:"8px 16px 8px 16px",textDecoration:"underline"})),description:w(u(u({},x(2)),{},{opacity:.8,fontSize:"12px",lineHeight:"16px",margin:"8px 16px 0px 16px"}))}),l=t.href||"",f=new URL(l).origin.replace(/^https?:\/\//,"").replace(/^www\./,"")||"Unknown domain",d=e.title,p=e.description;"github.com"==f?(d=(null===(o=e.title)||void 0===o?void 0:o.split(":")[0].replace("GitHub - ",""))||"No title",p=(null===(i=p)||void 0===i?void 0:i.replace(" - ".concat(d),"").replace("Contribute to ".concat(d," development by creating an account on GitHub."),""))||""):(d=e.title||"No title",p=e.description||"");return'<span style="display:block;">\n <a '.concat(a.rel," ").concat(a.target," ").concat(a.href," ").concat(a.title," ").concat(c.a,">\n <span ").concat(c.container,">\n <span ").concat(c.texts,">\n <span ").concat(c.title,">\n ").concat(s(d),"\n </span>\n <span ").concat(c.domain,">\n ").concat(s(f),"\n </span>\n <span ").concat(c.description,">\n ").concat(s(p),'\n </span>\n </span>\n <img src="').concat(null==e?void 0:e.logo,'" ').concat(c.img,"/>\n </span>\n </a>\n</span>")};function O(e){return new URL(e)}var C="https://resources.whatwg.org/logo-url.svg",k=/(<[A-Za-z]+\s*[^>]*>(.*)<\/[A-Za-z]+>)/,N=/content=["|']([^>]*)["|']/,T=/href=["|']([^>]*)["|']/,j=/(<title\s*[^>]*>(.*)<\/title>)/g,R=function(e){return new RegExp("<".concat(arguments.length>1&&void 0!==arguments[1]?arguments[1]:"meta","\\s[^>]*\\w+=['|\"]([a-zA-Z]|:|\\s)*").concat(e,"['|\"][^>]*\\/?>"))};function D(e){var t,r=e.match(R("title"));if(null!=r&&r.length){var n=r[0].match(N);n&&v(n[1])&&(t=n[1])}else{var o=e.match(j);if(null!=o&&o.length){var i=o[0].match(k);i&&v(i[2])&&(t=i[2])}}return t}function A(e){var t,r=e.match(R("description"));if(null!=r&&r.length){var n=r[0].match(N);n&&v(n[1])&&(t=n[1])}return t}function L(e){var t,r=e.match(R("image"));if(null!=r&&r.length){var n=r[0].match(N);n&&v(n[1])&&(t=n[1])}else{var o=e.match(R("icon","link"));if(null!=o&&o.length){var i=o[0].match(T);i&&v(i[1])&&(t=i[1])}}return t}function P(e,t){var r,n,o,i={title:D(e),description:A(e),logo:(r=L(e),r?O(r)?r:"".concat(null===(n=O(t))||void 0===n?void 0:n.origin).concat("/".concat(r).replace(/\/\//g,"/")):C)};return o=i,Object.values(o).filter(function(e){return v(e)}).length?i:null}function q(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var H,F={};
12
+ /**
13
+ * Wrapper for built-in http.js to emulate the browser XMLHttpRequest object.
14
+ *
15
+ * This can be used with JS designed for browsers to improve reuse of code and
16
+ * allow the use of existing libraries.
17
+ *
18
+ * Usage: include("XMLHttpRequest.js") and use XMLHttpRequest per W3C specs.
19
+ *
20
+ * @author Dan DeFelippi <dan@driverdan.com>
21
+ * @contributor David Ellis <d.f.ellis@ieee.org>
22
+ * @license MIT
23
+ */var I=function(){if(H)return F;H=1;var i=e,a=t.spawn,s=r;return F.XMLHttpRequest=function(){var e,t,r=this,c=n,l=o,u={},f=!1,d={"User-Agent":"node-XMLHttpRequest",Accept:"*/*"},p={},h={},v=["accept-charset","accept-encoding","access-control-request-headers","access-control-request-method","connection","content-length","content-transfer-encoding","cookie","cookie2","date","expect","host","keep-alive","origin","referer","te","trailer","transfer-encoding","upgrade","via"],g=["TRACE","TRACK","CONNECT"],y=!1,b=!1,m={};this.UNSENT=0,this.OPENED=1,this.HEADERS_RECEIVED=2,this.LOADING=3,this.DONE=4,this.readyState=this.UNSENT,this.onreadystatechange=null,this.responseText="",this.responseXML="",this.status=null,this.statusText=null,this.withCredentials=!1;this.open=function(e,t,r,n,o){if(this.abort(),b=!1,!function(e){return e&&-1===g.indexOf(e)}(e))throw new Error("SecurityError: Request method not allowed");u={method:e,url:t.toString(),async:"boolean"!=typeof r||r,user:n||null,password:o||null},E(this.OPENED)},this.setDisableHeaderCheck=function(e){f=e},this.setRequestHeader=function(e,t){if(this.readyState!==this.OPENED)throw new Error("INVALID_STATE_ERR: setRequestHeader can only be called when state is OPEN");if(function(e){return f||e&&-1===v.indexOf(e.toLowerCase())}(e)){if(y)throw new Error("INVALID_STATE_ERR: send flag is true");e=h[e.toLowerCase()]||e,h[e.toLowerCase()]=e,p[e]=p[e]?p[e]+", "+t:t}else console.warn('Refused to set unsafe header "'+e+'"')},this.getResponseHeader=function(e){return"string"==typeof e&&this.readyState>this.OPENED&&t&&t.headers&&t.headers[e.toLowerCase()]&&!b?t.headers[e.toLowerCase()]:null},this.getAllResponseHeaders=function(){if(this.readyState<this.HEADERS_RECEIVED||b)return"";var e="";for(var r in t.headers)"set-cookie"!==r&&"set-cookie2"!==r&&(e+=r+": "+t.headers[r]+"\r\n");return e.substr(0,e.length-2)},this.getRequestHeader=function(e){return"string"==typeof e&&h[e.toLowerCase()]?p[h[e.toLowerCase()]]:""},this.send=function(n){if(this.readyState!==this.OPENED)throw new Error("INVALID_STATE_ERR: connection must be opened before send() is called");if(y)throw new Error("INVALID_STATE_ERR: send has already been called");var o,f=!1,v=!1,g=i.parse(u.url);switch(g.protocol){case"https:":f=!0;case"http:":o=g.hostname;break;case"file:":v=!0;break;case void 0:case null:case"":o="localhost";break;default:throw new Error("Protocol not supported.")}if(v){if("GET"!==u.method)throw new Error("XMLHttpRequest: Only GET method is supported");if(u.async)s.readFile(g.pathname,"utf8",function(e,t){e?r.handleError(e):(r.status=200,r.responseText=t,E(r.DONE))});else try{this.responseText=s.readFileSync(g.pathname,"utf8"),this.status=200,E(r.DONE)}catch(e){this.handleError(e)}}else{var m=g.port||(f?443:80),w=g.pathname+(g.search?g.search:"");for(var x in d)h[x.toLowerCase()]||(p[x]=d[x]);if(p.Host=o,f&&443===m||80===m||(p.Host+=":"+g.port),u.user){void 0===u.password&&(u.password="");var S=new Buffer(u.user+":"+u.password);p.Authorization="Basic "+S.toString("base64")}"GET"===u.method||"HEAD"===u.method?n=null:n?(p["Content-Length"]=Buffer.isBuffer(n)?n.length:Buffer.byteLength(n),p["Content-Type"]||(p["Content-Type"]="text/plain;charset=UTF-8")):"POST"===u.method&&(p["Content-Length"]=0);var O={host:o,port:m,path:w,method:u.method,headers:p,agent:!1,withCredentials:r.withCredentials};if(b=!1,u.async){var C=f?l.request:c.request;y=!0,r.dispatchEvent("readystatechange");var k=function(e){r.handleError(e)};e=C(O,function n(a){if(301!==(t=a).statusCode&&302!==t.statusCode&&303!==t.statusCode&&307!==t.statusCode)t.setEncoding("utf8"),E(r.HEADERS_RECEIVED),r.status=t.statusCode,t.on("data",function(e){e&&(r.responseText+=e),y&&E(r.LOADING)}),t.on("end",function(){y&&(E(r.DONE),y=!1)}),t.on("error",function(e){r.handleError(e)});else{u.url=t.headers.location;var s=i.parse(u.url);o=s.hostname;var c={hostname:s.hostname,port:s.port,path:s.path,method:303===t.statusCode?"GET":u.method,headers:p,withCredentials:r.withCredentials};(e=C(c,n).on("error",k)).end()}}).on("error",k),n&&e.write(n),e.end(),r.dispatchEvent("loadstart")}else{var N=".node-xmlhttprequest-content-"+process.pid,T=".node-xmlhttprequest-sync-"+process.pid;s.writeFileSync(T,"","utf8");for(var j="var http = require('http'), https = require('https'), fs = require('fs');var doRequest = http"+(f?"s":"")+".request;var options = "+JSON.stringify(O)+";var responseText = '';var req = doRequest(options, function(response) {response.setEncoding('utf8');response.on('data', function(chunk) { responseText += chunk;});response.on('end', function() {fs.writeFileSync('"+N+"', JSON.stringify({err: null, data: {statusCode: response.statusCode, headers: response.headers, text: responseText}}), 'utf8');fs.unlinkSync('"+T+"');});response.on('error', function(error) {fs.writeFileSync('"+N+"', JSON.stringify({err: error}), 'utf8');fs.unlinkSync('"+T+"');});}).on('error', function(error) {fs.writeFileSync('"+N+"', JSON.stringify({err: error}), 'utf8');fs.unlinkSync('"+T+"');});"+(n?"req.write('"+JSON.stringify(n).slice(1,-1).replace(/'/g,"\\'")+"');":"")+"req.end();",R=a(process.argv[0],["-e",j]);s.existsSync(T););var D=JSON.parse(s.readFileSync(N,"utf8"));R.stdin.end(),s.unlinkSync(N),D.err?r.handleError(D.err):(t=D.data,r.status=D.data.statusCode,r.responseText=D.data.text,E(r.DONE))}}},this.handleError=function(e){this.status=0,this.statusText=e,this.responseText=e.stack,b=!0,E(this.DONE),this.dispatchEvent("error")},this.abort=function(){e&&(e.abort(),e=null),p=d,this.status=0,this.responseText="",this.responseXML="",b=!0,this.readyState===this.UNSENT||this.readyState===this.OPENED&&!y||this.readyState===this.DONE||(y=!1,E(this.DONE)),this.readyState=this.UNSENT,this.dispatchEvent("abort")},this.addEventListener=function(e,t){e in m||(m[e]=[]),m[e].push(t)},this.removeEventListener=function(e,t){e in m&&(m[e]=m[e].filter(function(e){return e!==t}))},this.dispatchEvent=function(e){if("function"==typeof r["on"+e]&&r["on"+e](),e in m)for(var t=0,n=m[e].length;t<n;t++)m[e][t].call(r)};var E=function(e){e!=r.LOADING&&r.readyState===e||(r.readyState=e,(u.async||r.readyState<r.OPENED||r.readyState===r.DONE)&&r.dispatchEvent("readystatechange"),r.readyState!==r.DONE||b||(r.dispatchEvent("load"),r.dispatchEvent("loadend")))}},F}(),_=q(I),U=new Map,B=y?window.XMLHttpRequest:_.XMLHttpRequest;function M(e){if(U.has(e))return U.get(e);var t;try{var r=new B;r.open("GET",e,!1),r.setRequestHeader("Content-Type","text/html"),r.send(),200===r.status&&r.responseText&&(t=r.responseText,U.set(e,r.responseText))}catch(e){console.error("【XHR Error】:".concat(e instanceof Error?e.message:"get remote URL resource exception!"))}return t}var G=new Map;function z(e,t){return new Promise(function(r){var n=M(e);if(n){var o=P(n,e);if(o){var i={linkTitle:t.linkTitle,target:t.target||"_blank",classPrefix:t.classPrefix},a=S(o,u(u({},i),{},{href:e})),s={url:e,data:o,options:i,dom:a};G.set(e,s),r(s)}}})}var J=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};function r(e){var r,n=function(e){if(E.has(e))return E.get(e);var t=null,r=M(e);return r&&(t=P(r,e))&&E.set(e,t),t}(e.url);if(n){!function(e,t){e.forEach(function(e,r){r!==t&&(e.hidden=!0)})}(e.tokens,e.i);var o={href:e.url,linkTitle:(r=e.tokens,r.map(function(e){var t=e.hidden,r=e.content;return t?r:""}).filter(Boolean).join("")),target:t.target||"_blank",classPrefix:t.classPrefix,borderColor:t.borderColor,bgColor:t.bgColor};return g(t.render)?t.render(n,o):S(n,o)}}e.renderer.renderInline=function(t,r,n){for(var o="",i=0;i<t.length;i++){var a=t[i],s=e.renderer.rules[a.type];a.hidden?o+="":g(s)?o+=s(t,i,r,n,e.renderer):o+=e.renderer.renderToken(t,i,r)}return o},e.renderer.rules.link_open=function(e,t,n,o,i){var a,s=e[t],c="a"===s.tag&&"link_open"===s.type,l=function(e){var t=new RegExp("^(".concat("@",":)([a-zA-Z0-9]+.*)")),r=null==e?void 0:e.match(t);return{isCardLink:!!r,url:null==r?void 0:r[2]}}(null===(a=s.attrs)||void 0===a||null===(a=a.filter(function(e){return e.includes("href")})[0])||void 0===a?void 0:a[1]),u=l.url,f=l.isCardLink;if(c&&f&&u){var d=r({url:u,tokens:e,i:t});if(d)return d}return i.renderToken(e,t,n)}};export{z as generateCard,J as linkToCardPlugin};
package/dist/api.js ADDED
@@ -0,0 +1,34 @@
1
+ import { xhr, generateCardDomFragment, parserMetadata } from "./assemble";
2
+ const cache = new Map();
3
+ /**
4
+ * @param url
5
+ * @param options
6
+ * @returns
7
+ */
8
+ export function generateCard(url, options) {
9
+ return new Promise((resolve) => {
10
+ const htmlString = xhr.sync(url);
11
+ if (htmlString) {
12
+ const urlMetadata = parserMetadata(htmlString, url);
13
+ if (urlMetadata) {
14
+ const _options = {
15
+ linkTitle: options.linkTitle,
16
+ target: options.target || "_blank",
17
+ classPrefix: options.classPrefix,
18
+ };
19
+ const card = generateCardDomFragment(urlMetadata, {
20
+ ..._options,
21
+ href: url,
22
+ });
23
+ const response = {
24
+ url,
25
+ data: urlMetadata,
26
+ options: _options,
27
+ dom: card,
28
+ };
29
+ cache.set(url, response);
30
+ resolve(response);
31
+ }
32
+ }
33
+ });
34
+ }
@@ -0,0 +1,60 @@
1
+ import { STYLE } from "./style";
2
+ /**
3
+ * @param data
4
+ * @param options
5
+ * @returns
6
+ */
7
+ export const generateCardDomFragment = (data, options) => {
8
+ const aa = {
9
+ rel: `rel="noopener noreferrer"`,
10
+ target: `target="${options.target}"`,
11
+ href: `href="${options.href}"`,
12
+ title: `title="${options.linkTitle}"`,
13
+ borderColor: `borderColor="${options.borderColor}"`,
14
+ bgColor: `bgColor="${options.bgColor}"`,
15
+ };
16
+ const inject = (s) => {
17
+ return s;
18
+ };
19
+ const escapeHTML = (str) => str
20
+ .replace(/&amp;/g, "&")
21
+ .replace(/&lt;/g, "<")
22
+ .replace(/&gt;/g, ">")
23
+ .replace(/&quot;/g, '"')
24
+ .replace(/&#039;/g, "'");
25
+ const style = STYLE(options.borderColor || "#7d7d7dff", options.bgColor || "#7d7d7d00");
26
+ const url = options.href || "";
27
+ const domain = new URL(url).origin.replace(/^https?:\/\//, "").replace(/^www\./, "") ||
28
+ "Unknown domain";
29
+ let title = data.title;
30
+ let description = data.description;
31
+ // Special handling for gitub.com
32
+ if (domain == "github.com") {
33
+ title = data.title?.split(":")[0].replace("GitHub - ", "") || "No title";
34
+ description =
35
+ description?.replace(` - ${title}`, "").replace(`Contribute to ${title} development by creating an account on GitHub.`, // 定型句
36
+ "") || "";
37
+ }
38
+ else {
39
+ title = data.title || "No title";
40
+ description = data.description || "";
41
+ }
42
+ return `<span style="display:block;">
43
+ <a ${aa.rel} ${aa.target} ${aa.href} ${aa.title} ${style.a}>
44
+ <span ${inject(style.container)}>
45
+ <span ${inject(style.texts)}>
46
+ <span ${inject(style.title)}>
47
+ ${escapeHTML(title)}
48
+ </span>
49
+ <span ${inject(style.domain)}>
50
+ ${escapeHTML(domain)}
51
+ </span>
52
+ <span ${inject(style.description)}>
53
+ ${escapeHTML(description)}
54
+ </span>
55
+ </span>
56
+ <img src="${data?.logo}" ${inject(style.img)}/>
57
+ </span>
58
+ </a>
59
+ </span>`;
60
+ };
@@ -0,0 +1,4 @@
1
+ export * from "./metadata";
2
+ export * from "./html";
3
+ export * from "./parser";
4
+ export * as xhr from "./xhr";
@@ -0,0 +1,49 @@
1
+ import { isPureObject } from "@luckrya/utility";
2
+ import fs from "node:fs";
3
+ const CONFIG_FILE = () => `${process.cwd()}/.linkcardrc`;
4
+ export default class LocalFileCache {
5
+ constructor() { }
6
+ /**
7
+ * @param data
8
+ */
9
+ setFile(data) {
10
+ let content = data;
11
+ const _content = this.readFile();
12
+ if (_content) {
13
+ content = Object.assign(_content, content);
14
+ }
15
+ fs.writeFileSync(CONFIG_FILE(), JSON.stringify(content));
16
+ }
17
+ /**
18
+ * @returns
19
+ */
20
+ readFile() {
21
+ const content = fs.readFileSync(CONFIG_FILE(), "utf-8");
22
+ const data = JSON.parse(content);
23
+ if (isPureObject(data))
24
+ return data;
25
+ return undefined;
26
+ }
27
+ /**
28
+ * @param url
29
+ * @returns
30
+ */
31
+ has(url) {
32
+ return !!this.get(url);
33
+ }
34
+ /**
35
+ * @param url
36
+ * @returns
37
+ */
38
+ get(url) {
39
+ const cache = this.readFile();
40
+ return cache?.[url];
41
+ }
42
+ /**
43
+ * @param url
44
+ * @param data
45
+ */
46
+ set(url, data) {
47
+ this.setFile({ [url]: data });
48
+ }
49
+ }
@@ -0,0 +1,19 @@
1
+ import { parserMetadata, xhr } from ".";
2
+ import LocalFileCache from "./local-file-cache";
3
+ const cache = new LocalFileCache();
4
+ /**
5
+ * @param url
6
+ * @returns
7
+ */
8
+ export function getUrlMetadata(url) {
9
+ if (cache.has(url))
10
+ return cache.get(url);
11
+ let metadata = null;
12
+ const htmlString = xhr.sync(url);
13
+ if (htmlString) {
14
+ metadata = parserMetadata(htmlString, url);
15
+ if (metadata)
16
+ cache.set(url, metadata);
17
+ }
18
+ return metadata;
19
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * TODO: Refactor
3
+ */
4
+ import { isString } from "@luckrya/utility";
5
+ import { cleanPath, extractUrl } from "./url";
6
+ const DEFAULT_LOGO = "https://resources.whatwg.org/logo-url.svg";
7
+ const HtmlTagContentReg = /(<[A-Za-z]+\s*[^>]*>(.*)<\/[A-Za-z]+>)/;
8
+ const ContentAttrValueHtmlMetaTagReg = /content=["|']([^>]*)["|']/;
9
+ const HrefAttrValueHtmlLinkTagReg = /href=["|']([^>]*)["|']/;
10
+ const HtmlTitleTagReg = /(<title\s*[^>]*>(.*)<\/title>)/g;
11
+ // const HtmlMetaTagReg = /<meta\s[^>]*\/?>/g;
12
+ // const HtmlLinkTagReg = /<link\s[^>]*\/?>/g;
13
+ const containArrSelfLosingHtmlTagReg = (attr, tag = "meta") => new RegExp(`<${tag}\\s[^>]*\\w+=['|"]([a-zA-Z]|:|\\s)*${attr}['|"][^>]*\\/?>`);
14
+ /**
15
+ * @param htmlString
16
+ * @returns
17
+ * <title>$value</title>
18
+ * <meta property="og:title" content="$value" />
19
+ * <link data-react-helmet="true" href="https://s.xml" rel="search" title="$value" type="applon+xml">
20
+ */
21
+ function matchTitleByMetaTag(htmlString) {
22
+ let title;
23
+ const metas = htmlString.match(containArrSelfLosingHtmlTagReg("title"));
24
+ if (metas?.length) {
25
+ const content = metas[0].match(ContentAttrValueHtmlMetaTagReg);
26
+ if (content && isString(content[1]))
27
+ title = content[1];
28
+ }
29
+ else {
30
+ const titleHtmlTag = htmlString.match(HtmlTitleTagReg);
31
+ if (titleHtmlTag?.length) {
32
+ const content = titleHtmlTag[0].match(HtmlTagContentReg);
33
+ if (content && isString(content[2]))
34
+ title = content[2];
35
+ }
36
+ }
37
+ return title;
38
+ }
39
+ /**
40
+ * @returns
41
+ * <meta name="description" content="$value" />
42
+ * <meta property="og:description" content="$value" />
43
+ */
44
+ function matchDescriptionByMetaTag(htmlString) {
45
+ let description;
46
+ const metas = htmlString.match(containArrSelfLosingHtmlTagReg("description"));
47
+ if (metas?.length) {
48
+ const content = metas[0].match(ContentAttrValueHtmlMetaTagReg);
49
+ if (content && isString(content[1]))
50
+ description = content[1];
51
+ }
52
+ return description;
53
+ }
54
+ /**
55
+ * @returns
56
+ * <meta property="og:image" content="$value" />
57
+ * <link rel="icon" href="$value">
58
+ */
59
+ function matchLogoByLinkOrMetaTag(htmlString) {
60
+ let logo;
61
+ const metas = htmlString.match(containArrSelfLosingHtmlTagReg("image"));
62
+ if (metas?.length) {
63
+ const content = metas[0].match(ContentAttrValueHtmlMetaTagReg);
64
+ if (content && isString(content[1]))
65
+ logo = content[1];
66
+ }
67
+ else {
68
+ const linkHtmlTags = htmlString.match(containArrSelfLosingHtmlTagReg("icon", "link"));
69
+ if (linkHtmlTags?.length) {
70
+ const content = linkHtmlTags[0].match(HrefAttrValueHtmlLinkTagReg);
71
+ // logo 判断是否是完整地址
72
+ if (content && isString(content[1]))
73
+ logo = content[1];
74
+ }
75
+ }
76
+ return logo;
77
+ }
78
+ /**
79
+ * @param htmlString
80
+ * @param url
81
+ * @returns
82
+ */
83
+ export function parserMetadata(htmlString, url) {
84
+ function absolute(logo) {
85
+ if (!logo)
86
+ return DEFAULT_LOGO;
87
+ return extractUrl(logo)
88
+ ? logo
89
+ : `${extractUrl(url)?.origin}${cleanPath(`/${logo}`)}`; // TODO: no match "content='//img.xx.com/a.png'"
90
+ }
91
+ const metadata = {
92
+ title: matchTitleByMetaTag(htmlString),
93
+ description: matchDescriptionByMetaTag(htmlString),
94
+ logo: absolute(matchLogoByLinkOrMetaTag(htmlString)),
95
+ };
96
+ if (isEmptyStringObject(metadata))
97
+ return null;
98
+ else
99
+ return metadata;
100
+ }
101
+ /**
102
+ * @param obj
103
+ * @returns
104
+ */
105
+ function isEmptyStringObject(obj) {
106
+ return !Object.values(obj).filter((v) => isString(v)).length;
107
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * @param str
3
+ * @returns
4
+ */
5
+ function hyphenate(str) {
6
+ return str.replace(/\B([A-Z])/g, "-$1").toLowerCase();
7
+ }
8
+ /**
9
+ * @param style
10
+ * @returns
11
+ */
12
+ function join(style) {
13
+ return Object.entries(style)
14
+ .map(([k, v]) => {
15
+ if (k && v)
16
+ return `${hyphenate(k)}: ${v};`;
17
+ })
18
+ .filter(Boolean)
19
+ .join(" ");
20
+ }
21
+ /**
22
+ * @param style
23
+ * @returns
24
+ */
25
+ function inlineStyle(style) {
26
+ return `style="${join(style)}"`;
27
+ }
28
+ /**
29
+ * @param line
30
+ * @returns
31
+ */
32
+ const ellipsisStyle = (line) => ({
33
+ "-webkit-box-orient": "vertical",
34
+ "-webkit-line-clamp": line,
35
+ display: "-webkit-box",
36
+ hyphens: "auto",
37
+ lineClamp: line,
38
+ overflow: "hidden",
39
+ overflowWrap: "anywhere",
40
+ textOverflow: "ellipsis",
41
+ wordBreak: "break-word",
42
+ });
43
+ /**
44
+ See: * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/components/VPFeature.vue
45
+ * @param borderColor
46
+ * @param bgColor
47
+ * @returns
48
+ */
49
+ export const STYLE = (borderColor, bgColor) => ({
50
+ a: inlineStyle({
51
+ color: "unset !important",
52
+ display: "block",
53
+ width: "100%",
54
+ textDecoration: "none",
55
+ }),
56
+ container: inlineStyle({
57
+ display: "flex",
58
+ alignItems: "center",
59
+ flexWrap: "wrap",
60
+ gap: "10px",
61
+ borderRadius: "12px",
62
+ border: `1px solid ${borderColor}`,
63
+ backgroundColor: bgColor,
64
+ boxSizing: "border-box",
65
+ width: "100%",
66
+ height: "130px",
67
+ }),
68
+ img: inlineStyle({
69
+ borderRadius: "0px 12px 12px 0px",
70
+ maxWidth: "40%",
71
+ height: "128px", // container.height - 2px
72
+ flexShrink: 0,
73
+ objectFit: "contain",
74
+ overflow: "hidden",
75
+ }),
76
+ texts: inlineStyle({
77
+ flex: "1 1 0%",
78
+ minWidth: "0", // ellipsisを有効にするために必要
79
+ }),
80
+ title: inlineStyle({
81
+ ...ellipsisStyle(2),
82
+ opacity: 1,
83
+ fontSize: "16px",
84
+ lineHeight: "22px",
85
+ margin: "0 16px 8px 16px",
86
+ fontWeight: "bold",
87
+ }),
88
+ domain: inlineStyle({
89
+ ...ellipsisStyle(1),
90
+ opacity: 1,
91
+ fontSize: "12px",
92
+ lineHeight: "16px",
93
+ margin: "8px 16px 8px 16px",
94
+ textDecoration: "underline",
95
+ }),
96
+ description: inlineStyle({
97
+ ...ellipsisStyle(2),
98
+ opacity: 0.8,
99
+ fontSize: "12px",
100
+ lineHeight: "16px",
101
+ margin: "8px 16px 0px 16px",
102
+ }),
103
+ });
104
+ /**
105
+ * @param prefix
106
+ * @returns
107
+ */
108
+ export const classNames = (prefix) => ({
109
+ container: `${prefix}__container`,
110
+ img: `${prefix}__img`,
111
+ texts: `${prefix}__texts`,
112
+ title: `${prefix}__texts--title`,
113
+ domain: `${prefix}__texts--domain`,
114
+ description: `${prefix}__texts--desc`,
115
+ });
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @param url
3
+ * @returns
4
+ */
5
+ export function extractUrl(url) {
6
+ return new URL(url);
7
+ }
8
+ /**
9
+ * @param path
10
+ * @returns
11
+ */
12
+ export function cleanPath(path) {
13
+ return path.replace(/\/\//g, "/");
14
+ }
@@ -0,0 +1,59 @@
1
+ // Refactor: xmlhttprequest will be replaced later
2
+ // @ts-expect-error: xmlhttprequest has no types
3
+ import xhrForNode from "xmlhttprequest";
4
+ import { inBrowser, isString } from "@luckrya/utility";
5
+ // TODO: Local File Cache
6
+ const cache = new Map();
7
+ const XHR = inBrowser ? window.XMLHttpRequest : xhrForNode.XMLHttpRequest;
8
+ /**
9
+ * @param url
10
+ * @returns
11
+ */
12
+ export function sync(url) {
13
+ if (cache.has(url))
14
+ return cache.get(url);
15
+ let result;
16
+ try {
17
+ const xhr = new XHR();
18
+ xhr.open("GET", url, false);
19
+ xhr.setRequestHeader("Content-Type", "text/html");
20
+ xhr.send();
21
+ if (xhr.status === 200 && !!xhr.responseText) {
22
+ result = xhr.responseText;
23
+ cache.set(url, xhr.responseText);
24
+ }
25
+ }
26
+ catch (err) {
27
+ console.error(`【XHR Error】:${err instanceof Error
28
+ ? err.message
29
+ : "get remote URL resource exception!"}`);
30
+ }
31
+ return result;
32
+ }
33
+ /**
34
+ * @param url
35
+ * @returns
36
+ */
37
+ export function async(url) {
38
+ return new Promise((resolve, reject) => {
39
+ try {
40
+ if (cache.has(url))
41
+ return resolve(cache.get(url));
42
+ const xhr = new XHR();
43
+ xhr.open("GET", url, false);
44
+ xhr.setRequestHeader("Content-Type", "text/html");
45
+ xhr.onreadystatechange = function () {
46
+ if (xhr.readyState === 4 &&
47
+ xhr.status === 200 &&
48
+ isString(xhr.responseText)) {
49
+ cache.set(url, xhr.responseText);
50
+ resolve(xhr.responseText);
51
+ }
52
+ };
53
+ xhr.send();
54
+ }
55
+ catch (err) {
56
+ reject(err);
57
+ }
58
+ });
59
+ }
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./api";
2
+ export * from "./link-to-card-plugin";
3
+ export * from "./types";
@@ -0,0 +1,107 @@
1
+ import { isFunction } from "@luckrya/utility";
2
+ import { getUrlMetadata, generateCardDomFragment } from "./assemble";
3
+ /**
4
+ * @param md
5
+ * @param pluginOptions
6
+ */
7
+ export const linkToCardPlugin = (md, pluginOptions = {}) => {
8
+ function parseCardLinkHref(href) {
9
+ const tagRegexp = new RegExp(`^(${"@"}:)([a-zA-Z0-9]+.*)`);
10
+ const match = href?.match(tagRegexp);
11
+ return {
12
+ isCardLink: !!match,
13
+ url: match?.[2],
14
+ };
15
+ }
16
+ /**
17
+ * @param options
18
+ * @returns
19
+ */
20
+ function assembleCardTpl(options) {
21
+ const urlMetadata = getUrlMetadata(options.url);
22
+ if (urlMetadata) {
23
+ ignoreRestToken(options.tokens, options.i); // linkTitle 依赖 ignoreRestToken 的处理结果
24
+ const cardDomOptions = {
25
+ href: options.url,
26
+ linkTitle: joinLinkTitle(options.tokens),
27
+ target: pluginOptions.target || "_blank",
28
+ classPrefix: pluginOptions.classPrefix,
29
+ borderColor: pluginOptions.borderColor,
30
+ bgColor: pluginOptions.bgColor,
31
+ };
32
+ return isFunction(pluginOptions.render)
33
+ ? pluginOptions.render(urlMetadata, cardDomOptions)
34
+ : generateCardDomFragment(urlMetadata, cardDomOptions);
35
+ }
36
+ }
37
+ /**
38
+ * https://markdown-it.github.io/markdown-it/#MarkdownIt.renderInline
39
+ * @param tokens
40
+ * @param rootOptions
41
+ * @param env
42
+ * @returns
43
+ */
44
+ md.renderer.renderInline = (tokens, rootOptions, env) => {
45
+ let result = "";
46
+ for (let i = 0; i < tokens.length; i++) {
47
+ const currentToken = tokens[i];
48
+ const ruleFunction = md.renderer.rules[currentToken.type];
49
+ if (currentToken.hidden) {
50
+ result += "";
51
+ }
52
+ else if (isFunction(ruleFunction)) {
53
+ result += ruleFunction(tokens, i, rootOptions, env, md.renderer);
54
+ }
55
+ else {
56
+ result += md.renderer.renderToken(tokens, i, rootOptions);
57
+ }
58
+ }
59
+ return result;
60
+ };
61
+ /**
62
+ * envは呼ばれなくても消さないこと
63
+ * @param tokens
64
+ * @param i
65
+ * @param rootOptions
66
+ * @param env
67
+ * @param self
68
+ * @returns
69
+ */
70
+ md.renderer.rules.link_open = (tokens, i, rootOptions, env, self) => {
71
+ const token = tokens[i];
72
+ const isLinkOpenToken = token.tag === "a" && token.type === "link_open";
73
+ const href = token.attrs?.filter((attr) => attr.includes("href"))[0]?.[1];
74
+ const { url, isCardLink } = parseCardLinkHref(href);
75
+ if (isLinkOpenToken && isCardLink && url) {
76
+ const card = assembleCardTpl({ url, tokens, i });
77
+ if (card)
78
+ return card;
79
+ }
80
+ return self.renderToken(tokens, i, rootOptions);
81
+ };
82
+ };
83
+ /**
84
+ * TODO: handle softbreak https://markdown-it.github.io/
85
+ * @param tokens
86
+ * @param i
87
+ */
88
+ function ignoreRestToken(tokens, i) {
89
+ tokens.forEach((token, index) => {
90
+ if (index !== i)
91
+ token.hidden = true;
92
+ });
93
+ }
94
+ /**
95
+ * @param tokens
96
+ * @returns
97
+ */
98
+ function joinLinkTitle(tokens) {
99
+ return tokens
100
+ .map(({ hidden, content }) => {
101
+ if (hidden)
102
+ return content;
103
+ return "";
104
+ })
105
+ .filter(Boolean)
106
+ .join("");
107
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "bugs": {
10
10
  "url": "https://github.com/asumo-1xts/vitepress-linkcard"
11
11
  },
12
- "description": "A vitepress-plugin to generate a pretty linkcard with OGP.",
12
+ "description": "A Vitepress plugin to generate a pretty linkcard with OGP.",
13
13
  "devDependencies": {
14
14
  "@babel/core": "^7.28.5",
15
15
  "@babel/preset-env": "^7.28.5",
@@ -75,5 +75,5 @@
75
75
  },
76
76
  "type": "module",
77
77
  "types": "./types/index.d.ts",
78
- "version": "0.9.9"
79
- }
78
+ "version": "1.0.0"
79
+ }
package/types/api.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import type { UrlMetadata, CardDomRenderOptions } from "./types";
2
+ interface CardResponse {
3
+ url: string;
4
+ data: UrlMetadata;
5
+ options: Omit<CardDomRenderOptions, "href">;
6
+ dom: string;
7
+ }
8
+ /**
9
+ * @param url
10
+ * @param options
11
+ * @returns
12
+ */
13
+ export declare function generateCard(url: string, options: Omit<CardDomRenderOptions, "href">): Promise<CardResponse>;
14
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { CardDomRender } from "../types";
2
+ /**
3
+ * @param data
4
+ * @param options
5
+ * @returns
6
+ */
7
+ export declare const generateCardDomFragment: CardDomRender;
@@ -0,0 +1,4 @@
1
+ export * from "./metadata";
2
+ export * from "./html";
3
+ export * from "./parser";
4
+ export * as xhr from "./xhr";
@@ -0,0 +1,26 @@
1
+ export default class LocalFileCache<V extends Record<string, unknown>> {
2
+ constructor();
3
+ /**
4
+ * @param data
5
+ */
6
+ private setFile;
7
+ /**
8
+ * @returns
9
+ */
10
+ private readFile;
11
+ /**
12
+ * @param url
13
+ * @returns
14
+ */
15
+ has(url: string): boolean;
16
+ /**
17
+ * @param url
18
+ * @returns
19
+ */
20
+ get(url: string): V | undefined;
21
+ /**
22
+ * @param url
23
+ * @param data
24
+ */
25
+ set(url: string, data: V): void;
26
+ }
@@ -0,0 +1,6 @@
1
+ import type { UrlMetadata } from "../types";
2
+ /**
3
+ * @param url
4
+ * @returns
5
+ */
6
+ export declare function getUrlMetadata(url: string): UrlMetadata | null | undefined;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * TODO: Refactor
3
+ */
4
+ import type { UrlMetadata } from "../types";
5
+ /**
6
+ * @param htmlString
7
+ * @param url
8
+ * @returns
9
+ */
10
+ export declare function parserMetadata(htmlString: string, url: string): UrlMetadata | null;
@@ -0,0 +1,27 @@
1
+ /**
2
+ See: * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/components/VPFeature.vue
3
+ * @param borderColor
4
+ * @param bgColor
5
+ * @returns
6
+ */
7
+ export declare const STYLE: (borderColor: string, bgColor: string) => {
8
+ a: string;
9
+ container: string;
10
+ img: string;
11
+ texts: string;
12
+ title: string;
13
+ domain: string;
14
+ description: string;
15
+ };
16
+ /**
17
+ * @param prefix
18
+ * @returns
19
+ */
20
+ export declare const classNames: (prefix?: string) => {
21
+ container: string;
22
+ img: string;
23
+ texts: string;
24
+ title: string;
25
+ domain: string;
26
+ description: string;
27
+ };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @param url
3
+ * @returns
4
+ */
5
+ export declare function extractUrl(url: string): URL;
6
+ /**
7
+ * @param path
8
+ * @returns
9
+ */
10
+ export declare function cleanPath(path: string): string;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @param url
3
+ * @returns
4
+ */
5
+ export declare function sync(url: string): string | undefined;
6
+ /**
7
+ * @param url
8
+ * @returns
9
+ */
10
+ export declare function async(url: string): Promise<string | undefined>;
@@ -0,0 +1,3 @@
1
+ export * from "./api";
2
+ export * from "./link-to-card-plugin";
3
+ export * from "./types";
@@ -0,0 +1,6 @@
1
+ import type { LinkToCardPlugin } from "./types";
2
+ /**
3
+ * @param md
4
+ * @param pluginOptions
5
+ */
6
+ export declare const linkToCardPlugin: LinkToCardPlugin;
@@ -0,0 +1,25 @@
1
+ import type MarkdownIt from "markdown-it";
2
+ export type LinkToCardPlugin = MarkdownIt.PluginWithOptions<LinkToCardPluginOptions>;
3
+ export interface LinkToCardPluginOptions {
4
+ target?: ATarget;
5
+ classPrefix?: string;
6
+ render?: CardDomRender;
7
+ borderColor?: string;
8
+ bgColor?: string;
9
+ }
10
+ export interface UrlMetadata {
11
+ title?: string;
12
+ description?: string;
13
+ logo?: string;
14
+ [key: string]: unknown;
15
+ }
16
+ export type ATarget = "_self" | "_blank" | "_top" | "_parent";
17
+ export interface CardDomRenderOptions {
18
+ href: string;
19
+ linkTitle: string;
20
+ target: ATarget;
21
+ classPrefix?: string;
22
+ borderColor?: string;
23
+ bgColor?: string;
24
+ }
25
+ export type CardDomRender = (data: UrlMetadata, options: CardDomRenderOptions) => string;