';
@@ -50,7 +52,7 @@ hexo.extend.tag.register('link', function(args) {
} else {
// left
el += '
';
- el += loadTitle() + loadDesc();
+ el += loadTitle() + loadLink();
el += '
';
// right
el += '
';
diff --git a/source/css/_common/base.styl b/source/css/_common/base.styl
index 1f02218..8c418e6 100644
--- a/source/css/_common/base.styl
+++ b/source/css/_common/base.styl
@@ -12,7 +12,7 @@ a
&:hover
color: $color-hover
-p:not([class])
+.md p:not([class])
text-align: convert(hexo-config('style.text-align'))
hr
diff --git a/source/css/_layout/tag-plugins/link.styl b/source/css/_layout/tag-plugins/link.styl
index 32fd775..293e5ca 100644
--- a/source/css/_layout/tag-plugins/link.styl
+++ b/source/css/_layout/tag-plugins/link.styl
@@ -14,8 +14,8 @@
cursor: pointer
min-width 280px
max-width: 100%
- width: 320px
- @media screen and (max-width: $device-mobile-375)
+ width: 350px
+ @media screen and (max-width: $device-mobile-425)
width: 100%
box-shadow: $boxshadow-card
border-radius: $border-block
@@ -35,54 +35,68 @@
text-align: justify
.md .link-card
- line-height: 1.2
- > div
- pointer-events: none
+ >.left
+ overflow: hidden
+ margin: 0.75rem 0 0.75rem 0.75rem
+ .title
+ font-size: $fs-14
+ span+span
+ margin-top: 0.25rem
+ >.right
+ width: 2.75rem
+ height: 2.75rem
+ margin: 0.75rem
+ overflow: hidden
+ flex-shrink: 0
>.top
display: flex
- margin: .75rem 1rem .25rem
+ margin: .75rem 0.75rem .25rem
overflow: hidden
max-width: 'calc(100% - %s * 2)' % 1rem
align-items: center
.img
line-height: 0
height: 16px
- width: 20px
- margin-right: 4px
+ width: 16px
+ border-radius: 16px
+ margin-right: 8px
background-repeat: no-repeat
background-size: contain
background-position: left center
- .desc
- opacity: .75
- line-height: 1.5
span
txt-ellipsis()
max-width: 100%
>.bottom
- margin: 0 1rem .75rem 1rem
+ margin: 0 0.75rem 0.75rem 0.75rem
.title
font-size: $fs-15
margin: 4px 0 8px 0
- >.right
- width: 2.5rem
- height: 2.5rem
- margin: .75rem
+
+.md .link-card
+ line-height: 1.2
+ .title
+ display: -webkit-box
+ -webkit-box-orient: vertical
overflow: hidden
+ -webkit-line-clamp: 2
+ .cap
flex-shrink: 0
- >.left
+ color: var(--text-p3)
+ .link
+ line-height: 1.5
+ opacity: .75
+ txt-ellipsis()
+ .desc
+ display: -webkit-box
+ -webkit-box-orient: vertical
overflow: hidden
- margin: .5rem 0 .5rem 1rem
- .title
- font-size: $fs-14
- margin: 1px 0 5px 0
- .desc
- flex-shrink: 0
- txt-ellipsis()
+ -webkit-line-clamp: 3
+ .img
+ border-radius: 4px
+
span
margin: 0
display: block
- span.title
+ .title
font-weight: 500
color: var(--text-p1)
- span.desc
- color: var(--text-p3)
diff --git a/source/js/main.js b/source/js/main.js
index b976efe..2895a2b 100644
--- a/source/js/main.js
+++ b/source/js/main.js
@@ -217,14 +217,20 @@ if (stellar.plugins.lazyload) {
if (stellar.plugins.stellar) {
for (let key of Object.keys(stellar.plugins.stellar)) {
let js = stellar.plugins.stellar[key];
- const els = document.getElementsByClassName('stellar-' + key + '-api');
- if (els != undefined && els.length > 0) {
- stellar.jQuery(() => {
- stellar.loadScript(js, { defer: true });
- if (key == 'timeline') {
- stellar.loadScript(stellar.plugins.marked);
- }
- })
+ if (key == 'linkcard') {
+ stellar.loadScript(js, { defer: true }).then(function () {
+ setCardLink(document.querySelectorAll('a.link-card'));
+ });
+ } else {
+ const els = document.getElementsByClassName('stellar-' + key + '-api');
+ if (els != undefined && els.length > 0) {
+ stellar.jQuery(() => {
+ stellar.loadScript(js, { defer: true });
+ if (key == 'timeline') {
+ stellar.loadScript(stellar.plugins.marked);
+ }
+ })
+ }
}
}
}
diff --git a/source/js/plugins/linkcard.js b/source/js/plugins/linkcard.js
new file mode 100644
index 0000000..6309954
--- /dev/null
+++ b/source/js/plugins/linkcard.js
@@ -0,0 +1,118 @@
+// 本插件由CardLink定制而成,原项目源码: https://github.com/Lete114/CardLink
+
+var cardLink = {};
+cardLink.caches = {};
+cardLink.server = 'https://api.allorigins.win/raw?url=';
+
+/**
+ * Remove '/' and '/index.html'
+ * @param {String} params
+ * @returns { String }
+ */
+function indexHandler(params) {
+ let path = params.replace(/(\/index\.html|\/)*$/gi, '')
+ if (path.length === 0) path += '/'
+ return path
+}
+
+/**
+ * Determine if it is a ['https://', 'http://', '//'] protocol
+ * @param {String} url Website url
+ * @returns {Boolean}
+ */
+function isHttp(url) {
+ return /^(https?:)?\/\//g.test(url)
+}
+
+function renderer(el, obj) {
+ el.querySelector('.title').innerHTML = obj.title;
+ if (obj.icon && obj.icon.length > 0) {
+ el.querySelector('.img').style = 'background-image: url("' + obj.icon + '");';
+ }
+ if (obj.desc && obj.desc.length > 0) {
+ el.querySelector('.desc').innerHTML = obj.desc;
+ }
+}
+
+/**
+ * Get info
+ * @param {Element} el Element
+ * @param {String} html String type html
+ * @param {String} link Website address
+ */
+// eslint-disable-next-line max-statements
+function getInfo(el, html, link) {
+ try {
+ let title, icon, desc
+ const doc = new DOMParser().parseFromString(html, 'text/html')
+ // If there is no title, no card link is generated
+ title = doc.querySelector('title')
+ if (title) {
+ title = title.textContent
+
+ // Get the src of the first img tag in the body tag
+ // icon = doc.querySelector('body img')
+ // icon = icon && icon.getAttribute('src')
+
+ if (/^data:image/.test(icon)) icon = ''
+
+ // If there is no src then get the site icon
+ if (!icon) {
+ const links = [].slice.call(doc.querySelectorAll('link[rel][href]'))
+ icon = links.find((_el) => _el.rel.includes('icon'))
+ icon = icon && icon.getAttribute('href')
+ }
+
+ desc = doc.querySelector('head meta[name="description"]')
+
+ if (desc) {
+ desc = desc.content;
+ }
+ // If `icon` is not the ['https://', 'http://', '//'] protocol, splice on the `origin` of the a tag
+ if (icon && !isHttp(icon)) icon = new URL(link).origin + icon
+ cardLink.caches[link] = { title, link, icon, desc }
+
+ renderer(el, cardLink.caches[link])
+ }
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.warn('CardLink Error: Failed to parse', error)
+ }
+}
+
+function fetchPage(link, callback) {
+ fetch(link)
+ .then((result) => result.text())
+ .then(callback)
+ .catch((error) => {
+ const server = cardLink.server
+ // eslint-disable-next-line no-console
+ if (link.includes(server) || !server) return console.error('CardLink Error:', error)
+ fetchPage(server + link, callback)
+ })
+}
+
+/**
+ * Create card links
+ * @param {NodeList} nodes A collection of nodes or a collection of arrays,
+ * if it is an array then the array must always contain node element
+ */
+function setCardLink(nodes) {
+ // If the `nodes` do not contain a `forEach` method, then the default `a[cardlink]` is used
+ nodes = 'forEach' in (nodes || {}) ? nodes : document.querySelectorAll('a[cardlink]')
+ nodes.forEach((el) => {
+ // If it is not a tag element then it is not processed
+ if (el.nodeType !== 1) return
+ el.removeAttribute('cardlink')
+ const link = el.href
+
+ const cache = cardLink.caches[link]
+ if (cache) return renderer(el, cache)
+
+ if (isHttp(link)) {
+ fetchPage(link, (html) => {
+ getInfo(el, html, link)
+ })
+ }
+ })
+}