Files
blog/themes/Ying/assets/js/modules/linkify.js
T

85 lines
3.1 KiB
JavaScript
Raw Normal View History

2026-06-02 12:44:47 +08:00
// linkify.js - Convert bare URLs to link cards, video URLs to players
(function() {
var URL_RE = /(https?:\/\/[^\s一-鿿 -〿＀-￯<>"')\]]+)/g;
var VIDEO_RE = /\.(mp4|webm|ogg|mov)(\?|$)/i;
var FAVICON_API = 'https://api.usj.cc/api/favicon?url=';
function getDomain(url) {
try { return new URL(url).hostname; } catch (e) { return url; }
}
function makeVideo(url) {
var video = document.createElement('video');
video.className = 'post-video';
video.controls = true;
video.preload = 'metadata';
video.playsInline = true;
var source = document.createElement('source');
source.src = url;
video.appendChild(source);
return video;
}
function makeCard(url) {
var domain = getDomain(url);
var card = document.createElement('a');
card.className = 'link-card';
card.href = '/tiaozhuan.html?target=' + btoa(url);
card.target = '_blank';
card.rel = 'noopener';
card.innerHTML = '<img class="link-card-icon" src="' + FAVICON_API + domain + '" alt="" loading="lazy" onerror="this.style.display=\'none\'">' +
'<span class="link-card-body"><span class="link-card-title">' + domain + '</span>' +
'<span class="link-card-url">' + url + '</span></span>' +
'<span class="link-card-arrow">↗</span>';
return card;
}
function linkify(root) {
var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
var textNodes = [];
while (walker.nextNode()) {
var node = walker.currentNode;
2026-06-02 15:37:49 +08:00
if (node.parentElement && node.parentElement.closest('a, .link-card, code, pre, video, #szh')) continue;
2026-06-02 12:44:47 +08:00
if (URL_RE.test(node.textContent)) textNodes.push(node);
}
textNodes.forEach(function(node) {
URL_RE.lastIndex = 0;
var text = node.textContent;
var parts = [];
var lastIdx = 0;
var match;
while ((match = URL_RE.exec(text)) !== null) {
if (match.index > lastIdx) {
parts.push(document.createTextNode(text.slice(lastIdx, match.index)));
}
if (VIDEO_RE.test(match[1])) {
parts.push(makeVideo(match[1]));
} else {
parts.push(makeCard(match[1]));
}
lastIdx = match.index + match[0].length;
}
if (parts.length === 0) return;
if (lastIdx < text.length) {
parts.push(document.createTextNode(text.slice(lastIdx)));
}
var frag = document.createDocumentFragment();
parts.forEach(function(p) { frag.appendChild(p); });
node.parentNode.replaceChild(frag, node);
});
}
function run() {
var content = document.querySelector('.post-content');
if (content) linkify(content);
}
2026-06-02 14:34:23 +08:00
window.initLinkify = run;
2026-06-02 12:44:47 +08:00
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', run);
} else {
run();
}
})();