typecho-theme-farallon/functions.php

783 lines
34 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
//主题设置
function themeConfig($form) {
$logoUrl = new Typecho_Widget_Helper_Form_Element_Text('logoUrl', NULL, NULL, _t('站点 LOGO 地址'));
$form->addInput($logoUrl);
$icoUrl = new Typecho_Widget_Helper_Form_Element_Text('icoUrl', NULL, NULL, _t('站点 Favicon 地址'));
$form->addInput($icoUrl);
$sticky = new Typecho_Widget_Helper_Form_Element_Text('sticky', NULL, NULL, _t('置顶文章cid'), _t('多篇文章以`|`符号隔开'), _t('会在首页展示置顶文章。'));
$form->addInput($sticky);
$travel = new Typecho_Widget_Helper_Form_Element_Text('travel', NULL, NULL, _t('travel分类 Mid'), _t('填写分类的mid'), _t('指定分类ID用于足迹分类展示'));
$form->addInput($travel);
$memos = new Typecho_Widget_Helper_Form_Element_Text('memos', NULL, NULL, _t('说说分类 Mid'), _t('填写分类的mid'), _t('指定分类ID用于说说分类展示'));
$form->addInput($memos);
$instagramurl = new Typecho_Widget_Helper_Form_Element_Text('instagramurl', NULL, NULL, _t('Instagram'), _t('会在个人信息显示'));
$form->addInput($instagramurl);
$telegramurl = new Typecho_Widget_Helper_Form_Element_Text('telegramurl', NULL, 'https://t.me/', _t('电报'), _t('会在个人信息显示'));
$form->addInput($telegramurl);
$githuburl = new Typecho_Widget_Helper_Form_Element_Text('githuburl', NULL, 'https://github.com/', _t('github'), _t('会在个人信息显示'));
$form->addInput($githuburl);
$twitterurl = new Typecho_Widget_Helper_Form_Element_Text('twitterurl', NULL, NULL, _t('twitter'), _t('会在个人信息显示'));
$form->addInput($twitterurl);
$mastodonurl = new Typecho_Widget_Helper_Form_Element_Text('mastodonurl', NULL, NULL, _t('mastodon'), _t('会在个人信息显示'));
$form->addInput($mastodonurl);
$friendlyTime = new Typecho_Widget_Helper_Form_Element_Radio('friendlyTime',
array('0' => _t('否'),
'1' => _t('是')),
'0', _t('是否显示友好时间'), _t('默认不显示友好时间,显示标准时间格式'));
$form->addInput($friendlyTime);
$showProfile = new Typecho_Widget_Helper_Form_Element_Radio('showProfile',
array('0'=> _t('否'), '1'=> _t('是')),
'0', _t('是否在文章页面显示作者信息'), _t('选择"是"将在文章页面包含显示作者信息。'));
$form->addInput($showProfile);
$showcate = new Typecho_Widget_Helper_Form_Element_Radio('showcate',
array('0'=> _t('否'), '1'=> _t('是')),
'0', _t('是否在文章页面显示文章分类'), _t('选择"是"将在文章页面显示文章的分类信息。'));
$form->addInput($showcate);
$showrelated = new Typecho_Widget_Helper_Form_Element_Radio('showrelated',
array('0'=> _t('否'), '1'=> _t('是')),
'0', _t('是否显示相关文章'), _t('选择"是"将在文章页面显示相关文章。'));
$form->addInput($showrelated);
$showshare = new Typecho_Widget_Helper_Form_Element_Radio('showshare',
array('0'=> _t('否'), '1'=> _t('是')),
'0', _t('是否显示复制链接'), _t('选择"是"将在文章页面显示复制链接。'));
$form->addInput($showshare);
$showtime = new Typecho_Widget_Helper_Form_Element_Radio('showtime',
array('0'=> _t('否'), '1'=> _t('是')),
'0', _t('是否显示页面加载时间'), _t('选择"是"将在页脚显示加载时间。'));
$form->addInput($showtime);
$loadmore = new Typecho_Widget_Helper_Form_Element_Radio('loadmore',
array('0'=> _t('加载更多'), '1'=> _t('页码模式')),
'0', _t('加载文章列表方式'), _t('加载更多将在文章列表底部显示加载更多按钮'));
$form->addInput($loadmore);
$sitemapurl = new Typecho_Widget_Helper_Form_Element_Text('sitemapurl', NULL, NULL, _t('sitemap'), _t('网站地图链接'));
$form->addInput($sitemapurl);
$cnavatar = new Typecho_Widget_Helper_Form_Element_Text('cnavatar', NULL, NULL , _t('Gravatar镜像'), _t('默认https://cravatar.cn/avatar/'));
$form->addInput($cnavatar);
$midimg = new Typecho_Widget_Helper_Form_Element_Text('midimg', NULL, '/img/', _t('填写分类图片路径,以"/"结尾'), _t('默认使用网站根目录下的img文件夹,也可以填写绝对或者CDN地址,自动匹配目录下以分类ID为文件名的mid.jpg格式的图片'));
$form->addInput($midimg);
$wxpay = new Typecho_Widget_Helper_Form_Element_Text('wxpay', NULL, 'https://blog.loliko.cn/images/wechatpay.png', _t('微信收款码'), _t('赞赏二维码'));
$form->addInput($wxpay);
$alipay= new Typecho_Widget_Helper_Form_Element_Text('alipay', NULL, 'https://blog.loliko.cn/images/alipay.png', _t('支付宝收款码'), _t('赞赏二维码'));
$form->addInput($alipay);
$addhead = new Typecho_Widget_Helper_Form_Element_Textarea('addhead', NULL, NULL, _t('Head内代码用于网站验证等'), _t('支持HTML'));
$form->addInput($addhead);
$tongji = new Typecho_Widget_Helper_Form_Element_Textarea('tongji', NULL, NULL, _t('统计代码'), _t('支持HTML'));
$form->addInput($tongji);
}
function saveThemeConfig($config) {
// 可以在这里添加额外的验证或处理逻辑
return $config;
}
// 自定义字段
function themeFields($layout) {
$summary= new Typecho_Widget_Helper_Form_Element_Textarea('summary', NULL, NULL, _t('文章摘要'), _t('自定义摘要'));
$layout->addItem($summary);
$cover= new Typecho_Widget_Helper_Form_Element_Text('cover', NULL, NULL, _t('文章封面'), _t('自定义文章封面'));
$layout->addItem($cover);
}
/*
* 文章浏览数统计
*/
function get_post_view($archive) {
$cid = $archive->cid;
$db = Typecho_Db::get();
$prefix = $db->getPrefix();
if (!array_key_exists('views', $db->fetchRow($db->select()->from('table.contents')))) {
$db->query('ALTER TABLE `' . $prefix . 'contents` ADD `views` INT(10) DEFAULT 0;');
echo 0;
return;
}
$row = $db->fetchRow($db->select('views')->from('table.contents')->where('cid = ?', $cid));
if ($archive->is('single')) {
$views = Typecho_Cookie::get('extend_contents_views');
if (empty($views)) {
$views = array();
} else {
$views = explode(',', $views);
}
if (!in_array($cid, $views)) {
$db->query($db->update('table.contents')->rows(array('views' => (int)$row['views'] + 1))->where('cid = ?', $cid));
array_push($views, $cid);
$views = implode(',', $views);
Typecho_Cookie::set('extend_contents_views', $views); //记录查看cookie
}
}
echo $row['views'];
}
/** 头像镜像 */
$options = Typecho_Widget::widget('Widget_Options');
$gravatarPrefix = empty($options->cnavatar) ? 'https://cravatar.cn/avatar/' : $options->cnavatar;
define('__TYPECHO_GRAVATAR_PREFIX__', $gravatarPrefix);
// 初始化主题
function init_theme() {
// 检查并创建封面图片目录
$coversDir = dirname(__FILE__) . '/assets/images/covers';
if (!is_dir($coversDir)) {
@mkdir($coversDir, 0755, true);
}
}
// 在主题加载时执行初始化
init_theme();
/**
* 页面加载时间
*/
function timer_start() {
global $timestart;
$mtime = explode( ' ', microtime() );
$timestart = $mtime[1] + $mtime[0];
return true;
}
timer_start();
function timer_stop( $display = 0, $precision = 3 ) {
global $timestart, $timeend;
$mtime = explode( ' ', microtime() );
$timeend = $mtime[1] + $mtime[0];
$timetotal = number_format( $timeend - $timestart, $precision );
$r = $timetotal < 1 ? $timetotal * 1000 . " ms" : $timetotal . " s";
if ( $display ) {
echo $r;
}
return $r;
}
/**
* 获取文章第一张图片
*/
function img_postthumb($cid) {
$db = Typecho_Db::get();
// 首先检查是否设置了自定义封面
$cover = $db->fetchRow($db->select('str_value')
->from('table.fields')
->where('cid = ?', $cid)
->where('name = ?', 'cover'));
// 如果找到自定义封面,直接返回
if ($cover && !empty($cover['str_value'])) {
return $cover['str_value'];
}
// 否则尝试从文章内容中获取第一张图片
$rs = $db->fetchRow($db->select('table.contents.text')
->from('table.contents')
->where('table.contents.cid=?', $cid)
->order('table.contents.cid', Typecho_Db::SORT_ASC)
->limit(1));
// 检查是否获取到结果
if (!$rs) {
return "";
}
preg_match_all("/https?:\/\/[^\s]*.(png|jpeg|jpg|gif|bmp|webp)/", $rs['text'], $thumbUrl); //通过正则式获取图片地址
// 检查是否匹配到图片URL
if (count($thumbUrl[0]) > 0) {
return $thumbUrl[0][0]; // 返回第一张图片的URL
} else {
return ""; // 没有匹配到图片URL返回空字符串
}
}
//回复加上@
function getPermalinkFromCoid($coid) {
$db = Typecho_Db::get();
$row = $db->fetchRow($db->select('author')->from('table.comments')->where('coid = ? AND status = ?', $coid, 'approved'));
if (empty($row)) return '';
return '<a href="#comment-'.$coid.'" style="text-decoration: none;">@'.$row['author'].'</a>';
}
/**
* 图片灯箱
*/
class ImageStructureProcessor {
public static function processContent($content, $widget, $lastResult = null) {
$content = empty($lastResult) ? $content : $lastResult;
if (empty($content) || !is_string($content)) {
return $content;
}
if ($widget instanceof Widget_Archive) {
try {
$dom = new DOMDocument('1.0', 'UTF-8');
libxml_use_internal_errors(true);
$content = '<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><div>' . $content . '</div></body></html>';
$dom->loadHTML($content,
LIBXML_HTML_NOIMPLIED |
LIBXML_HTML_NODEFDTD |
LIBXML_NOERROR |
LIBXML_NOWARNING
);
$xpath = new DOMXPath($dom);
// 查找所有没有父 figure 的图片,排除 SVG
$images = $xpath->query("//img[not(ancestor::figure) and not(contains(@src, '.svg'))]");
if ($images->length > 0) {
foreach ($images as $img) {
// 获取必要的属性
$src = $img->getAttribute('src');
$alt = $img->getAttribute('alt');
// 跳过没有 src 的图片或 SVG 格式的图片
if (empty($src) || stripos($src, '.svg') !== false) {
continue;
}
// 创建容器元素
$figure = $dom->createElement('figure');
$figure->setAttribute('class', 'grap--figure');
// 创建链接元素用于 lightbox
$link = $dom->createElement('a');
$link->setAttribute('href', $src);
$link->setAttribute('data-lightbox', 'image-set');
$link->setAttribute('data-title', $alt);
$link->setAttribute('class', 'no-style-link');
// 只有在有 alt 属性时才创建 figcaption
if (!empty($alt)) {
$caption = $dom->createElement('figcaption', $alt);
$caption->setAttribute('class', 'imageCaption');
}
// 重组 DOM 结构
if ($img->parentNode) {
$img->parentNode->replaceChild($figure, $img);
$link->appendChild($img);
$figure->appendChild($link);
if (isset($caption)) {
$figure->appendChild($caption);
}
}
}
}
// 获取处理后的内容
$content = $dom->saveHTML();
// 提取 body 部分的内容
$content = preg_replace('/^.*<body>(.*)<\/body>.*$/is', '$1', $content);
// 清理临时添加的 div 标签
$content = preg_replace('/^<div>|<\/div>$/i', '', $content);
// 清理 libxml 错误
libxml_clear_errors();
} catch (Exception $e) {
// 记录错误但返回原始内容
error_log('Image processing error: ' . $e->getMessage());
// 如果发生错误,返回上一个过滤器结果或原始内容
return empty($lastResult) ? $content : $lastResult;
}
}
return $content;
}
}
/**
* 处理图片为封面图裁剪为5:3最大宽度500px转换为webp
*
* @param string $imageUrl 原始图片URL
* @return string 处理后的图片URL
*/
function process_cover_image($imageUrl) {
// 检查GD库是否可用
if (!function_exists('imagecreatetruecolor')) {
return $imageUrl; // 如果GD库不可用返回原图
}
// 分析URL
$parsed = parse_url($imageUrl);
// 如果图片是外部链接,需要下载
$isExternalUrl = !empty($parsed['host']) && $parsed['host'] !== $_SERVER['HTTP_HOST'];
// 生成唯一的文件名使用MD5哈希
$filename = md5($imageUrl) . '.webp';
// 处理后图片的保存路径
$themeDir = dirname(__FILE__);
$savePath = $themeDir . '/assets/images/covers/' . $filename;
$webPath = Helper::options()->themeUrl . '/assets/images/covers/' . $filename;
// 如果缓存文件已存在,直接返回
if (file_exists($savePath)) {
return $webPath;
}
// 获取原始图片内容
if ($isExternalUrl) {
// 外部图片,需要下载
$imageContent = @file_get_contents($imageUrl);
if (!$imageContent) {
return $imageUrl; // 无法下载,返回原图
}
} else {
// 本地图片
$localPath = $_SERVER['DOCUMENT_ROOT'] . $parsed['path'];
if (!file_exists($localPath)) {
return $imageUrl; // 无法找到本地文件,返回原图
}
$imageContent = @file_get_contents($localPath);
}
// 创建图像资源
$originalImage = @imagecreatefromstring($imageContent);
if (!$originalImage) {
return $imageUrl; // 无法创建图像资源,返回原图
}
// 获取原始图片尺寸
$originalWidth = imagesx($originalImage);
$originalHeight = imagesy($originalImage);
// 计算目标尺寸5:3比例最大宽度500px
$targetWidth = min(500, $originalWidth);
$targetHeight = intval($targetWidth * 3 / 5);
// 计算裁剪坐标(居中裁剪)
$cropX = 0;
$cropY = 0;
$cropWidth = $originalWidth;
$cropHeight = $originalHeight;
// 计算比例
$originalRatio = $originalWidth / $originalHeight;
$targetRatio = 5 / 3;
if ($originalRatio > $targetRatio) {
// 原图过宽,需要裁剪宽度
$cropWidth = intval($originalHeight * $targetRatio);
$cropX = intval(($originalWidth - $cropWidth) / 2);
} else {
// 原图过高,需要裁剪高度
$cropHeight = intval($originalWidth / $targetRatio);
$cropY = intval(($originalHeight - $cropHeight) / 2);
}
// 创建目标图像
$targetImage = imagecreatetruecolor($targetWidth, $targetHeight);
// 裁剪并调整大小
imagecopyresampled(
$targetImage,
$originalImage,
0, 0,
$cropX, $cropY,
$targetWidth, $targetHeight,
$cropWidth, $cropHeight
);
// 保存为webp格式
if (!function_exists('imagewebp')) {
// 如果不支持webp保存为png
$filename = md5($imageUrl) . '.png';
$savePath = $themeDir . '/assets/images/covers/' . $filename;
$webPath = Helper::options()->themeUrl . '/assets/images/covers/' . $filename;
imagepng($targetImage, $savePath, 9); // 9是最高压缩质量
} else {
// 保存为webp
imagewebp($targetImage, $savePath, 80); // 80是质量参数
}
// 释放资源
imagedestroy($originalImage);
imagedestroy($targetImage);
return $webPath;
}
/**
* *获取文章卡片
* **
*
*/
function get_article_summary($post) {
// 首先尝试从自定义字段获取摘要
$db = Typecho_Db::get();
// 查询自定义字段表
$row = $db->fetchRow($db->select()
->from('table.fields')
->where('cid = ?', $post['cid'])
->where('name = ?', 'summary'));
// 如果找到自定义摘要字段
if ($row && !empty($row['str_value'])) {
return $row['str_value'];
}
// 如果没有自定义摘要,截取文章内容
// 去除HTML标签
$text = strip_tags($post['text']);
// 截取指定长度的摘要
return Typecho_Common::subStr($text, 0, 100, '...');
}
// 在原函数中使用
function get_article_info($atts) {
$default_atts = array(
'id' => '',
);
$atts = array_merge($default_atts, $atts);
$db = Typecho_Db::get();
// 根据 ID获取文章
if (!empty($atts['id'])) {
$post = $db->fetchRow($db->select()->from('table.contents')
->where('cid = ?', $atts['id'])
->limit(1));
} else {
return '请提供文章ID';
}
if (!$post) {
return '未找到文章';
}
// 将文章数据推送到抽象内容小部件中
$post = Typecho_Widget::widget('Widget_Abstract_Contents')->push($post);
// 获取摘要
$summary = get_article_summary($post);
// 获取缩略图
$default_thumbnail = Helper::options()->themeUrl . '/assets/images/nopic.svg';
$imageToDisplay = img_postthumb($post['cid']);
if (empty($imageToDisplay)) {
$imageToDisplay = $default_thumbnail;
} else {
// 处理封面图片
$imageToDisplay = process_cover_image($imageToDisplay);
}
// 构建输出
$output = '<div class="graph--mixtapeEmbed">';
$output .= '<a class="mixtapeContent" href="' . $post['permalink'] . '" target="_blank">';
$output .= '<span class="markup--strong markup--mixtapeEmbed-strong">' . htmlspecialchars($post['title']) . '</span>';
$output .= '<em class="markup--em markup--mixtapeEmbed-em">' . htmlspecialchars($summary) . '</em>';
$output .= '</a>';
$output .= '<a class="mixtapeImage" href="' . $post['permalink'] . '" target="_blank" style="background-image:url(' . htmlspecialchars($imageToDisplay) . ')"></a>';
$output .= '</div>';
return $output;
}
// 创建一个新的类来处理内容过滤
class ContentFilter
{
public static function filterContent($content, $widget, $lastResult)
{
// 首先运行之前的过滤器结果
$content = empty($lastResult) ? $content : $lastResult;
// 处理图片灯箱
$content = ImageStructureProcessor::processContent($content, $widget, $content);
// 然后处理我们的文章短代码
$content = preg_replace_callback('/\[article\s+([^\]]+)\]/', function($matches) {
$atts = self::parse_atts($matches[1]);
return get_article_info($atts);
}, $content);
return $content;
}
// 解析短代码属性
private static function parse_atts($text) {
$atts = array();
$pattern = '/(\w+)\s*=\s*"([^"]*)"(?:\s|$)|(\w+)\s*=\s*\'([^\']*)\'(?:\s|$)|(\w+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/';
$text = preg_replace("/[\x{00a0}\x{200b}]+/u", " ", $text);
if (preg_match_all($pattern, $text, $match, PREG_SET_ORDER)) {
foreach ($match as $m) {
if (!empty($m[1]))
$atts[strtolower($m[1])] = stripcslashes($m[2]);
elseif (!empty($m[3]))
$atts[strtolower($m[3])] = stripcslashes($m[4]);
elseif (!empty($m[5]))
$atts[strtolower($m[5])] = stripcslashes($m[6]);
elseif (isset($m[7]) && strlen($m[7]))
$atts[] = stripcslashes($m[7]);
elseif (isset($m[8]))
$atts[] = stripcslashes($m[8]);
}
}
return $atts;
}
}
// 注册钩子
Typecho_Plugin::factory('Widget_Abstract_Contents')->contentEx = array('ContentFilter', 'filterContent');
// 编辑器按钮类
class EditorButton {
public static function render()
{
echo <<<EOF
<script>
$(document).ready(function() {
$('#wmd-button-row').append('<li class="wmd-button" id="wmd-article-button" title="插入文章引用"><span style="background: none;"><svg t="1687164718203" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4158" width="20" height="20"><path d="M810.666667 213.333333H213.333333c-46.933333 0-85.333333 38.4-85.333333 85.333334v426.666666c0 46.933333 38.4 85.333333 85.333333 85.333334h597.333334c46.933333 0 85.333333-38.4 85.333333-85.333334V298.666667c0-46.933333-38.4-85.333333-85.333333-85.333334z m0 512H213.333333V298.666667h597.333334v426.666666z" p-id="4159"></path><path d="M298.666667 384h426.666666v85.333333H298.666667zM298.666667 554.666667h426.666666v85.333333H298.666667z" p-id="4160"></path></svg></span></li>');
$('#wmd-article-button').click(function() {
var articleId = prompt("请输入要引用的文章ID");
if (articleId) {
var text = "[article id=\"" + articleId + "\"]";
var textarea = $('#text')[0];
var start = textarea.selectionStart;
var end = textarea.selectionEnd;
var value = textarea.value;
textarea.value = value.substring(0, start) + text + value.substring(end);
// 将光标移动到插入的文本之后
textarea.setSelectionRange(start + text.length, start + text.length);
textarea.focus();
// 触发change事件确保编辑器更新
$('#text').trigger('change');
}
});
});
</script>
EOF;
}
}
// 注册编辑器按钮钩子
// 避免重复注册,在最后执行
if (!Typecho_Plugin::exists('Widget_Abstract_Contents', 'editor')) {
Typecho_Plugin::factory('admin/write-post.php')->bottom = array('EditorButton', 'render');
Typecho_Plugin::factory('admin/write-page.php')->bottom = array('EditorButton', 'render');
}
/**
* 评论者认证等级 + 身份
*
* @author Chrison
* @access public
* @param str $email 评论者邮址
* @return result
*/
function commentApprove($widget, $email = NULL)
{
$result = array(
"state" => -1,//状态
"isAuthor" => 0,//是否是博主
"userLevel" => '',//用户身份或等级名称
"userDesc" => '',//用户title描述
"bgColor" => '',//用户身份或等级背景色
"commentNum" => 0//评论数量
);
if (empty($email)) return $result;
$result['state'] = 1;
if ($widget->authorId == $widget->ownerId) {
$result['isAuthor'] = 1;
$result['userLevel'] = '作者';
$result['userDesc'] = '博主';
$result['bgColor'] = '#FFD700';
$result['commentNum'] = 999;
} else {
$db = Typecho_Db::get();
$commentNumSql = $db->fetchAll($db->select(array('COUNT(cid)'=>'commentNum'))
->from('table.comments')
->where('mail = ?', $email));
$commentNum = $commentNumSql[0]['commentNum'];
//$linkSql = $db->fetchAll($db->select()->from('table.links')
// ->where('user = ?',$email));
if($commentNum==1){
$result['userLevel'] = '初识';
$result['bgColor'] = '#999999';
$userDesc = '初来乍到的新朋友';
} else {
if ($commentNum<3 && $commentNum>1) {
$result['userLevel'] = '初识';
$result['bgColor'] = '#999999';
}elseif ($commentNum<9 && $commentNum>=3) {
$result['userLevel'] = '朋友';
$result['bgColor'] = '#A0DAD0';
}elseif ($commentNum<27 && $commentNum>=9) {
$result['userLevel'] = '好友';
$result['bgColor'] = '#FF8C00';
}elseif ($commentNum<81 && $commentNum>=27) {
$result['userLevel'] = '挚友';
$result['bgColor'] = '#FF0000';
}elseif ($commentNum<100 && $commentNum>=81) {
$result['userLevel'] = '兄弟';
$result['bgColor'] = '#006400';
}elseif ($commentNum>=100) {
$result['userLevel'] = '老铁';
$result['bgColor'] = '#A0DAD0';
}
$userDesc = '已有'.$commentNum.'条评论';
}
// if($linkSql){
// $result['userLevel'] = '博友';
// $result['bgColor'] = '#21b9bb';
// $userDesc = '🔗'.$linkSql[0]['description'].'&#10;✌️'.$userDesc;
// }
$result['userDesc'] = $userDesc;
$result['commentNum'] = $commentNum;
}
return $result;
}
/**
* Typecho后台附件增强图片预览、批量插入、保留官方删除按钮与逻辑
* @author 老孙博客
* @date 2025-04-25
*/
Typecho_Plugin::factory('admin/write-post.php')->bottom = array('AttachmentHelper', 'addEnhancedFeatures');
Typecho_Plugin::factory('admin/write-page.php')->bottom = array('AttachmentHelper', 'addEnhancedFeatures');
class AttachmentHelper {
public static function addEnhancedFeatures() {
?>
<style>
#file-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:15px;padding:15px;list-style:none;margin:0;}
#file-list li{position:relative;border:1px solid #e0e0e0;border-radius:4px;padding:10px;background:#fff;transition:all 0.3s ease;list-style:none;margin:0;}
#file-list li:hover{box-shadow:0 2px 8px rgba(0,0,0,0.1);}
#file-list li.loading{opacity:0.7;pointer-events:none;}
.att-enhanced-thumb{position:relative;width:100%;height:150px;margin-bottom:8px;background:#f5f5f5;overflow:hidden;border-radius:3px;display:flex;align-items:center;justify-content:center;}
.att-enhanced-thumb img{width:100%;height:100%;object-fit:contain;display:block;}
.att-enhanced-thumb .file-icon{display:flex;align-items:center;justify-content:center;width:100%;height:100%;font-size:40px;color:#999;}
.att-enhanced-finfo{padding:5px 0;}
.att-enhanced-fname{font-size:13px;margin-bottom:5px;word-break:break-all;color:#333;}
.att-enhanced-fsize{font-size:12px;color:#999;}
.att-enhanced-factions{display:flex;justify-content:space-between;align-items:center;margin-top:8px;gap:8px;}
.att-enhanced-factions button{flex:1;padding:4px 8px;border:none;border-radius:3px;background:#e0e0e0;color:#333;cursor:pointer;font-size:12px;transition:all 0.2s ease;}
.att-enhanced-factions button:hover{background:#d0d0d0;}
.att-enhanced-factions .btn-insert{background:#467B96;color:white;}
.att-enhanced-factions .btn-insert:hover{background:#3c6a81;}
.att-enhanced-checkbox{position:absolute;top:5px;right:5px;z-index:2;width:18px;height:18px;cursor:pointer;}
.batch-actions{margin:15px;display:flex;gap:10px;align-items:center;}
.btn-batch{padding:8px 15px;border-radius:4px;border:none;cursor:pointer;transition:all 0.3s ease;font-size:10px;display:inline-flex;align-items:center;justify-content:center;}
.btn-batch.primary{background:#467B96;color:white;}
.btn-batch.primary:hover{background:#3c6a81;}
.btn-batch.secondary{background:#e0e0e0;color:#333;}
.btn-batch.secondary:hover{background:#d0d0d0;}
.upload-progress{position:absolute;bottom:0;left:0;width:100%;height:2px;background:#467B96;transition:width 0.3s ease;}
</style>
<script>
$(document).ready(function() {
// 批量操作UI按钮
var $batchActions = $('<div class="batch-actions"></div>')
.append('<button type="button" class="btn-batch primary" id="batch-insert">批量插入</button>')
.append('<button type="button" class="btn-batch secondary" id="select-all">全选</button>')
.append('<button type="button" class="btn-batch secondary" id="unselect-all">取消全选</button>');
$('#file-list').before($batchActions);
// 插入格式
Typecho.insertFileToEditor = function(title, url, isImage) {
var textarea = $('#text'),
sel = textarea.getSelection(),
insertContent = isImage ? '![' + title + '](' + url + ')' :
'[' + title + '](' + url + ')';
textarea.replaceSelection(insertContent + '\n');
textarea.focus();
};
// 批量插入
$('#batch-insert').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
var content = '';
$('#file-list li').each(function() {
if ($(this).find('.att-enhanced-checkbox').is(':checked')) {
var $li = $(this);
var title = $li.find('.att-enhanced-fname').text();
var url = $li.data('url');
var isImage = $li.data('image') == 1;
content += isImage ? '![' + title + '](' + url + ')\n' : '[' + title + '](' + url + ')\n';
}
});
if (content) {
var textarea = $('#text');
var pos = textarea.getSelection();
var newContent = textarea.val();
newContent = newContent.substring(0, pos.start) + content + newContent.substring(pos.end);
textarea.val(newContent);
textarea.focus();
}
});
$('#select-all').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
$('#file-list .att-enhanced-checkbox').prop('checked', true);
return false;
});
$('#unselect-all').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
$('#file-list .att-enhanced-checkbox').prop('checked', false);
return false;
});
// 防止复选框冒泡
$(document).on('click', '.att-enhanced-checkbox', function(e) {e.stopPropagation();});
// 增强文件列表样式但不破坏li原结构和官方按钮
function enhanceFileList() {
$('#file-list li').each(function() {
var $li = $(this);
if ($li.hasClass('att-enhanced')) return;
$li.addClass('att-enhanced');
// 只增强不清空li
// 增加批量选择框
if ($li.find('.att-enhanced-checkbox').length === 0) {
$li.prepend('<input type="checkbox" class="att-enhanced-checkbox" />');
}
// 增加图片预览(如已有则不重复加)
if ($li.find('.att-enhanced-thumb').length === 0) {
var url = $li.data('url');
var isImage = $li.data('image') == 1;
var fileName = $li.find('.insert').text();
var $thumbContainer = $('<div class="att-enhanced-thumb"></div>');
if (isImage) {
var $img = $('<img src="' + url + '" alt="' + fileName + '" />');
$img.on('error', function() {
$(this).replaceWith('<div class="file-icon">🖼️</div>');
});
$thumbContainer.append($img);
} else {
$thumbContainer.append('<div class="file-icon">📄</div>');
}
// 插到插入按钮之前
$li.find('.insert').before($thumbContainer);
}
});
}
// 插入按钮事件
$(document).on('click', '.btn-insert', function(e) {
e.preventDefault();
e.stopPropagation();
var $li = $(this).closest('li');
var title = $li.find('.att-enhanced-fname').text();
Typecho.insertFileToEditor(title, $li.data('url'), $li.data('image') == 1);
});
// 上传完成后增强新项
var originalUploadComplete = Typecho.uploadComplete;
Typecho.uploadComplete = function(attachment) {
setTimeout(function() {
enhanceFileList();
}, 200);
if (typeof originalUploadComplete === 'function') {
originalUploadComplete(attachment);
}
};
// 首次增强
enhanceFileList();
});
</script>
<?php
}
}
?>
<?php
/**
* 友好时间显示函数
* @param int $time 时间戳
* @param int $threshold 阈值超过此值则显示标准日期格式Y-m-d
* @return string
*/
function time_ago($time, $threshold = 31536000) { // 31536000秒 = 1年
$now = time();
$difference = $now - $time;
// 如果时间差超过阈值默认1年则返回标准日期格式不带时间
if ($difference >= $threshold) {
return date('Y-m-d', $time);
}
// 1年以内的时间返回友好格式如 "3天前"
$periods = array("秒", "分钟", "小时", "天", "周", "个月", "年");
$lengths = array("60", "60", "24", "7", "4.35", "12");
for ($j = 0; $difference >= $lengths[$j] && $j < count($lengths); $j++) {
$difference /= $lengths[$j];
}
$difference = round($difference);
return $difference . $periods[$j] . "前";
}