├── .eslintignore ├── .eslintrc.yml ├── .gitignore ├── .npmignore ├── .prettierrc.yml ├── LICENSE ├── README.md ├── README_CN.md ├── dist ├── cardlink.cjs ├── cardlink.js ├── cardlink.js.map ├── cardlink.mjs └── parse.js ├── package-lock.json ├── package.json ├── public ├── card-link.png └── index.html ├── rollup.config.js ├── src ├── inject.js ├── main.css ├── main.js ├── parse.js ├── renderer.js └── utils.js └── types └── index.d.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | node: true 3 | es6: true 4 | browser: true 5 | extends: eslint:recommended 6 | parserOptions: 7 | ecmaVersion: latest 8 | sourceType: module 9 | 10 | rules: 11 | # off 或 0 - 关闭规则 12 | # warn 或 1 - 开启规则, 使用警告 程序不会退出 13 | # error 或 2 - 开启规则, 使用错误 程序退出 14 | 15 | no-console: error # 禁用console 16 | no-var: error ## 禁止使用var 17 | quotes: [error, single, { avoidEscape: true }] ## 使用单引号 18 | indent: [error, 2, { SwitchCase: 1 }] ## 缩进必须为2格 19 | comma-dangle: [error, never] ## 禁止末尾逗号 20 | semi: [error, never] ## 禁止末尾有分号 21 | arrow-parens: [error, always] ## 箭头函数的参数使用圆括号 22 | array-bracket-spacing: [error, never] ## 禁止在数组括号内出现空格 23 | brace-style: error ## 强制 one true brace style 24 | camelcase: warn ## 强制属性名称为驼峰风格 25 | computed-property-spacing: [error, never] ## 禁止在计算属性内使用空格 26 | curly: [error, multi-line] ## https://cn.eslint.org/docs/rules/curly#multi 27 | eol-last: [error, always] ## 强制使用换行 (LF) 28 | eqeqeq: [error, smart] ## https://cn.eslint.org/docs/rules/eqeqeq#smart 29 | max-depth: [error, 3] ## 强制块语句的最大可嵌套深度 30 | max-len: [warn, 120] ## 强制行的最大长度 31 | max-statements: [warn, 20] ## 限制函数块中的语句的最大数量 32 | new-cap: [warn, { capIsNew: false }] ## 要求构造函数首字母大写 33 | no-extend-native: error ## 禁止扩展原生类型 34 | no-mixed-spaces-and-tabs: error ## 禁止空格和 tab 的混合缩进 35 | no-trailing-spaces: error ## 禁用行尾空格 36 | no-unused-vars: warn ## 禁止出现未使用过的变量 37 | no-use-before-define: [error, nofunc] ## 禁止在变量定义之前使用它们 38 | object-curly-spacing: [error, always] ## 不允许花括号中有空格 39 | keyword-spacing: [error, { before: true, after: true }] ## 强制在关键字前后使用一致的空格 40 | space-unary-ops: error ## 禁止在一元操作符之前或之后存在空格 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | public 3 | src 4 | 5 | .eslintignore 6 | .eslintrc.yml 7 | .gitignore 8 | .prettierrc.yml 9 | package-lock.json 10 | rollup.config.js 11 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | # .prettierrc or .prettierrc.yaml 2 | trailingComma: none # 对象或数组最后是否添加逗号 es5|none|all 3 | tabWidth: 2 # 缩进 4 | printWidth: 120 # 多少个字符后换行 5 | semi: false # 结尾处是否添加分号 6 | singleQuote: true # 是否使用单引号 7 | # always:(x) => x 8 | # avoid:x => x 9 | arrowParens: always # 箭头函数在只有一个参数时 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Lete乐特 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
Generate card-based links for hyperlinks on the page
9 | 10 | 15 | 16 | ## Installation 17 | 18 | Using npm: 19 | 20 | ```bash 21 | npm install cardlink --save 22 | ``` 23 | 24 | Using CDN: 25 | 26 | ```html 27 | 28 | 29 | 30 | 31 | 61 | ``` 62 | 63 | 2. Usage 64 | 65 | ```html 66 | 78 | ``` 79 | 80 | ## Problems 81 | 82 | Since this is a front-end request for HTML, some sites may have cross-domain (CORS) issues, so `cardLink` allows you to use a proxy server to request HTML from the target site 83 | 84 | ```html 85 | 92 | ``` 93 | 94 | ## Options API 95 | 96 | ### cardLink(nodes) 97 | 98 | Type: `NodeList` 99 | 100 | default: `document.querySelectorAll('a[cardlink]')` 101 | 102 | By default, card links are generated for all `a[cardlink]` on the page 103 | 104 | ## Design Sketch 105 | 106 |  107 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 |为页面上的超链接生成卡片式链接
9 | 10 | 15 | 16 | ## 安装 17 | 18 | Using npm: 19 | 20 | ```bash 21 | npm install cardlink --save 22 | ``` 23 | 24 | Using CDN: 25 | 26 | ```html 27 | 28 | 29 | 30 | 31 | 62 | ``` 63 | 64 | 2. 使用方法 65 | 66 | ```html 67 | 79 | ``` 80 | 81 | ## 存在的问题 82 | 83 | 由于这是前端发送请求获取 HTML,可能部分网站会存在跨域 (CORS) 问题,所以 `cardLink` 允许你使用代理服务器去请求目标网站的 HTML 84 | 85 | ```html 86 | 94 | ``` 95 | 96 | ## 选项 API 97 | 98 | ### cardLink(nodes) 99 | 100 | 类型: `NodeList` 101 | 102 | 默认值: `document.querySelectorAll('a[cardlink]')` 103 | 104 | 默认会对页面上所有`a[cardlink]`生成卡片链接 105 | 106 | ## 效果图 107 | 108 |  109 | -------------------------------------------------------------------------------- /dist/cardlink.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Creating element 5 | * @param {String} name Element Name 6 | * @param {String} className Element className 7 | * @returns {HTMLElement} 8 | */ 9 | function createElement(name, className) { 10 | var dom = document.createElement(name); 11 | if (className) dom.className = className; 12 | return dom; 13 | } 14 | /** 15 | * Creates svg element 16 | * @param {String} name Element Name 17 | * @returns {SVGElementTagNameMap} 18 | */ 19 | 20 | function createSVG(name) { 21 | return document.createElementNS('http://www.w3.org/2000/svg', name); 22 | } 23 | /** 24 | * Set element attribute 25 | * @param {HTMLElement} el Element 26 | * @param {Object} obj Objects of key-value pairs 27 | */ 28 | 29 | function setAttribute(el, obj) { 30 | for (var key in obj) { 31 | el.setAttribute(key, obj[key]); 32 | } 33 | } 34 | /** 35 | * Append child node or element 36 | * @param {HTMLElement} el Element 37 | * @param {Node} child The node to append to the given parent node (commonly an element). 38 | */ 39 | 40 | function appendChild(el, child) { 41 | el.appendChild(child); 42 | } 43 | /** 44 | * Remove '/' and '/index.html' 45 | * @param {String} params 46 | * @returns { String } 47 | */ 48 | 49 | function indexHandler(params) { 50 | var path = params.replace(/(\/index\.html|\/)*$/gi, ''); 51 | if (path.length === 0) path += '/'; 52 | return path; 53 | } 54 | /** 55 | * @param {Object} param 56 | * @param {Array} requiredParams 57 | */ 58 | 59 | function verifyParams(param, requiredParams) { 60 | for (var index in requiredParams) { 61 | var requiredParam = requiredParams[index]; 62 | 63 | if (!param[requiredParam]) { 64 | throw new Error("Parameter '".concat(requiredParam, "' not legal")); 65 | } 66 | } 67 | } 68 | /** 69 | * Determine if it is a ['https://', 'http://', '//'] protocol 70 | * @param {String} url Website url 71 | * @returns {Boolean} 72 | */ 73 | 74 | function isHttp(url) { 75 | return /^(https?:)?\/\//g.test(url); 76 | } 77 | 78 | var styles = ".card-link{position:relative;width:100%;min-width:200px;max-width:400px;height:80px;margin:20px auto;border-radius:12px;overflow:hidden;}.card-link .card-link-content{text-decoration:none;position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:space-around;padding:12px;background-color:hsla(0,0%,96.5%,0.88);box-sizing:border-box;}.card-link [cardlink-left] .card-link-text{order:1;}.card-link [cardlink-left] .card-link-icon{margin:0 8px 0 0;}.card-link .card-link-text{flex:1;}.card-link .card-link-title{max-height:40px;font-size:16px;font-weight:500;line-height:1.25;color:#121212;margin:0 0 5px;letter-spacing:0.2px;word-break:break-all;display:-webkit-box;text-overflow:ellipsis;overflow:hidden;-webkit-box-orient:vertical;-webkit-line-clamp:2;}.card-link .card-link-url{display:-webkit-box;font-size:13px;color:#999;margin:0;line-height:1.3;overflow:hidden;word-break:break-word;text-overflow:ellipsis;-webkit-box-orient:vertical;-webkit-line-clamp:1;}.card-link .card-link-url svg{float:left;margin:2px 5px 0 0;}.card-link .card-link-icon{width:60px;height:60px;margin:0 0 0 8px;overflow:hidden;border-radius:6px;object-fit:contain;}"; 79 | 80 | var style = createElement('style'); 81 | style.textContent = styles; 82 | document.head.appendChild(style); 83 | 84 | /** 85 | * Create card link icon 86 | * @param {String} icon Icon URL 87 | * @returns {SVGElementTagNameMap} 88 | */ 89 | 90 | function createIcon(icon) { 91 | var svg = createSVG('svg'); 92 | svg.classList.add('card-link-icon'); 93 | svg.style.background = '#ebebeb'; 94 | setAttribute(svg, { 95 | fill: '#d4d4d4', 96 | viewBox: '0 0 1024 1024' 97 | }); 98 | var path = createSVG('path'); 99 | setAttribute(path, { 100 | // eslint-disable-next-line max-len 101 | d: 'M177.152 384a357.546667 357.546667 0 0 0-23.552 128 357.546667 357.546667 0 0 0 23.552 128h140.245333a791.808 791.808 0 0 1-10.197333-128c0-44.714667 3.584-87.722667 10.197333-128H177.152z m24.405333-51.2h126.250667c15.786667-65.024 40.021333-120.405333 69.76-160.554667A359.466667 359.466667 0 0 0 201.557333 332.8zM844.8 378.709333V384h-138.197333c6.613333 40.277333 10.197333 83.285333 10.197333 128s-3.584 87.722667-10.197333 128H844.8v5.290667c16.512-41.216 25.6-86.186667 25.6-133.290667a357.418667 357.418667 0 0 0-25.6-133.290667zM822.442667 332.8a359.466667 359.466667 0 0 0-196.010667-160.554667c29.738667 40.149333 53.930667 95.573333 69.76 160.554667h126.293333zM369.365333 384a736.042667 736.042667 0 0 0-10.965333 128c0 45.354667 3.968 88.448 10.965333 128h285.269334c6.997333-39.552 10.965333-82.645333 10.965333-128s-3.968-88.448-10.965333-128H369.365333z m11.264-51.2h262.741334c-28.586667-108.032-80.725333-179.2-131.370667-179.2-50.645333 0-102.741333 71.168-131.370667 179.2z m-179.072 358.4a359.466667 359.466667 0 0 0 196.010667 160.554667c-29.738667-40.149333-53.930667-95.573333-69.76-160.554667h-126.293333z m620.885334 0h-126.250667c-15.786667 65.024-40.021333 120.405333-69.76 160.554667a359.466667 359.466667 0 0 0 196.010667-160.554667z m-441.813334 0c28.586667 108.032 80.725333 179.2 131.370667 179.2 50.645333 0 102.741333-71.168 131.370667-179.2H380.629333zM512 921.6a409.6 409.6 0 1 1 0-819.2 409.6 409.6 0 0 1 0 819.2z' 102 | }); 103 | appendChild(svg, path); 104 | 105 | if (icon) { 106 | var img = createElement('img', 'card-link-icon'); 107 | img.src = icon; // If icon is valid, replace `svgIcon` with `imgIcon` 108 | 109 | img.onload = function () { 110 | img.onload = null; 111 | svg.parentNode.replaceChild(img, svg); 112 | }; 113 | } 114 | 115 | return svg; 116 | } 117 | /** 118 | * Creating a Document Object Model 119 | * @param {String} title Website title 120 | * @param {String} link Website address 121 | * @param {String} icon Website icon 122 | * @returns {HTMLElement} 123 | */ 124 | // eslint-disable-next-line max-statements 125 | 126 | 127 | function createDOM(title, link, icon) { 128 | var wrapDOM = createElement('div', 'card-link'); 129 | var iconDOM = createIcon(icon); 130 | var aDOM = createElement('a', 'card-link-content'); 131 | aDOM.href = link; 132 | var textDOM = createElement('div', 'card-link-text'); 133 | var titleDOM = createElement('span', 'card-link-title'); 134 | titleDOM.textContent = title; 135 | var linkDOM = createElement('span', 'card-link-url'); 136 | var linkSvgDOM = createSVG('svg'); 137 | setAttribute(linkSvgDOM, { 138 | width: 14, 139 | height: 14, 140 | viewBox: '0 0 24 24', 141 | fill: 'currentColor' 142 | }); 143 | var linkPathDOM = createSVG('path'); 144 | setAttribute(linkPathDOM, { 145 | // eslint-disable-next-line max-len 146 | d: 'M5.327 18.883a3.005 3.005 0 010-4.25l2.608-2.607a.75.75 0 10-1.06-1.06l-2.608 2.607a4.505 4.505 0 006.37 6.37l2.608-2.607a.75.75 0 00-1.06-1.06l-2.608 2.607a3.005 3.005 0 01-4.25 0zm5.428-11.799a.75.75 0 001.06 1.06L14.48 5.48a3.005 3.005 0 014.25 4.25l-2.665 2.665a.75.75 0 001.061 1.06l2.665-2.664a4.505 4.505 0 00-6.371-6.372l-2.665 2.665zm5.323 2.117a.75.75 0 10-1.06-1.06l-7.072 7.07a.75.75 0 001.061 1.06l7.071-7.07z' 147 | }); 148 | appendChild(linkSvgDOM, linkPathDOM); 149 | appendChild(linkDOM, linkSvgDOM); 150 | appendChild(linkDOM, document.createTextNode(link)); 151 | appendChild(textDOM, titleDOM); 152 | appendChild(textDOM, linkDOM); 153 | appendChild(aDOM, textDOM); 154 | appendChild(aDOM, iconDOM); 155 | appendChild(wrapDOM, aDOM); 156 | return wrapDOM; 157 | } 158 | 159 | function renderer(el, title, link, icon) { 160 | var dom = createDOM(title, link, icon); // Reset the attribute 161 | 162 | Array.from(el.attributes).forEach(function (attr) { 163 | dom.querySelector('a').setAttribute(attr.name, attr.value); 164 | }); 165 | el.parentNode.replaceChild(dom, el); 166 | } 167 | 168 | /** 169 | * Get info 170 | * @param {String} html String type html 171 | * @param {String} link Website address 172 | * @returns {{ title: string; link: string; icon: string }} Website info 173 | */ 174 | // eslint-disable-next-line max-statements 175 | 176 | function parse (html, link) { 177 | var result = { 178 | title: '', 179 | link: link, 180 | icon: '' 181 | }; 182 | 183 | try { 184 | var doc = new DOMParser().parseFromString(html, 'text/html'); // If there is no title, no card link is generated 185 | 186 | result.title = doc.querySelector('title'); 187 | 188 | if (result.title) { 189 | result.title = result.title.textContent; // Get the src of the first img tag in the body tag 190 | 191 | result.icon = doc.querySelector('body img'); 192 | result.icon = result.icon && result.icon.getAttribute('src'); 193 | if (/^data:image/.test(result.icon)) result.icon = ''; // If there is no src then get the site icon 194 | 195 | if (!result.icon) { 196 | var links = [].slice.call(doc.querySelectorAll('link[rel][href]')); 197 | result.icon = links.find(function (_el) { 198 | return _el.rel.includes('icon'); 199 | }); 200 | result.icon = result.icon && result.icon.getAttribute('href'); 201 | } // If `icon` is not the ['https://', 'http://', '//'] protocol, splice on the `origin` of the a tag 202 | 203 | 204 | if (result.icon && !isHttp(result.icon)) result.icon = new URL(link).origin + result.icon; 205 | } 206 | } catch (error) { 207 | // eslint-disable-next-line no-console 208 | console.warn('CardLink Error: Failed to parse', error); 209 | } 210 | 211 | return result; 212 | } 213 | 214 | var proxyHandler = { 215 | set: function set(target, name, value) { 216 | verifyParams(value, ['title', 'link']); 217 | name = indexHandler(name); 218 | target[name] = value; 219 | return true; 220 | } 221 | }; 222 | cardLink.cache = new Proxy({}, proxyHandler); 223 | 224 | function fetchPage(link, callback) { 225 | fetch(link).then(function (result) { 226 | return result.text(); 227 | }).then(callback)["catch"](function (error) { 228 | var server = cardLink.server; // eslint-disable-next-line no-console 229 | 230 | if (link.includes(server) || !server) return console.error('CardLink Error:', error); 231 | fetchPage(server + link, callback); 232 | }); 233 | } 234 | /** 235 | * Create card links 236 | * @param {NodeList} nodes A collection of nodes or a collection of arrays, 237 | * if it is an array then the array must always contain node element 238 | */ 239 | 240 | 241 | function cardLink(nodes) { 242 | // If the `nodes` do not contain a `forEach` method, then the default `a[cardlink]` is used 243 | nodes = 'forEach' in (nodes || {}) ? nodes : document.querySelectorAll('a[cardlink]'); 244 | nodes.forEach(function (el) { 245 | // If it is not a tag element then it is not processed 246 | if (el.nodeType !== 1) return; 247 | el.removeAttribute('cardlink'); 248 | var href = el.href; 249 | var cache = cardLink.cache[href]; 250 | if (cache) return renderer(el, cache.title, cache.link, cache.icon); 251 | 252 | if (isHttp(href)) { 253 | fetchPage(href, function (html) { 254 | var _parse = parse(html, href), 255 | title = _parse.title, 256 | link = _parse.link, 257 | icon = _parse.icon; 258 | 259 | cardLink.cache[link] = { 260 | title: title, 261 | link: link, 262 | icon: icon 263 | }; 264 | renderer(el, title, link, icon); 265 | }); 266 | } 267 | }); 268 | } 269 | 270 | module.exports = cardLink; 271 | -------------------------------------------------------------------------------- /dist/cardlink.js: -------------------------------------------------------------------------------- 1 | var cardLink=function(){"use strict";function e(e,t){var n=document.createElement(e);return t&&(n.className=t),n}function t(e){return document.createElementNS("http://www.w3.org/2000/svg",e)}function n(e,t){for(var n in t)e.setAttribute(n,t[n])}function i(e,t){e.appendChild(t)}function r(e){return/^(https?:)?\/\//g.test(e)}var a=e("style");function c(r,a,c){var o=e("div","card-link"),l=function(r){var a=t("svg");a.classList.add("card-link-icon"),a.style.background="#ebebeb",n(a,{fill:"#d4d4d4",viewBox:"0 0 1024 1024"});var c=t("path");if(n(c,{d:"M177.152 384a357.546667 357.546667 0 0 0-23.552 128 357.546667 357.546667 0 0 0 23.552 128h140.245333a791.808 791.808 0 0 1-10.197333-128c0-44.714667 3.584-87.722667 10.197333-128H177.152z m24.405333-51.2h126.250667c15.786667-65.024 40.021333-120.405333 69.76-160.554667A359.466667 359.466667 0 0 0 201.557333 332.8zM844.8 378.709333V384h-138.197333c6.613333 40.277333 10.197333 83.285333 10.197333 128s-3.584 87.722667-10.197333 128H844.8v5.290667c16.512-41.216 25.6-86.186667 25.6-133.290667a357.418667 357.418667 0 0 0-25.6-133.290667zM822.442667 332.8a359.466667 359.466667 0 0 0-196.010667-160.554667c29.738667 40.149333 53.930667 95.573333 69.76 160.554667h126.293333zM369.365333 384a736.042667 736.042667 0 0 0-10.965333 128c0 45.354667 3.968 88.448 10.965333 128h285.269334c6.997333-39.552 10.965333-82.645333 10.965333-128s-3.968-88.448-10.965333-128H369.365333z m11.264-51.2h262.741334c-28.586667-108.032-80.725333-179.2-131.370667-179.2-50.645333 0-102.741333 71.168-131.370667 179.2z m-179.072 358.4a359.466667 359.466667 0 0 0 196.010667 160.554667c-29.738667-40.149333-53.930667-95.573333-69.76-160.554667h-126.293333z m620.885334 0h-126.250667c-15.786667 65.024-40.021333 120.405333-69.76 160.554667a359.466667 359.466667 0 0 0 196.010667-160.554667z m-441.813334 0c28.586667 108.032 80.725333 179.2 131.370667 179.2 50.645333 0 102.741333-71.168 131.370667-179.2H380.629333zM512 921.6a409.6 409.6 0 1 1 0-819.2 409.6 409.6 0 0 1 0 819.2z"}),i(a,c),r){var o=e("img","card-link-icon");o.src=r,o.onload=function(){o.onload=null,a.parentNode.replaceChild(o,a)}}return a}(c),d=e("a","card-link-content");d.href=a;var u=e("div","card-link-text"),h=e("span","card-link-title");h.textContent=r;var f=e("span","card-link-url"),s=t("svg");n(s,{width:14,height:14,viewBox:"0 0 24 24",fill:"currentColor"});var k=t("path");return n(k,{d:"M5.327 18.883a3.005 3.005 0 010-4.25l2.608-2.607a.75.75 0 10-1.06-1.06l-2.608 2.607a4.505 4.505 0 006.37 6.37l2.608-2.607a.75.75 0 00-1.06-1.06l-2.608 2.607a3.005 3.005 0 01-4.25 0zm5.428-11.799a.75.75 0 001.06 1.06L14.48 5.48a3.005 3.005 0 014.25 4.25l-2.665 2.665a.75.75 0 001.061 1.06l2.665-2.664a4.505 4.505 0 00-6.371-6.372l-2.665 2.665zm5.323 2.117a.75.75 0 10-1.06-1.06l-7.072 7.07a.75.75 0 001.061 1.06l7.071-7.07z"}),i(s,k),i(f,s),i(f,document.createTextNode(a)),i(u,h),i(u,f),i(d,u),i(d,l),i(o,d),o}function o(e,t,n,i){var r=c(t,n,i);Array.from(e.attributes).forEach((function(e){r.querySelector("a").setAttribute(e.name,e.value)})),e.parentNode.replaceChild(r,e)}a.textContent=".card-link{position:relative;width:100%;min-width:200px;max-width:400px;height:80px;margin:20px auto;border-radius:12px;overflow:hidden;}.card-link .card-link-content{text-decoration:none;position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:space-around;padding:12px;background-color:hsla(0,0%,96.5%,0.88);box-sizing:border-box;}.card-link [cardlink-left] .card-link-text{order:1;}.card-link [cardlink-left] .card-link-icon{margin:0 8px 0 0;}.card-link .card-link-text{flex:1;}.card-link .card-link-title{max-height:40px;font-size:16px;font-weight:500;line-height:1.25;color:#121212;margin:0 0 5px;letter-spacing:0.2px;word-break:break-all;display:-webkit-box;text-overflow:ellipsis;overflow:hidden;-webkit-box-orient:vertical;-webkit-line-clamp:2;}.card-link .card-link-url{display:-webkit-box;font-size:13px;color:#999;margin:0;line-height:1.3;overflow:hidden;word-break:break-word;text-overflow:ellipsis;-webkit-box-orient:vertical;-webkit-line-clamp:1;}.card-link .card-link-url svg{float:left;margin:2px 5px 0 0;}.card-link .card-link-icon{width:60px;height:60px;margin:0 0 0 8px;overflow:hidden;border-radius:6px;object-fit:contain;}",document.head.appendChild(a);var l={set:function(e,t,n){var i;return function(e,t){for(var n in t){var i=t[n];if(!e[i])throw new Error("Parameter '".concat(i,"' not legal"))}}(n,["title","link"]),0===(i=t.replace(/(\/index\.html|\/)*$/gi,"")).length&&(i+="/"),e[t=i]=n,!0}};function d(e,t){fetch(e).then((function(e){return e.text()})).then(t).catch((function(n){var i=u.server;if(e.includes(i)||!i)return console.error("CardLink Error:",n);d(i+e,t)}))}function u(e){(e="forEach"in(e||{})?e:document.querySelectorAll("a[cardlink]")).forEach((function(e){if(1===e.nodeType){e.removeAttribute("cardlink");var t=e.href,n=u.cache[t];if(n)return o(e,n.title,n.link,n.icon);r(t)&&d(t,(function(n){var i=function(e,t){var n={title:"",link:t,icon:""};try{var i=(new DOMParser).parseFromString(e,"text/html");if(n.title=i.querySelector("title"),n.title){if(n.title=n.title.textContent,n.icon=i.querySelector("body img"),n.icon=n.icon&&n.icon.getAttribute("src"),/^data:image/.test(n.icon)&&(n.icon=""),!n.icon){var a=[].slice.call(i.querySelectorAll("link[rel][href]"));n.icon=a.find((function(e){return e.rel.includes("icon")})),n.icon=n.icon&&n.icon.getAttribute("href")}n.icon&&!r(n.icon)&&(n.icon=new URL(t).origin+n.icon)}}catch(e){console.warn("CardLink Error: Failed to parse",e)}return n}(n,t),a=i.title,c=i.link,l=i.icon;u.cache[c]={title:a,link:c,icon:l},o(e,a,c,l)}))}}))}return u.cache=new Proxy({},l),u}(); 2 | //# sourceMappingURL=cardlink.js.map 3 | -------------------------------------------------------------------------------- /dist/cardlink.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"cardlink.js","sources":["../src/utils.js","../src/inject.js","../src/renderer.js","../src/main.js","../src/parse.js"],"sourcesContent":["/**\n * Creating element\n * @param {String} name Element Name\n * @param {String} className Element className\n * @returns {HTMLElement}\n */\nexport function createElement(name, className) {\n const dom = document.createElement(name)\n if (className) dom.className = className\n return dom\n}\n\n/**\n * Creates svg element\n * @param {String} name Element Name\n * @returns {SVGElementTagNameMap}\n */\nexport function createSVG(name) {\n return document.createElementNS('http://www.w3.org/2000/svg', name)\n}\n\n/**\n * Set element attribute\n * @param {HTMLElement} el Element\n * @param {Object} obj Objects of key-value pairs\n */\nexport function setAttribute(el, obj) {\n for (const key in obj) el.setAttribute(key, obj[key])\n}\n\n/**\n * Append child node or element\n * @param {HTMLElement} el Element\n * @param {Node} child The node to append to the given parent node (commonly an element).\n */\nexport function appendChild(el, child) {\n el.appendChild(child)\n}\n\n/**\n * Remove '/' and '/index.html'\n * @param {String} params\n * @returns { String }\n */\nexport function indexHandler(params) {\n let path = params.replace(/(\\/index\\.html|\\/)*$/gi, '')\n if (path.length === 0) path += '/'\n return path\n}\n\n/**\n * @param {Object} param\n * @param {Array} requiredParams\n */\nexport function verifyParams(param, requiredParams) {\n for (const index in requiredParams) {\n const requiredParam = requiredParams[index]\n if (!param[requiredParam]) {\n throw new Error(`Parameter '${requiredParam}' not legal`)\n }\n }\n}\n\n/**\n * Determine if it is a ['https://', 'http://', '//'] protocol\n * @param {String} url Website url\n * @returns {Boolean}\n */\nexport function isHttp(url) {\n return /^(https?:)?\\/\\//g.test(url)\n}\n","import { createElement } from './utils'\nimport styles from './main.css'\n\nconst style = createElement('style')\nstyle.textContent = styles\ndocument.head.appendChild(style)\n","import { createSVG, setAttribute, appendChild, createElement } from './utils'\n\n/**\n * Create card link icon\n * @param {String} icon Icon URL\n * @returns {SVGElementTagNameMap}\n */\nfunction createIcon(icon) {\n const svg = createSVG('svg')\n svg.classList.add('card-link-icon')\n svg.style.background = '#ebebeb'\n setAttribute(svg, { fill: '#d4d4d4', viewBox: '0 0 1024 1024' })\n const path = createSVG('path')\n\n setAttribute(path, {\n // eslint-disable-next-line max-len\n d: 'M177.152 384a357.546667 357.546667 0 0 0-23.552 128 357.546667 357.546667 0 0 0 23.552 128h140.245333a791.808 791.808 0 0 1-10.197333-128c0-44.714667 3.584-87.722667 10.197333-128H177.152z m24.405333-51.2h126.250667c15.786667-65.024 40.021333-120.405333 69.76-160.554667A359.466667 359.466667 0 0 0 201.557333 332.8zM844.8 378.709333V384h-138.197333c6.613333 40.277333 10.197333 83.285333 10.197333 128s-3.584 87.722667-10.197333 128H844.8v5.290667c16.512-41.216 25.6-86.186667 25.6-133.290667a357.418667 357.418667 0 0 0-25.6-133.290667zM822.442667 332.8a359.466667 359.466667 0 0 0-196.010667-160.554667c29.738667 40.149333 53.930667 95.573333 69.76 160.554667h126.293333zM369.365333 384a736.042667 736.042667 0 0 0-10.965333 128c0 45.354667 3.968 88.448 10.965333 128h285.269334c6.997333-39.552 10.965333-82.645333 10.965333-128s-3.968-88.448-10.965333-128H369.365333z m11.264-51.2h262.741334c-28.586667-108.032-80.725333-179.2-131.370667-179.2-50.645333 0-102.741333 71.168-131.370667 179.2z m-179.072 358.4a359.466667 359.466667 0 0 0 196.010667 160.554667c-29.738667-40.149333-53.930667-95.573333-69.76-160.554667h-126.293333z m620.885334 0h-126.250667c-15.786667 65.024-40.021333 120.405333-69.76 160.554667a359.466667 359.466667 0 0 0 196.010667-160.554667z m-441.813334 0c28.586667 108.032 80.725333 179.2 131.370667 179.2 50.645333 0 102.741333-71.168 131.370667-179.2H380.629333zM512 921.6a409.6 409.6 0 1 1 0-819.2 409.6 409.6 0 0 1 0 819.2z'\n })\n appendChild(svg, path)\n\n if (icon) {\n const img = createElement('img', 'card-link-icon')\n img.src = icon\n\n // If icon is valid, replace `svgIcon` with `imgIcon`\n img.onload = function () {\n img.onload = null\n svg.parentNode.replaceChild(img, svg)\n }\n }\n return svg\n}\n\n/**\n * Creating a Document Object Model\n * @param {String} title Website title\n * @param {String} link Website address\n * @param {String} icon Website icon\n * @returns {HTMLElement}\n */\n// eslint-disable-next-line max-statements\nfunction createDOM(title, link, icon) {\n const wrapDOM = createElement('div', 'card-link')\n const iconDOM = createIcon(icon)\n\n const aDOM = createElement('a', 'card-link-content')\n aDOM.href = link\n\n const textDOM = createElement('div', 'card-link-text')\n\n const titleDOM = createElement('span', 'card-link-title')\n titleDOM.textContent = title\n\n const linkDOM = createElement('span', 'card-link-url')\n\n const linkSvgDOM = createSVG('svg')\n setAttribute(linkSvgDOM, {\n width: 14,\n height: 14,\n viewBox: '0 0 24 24',\n fill: 'currentColor'\n })\n\n const linkPathDOM = createSVG('path')\n setAttribute(linkPathDOM, {\n // eslint-disable-next-line max-len\n d: 'M5.327 18.883a3.005 3.005 0 010-4.25l2.608-2.607a.75.75 0 10-1.06-1.06l-2.608 2.607a4.505 4.505 0 006.37 6.37l2.608-2.607a.75.75 0 00-1.06-1.06l-2.608 2.607a3.005 3.005 0 01-4.25 0zm5.428-11.799a.75.75 0 001.06 1.06L14.48 5.48a3.005 3.005 0 014.25 4.25l-2.665 2.665a.75.75 0 001.061 1.06l2.665-2.664a4.505 4.505 0 00-6.371-6.372l-2.665 2.665zm5.323 2.117a.75.75 0 10-1.06-1.06l-7.072 7.07a.75.75 0 001.061 1.06l7.071-7.07z'\n })\n\n appendChild(linkSvgDOM, linkPathDOM)\n appendChild(linkDOM, linkSvgDOM)\n appendChild(linkDOM, document.createTextNode(link))\n\n appendChild(textDOM, titleDOM)\n appendChild(textDOM, linkDOM)\n appendChild(aDOM, textDOM)\n appendChild(aDOM, iconDOM)\n appendChild(wrapDOM, aDOM)\n return wrapDOM\n}\n\nexport default function renderer(el, title, link, icon) {\n const dom = createDOM(title, link, icon)\n\n // Reset the attribute\n Array.from(el.attributes).forEach((attr) => {\n dom.querySelector('a').setAttribute(attr.name, attr.value)\n })\n el.parentNode.replaceChild(dom, el)\n}\n","import './inject'\nimport renderer from './renderer'\nimport parse from './parse'\nimport { verifyParams, indexHandler, isHttp } from './utils'\n\nconst proxyHandler = {\n set(target, name, value) {\n verifyParams(value, ['title', 'link'])\n name = indexHandler(name)\n target[name] = value\n return true\n }\n}\ncardLink.cache = new Proxy({}, proxyHandler)\n\nfunction fetchPage(link, callback) {\n fetch(link)\n .then((result) => result.text())\n .then(callback)\n .catch((error) => {\n const server = cardLink.server\n // eslint-disable-next-line no-console\n if (link.includes(server) || !server) return console.error('CardLink Error:', error)\n fetchPage(server + link, callback)\n })\n}\n\n/**\n * Create card links\n * @param {NodeList} nodes A collection of nodes or a collection of arrays,\n * if it is an array then the array must always contain node element\n */\nexport default function cardLink(nodes) {\n // If the `nodes` do not contain a `forEach` method, then the default `a[cardlink]` is used\n nodes = 'forEach' in (nodes || {}) ? nodes : document.querySelectorAll('a[cardlink]')\n nodes.forEach((el) => {\n // If it is not a tag element then it is not processed\n if (el.nodeType !== 1) return\n el.removeAttribute('cardlink')\n const href = el.href\n\n const cache = cardLink.cache[href]\n if (cache) return renderer(el, cache.title, cache.link, cache.icon)\n\n if (isHttp(href)) {\n fetchPage(href, (html) => {\n const { title, link, icon } = parse(html, href)\n cardLink.cache[link] = { title, link, icon }\n renderer(el, title, link, icon)\n })\n }\n })\n}\n","import { isHttp } from './utils'\n\n/**\n * Get info\n * @param {String} html String type html\n * @param {String} link Website address\n * @returns {{ title: string; link: string; icon: string }} Website info\n */\n// eslint-disable-next-line max-statements\nexport default function (html, link) {\n const result = { title: '', link, icon: '' }\n try {\n const doc = new DOMParser().parseFromString(html, 'text/html')\n // If there is no title, no card link is generated\n result.title = doc.querySelector('title')\n if (result.title) {\n result.title = result.title.textContent\n\n // Get the src of the first img tag in the body tag\n result.icon = doc.querySelector('body img')\n result.icon = result.icon && result.icon.getAttribute('src')\n\n if (/^data:image/.test(result.icon)) result.icon = ''\n\n // If there is no src then get the site icon\n if (!result.icon) {\n const links = [].slice.call(doc.querySelectorAll('link[rel][href]'))\n result.icon = links.find((_el) => _el.rel.includes('icon'))\n result.icon = result.icon && result.icon.getAttribute('href')\n }\n\n // If `icon` is not the ['https://', 'http://', '//'] protocol, splice on the `origin` of the a tag\n if (result.icon && !isHttp(result.icon)) result.icon = new URL(link).origin + result.icon\n }\n } catch (error) {\n // eslint-disable-next-line no-console\n console.warn('CardLink Error: Failed to parse', error)\n }\n return result\n}\n"],"names":["createElement","name","className","dom","document","createSVG","createElementNS","setAttribute","el","obj","key","appendChild","child","isHttp","url","test","style","createDOM","title","link","icon","wrapDOM","iconDOM","svg","classList","add","background","fill","viewBox","path","d","img","src","onload","parentNode","replaceChild","createIcon","aDOM","href","textDOM","titleDOM","textContent","linkDOM","linkSvgDOM","width","height","linkPathDOM","createTextNode","renderer","Array","from","attributes","forEach","attr","querySelector","value","head","proxyHandler","set","target","param","requiredParams","index","requiredParam","Error","verifyParams","replace","length","fetchPage","callback","fetch","then","result","text","error","server","cardLink","includes","console","nodes","querySelectorAll","nodeType","removeAttribute","cache","html","_parse","doc","DOMParser","parseFromString","getAttribute","links","slice","call","find","_el","rel","URL","origin","warn","parse","Proxy"],"mappings":"qCAMO,SAASA,EAAcC,EAAMC,GAClC,IAAMC,EAAMC,SAASJ,cAAcC,GAEnC,OADIC,IAAWC,EAAID,UAAYA,GACxBC,CACR,CAOM,SAASE,EAAUJ,GACxB,OAAOG,SAASE,gBAAgB,6BAA8BL,EAC/D,CAOM,SAASM,EAAaC,EAAIC,GAC/B,IAAK,IAAMC,KAAOD,EAAKD,EAAGD,aAAaG,EAAKD,EAAIC,GACjD,CAOM,SAASC,EAAYH,EAAII,GAC9BJ,EAAGG,YAAYC,EAChB,CA+BM,SAASC,EAAOC,GACrB,MAAO,mBAAmBC,KAAKD,EAChC,KCnEKE,EAAQhB,EAAc,SCsC5B,SAASiB,EAAUC,EAAOC,EAAMC,GAC9B,IAAMC,EAAUrB,EAAc,MAAO,aAC/BsB,EApCR,SAAoBF,GAClB,IAAMG,EAAMlB,EAAU,OACtBkB,EAAIC,UAAUC,IAAI,kBAClBF,EAAIP,MAAMU,WAAa,UACvBnB,EAAagB,EAAK,CAAEI,KAAM,UAAWC,QAAS,kBAC9C,IAAMC,EAAOxB,EAAU,QAQvB,GANAE,EAAasB,EAAM,CAEjBC,EAAG,k7CAELnB,EAAYY,EAAKM,GAEbT,EAAM,CACR,IAAMW,EAAM/B,EAAc,MAAO,kBACjC+B,EAAIC,IAAMZ,EAGVW,EAAIE,OAAS,WACXF,EAAIE,OAAS,KACbV,EAAIW,WAAWC,aAAaJ,EAAKR,GAEpC,CACD,OAAOA,CACR,CAYiBa,CAAWhB,GAErBiB,EAAOrC,EAAc,IAAK,qBAChCqC,EAAKC,KAAOnB,EAEZ,IAAMoB,EAAUvC,EAAc,MAAO,kBAE/BwC,EAAWxC,EAAc,OAAQ,mBACvCwC,EAASC,YAAcvB,EAEvB,IAAMwB,EAAU1C,EAAc,OAAQ,iBAEhC2C,EAAatC,EAAU,OAC7BE,EAAaoC,EAAY,CACvBC,MAAO,GACPC,OAAQ,GACRjB,QAAS,YACTD,KAAM,iBAGR,IAAMmB,EAAczC,EAAU,QAe9B,OAdAE,EAAauC,EAAa,CAExBhB,EAAG,2aAGLnB,EAAYgC,EAAYG,GACxBnC,EAAY+B,EAASC,GACrBhC,EAAY+B,EAAStC,SAAS2C,eAAe5B,IAE7CR,EAAY4B,EAASC,GACrB7B,EAAY4B,EAASG,GACrB/B,EAAY0B,EAAME,GAClB5B,EAAY0B,EAAMf,GAClBX,EAAYU,EAASgB,GACdhB,CACR,CAEc,SAAS2B,EAASxC,EAAIU,EAAOC,EAAMC,GAChD,IAAMjB,EAAMc,EAAUC,EAAOC,EAAMC,GAGnC6B,MAAMC,KAAK1C,EAAG2C,YAAYC,SAAQ,SAACC,GACjClD,EAAImD,cAAc,KAAK/C,aAAa8C,EAAKpD,KAAMoD,EAAKE,UAEtD/C,EAAG0B,WAAWC,aAAahC,EAAKK,EACjC,CDrFDQ,EAAMyB,sqCACNrC,SAASoD,KAAK7C,YAAYK,GEA1B,IAAMyC,EAAe,CACnBC,aAAIC,EAAQ1D,EAAMsD,GHsCb,IACD1B,EGnCF,OH4CG,SAAsB+B,EAAOC,GAClC,IAAK,IAAMC,KAASD,EAAgB,CAClC,IAAME,EAAgBF,EAAeC,GACrC,IAAKF,EAAMG,GACT,MAAM,IAAIC,MAAoBD,cAAAA,OAAAA,EAA9B,eAEH,CACF,CGtDGE,CAAaV,EAAO,CAAC,QAAS,SHuCZ,KADhB1B,EGrCkB5B,EHqCJiE,QAAQ,yBAA0B,KAC3CC,SAActC,GAAQ,KGrC7B8B,EADA1D,EHuCK4B,GGtCU0B,GACR,CACR,GAIH,SAASa,EAAUjD,EAAMkD,GACvBC,MAAMnD,GACHoD,MAAK,SAACC,GAAD,OAAYA,EAAOC,MAAnB,IACLF,KAAKF,GACC,OAAA,SAACK,GACN,IAAMC,EAASC,EAASD,OAExB,GAAIxD,EAAK0D,SAASF,KAAYA,EAAQ,OAAOG,QAAQJ,MAAM,kBAAmBA,GAC9EN,EAAUO,EAASxD,EAAMkD,KAE9B,CAOc,SAASO,EAASG,IAE/BA,EAAQ,YAAcA,GAAS,CAAvB,GAA6BA,EAAQ3E,SAAS4E,iBAAiB,gBACjE5B,SAAQ,SAAC5C,GAEb,GAAoB,IAAhBA,EAAGyE,SAAP,CACAzE,EAAG0E,gBAAgB,YACnB,IAAM5C,EAAO9B,EAAG8B,KAEV6C,EAAQP,EAASO,MAAM7C,GAC7B,GAAI6C,EAAO,OAAOnC,EAASxC,EAAI2E,EAAMjE,MAAOiE,EAAMhE,KAAMgE,EAAM/D,MAE1DP,EAAOyB,IACT8B,EAAU9B,GAAM,SAAC8C,GACf,IAAAC,ECrCO,SAAUD,EAAMjE,GAC7B,IAAMqD,EAAS,CAAEtD,MAAO,GAAIC,KAAAA,EAAMC,KAAM,IACxC,IACE,IAAMkE,GAAM,IAAIC,WAAYC,gBAAgBJ,EAAM,aAGlD,GADAZ,EAAOtD,MAAQoE,EAAIhC,cAAc,SAC7BkB,EAAOtD,MAAO,CAUhB,GATAsD,EAAOtD,MAAQsD,EAAOtD,MAAMuB,YAG5B+B,EAAOpD,KAAOkE,EAAIhC,cAAc,YAChCkB,EAAOpD,KAAOoD,EAAOpD,MAAQoD,EAAOpD,KAAKqE,aAAa,OAElD,cAAc1E,KAAKyD,EAAOpD,QAAOoD,EAAOpD,KAAO,KAG9CoD,EAAOpD,KAAM,CAChB,IAAMsE,EAAQ,GAAGC,MAAMC,KAAKN,EAAIN,iBAAiB,oBACjDR,EAAOpD,KAAOsE,EAAMG,MAAK,SAACC,GAAD,OAASA,EAAIC,IAAIlB,SAAS,OAA1B,IACzBL,EAAOpD,KAAOoD,EAAOpD,MAAQoD,EAAOpD,KAAKqE,aAAa,OAbxC,CAiBZjB,EAAOpD,OAASP,EAAO2D,EAAOpD,QAAOoD,EAAOpD,KAAO,IAAI4E,IAAI7E,GAAM8E,OAASzB,EAAOpD,KACtF,CAIF,CAHC,MAAOsD,GAEPI,QAAQoB,KAAK,kCAAmCxB,EACjD,CACD,OAAOF,CACR,CDOqC2B,CAAMf,EAAM9C,GAAlCpB,IAAAA,MAAOC,IAAAA,KAAMC,IAAAA,KACrBwD,EAASO,MAAMhE,GAAQ,CAAED,MAAAA,EAAOC,KAAAA,EAAMC,KAAAA,GACtC4B,EAASxC,EAAIU,EAAOC,EAAMC,EAC3B,GAZoB,IAe1B,QAvCDwD,EAASO,MAAQ,IAAIiB,MAAM,CAAV,EAAc3C"} -------------------------------------------------------------------------------- /dist/cardlink.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating element 3 | * @param {String} name Element Name 4 | * @param {String} className Element className 5 | * @returns {HTMLElement} 6 | */ 7 | function createElement(name, className) { 8 | var dom = document.createElement(name); 9 | if (className) dom.className = className; 10 | return dom; 11 | } 12 | /** 13 | * Creates svg element 14 | * @param {String} name Element Name 15 | * @returns {SVGElementTagNameMap} 16 | */ 17 | 18 | function createSVG(name) { 19 | return document.createElementNS('http://www.w3.org/2000/svg', name); 20 | } 21 | /** 22 | * Set element attribute 23 | * @param {HTMLElement} el Element 24 | * @param {Object} obj Objects of key-value pairs 25 | */ 26 | 27 | function setAttribute(el, obj) { 28 | for (var key in obj) { 29 | el.setAttribute(key, obj[key]); 30 | } 31 | } 32 | /** 33 | * Append child node or element 34 | * @param {HTMLElement} el Element 35 | * @param {Node} child The node to append to the given parent node (commonly an element). 36 | */ 37 | 38 | function appendChild(el, child) { 39 | el.appendChild(child); 40 | } 41 | /** 42 | * Remove '/' and '/index.html' 43 | * @param {String} params 44 | * @returns { String } 45 | */ 46 | 47 | function indexHandler(params) { 48 | var path = params.replace(/(\/index\.html|\/)*$/gi, ''); 49 | if (path.length === 0) path += '/'; 50 | return path; 51 | } 52 | /** 53 | * @param {Object} param 54 | * @param {Array} requiredParams 55 | */ 56 | 57 | function verifyParams(param, requiredParams) { 58 | for (var index in requiredParams) { 59 | var requiredParam = requiredParams[index]; 60 | 61 | if (!param[requiredParam]) { 62 | throw new Error("Parameter '".concat(requiredParam, "' not legal")); 63 | } 64 | } 65 | } 66 | /** 67 | * Determine if it is a ['https://', 'http://', '//'] protocol 68 | * @param {String} url Website url 69 | * @returns {Boolean} 70 | */ 71 | 72 | function isHttp(url) { 73 | return /^(https?:)?\/\//g.test(url); 74 | } 75 | 76 | var styles = ".card-link{position:relative;width:100%;min-width:200px;max-width:400px;height:80px;margin:20px auto;border-radius:12px;overflow:hidden;}.card-link .card-link-content{text-decoration:none;position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:space-around;padding:12px;background-color:hsla(0,0%,96.5%,0.88);box-sizing:border-box;}.card-link [cardlink-left] .card-link-text{order:1;}.card-link [cardlink-left] .card-link-icon{margin:0 8px 0 0;}.card-link .card-link-text{flex:1;}.card-link .card-link-title{max-height:40px;font-size:16px;font-weight:500;line-height:1.25;color:#121212;margin:0 0 5px;letter-spacing:0.2px;word-break:break-all;display:-webkit-box;text-overflow:ellipsis;overflow:hidden;-webkit-box-orient:vertical;-webkit-line-clamp:2;}.card-link .card-link-url{display:-webkit-box;font-size:13px;color:#999;margin:0;line-height:1.3;overflow:hidden;word-break:break-word;text-overflow:ellipsis;-webkit-box-orient:vertical;-webkit-line-clamp:1;}.card-link .card-link-url svg{float:left;margin:2px 5px 0 0;}.card-link .card-link-icon{width:60px;height:60px;margin:0 0 0 8px;overflow:hidden;border-radius:6px;object-fit:contain;}"; 77 | 78 | var style = createElement('style'); 79 | style.textContent = styles; 80 | document.head.appendChild(style); 81 | 82 | /** 83 | * Create card link icon 84 | * @param {String} icon Icon URL 85 | * @returns {SVGElementTagNameMap} 86 | */ 87 | 88 | function createIcon(icon) { 89 | var svg = createSVG('svg'); 90 | svg.classList.add('card-link-icon'); 91 | svg.style.background = '#ebebeb'; 92 | setAttribute(svg, { 93 | fill: '#d4d4d4', 94 | viewBox: '0 0 1024 1024' 95 | }); 96 | var path = createSVG('path'); 97 | setAttribute(path, { 98 | // eslint-disable-next-line max-len 99 | d: 'M177.152 384a357.546667 357.546667 0 0 0-23.552 128 357.546667 357.546667 0 0 0 23.552 128h140.245333a791.808 791.808 0 0 1-10.197333-128c0-44.714667 3.584-87.722667 10.197333-128H177.152z m24.405333-51.2h126.250667c15.786667-65.024 40.021333-120.405333 69.76-160.554667A359.466667 359.466667 0 0 0 201.557333 332.8zM844.8 378.709333V384h-138.197333c6.613333 40.277333 10.197333 83.285333 10.197333 128s-3.584 87.722667-10.197333 128H844.8v5.290667c16.512-41.216 25.6-86.186667 25.6-133.290667a357.418667 357.418667 0 0 0-25.6-133.290667zM822.442667 332.8a359.466667 359.466667 0 0 0-196.010667-160.554667c29.738667 40.149333 53.930667 95.573333 69.76 160.554667h126.293333zM369.365333 384a736.042667 736.042667 0 0 0-10.965333 128c0 45.354667 3.968 88.448 10.965333 128h285.269334c6.997333-39.552 10.965333-82.645333 10.965333-128s-3.968-88.448-10.965333-128H369.365333z m11.264-51.2h262.741334c-28.586667-108.032-80.725333-179.2-131.370667-179.2-50.645333 0-102.741333 71.168-131.370667 179.2z m-179.072 358.4a359.466667 359.466667 0 0 0 196.010667 160.554667c-29.738667-40.149333-53.930667-95.573333-69.76-160.554667h-126.293333z m620.885334 0h-126.250667c-15.786667 65.024-40.021333 120.405333-69.76 160.554667a359.466667 359.466667 0 0 0 196.010667-160.554667z m-441.813334 0c28.586667 108.032 80.725333 179.2 131.370667 179.2 50.645333 0 102.741333-71.168 131.370667-179.2H380.629333zM512 921.6a409.6 409.6 0 1 1 0-819.2 409.6 409.6 0 0 1 0 819.2z' 100 | }); 101 | appendChild(svg, path); 102 | 103 | if (icon) { 104 | var img = createElement('img', 'card-link-icon'); 105 | img.src = icon; // If icon is valid, replace `svgIcon` with `imgIcon` 106 | 107 | img.onload = function () { 108 | img.onload = null; 109 | svg.parentNode.replaceChild(img, svg); 110 | }; 111 | } 112 | 113 | return svg; 114 | } 115 | /** 116 | * Creating a Document Object Model 117 | * @param {String} title Website title 118 | * @param {String} link Website address 119 | * @param {String} icon Website icon 120 | * @returns {HTMLElement} 121 | */ 122 | // eslint-disable-next-line max-statements 123 | 124 | 125 | function createDOM(title, link, icon) { 126 | var wrapDOM = createElement('div', 'card-link'); 127 | var iconDOM = createIcon(icon); 128 | var aDOM = createElement('a', 'card-link-content'); 129 | aDOM.href = link; 130 | var textDOM = createElement('div', 'card-link-text'); 131 | var titleDOM = createElement('span', 'card-link-title'); 132 | titleDOM.textContent = title; 133 | var linkDOM = createElement('span', 'card-link-url'); 134 | var linkSvgDOM = createSVG('svg'); 135 | setAttribute(linkSvgDOM, { 136 | width: 14, 137 | height: 14, 138 | viewBox: '0 0 24 24', 139 | fill: 'currentColor' 140 | }); 141 | var linkPathDOM = createSVG('path'); 142 | setAttribute(linkPathDOM, { 143 | // eslint-disable-next-line max-len 144 | d: 'M5.327 18.883a3.005 3.005 0 010-4.25l2.608-2.607a.75.75 0 10-1.06-1.06l-2.608 2.607a4.505 4.505 0 006.37 6.37l2.608-2.607a.75.75 0 00-1.06-1.06l-2.608 2.607a3.005 3.005 0 01-4.25 0zm5.428-11.799a.75.75 0 001.06 1.06L14.48 5.48a3.005 3.005 0 014.25 4.25l-2.665 2.665a.75.75 0 001.061 1.06l2.665-2.664a4.505 4.505 0 00-6.371-6.372l-2.665 2.665zm5.323 2.117a.75.75 0 10-1.06-1.06l-7.072 7.07a.75.75 0 001.061 1.06l7.071-7.07z' 145 | }); 146 | appendChild(linkSvgDOM, linkPathDOM); 147 | appendChild(linkDOM, linkSvgDOM); 148 | appendChild(linkDOM, document.createTextNode(link)); 149 | appendChild(textDOM, titleDOM); 150 | appendChild(textDOM, linkDOM); 151 | appendChild(aDOM, textDOM); 152 | appendChild(aDOM, iconDOM); 153 | appendChild(wrapDOM, aDOM); 154 | return wrapDOM; 155 | } 156 | 157 | function renderer(el, title, link, icon) { 158 | var dom = createDOM(title, link, icon); // Reset the attribute 159 | 160 | Array.from(el.attributes).forEach(function (attr) { 161 | dom.querySelector('a').setAttribute(attr.name, attr.value); 162 | }); 163 | el.parentNode.replaceChild(dom, el); 164 | } 165 | 166 | /** 167 | * Get info 168 | * @param {String} html String type html 169 | * @param {String} link Website address 170 | * @returns {{ title: string; link: string; icon: string }} Website info 171 | */ 172 | // eslint-disable-next-line max-statements 173 | 174 | function parse (html, link) { 175 | var result = { 176 | title: '', 177 | link: link, 178 | icon: '' 179 | }; 180 | 181 | try { 182 | var doc = new DOMParser().parseFromString(html, 'text/html'); // If there is no title, no card link is generated 183 | 184 | result.title = doc.querySelector('title'); 185 | 186 | if (result.title) { 187 | result.title = result.title.textContent; // Get the src of the first img tag in the body tag 188 | 189 | result.icon = doc.querySelector('body img'); 190 | result.icon = result.icon && result.icon.getAttribute('src'); 191 | if (/^data:image/.test(result.icon)) result.icon = ''; // If there is no src then get the site icon 192 | 193 | if (!result.icon) { 194 | var links = [].slice.call(doc.querySelectorAll('link[rel][href]')); 195 | result.icon = links.find(function (_el) { 196 | return _el.rel.includes('icon'); 197 | }); 198 | result.icon = result.icon && result.icon.getAttribute('href'); 199 | } // If `icon` is not the ['https://', 'http://', '//'] protocol, splice on the `origin` of the a tag 200 | 201 | 202 | if (result.icon && !isHttp(result.icon)) result.icon = new URL(link).origin + result.icon; 203 | } 204 | } catch (error) { 205 | // eslint-disable-next-line no-console 206 | console.warn('CardLink Error: Failed to parse', error); 207 | } 208 | 209 | return result; 210 | } 211 | 212 | var proxyHandler = { 213 | set: function set(target, name, value) { 214 | verifyParams(value, ['title', 'link']); 215 | name = indexHandler(name); 216 | target[name] = value; 217 | return true; 218 | } 219 | }; 220 | cardLink.cache = new Proxy({}, proxyHandler); 221 | 222 | function fetchPage(link, callback) { 223 | fetch(link).then(function (result) { 224 | return result.text(); 225 | }).then(callback)["catch"](function (error) { 226 | var server = cardLink.server; // eslint-disable-next-line no-console 227 | 228 | if (link.includes(server) || !server) return console.error('CardLink Error:', error); 229 | fetchPage(server + link, callback); 230 | }); 231 | } 232 | /** 233 | * Create card links 234 | * @param {NodeList} nodes A collection of nodes or a collection of arrays, 235 | * if it is an array then the array must always contain node element 236 | */ 237 | 238 | 239 | function cardLink(nodes) { 240 | // If the `nodes` do not contain a `forEach` method, then the default `a[cardlink]` is used 241 | nodes = 'forEach' in (nodes || {}) ? nodes : document.querySelectorAll('a[cardlink]'); 242 | nodes.forEach(function (el) { 243 | // If it is not a tag element then it is not processed 244 | if (el.nodeType !== 1) return; 245 | el.removeAttribute('cardlink'); 246 | var href = el.href; 247 | var cache = cardLink.cache[href]; 248 | if (cache) return renderer(el, cache.title, cache.link, cache.icon); 249 | 250 | if (isHttp(href)) { 251 | fetchPage(href, function (html) { 252 | var _parse = parse(html, href), 253 | title = _parse.title, 254 | link = _parse.link, 255 | icon = _parse.icon; 256 | 257 | cardLink.cache[link] = { 258 | title: title, 259 | link: link, 260 | icon: icon 261 | }; 262 | renderer(el, title, link, icon); 263 | }); 264 | } 265 | }); 266 | } 267 | 268 | export { cardLink as default }; 269 | -------------------------------------------------------------------------------- /dist/parse.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).cardLinkParse=t()}(this,(function(){"use strict";return function(e,t){var i,n={title:"",link:t,icon:""};try{var o=(new DOMParser).parseFromString(e,"text/html");if(n.title=o.querySelector("title"),n.title){if(n.title=n.title.textContent,n.icon=o.querySelector("body img"),n.icon=n.icon&&n.icon.getAttribute("src"),/^data:image/.test(n.icon)&&(n.icon=""),!n.icon){var r=[].slice.call(o.querySelectorAll("link[rel][href]"));n.icon=r.find((function(e){return e.rel.includes("icon")})),n.icon=n.icon&&n.icon.getAttribute("href")}n.icon&&(i=n.icon,!/^(https?:)?\/\//g.test(i))&&(n.icon=new URL(t).origin+n.icon)}}catch(e){console.warn("CardLink Error: Failed to parse",e)}return n}})); 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cardlink", 3 | "version": "1.0.5", 4 | "description": "为页面上的超链接生成卡片式链接 | Generate card-based links for hyperlinks on the page", 5 | "main": "dist/cardlink.cjs", 6 | "module": "dist/cardlink.mjs", 7 | "unpkg": "dist/cardlink.js", 8 | "jsdelivr": "dist/cardlink.js", 9 | "typings": "types/index.d.ts", 10 | "scripts": { 11 | "build": "rollup -c", 12 | "dev": "rollup -c -w", 13 | "lint": "npx eslint src && npx prettier --check src", 14 | "lint:fix": "npx eslint --fix src && npx prettier --check --write src" 15 | }, 16 | "exports": { 17 | ".": { 18 | "import": "./dist/cardlink.mjs", 19 | "require": "./dist/cardlink.cjs", 20 | "default": "./dist/cardlink.js" 21 | }, 22 | "./parse": { 23 | "import": "./src/parse.js", 24 | "default": "./dist/parse.js" 25 | } 26 | }, 27 | "files": [ 28 | "dist", 29 | "src" 30 | ], 31 | "repository": { 32 | "type": "git", 33 | "url": "git+https://github.com/Lete114/CardLink.git" 34 | }, 35 | "keywords": [ 36 | "card", 37 | "link", 38 | "card-link" 39 | ], 40 | "author": "Lete乐特", 41 | "license": "MIT", 42 | "bugs": { 43 | "url": "https://github.com/Lete114/CardLink/issues" 44 | }, 45 | "homepage": "https://github.com/Lete114/CardLink#readme", 46 | "devDependencies": { 47 | "@babel/core": "^7.18.2", 48 | "@babel/preset-env": "^7.18.2", 49 | "@rollup/plugin-babel": "^5.3.1", 50 | "eslint": "^8.16.0", 51 | "prettier": "^2.6.2", 52 | "rollup": "^2.75.5", 53 | "rollup-plugin-delete": "^2.0.0", 54 | "rollup-plugin-import-css": "^3.0.3", 55 | "rollup-plugin-serve": "^1.1.0", 56 | "rollup-plugin-terser": "^7.0.2" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /public/card-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lete114/CardLink/dccd7c403123597fddf53af7573fe00822a76ad9/public/card-link.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 15 |