Files
ImageCaptionWatermark/Plugin.php
2026-02-23 17:28:10 +08:00

1244 lines
51 KiB
PHP
Raw 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
/**
* 文章挂图片标题、水印
*
* @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>';
}
}