This commit is contained in:
xaoxuu 2022-10-25 22:41:56 +08:00
parent c1183fd0c1
commit 05564d4bbc
6 changed files with 186 additions and 46 deletions

View File

@ -250,7 +250,7 @@ plugins:
friends: /js/plugins/friends.js
ghinfo: /js/plugins/ghinfo.js
timeline: /js/plugins/timeline.js
linkcard: /js/plugins/linkcard.js
marked: https://cdn.bootcdn.net/ajax/libs/marked/4.0.18/marked.min.js

View File

@ -2,7 +2,7 @@
* link.js v1.1 | https://github.com/xaoxuu/hexo-theme-stellar/
* 格式与官方标签插件一致使用空格分隔中括号内的是可选参数中括号不需要写出来
*
* {% link url title [description] [icon:src] %}
* {% link url [title] [desc:true/false] [icon:src] %}
*/
'use strict';
@ -11,11 +11,11 @@ var util = require('hexo-util');
const full_url_for = require('hexo-util').full_url_for.bind(hexo);
hexo.extend.tag.register('link', function(args) {
args = hexo.args.map(args, ['icon'], ['url', 'title', 'description']);
args = hexo.args.map(args, ['icon', 'desc'], ['url', 'title']);
var el = '';
el += '<div class="tag-plugin link dis-select">';
el += '<a class="link-card' + (args.description ? ' rich' : ' plain') + '" title="' + args.title + '" href="' + args.url + '"';
el += '<a class="link-card' + (args.desc ? ' rich' : ' plain') + '" title="' + args.title + '" href="' + args.url + '"';
if (args.url.includes('://')) {
el += ' target="_blank" rel="external nofollow noopener noreferrer"';
}
@ -31,17 +31,19 @@ hexo.extend.tag.register('link', function(args) {
return el;
}
function loadTitle() {
return '<span class="title">' + args.title + '</span>';
return '<span class="title">' + (args.title || args.url) + '</span>';
}
function loadDesc() {
return '<span class="desc fs12">' + (args.description || full_url_for(args.url)) + '</span>';
return '<span class="cap desc fs12"></span>';
}
function loadLink() {
return '<span class="cap link fs12">' + full_url_for(args.url) + '</span>';
}
if (args.description) {
if (args.desc) {
// top
el += '<div class="top">';
el += loadIcon();
el += '<span class="desc fs12">' + full_url_for(args.url) + '</span>';
el += loadIcon() + loadLink();
el += '</div>';
// bottom
el += '<div class="bottom">';
@ -50,7 +52,7 @@ hexo.extend.tag.register('link', function(args) {
} else {
// left
el += '<div class="left">';
el += loadTitle() + loadDesc();
el += loadTitle() + loadLink();
el += '</div>';
// right
el += '<div class="right">';

View File

@ -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

View File

@ -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
overflow: hidden
flex-shrink: 0
>.left
overflow: hidden
margin: .5rem 0 .5rem 1rem
.md .link-card
line-height: 1.2
.title
font-size: $fs-14
margin: 1px 0 5px 0
.desc
display: -webkit-box
-webkit-box-orient: vertical
overflow: hidden
-webkit-line-clamp: 2
.cap
flex-shrink: 0
color: var(--text-p3)
.link
line-height: 1.5
opacity: .75
txt-ellipsis()
.desc
display: -webkit-box
-webkit-box-orient: vertical
overflow: hidden
-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)

View File

@ -217,6 +217,11 @@ if (stellar.plugins.lazyload) {
if (stellar.plugins.stellar) {
for (let key of Object.keys(stellar.plugins.stellar)) {
let js = stellar.plugins.stellar[key];
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(() => {
@ -228,6 +233,7 @@ if (stellar.plugins.stellar) {
}
}
}
}
// swiper
if (stellar.plugins.swiper) {

View File

@ -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)
})
}
})
}