Files

1244 lines
51 KiB
PHP
Raw Permalink Normal View History

2026-02-23 17:28:10 +08:00
<?php
/**
* 文章挂图片标题、水印
*
* @package ImageCaptionWatermark
* @author 石头厝
* @version 3.5
* @link https://www.shitoucuo.com/
*/
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
class ImageCaptionWatermark_Plugin implements Typecho_Plugin_Interface
{
/**
* 激活插件
*/
public static function activate()
{
Typecho_Plugin::factory('Widget_Abstract_Contents')->contentEx = array('ImageCaptionWatermark_Plugin', 'parseContent');
Typecho_Plugin::factory('Widget_Abstract_Contents')->excerptEx = array('ImageCaptionWatermark_Plugin', 'parseContent');
Typecho_Plugin::factory('Widget_Archive')->footer = array('ImageCaptionWatermark_Plugin', 'addFooter');
return '插件已激活,请到设置页面配置参数。';
}
/**
* 禁用插件
*/
public static function deactivate()
{
return '插件已禁用';
}
/**
* 插件配置面板
*/
public static function config(Typecho_Widget_Helper_Form $form)
{
// 标题设置部分
$titleEnable = new Typecho_Widget_Helper_Form_Element_Radio(
'titleEnable',
array('1' => '开启', '0' => '关闭'),
'1',
'<strong>启用图片标题</strong>',
'是否自动为文章中的图片添加标题'
);
$form->addInput($titleEnable);
$titlePosition = new Typecho_Widget_Helper_Form_Element_Select(
'titlePosition',
array(
'bottom-center' => '底部居中',
'bottom-right' => '右下角',
'bottom-left' => '左下角',
'top-center' => '顶部居中',
'top-right' => '右上角',
'top-left' => '左上角'
),
'bottom-center',
'标题显示位置',
'标题在图片上的显示位置'
);
$form->addInput($titlePosition);
$titleMinWidth = new Typecho_Widget_Helper_Form_Element_Text(
'titleMinWidth',
NULL,
'300',
'标题最小图片宽度',
'页面显示宽度大于此值(像素)的图片才显示标题'
);
$titleMinWidth->input->setAttribute('style', 'width: 120px;');
$form->addInput($titleMinWidth->addRule('isInteger', '请输入整数'));
// 新增:标题最小高度
$titleMinHeight = new Typecho_Widget_Helper_Form_Element_Text(
'titleMinHeight',
NULL,
'300',
'标题最小图片高度',
'页面显示高度大于此值(像素)的图片才显示标题'
);
$titleMinHeight->input->setAttribute('style', 'width: 120px;');
$form->addInput($titleMinHeight->addRule('isInteger', '请输入整数'));
$titleChineseOnly = new Typecho_Widget_Helper_Form_Element_Radio(
'titleChineseOnly',
array('1' => '开启', '0' => '关闭'),
'1',
'仅显示中文标题',
'开启如果图片alt/title中没有中文则不显示标题<br>关闭:显示所有标题'
);
$form->addInput($titleChineseOnly);
$titleColor = new Typecho_Widget_Helper_Form_Element_Text(
'titleColor',
NULL,
'#FFFFFF',
'标题文字颜色',
'标题文字的颜色'
);
$titleColor->input->setAttribute('style', 'width: 150px;');
$form->addInput($titleColor);
$titleBgColor = new Typecho_Widget_Helper_Form_Element_Text(
'titleBgColor',
NULL,
'rgba(0,0,0,0.85)',
'标题背景颜色',
'标题背景的颜色'
);
$titleBgColor->input->setAttribute('style', 'width: 150px;');
$form->addInput($titleBgColor);
// 新增:标题背景透明度
$titleBgOpacity = new Typecho_Widget_Helper_Form_Element_Text(
'titleBgOpacity',
NULL,
'0.85',
'标题背景透明度',
'0-1之间1为不透明'
);
$titleBgOpacity->input->setAttribute('style', 'width: 100px;');
$titleBgOpacity->input->setAttribute('placeholder', '0.0-1.0');
$form->addInput($titleBgOpacity);
// 修改:标题显示分类
$titleIncludeCategories = new Typecho_Widget_Helper_Form_Element_Text(
'titleIncludeCategories',
NULL,
'',
'标题显示分类',
'仅在此分类下的文章显示图片标题,留空表示所有分类都显示<br>多个分类用逗号间隔,如:技术,生活'
);
$titleIncludeCategories->input->setAttribute('style', 'width: 300px;');
$form->addInput($titleIncludeCategories);
$titleFontSize = new Typecho_Widget_Helper_Form_Element_Text(
'titleFontSize',
NULL,
'14',
'标题字体大小',
'标题文字的大小(像素)'
);
$titleFontSize->input->setAttribute('style', 'width: 120px;');
$form->addInput($titleFontSize->addRule('isInteger', '请输入整数'));
$titlePadding = new Typecho_Widget_Helper_Form_Element_Text(
'titlePadding',
NULL,
'8px 15px',
'标题内边距',
'标题的内边距,格式:上下 左右'
);
$titlePadding->input->setAttribute('style', 'width: 150px;');
$form->addInput($titlePadding);
// 新增:是否跳过横排组内的图片标题
$skipFlexGroupTitles = new Typecho_Widget_Helper_Form_Element_Radio(
'skipFlexGroupTitles',
array('1' => '跳过', '0' => '不跳过'),
'1',
'跳过横排组内的图片标题',
'开启在横排图片组flex-group中不显示单张图片的标题只显示组标题<br>关闭:同时显示组标题和单图标题'
);
$form->addInput($skipFlexGroupTitles);
// 水印设置部分
$watermarkEnable = new Typecho_Widget_Helper_Form_Element_Radio(
'watermarkEnable',
array('1' => '开启', '0' => '关闭'),
'1',
'<strong>启用图片水印</strong>',
'是否自动为文章中的图片添加水印'
);
$form->addInput($watermarkEnable);
$watermarkText = new Typecho_Widget_Helper_Form_Element_Text(
'watermarkText',
NULL,
'SHITOUCUO.COM',
'水印文字内容',
'水印文字的内容'
);
$watermarkText->input->setAttribute('style', 'width: 300px;');
$form->addInput($watermarkText);
$watermarkPosition = new Typecho_Widget_Helper_Form_Element_Select(
'watermarkPosition',
array(
'top-left' => '左上角',
'top-center' => '顶部居中',
'top-right' => '右上角',
'center' => '正中央',
'bottom-left' => '左下角',
'bottom-center' => '底部居中',
'bottom-right' => '右下角'
),
'top-center',
'水印显示位置',
'避免与标题重叠,建议使用顶部位置'
);
$form->addInput($watermarkPosition);
$watermarkMinWidth = new Typecho_Widget_Helper_Form_Element_Text(
'watermarkMinWidth',
NULL,
'200',
'水印最小图片宽度',
'页面显示宽度大于此值(像素)的图片才添加水印'
);
$watermarkMinWidth->input->setAttribute('style', 'width: 120px;');
$form->addInput($watermarkMinWidth->addRule('isInteger', '请输入整数'));
// 新增:水印最小高度
$watermarkMinHeight = new Typecho_Widget_Helper_Form_Element_Text(
'watermarkMinHeight',
NULL,
'500',
'水印最小图片高度',
'页面显示高度大于此值(像素)的图片才添加水印'
);
$watermarkMinHeight->input->setAttribute('style', 'width: 120px;');
$form->addInput($watermarkMinHeight->addRule('isInteger', '请输入整数'));
// 修改:水印显示分类
$watermarkIncludeCategories = new Typecho_Widget_Helper_Form_Element_Text(
'watermarkIncludeCategories',
NULL,
'',
'水印显示分类',
'仅在此分类下的文章显示图片水印,留空表示所有分类都显示<br>多个分类用逗号间隔,如:技术,生活'
);
$watermarkIncludeCategories->input->setAttribute('style', 'width: 300px;');
$form->addInput($watermarkIncludeCategories);
$watermarkOpacity = new Typecho_Widget_Helper_Form_Element_Text(
'watermarkOpacity',
NULL,
'0.7',
'水印透明度',
'0-1之间1为不透明'
);
$watermarkOpacity->input->setAttribute('style', 'width: 100px;');
$watermarkOpacity->input->setAttribute('placeholder', '0.0-1.0');
$form->addInput($watermarkOpacity);
$watermarkFontSize = new Typecho_Widget_Helper_Form_Element_Text(
'watermarkFontSize',
NULL,
'24',
'水印字体大小',
'文字水印的字体大小(像素)'
);
$watermarkFontSize->input->setAttribute('style', 'width: 100px;');
$form->addInput($watermarkFontSize->addRule('isInteger', '请输入整数'));
$watermarkFontColor = new Typecho_Widget_Helper_Form_Element_Text(
'watermarkFontColor',
NULL,
'rgba(255,255,255,0.9)',
'水印文字颜色',
'文字水印的颜色'
);
$watermarkFontColor->input->setAttribute('style', 'width: 150px;');
$form->addInput($watermarkFontColor);
// 性能设置部分
$batchSize = new Typecho_Widget_Helper_Form_Element_Text(
'batchSize',
NULL,
'10',
'批量处理数量',
'每次同时处理的图片数量,减少一次性处理压力'
);
$batchSize->input->setAttribute('style', 'width: 120px;');
$form->addInput($batchSize->addRule('isInteger', '请输入整数'));
$processDelay = new Typecho_Widget_Helper_Form_Element_Text(
'processDelay',
NULL,
'100',
'处理延迟(ms)',
'图片加载后延迟处理的时间(毫秒)'
);
$processDelay->input->setAttribute('style', 'width: 120px;');
$form->addInput($processDelay->addRule('isInteger', '请输入整数'));
$debugMode = new Typecho_Widget_Helper_Form_Element_Radio(
'debugMode',
array('1' => '开启', '0' => '关闭'),
'0',
'调试模式',
'开启后在控制台显示处理信息(调试完成后建议关闭)'
);
$form->addInput($debugMode);
}
/**
* 个人用户配置面板
*/
public static function personalConfig(Typecho_Widget_Helper_Form $form)
{
// 不需要个人配置
}
/**
* 处理文章内容
*/
public static function parseContent($content, $widget, $lastResult)
{
$content = empty($lastResult) ? $content : $lastResult;
if ($widget instanceof Widget_Archive) {
$content = self::parseImages($content, $widget);
}
return $content;
}
/**
* 解析图片并添加标题和水印
*/
private static function parseImages($text, $widget)
{
$options = Helper::options();
$config = $options->plugin('ImageCaptionWatermark');
if (!$config) {
return $text;
}
// 获取当前文章分类
$currentCategories = array();
if ($widget->categories && is_array($widget->categories)) {
foreach ($widget->categories as $category) {
if (isset($category['name'])) {
$currentCategories[] = $category['name'];
}
}
}
// 处理显示分类设置
$titleIncludeCategories = array();
if (!empty($config->titleIncludeCategories) && trim($config->titleIncludeCategories) !== '') {
$titleIncludeCategories = array_map('trim', explode(',', $config->titleIncludeCategories));
}
$watermarkIncludeCategories = array();
if (!empty($config->watermarkIncludeCategories) && trim($config->watermarkIncludeCategories) !== '') {
$watermarkIncludeCategories = array_map('trim', explode(',', $config->watermarkIncludeCategories));
}
// 检查当前分类是否在显示列表中
$isTitleIncluded = true; // 默认显示,除非设置了显示分类且当前分类不在其中
$isWatermarkIncluded = true; // 默认显示,除非设置了显示分类且当前分类不在其中
// 如果设置了显示分类,则检查当前分类是否在其中
if (!empty($titleIncludeCategories)) {
$isTitleIncluded = false;
foreach ($currentCategories as $category) {
if (in_array($category, $titleIncludeCategories)) {
$isTitleIncluded = true;
break;
}
}
}
if (!empty($watermarkIncludeCategories)) {
$isWatermarkIncluded = false;
foreach ($currentCategories as $category) {
if (in_array($category, $watermarkIncludeCategories)) {
$isWatermarkIncluded = true;
break;
}
}
}
// 匹配图片标签
$text = preg_replace_callback('/<img\s[^>]*>/i', function($matches) use ($config, $isTitleIncluded, $isWatermarkIncluded) {
$imgTag = $matches[0];
// 跳过特定图片
if (preg_match('/class=["\'][^"\']*(no-watermark|no-caption)[^"\']*["\']/i', $imgTag)) {
return $imgTag;
}
// 检查图片是否在横排组中(通过父元素判断)
$skipTitle = false;
if ($config->skipFlexGroupTitles == '1') {
// 检查img标签周围是否有flexphoto-group的迹象
if (strpos($imgTag, 'no-title') !== false ||
preg_match('/class=["\'][^"\']*no-title[^"\']*["\']/i', $imgTag)) {
$skipTitle = true;
}
}
$newTag = $imgTag;
// 获取图片URL
$imgUrl = '';
if (preg_match('/src=["\']([^"\']+)["\']/', $imgTag, $srcMatch)) {
$imgUrl = $srcMatch[1];
}
// 获取标题从alt或title属性
$caption = '';
$hasChinese = false;
if (!$skipTitle) { // 只有需要显示标题时才处理
if (preg_match('/alt=["\']([^"\']*)["\']/', $imgTag, $altMatch)) {
$caption = $altMatch[1];
$hasChinese = self::hasChineseText($caption);
} elseif (preg_match('/title=["\']([^"\']*)["\']/', $imgTag, $titleMatch)) {
$caption = $titleMatch[1];
$hasChinese = self::hasChineseText($caption);
}
}
// 如果开启了仅显示中文标题,且没有中文,则清空标题
if ($config->titleChineseOnly == '1' && !$hasChinese) {
$caption = '';
} else {
// 提取中文部分
$caption = self::extractChineseText($caption);
}
// 添加水印数据属性(如果当前分类在显示列表中)
$watermarkData = '';
if ($isWatermarkIncluded && $config->watermarkEnable == '1' && !empty($config->watermarkText)) {
$watermarkClass = 'icw-watermark';
$watermarkData = ' data-watermark="' . htmlspecialchars($config->watermarkText) . '"';
$watermarkData .= ' data-watermark-position="' . htmlspecialchars($config->watermarkPosition) . '"';
$watermarkData .= ' data-watermark-minwidth="' . intval($config->watermarkMinWidth) . '"';
$watermarkData .= ' data-watermark-minheight="' . intval($config->watermarkMinHeight) . '"';
$newTag = preg_replace('/<img\s/', '<img class="' . $watermarkClass . '"' . $watermarkData, $newTag, 1);
}
// 为所有需要处理的图片添加包装器
if ((!$skipTitle && $isTitleIncluded && $config->titleEnable == '1' && !empty($caption) && !empty($imgUrl)) ||
($isWatermarkIncluded && $config->watermarkEnable == '1' && !empty($imgUrl))) {
$wrapperClass = 'icw-wrapper';
// 添加标题数据(如果当前分类在显示列表中且需要显示标题)
$titleData = '';
if (!$skipTitle && $isTitleIncluded && $config->titleEnable == '1' && !empty($caption)) {
$titleData .= ' data-caption="' . htmlspecialchars($caption) . '"';
$titleData .= ' data-position="' . htmlspecialchars($config->titlePosition) . '"';
$titleData .= ' data-minwidth="' . intval($config->titleMinWidth) . '"';
$titleData .= ' data-minheight="' . intval($config->titleMinHeight) . '"';
$titleData .= ' data-chinese-only="' . $config->titleChineseOnly . '"';
if ($isWatermarkIncluded && $config->watermarkEnable == '1') {
$wrapperClass .= ' icw-has-both';
} else {
$wrapperClass .= ' icw-title-only';
}
} elseif ($isWatermarkIncluded && $config->watermarkEnable == '1') {
$wrapperClass .= ' icw-watermark-only';
}
// 如果是横排组内的图片,添加特殊标记
if ($skipTitle) {
$wrapperClass .= ' icw-in-flexgroup';
}
$newTag = '<div class="' . $wrapperClass . '"' . $titleData . '>
' . $newTag . '
</div>';
}
return $newTag;
}, $text);
return $text;
}
/**
* 检查是否包含中文字符
*/
private static function hasChineseText($text)
{
return preg_match('/[\x{4e00}-\x{9fa5}]/u', $text);
}
/**
* 提取中文字符
*/
private static function extractChineseText($text)
{
preg_match_all('/[\x{4e00}-\x{9fa5}\x{3000}-\x{303F}\x{FF00}-\x{FFEF}]+/u', $text, $matches);
if (!empty($matches[0])) {
return trim(implode('', $matches[0]));
}
return $text;
}
/**
* 添加页脚脚本和样式(前台)
*/
public static function addFooter()
{
$options = Helper::options();
$config = $options->plugin('ImageCaptionWatermark');
if (!$config) {
return;
}
$debugMode = $config->debugMode == '1' ? 'true' : 'false';
$batchSize = intval($config->batchSize) ?: 10;
$processDelay = intval($config->processDelay) ?: 100;
$skipFlexGroupTitles = $config->skipFlexGroupTitles == '1' ? 'true' : 'false';
// 输出CSS样式
echo '<style id="icw-styles">
/* ============================================ */
/* 图片标题和水印插件 - 双功能同步版 v3.5 */
/* ============================================ */
/* 1. 图片容器 - 确保不干扰现有布局 */
.icw-wrapper {
position: relative !important;
display: inline-block !important;
max-width: 100% !important;
margin: 15px 0 !important;
line-height: 0 !important;
overflow: visible !important;
vertical-align: middle !important;
box-sizing: border-box !important;
}
/* 保持与flexphoto兼容 */
.flexphoto .icw-wrapper {
margin: 0 !important;
flex: 1 !important;
}
/* 横排组内的图片容器特殊处理 */
.icw-wrapper.icw-in-flexgroup {
margin: 0 !important;
}
/* 2. 图片样式 */
.icw-wrapper img {
display: block !important;
max-width: 100% !important;
height: auto !important;
width: 100% !important;
vertical-align: middle !important;
border-radius: 10px !important;
}
/* flexphoto中的图片特殊处理 */
.flexphoto .icw-wrapper img {
height: 100% !important;
width: 100% !important;
object-fit: cover !important;
border-radius: 0 !important;
}
/* 3. 标题样式 */
.icw-caption {
position: absolute !important;
z-index: 100 !important;
box-sizing: border-box !important;
max-width: 100% !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
white-space: nowrap !important;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
font-weight: 500 !important;
line-height: 1.4 !important;
pointer-events: none !important;
opacity: 1 !important;
visibility: visible !important;
display: block !important;
color: ' . $config->titleColor . ' !important;
background-color: ' . $config->titleBgColor . ' !important;
font-size: ' . $config->titleFontSize . 'px !important;
padding: ' . $config->titlePadding . ' !important;
border-radius: 0 !important;
box-shadow: none !important;
opacity: 1 !important;
}
/* 标题背景透明度 - 新增功能 */
.icw-caption {
background-color: ' . str_replace(')', ', ' . floatval($config->titleBgOpacity) . ')',
str_replace('rgba(', 'rgba(', $config->titleBgColor)) . ' !important;
}
/* 如果背景色不是rgba格式转换为rgba格式并添加透明度 */
.icw-caption:not([style*="rgba"]) {
background-color: rgba(' . (hexdec(substr($config->titleBgColor, 1, 2)) ?: 0) . ', ' .
(hexdec(substr($config->titleBgColor, 3, 2)) ?: 0) . ', ' .
(hexdec(substr($config->titleBgColor, 5, 2)) ?: 0) . ', ' .
floatval($config->titleBgOpacity) . ') !important;
}
/* 标题位置 */
.caption-bottom-center {
bottom: 0 !important;
left: 0 !important;
right: 0 !important;
width: 100% !important;
text-align: center !important;
}
.caption-bottom-right {
bottom: 0 !important;
right: 0 !important;
}
.caption-bottom-left {
bottom: 0 !important;
left: 0 !important;
}
.caption-top-center {
top: 0 !important;
left: 0 !important;
right: 0 !important;
width: 100% !important;
text-align: center !important;
}
.caption-top-right {
top: 0 !important;
right: 0 !important;
}
.caption-top-left {
top: 0 !important;
left: 0 !important;
}
/* 4. 水印样式 */
.icw-watermark-element {
position: absolute !important;
z-index: 99 !important;
color: ' . $config->watermarkFontColor . ' !important;
font-size: ' . $config->watermarkFontSize . 'px !important;
font-weight: bold !important;
opacity: ' . $config->watermarkOpacity . ' !important;
text-shadow: 1px 1px 3px rgba(0,0,0,0.5) !important;
pointer-events: none !important;
font-family: Arial, sans-serif !important;
white-space: nowrap !important;
}
/* 水印位置 */
.icw-watermark-element.watermark-top-left {
top: 25px !important;
left: 25px !important;
}
.icw-watermark-element.watermark-top-center {
top: 25px !important;
left: 50% !important;
transform: translateX(-50%) !important;
text-align: center !important;
}
.icw-watermark-element.watermark-top-right {
top: 25px !important;
right: 25px !important;
text-align: right !important;
}
.icw-watermark-element.watermark-center {
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
text-align: center !important;
}
.icw-watermark-element.watermark-bottom-left {
bottom: 45px !important;
left: 25px !important;
}
.icw-watermark-element.watermark-bottom-center {
bottom: 45px !important;
left: 50% !important;
transform: translateX(-50%) !important;
text-align: center !important;
}
.icw-watermark-element.watermark-bottom-right {
bottom: 45px !important;
right: 25px !important;
text-align: right !important;
}
/* 5. 横排组特殊处理 */
.flexphoto-group .icw-caption {
display: none !important;
}
/* 6. 夜间模式适配 */
body.dark .icw-caption {
color: #faf8f1 !important;
background-color: rgba(10, 12, 25, 0.9) !important;
}
body.dark .icw-watermark-element {
color: rgba(255, 255, 255, 0.8) !important;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.7) !important;
}
/* 7. 移动端适配 */
@media screen and (max-width: 767px) {
.icw-caption {
font-size: ' . (intval($config->titleFontSize) - 2) . 'px !important;
padding: 6px 10px !important;
}
.icw-watermark-element {
font-size: ' . (intval($config->watermarkFontSize) - 6) . 'px !important;
}
.icw-watermark-element.watermark-top-left,
.icw-watermark-element.watermark-bottom-left {
left: 15px !important;
}
.icw-watermark-element.watermark-top-right,
.icw-watermark-element.watermark-bottom-right {
right: 15px !important;
}
.icw-watermark-element.watermark-bottom-left,
.icw-watermark-element.watermark-bottom-center,
.icw-watermark-element.watermark-bottom-right {
bottom: 35px !important;
}
}
/* 8. 覆盖主题可能冲突的样式 */
.markdown-body .icw-wrapper img {
max-width: 100% !important;
margin: 0 auto !important;
box-sizing: content-box !important;
background-color: transparent !important;
}
body.dark .markdown-body .icw-wrapper img {
background-color: inherit !important;
}
</style>';
// 输出JavaScript - 修改以支持跳过横排组标题
echo '<script id="icw-script">
(function() {
"use strict";
var ICW = {
// 配置参数
config: {
debug: ' . $debugMode . ',
titleEnable: ' . ($config->titleEnable == '1' ? 'true' : 'false') . ',
watermarkEnable: ' . ($config->watermarkEnable == '1' ? 'true' : 'false') . ',
skipFlexGroupTitles: ' . $skipFlexGroupTitles . ',
titleMinWidth: ' . intval($config->titleMinWidth) . ',
titleMinHeight: ' . intval($config->titleMinHeight) . ',
watermarkMinWidth: ' . intval($config->watermarkMinWidth) . ',
watermarkMinHeight: ' . intval($config->watermarkMinHeight) . ',
titleChineseOnly: ' . ($config->titleChineseOnly == '1' ? 'true' : 'false') . ',
watermarkPosition: "' . htmlspecialchars($config->watermarkPosition) . '",
watermarkFontSize: "' . intval($config->watermarkFontSize) . '",
watermarkFontColor: "' . htmlspecialchars($config->watermarkFontColor) . '",
watermarkOpacity: "' . floatval($config->watermarkOpacity) . '",
batchSize: ' . $batchSize . ',
processDelay: ' . $processDelay . '
},
// 状态管理
state: {
processedWrappers: new Set(),
isProcessing: false,
processingQueue: []
},
// 日志函数
log: function() {
if (this.config.debug) {
console.log("[ICW]", ...arguments);
}
},
// 检查是否包含中文
hasChineseText: function(text) {
if (!text) return false;
var chineseRegex = /[\\u4e00-\\u9fa5]/;
return chineseRegex.test(text);
},
// 获取包装器唯一标识
getWrapperId: function(wrapper) {
var img = wrapper.querySelector("img");
if (img && img.src) {
return img.src;
}
return wrapper.outerHTML;
},
// 检查是否在横排组中
isInFlexGroup: function(wrapper) {
// 检查是否在flexphoto-group中
var parentFlexGroup = wrapper.closest(".flexphoto-group");
if (parentFlexGroup) {
return true;
}
// 检查是否有特定类名
if (wrapper.classList.contains("icw-in-flexgroup")) {
return true;
}
// 检查图片是否有no-title类
var img = wrapper.querySelector("img");
if (img && (img.classList.contains("no-title") ||
img.getAttribute("class") && img.getAttribute("class").includes("no-title"))) {
return true;
}
return false;
},
// 初始化插件
init: function() {
this.log("插件初始化开始");
this.log("标题功能:", this.config.titleEnable ? "开启" : "关闭");
this.log("水印功能:", this.config.watermarkEnable ? "开启" : "关闭");
this.log("跳过横排组标题:", this.config.skipFlexGroupTitles ? "" : "");
this.log("标题最小尺寸:", this.config.titleMinWidth + "×" + this.config.titleMinHeight + "px");
this.log("水印最小尺寸:", this.config.watermarkMinWidth + "×" + this.config.watermarkMinHeight + "px");
// 启动统一处理
setTimeout(this.startUnifiedProcessing.bind(this), 300);
},
// ============ 统一处理逻辑 ============
startUnifiedProcessing: function() {
this.log("开始统一处理");
// 收集所有需要处理的包装器
var wrappers = document.querySelectorAll(".icw-wrapper");
this.log("找到需要处理的包装器:", wrappers.length);
if (wrappers.length === 0) {
this.log("没有需要处理的图片");
return;
}
// 初始化处理队列
this.state.processingQueue = Array.from(wrappers);
// 开始处理队列
this.processUnifiedQueue();
},
// 处理统一队列
processUnifiedQueue: function() {
if (this.state.isProcessing || this.state.processingQueue.length === 0) {
return;
}
this.state.isProcessing = true;
var batchSize = Math.min(this.config.batchSize, this.state.processingQueue.length);
var batch = this.state.processingQueue.splice(0, batchSize);
var processedInBatch = 0;
this.log("统一批次处理: 本次处理 " + batch.length + " 个包装器");
// 处理批次中的每个包装器
var processNextWrapper = function(index) {
if (index >= batch.length) {
// 批次处理完成
ICW.state.isProcessing = false;
ICW.log("批次处理完成,已处理 " + processedInBatch + " 个包装器");
// 如果队列中还有包装器,继续处理
if (ICW.state.processingQueue.length > 0) {
setTimeout(function() {
ICW.processUnifiedQueue();
}, 200);
} else {
ICW.log("所有处理完成!");
// 最终检查
setTimeout(ICW.finalCheck.bind(ICW), 1000);
}
return;
}
var wrapper = batch[index];
var wrapperId = ICW.getWrapperId(wrapper);
// 检查是否已处理过
if (ICW.state.processedWrappers.has(wrapperId)) {
processNextWrapper(index + 1);
return;
}
ICW.processSingleWrapper(wrapper).then(function(success) {
if (success) {
ICW.state.processedWrappers.add(wrapperId);
processedInBatch++;
}
// 处理下一个包装器
setTimeout(function() {
processNextWrapper(index + 1);
}, 50);
}).catch(function(error) {
ICW.log("包装器处理失败:", error, wrapper);
setTimeout(function() {
processNextWrapper(index + 1);
}, 50);
});
};
// 开始处理批次
processNextWrapper(0);
},
// 处理单个包装器(标题和水印一起处理)
processSingleWrapper: function(wrapper) {
return new Promise(function(resolve) {
var img = wrapper.querySelector("img");
if (!img) {
resolve(false);
return;
}
ICW.waitForImageReady(img).then(function() {
var displayWidth = wrapper.offsetWidth || img.offsetWidth || img.width;
var displayHeight = wrapper.offsetHeight || img.offsetHeight || img.height;
// 检查是否在横排组中且需要跳过标题
var isInFlexGroup = ICW.isInFlexGroup(wrapper);
var skipTitle = ICW.config.skipFlexGroupTitles && isInFlexGroup;
// 处理标题
var titleSuccess = false;
if (!skipTitle) {
titleSuccess = ICW.processTitleForWrapper(wrapper, displayWidth, displayHeight);
} else {
ICW.log("跳过横排组内的图片标题:", img.src);
}
// 处理水印
var watermarkSuccess = ICW.processWatermarkForWrapper(wrapper, displayWidth, displayHeight);
resolve(titleSuccess || watermarkSuccess);
}).catch(function(error) {
ICW.log("图片加载失败,跳过处理:", img.src, error);
resolve(false);
});
});
},
// 为包装器处理标题
processTitleForWrapper: function(wrapper, displayWidth, displayHeight) {
if (!ICW.config.titleEnable) {
return false;
}
var caption = wrapper.getAttribute("data-caption");
if (!caption || caption.trim() === "") {
return false;
}
var position = wrapper.getAttribute("data-position") || "bottom-center";
var minWidth = parseInt(wrapper.getAttribute("data-minwidth")) || ICW.config.titleMinWidth;
var minHeight = parseInt(wrapper.getAttribute("data-minheight")) || ICW.config.titleMinHeight;
var chineseOnly = wrapper.getAttribute("data-chinese-only") === "1";
// 检查是否仅显示中文标题
if (chineseOnly && !ICW.hasChineseText(caption)) {
return false;
}
// 检查图片是否足够大(需要同时满足宽度和高度限制)
if ((displayWidth < minWidth && minWidth > 0) ||
(displayHeight < minHeight && minHeight > 0) ||
displayWidth === 0 || displayHeight === 0) {
ICW.log("图片太小,跳过标题:", caption.substring(0, 30) + "...",
"实际尺寸:", displayWidth + "×" + displayHeight + "px",
"要求尺寸:", minWidth + "×" + minHeight + "px");
return false;
}
// 移除旧的标题(如果有)
var oldCaption = wrapper.querySelector(".icw-caption");
if (oldCaption) {
oldCaption.remove();
}
// 创建新标题
var captionEl = document.createElement("div");
captionEl.className = "icw-caption caption-" + position;
captionEl.textContent = caption;
captionEl.style.zIndex = "100";
wrapper.appendChild(captionEl);
ICW.log("标题添加成功:", caption.substring(0, 30) + "...",
"图片尺寸:", displayWidth + "×" + displayHeight + "px");
return true;
},
// 为包装器处理水印
processWatermarkForWrapper: function(wrapper, displayWidth, displayHeight) {
if (!ICW.config.watermarkEnable) {
return false;
}
var img = wrapper.querySelector("img.icw-watermark");
if (!img) {
return false;
}
var watermarkText = img.getAttribute("data-watermark");
var watermarkPosition = img.getAttribute("data-watermark-position") || ICW.config.watermarkPosition;
var watermarkMinWidth = parseInt(img.getAttribute("data-watermark-minwidth")) || ICW.config.watermarkMinWidth;
var watermarkMinHeight = parseInt(img.getAttribute("data-watermark-minheight")) || ICW.config.watermarkMinHeight;
if (!watermarkText) {
return false;
}
// 检查图片是否足够大(需要同时满足宽度和高度限制)
if ((displayWidth < watermarkMinWidth && watermarkMinWidth > 0) ||
(displayHeight < watermarkMinHeight && watermarkMinHeight > 0) ||
displayWidth === 0 || displayHeight === 0) {
ICW.log("图片太小,跳过水印:", img.src.substring(0, 50) + "...",
"实际尺寸:", displayWidth + "×" + displayHeight + "px",
"要求尺寸:", watermarkMinWidth + "×" + watermarkMinHeight + "px");
return false;
}
// 移除旧的水印(如果有)
var existingWatermark = wrapper.querySelector(".icw-watermark-element");
if (existingWatermark) {
existingWatermark.remove();
}
// 创建水印元素
var watermarkEl = document.createElement("div");
watermarkEl.className = "icw-watermark-element watermark-" + watermarkPosition.replace("-", "-");
watermarkEl.textContent = watermarkText;
// 应用水印样式
watermarkEl.style.color = ICW.config.watermarkFontColor;
watermarkEl.style.fontSize = ICW.config.watermarkFontSize + "px";
watermarkEl.style.opacity = ICW.config.watermarkOpacity;
wrapper.appendChild(watermarkEl);
ICW.log("水印添加成功:", watermarkText, "位置:", watermarkPosition,
"图片:", img.src.substring(0, 50) + "...",
"图片尺寸:", displayWidth + "×" + displayHeight + "px");
return true;
},
// 最终检查
finalCheck: function() {
this.log("进行最终检查");
// 检查所有包装器
var allWrappers = document.querySelectorAll(".icw-wrapper");
var missedWrappers = [];
for (var i = 0; i < allWrappers.length; i++) {
var wrapper = allWrappers[i];
var wrapperId = this.getWrapperId(wrapper);
if (!this.state.processedWrappers.has(wrapperId)) {
missedWrappers.push(wrapper);
}
}
if (missedWrappers.length > 0) {
this.log("发现遗漏的包装器:", missedWrappers.length);
this.state.processingQueue = missedWrappers;
this.processUnifiedQueue();
}
},
// 等待图片准备就绪
waitForImageReady: function(img) {
return new Promise(function(resolve, reject) {
// 如果图片已经加载完成
if (img.complete && img.naturalWidth > 0) {
setTimeout(resolve, ICW.config.processDelay);
return;
}
// 监听加载事件
var loadHandler = function() {
img.removeEventListener("load", loadHandler);
img.removeEventListener("error", errorHandler);
setTimeout(resolve, ICW.config.processDelay);
};
var errorHandler = function() {
img.removeEventListener("load", loadHandler);
img.removeEventListener("error", errorHandler);
reject(new Error("图片加载失败"));
};
img.addEventListener("load", loadHandler);
img.addEventListener("error", errorHandler);
// 如果图片已经有尺寸,也认为已加载
if (img.width > 0 || img.height > 0) {
setTimeout(resolve, ICW.config.processDelay);
return;
}
// 设置超时5秒
setTimeout(function() {
img.removeEventListener("load", loadHandler);
img.removeEventListener("error", errorHandler);
resolve();
}, 5000);
});
},
// 处理窗口大小变化
handleResize: function() {
if (this.resizeTimeout) {
clearTimeout(this.resizeTimeout);
}
this.resizeTimeout = setTimeout(function() {
ICW.log("窗口大小变化,重新处理");
// 移除所有标题和水印
var titles = document.querySelectorAll(".icw-caption");
var watermarks = document.querySelectorAll(".icw-watermark-element");
titles.forEach(function(title) {
title.remove();
});
watermarks.forEach(function(watermark) {
watermark.remove();
});
// 重置状态
ICW.state.processedWrappers.clear();
ICW.state.processingQueue = [];
// 重新处理
setTimeout(function() {
ICW.startUnifiedProcessing();
}, 300);
}, 500);
},
// 处理动态加载的内容
handleDynamicContent: function() {
this.log("检测到动态内容");
setTimeout(function() {
var newWrappers = document.querySelectorAll(".icw-wrapper:not([data-processed])");
if (newWrappers.length > 0) {
ICW.log("发现新的包装器:", newWrappers.length);
// 添加到队列
ICW.state.processingQueue.push(...Array.from(newWrappers));
// 如果没有正在处理,开始处理
if (!ICW.state.isProcessing) {
ICW.processUnifiedQueue();
}
}
}, 500);
}
};
// 初始化插件
function initPlugin() {
setTimeout(function() {
ICW.init();
}, 300);
}
// DOM加载完成后初始化
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initPlugin);
} else {
initPlugin();
}
// 监听窗口大小变化
window.addEventListener("resize", function() {
ICW.handleResize();
});
// 监听DOM变化
var observer = new MutationObserver(function(mutations) {
var hasNewContent = false;
mutations.forEach(function(mutation) {
if (mutation.addedNodes.length) {
mutation.addedNodes.forEach(function(node) {
if (node.nodeType === 1) {
if (node.tagName === "IMG" || node.tagName === "DIV" ||
(node.querySelector && node.querySelector("img"))) {
hasNewContent = true;
}
}
});
}
});
if (hasNewContent) {
ICW.handleDynamicContent();
}
});
// 开始观察DOM变化
setTimeout(function() {
observer.observe(document.body, {
childList: true,
subtree: true
});
}, 1000);
// 导出到全局以便调试
window.ICW = ICW;
})();
</script>';
}
}