文章页优化
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
export const POSTS_PER_PAGE = 16;
|
||||
|
||||
export type PageSlice<T> = {
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
items: T[];
|
||||
};
|
||||
|
||||
export function getPageSlice<T>(items: T[], currentPage: number, perPage = POSTS_PER_PAGE): PageSlice<T> {
|
||||
const totalPages = Math.max(1, Math.ceil(items.length / perPage));
|
||||
const safePage = Math.min(Math.max(currentPage, 1), totalPages);
|
||||
const start = (safePage - 1) * perPage;
|
||||
|
||||
return {
|
||||
currentPage: safePage,
|
||||
totalPages,
|
||||
items: items.slice(start, start + perPage),
|
||||
};
|
||||
}
|
||||
|
||||
export function getPaginationPaths<T>(items: T[], perPage = POSTS_PER_PAGE): number[] {
|
||||
const totalPages = Math.ceil(items.length / perPage);
|
||||
|
||||
return Array.from({ length: Math.max(0, totalPages - 1) }, (_, index) => index + 2);
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
const linkTagPattern = /\{%\s*link\s+([\s\S]*?)\s*%\}/g;
|
||||
const markdownLinkPattern = /^\[[^\]]*]\(([^)\s]+)(?:\s+["'][^"']*["'])?\)$/;
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function splitLinkArgs(value) {
|
||||
const parts = [];
|
||||
let current = '';
|
||||
let bracketDepth = 0;
|
||||
let parenDepth = 0;
|
||||
|
||||
for (const char of value) {
|
||||
if (char === '[') bracketDepth += 1;
|
||||
if (char === ']' && bracketDepth > 0) bracketDepth -= 1;
|
||||
if (char === '(') parenDepth += 1;
|
||||
if (char === ')' && parenDepth > 0) parenDepth -= 1;
|
||||
|
||||
if (char === ',' && bracketDepth === 0 && parenDepth === 0 && parts.length < 2) {
|
||||
parts.push(current.trim());
|
||||
current = '';
|
||||
} else {
|
||||
current += char;
|
||||
}
|
||||
}
|
||||
|
||||
parts.push(current.trim());
|
||||
return parts;
|
||||
}
|
||||
|
||||
function normalizeUrl(value) {
|
||||
const trimmed = value.trim();
|
||||
const markdownLink = trimmed.match(markdownLinkPattern);
|
||||
return markdownLink ? markdownLink[1] : trimmed;
|
||||
}
|
||||
|
||||
function getHostname(url) {
|
||||
try {
|
||||
return new URL(url).hostname.replace(/^www\./, '');
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function renderLinkCard(rawValue) {
|
||||
const [title, source, rawUrl] = splitLinkArgs(rawValue);
|
||||
const url = normalizeUrl(rawUrl ?? '');
|
||||
|
||||
if (!title || !url) return `{% link ${rawValue} %}`;
|
||||
|
||||
const host = getHostname(url);
|
||||
const label = source?.split('@')[0]?.trim() || host || 'Link';
|
||||
const initial = label.slice(0, 1).toUpperCase();
|
||||
|
||||
return `<a class="hexo-link-card" href="${escapeHtml(url)}" target="_blank" rel="external nofollow noopener noreferrer">
|
||||
<span class="hexo-link-card__icon" aria-hidden="true">${escapeHtml(initial)}</span>
|
||||
<span class="hexo-link-card__body">
|
||||
<strong>${escapeHtml(title)}</strong>
|
||||
<span>${escapeHtml(label)}${host ? ` · ${escapeHtml(host)}` : ''}</span>
|
||||
</span>
|
||||
</a>`;
|
||||
}
|
||||
|
||||
function transformText(value) {
|
||||
let matched = false;
|
||||
const transformed = value.replace(linkTagPattern, (_, rawValue) => {
|
||||
matched = true;
|
||||
return renderLinkCard(rawValue);
|
||||
});
|
||||
|
||||
return matched ? transformed : value;
|
||||
}
|
||||
|
||||
function inlineToSource(node) {
|
||||
if (node.type === 'text' || node.type === 'inlineCode') return node.value ?? '';
|
||||
|
||||
if (node.type === 'link') {
|
||||
const text = Array.isArray(node.children) ? node.children.map(inlineToSource).join('') : node.url;
|
||||
return `[${text}](${node.url})`;
|
||||
}
|
||||
|
||||
if (Array.isArray(node.children)) return node.children.map(inlineToSource).join('');
|
||||
return '';
|
||||
}
|
||||
|
||||
function visit(node) {
|
||||
if (!node || typeof node !== 'object') return;
|
||||
|
||||
if (node.type === 'paragraph' && Array.isArray(node.children)) {
|
||||
const source = node.children.map(inlineToSource).join('');
|
||||
const transformed = transformText(source);
|
||||
|
||||
if (transformed !== source) {
|
||||
node.type = 'html';
|
||||
node.value = transformed;
|
||||
delete node.children;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (node.type === 'text' && typeof node.value === 'string') {
|
||||
const transformed = transformText(node.value);
|
||||
|
||||
if (transformed !== node.value) {
|
||||
node.type = 'html';
|
||||
node.value = transformed;
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(node.children)) {
|
||||
for (const child of node.children) visit(child);
|
||||
}
|
||||
}
|
||||
|
||||
export default function remarkHexoLinkCards() {
|
||||
return (tree) => {
|
||||
visit(tree);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user