872 lines
32 KiB
PHP
872 lines
32 KiB
PHP
<?php
|
||
/**
|
||
* 编辑器+短代码行内自定义标签
|
||
*
|
||
* @package CustomInlineTags
|
||
* @author 石头厝
|
||
* @version 1.0.0
|
||
* @link https://www.shitoucuo.com
|
||
*/
|
||
class CustomInlineTags_Plugin implements Typecho_Plugin_Interface
|
||
{
|
||
// 存储所有标签的静态变量
|
||
private static $_allTags = array();
|
||
|
||
/**
|
||
* 激活插件
|
||
*/
|
||
public static function activate()
|
||
{
|
||
// 添加文章保存时的钩子
|
||
Typecho_Plugin::factory('Widget_Contents_Post_Edit')->write = array('CustomInlineTags_Plugin', 'parseTagsOnSave');
|
||
Typecho_Plugin::factory('Widget_Contents_Page_Edit')->write = array('CustomInlineTags_Plugin', 'parseTagsOnSave');
|
||
|
||
// 添加内容解析钩子
|
||
Typecho_Plugin::factory('Widget_Abstract_Contents')->contentEx = array('CustomInlineTags_Plugin', 'parseContent');
|
||
Typecho_Plugin::factory('Widget_Abstract_Contents')->excerptEx = array('CustomInlineTags_Plugin', 'parseContent');
|
||
|
||
// 添加编辑器脚本 - 关键:使用header钩子添加脚本
|
||
Typecho_Plugin::factory('admin/header.php')->header = array('CustomInlineTags_Plugin', 'addHeader');
|
||
Typecho_Plugin::factory('admin/write-post.php')->bottom = array('CustomInlineTags_Plugin', 'addEditorButton');
|
||
Typecho_Plugin::factory('admin/write-page.php')->bottom = array('CustomInlineTags_Plugin', 'addEditorButton');
|
||
|
||
return _t('插件已激活');
|
||
}
|
||
|
||
/**
|
||
* 禁用插件
|
||
*/
|
||
public static function deactivate()
|
||
{
|
||
return _t('插件已禁用');
|
||
}
|
||
|
||
/**
|
||
* 插件配置面板
|
||
*/
|
||
public static function config(Typecho_Widget_Helper_Form $form)
|
||
{
|
||
// 标签样式设置
|
||
$style = new Typecho_Widget_Helper_Form_Element_Textarea('tagStyle',
|
||
NULL,
|
||
"display: inline-block;\nbackground: #f0f0f0;\ncolor: #333;\npadding: 2px 8px;\nmargin: 0 4px;\nborder-radius: 12px;\nfont-size: 0.9em;\nfont-weight: normal;\ntext-decoration: none;",
|
||
_t('标签样式'),
|
||
_t('自定义标签的CSS样式'));
|
||
$form->addInput($style);
|
||
|
||
// 标签前缀
|
||
$prefix = new Typecho_Widget_Helper_Form_Element_Text('tagPrefix',
|
||
NULL,
|
||
'#',
|
||
_t('标签前缀'),
|
||
_t('在标签前显示的前缀符号'));
|
||
$form->addInput($prefix);
|
||
|
||
// 是否启用标签链接
|
||
$enableLink = new Typecho_Widget_Helper_Form_Element_Radio('enableLink',
|
||
array(
|
||
'1' => _t('启用'),
|
||
'0' => _t('禁用')
|
||
),
|
||
'1',
|
||
_t('启用标签链接'),
|
||
_t('点击标签是否跳转到标签页面'));
|
||
$form->addInput($enableLink);
|
||
|
||
// 伪静态标签链接格式
|
||
$tagUrlFormat = new Typecho_Widget_Helper_Form_Element_Text('tagUrlFormat',
|
||
NULL,
|
||
'/tag-{slug}.html',
|
||
_t('标签链接格式'),
|
||
_t('根据你的伪静态设置填写标签链接格式,{slug}会被替换为标签slug'));
|
||
$form->addInput($tagUrlFormat);
|
||
|
||
// 是否新窗口打开
|
||
$openNewWindow = new Typecho_Widget_Helper_Form_Element_Radio('openNewWindow',
|
||
array(
|
||
'1' => _t('是'),
|
||
'0' => _t('否')
|
||
),
|
||
'1',
|
||
_t('新窗口打开'),
|
||
_t('点击标签是否在新窗口打开'));
|
||
$form->addInput($openNewWindow);
|
||
}
|
||
|
||
/**
|
||
* 个人配置面板
|
||
*/
|
||
public static function personalConfig(Typecho_Widget_Helper_Form $form) {}
|
||
|
||
/**
|
||
* 获取所有标签
|
||
*/
|
||
private static function getAllTags()
|
||
{
|
||
if (!empty(self::$_allTags)) {
|
||
return self::$_allTags;
|
||
}
|
||
|
||
try {
|
||
$db = Typecho_Db::get();
|
||
$rows = $db->fetchAll($db->select('name')
|
||
->from('table.metas')
|
||
->where('type = ?', 'tag')
|
||
->order('name', Typecho_Db::SORT_ASC));
|
||
|
||
foreach ($rows as $row) {
|
||
self::$_allTags[] = $row['name'];
|
||
}
|
||
} catch (Exception $e) {
|
||
self::$_allTags = array();
|
||
}
|
||
|
||
return self::$_allTags;
|
||
}
|
||
|
||
/**
|
||
* 添加头部资源
|
||
*/
|
||
public static function addHeader()
|
||
{
|
||
// 只在写文章/页面页面添加
|
||
if (strpos($_SERVER['REQUEST_URI'], 'write-post') !== false ||
|
||
strpos($_SERVER['REQUEST_URI'], 'write-page') !== false) {
|
||
|
||
// 获取所有标签并转为JSON
|
||
$allTags = self::getAllTags();
|
||
$tagsJson = json_encode($allTags);
|
||
|
||
echo '<style>
|
||
.custom-tag-btn {
|
||
display: inline-block;
|
||
width: 24px;
|
||
height: 24px;
|
||
line-height: 24px;
|
||
text-align: center;
|
||
cursor: pointer;
|
||
background: #f5f5f5;
|
||
border: 1px solid #ddd;
|
||
border-radius: 3px;
|
||
margin-left: 5px;
|
||
font-weight: bold;
|
||
}
|
||
.custom-tag-btn:hover {
|
||
background: #e5e5e5;
|
||
}
|
||
.custom-inline-tag {
|
||
transition: all 0.2s ease;
|
||
}
|
||
.custom-inline-tag:hover {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||
}
|
||
/* 标签选择弹窗样式 */
|
||
.custom-tag-modal {
|
||
position: fixed;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
background: white;
|
||
border: 1px solid #ddd;
|
||
border-radius: 8px;
|
||
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
|
||
z-index: 10000;
|
||
width: 400px;
|
||
max-width: 90%;
|
||
padding: 20px;
|
||
}
|
||
.custom-tag-modal h3 {
|
||
margin-top: 0;
|
||
margin-bottom: 15px;
|
||
color: #333;
|
||
font-size: 16px;
|
||
border-bottom: 1px solid #eee;
|
||
padding-bottom: 10px;
|
||
}
|
||
|
||
.dark .custom-tag-input{color:#000!important;}
|
||
.custom-tag-input {
|
||
width: 100%;
|
||
padding: 10px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
box-sizing: border-box;
|
||
margin-bottom: 15px;
|
||
}
|
||
.custom-tag-input:focus {
|
||
border-color: #467B96;
|
||
outline: none;
|
||
}
|
||
.custom-tag-suggestions {
|
||
max-height: 200px;
|
||
overflow-y: auto;
|
||
border: 1px solid #eee;
|
||
border-radius: 4px;
|
||
margin-bottom: 15px;
|
||
}
|
||
.custom-tag-suggestion {
|
||
padding: 8px 12px;
|
||
cursor: pointer;
|
||
color:#000;
|
||
border-bottom: 1px solid #f5f5f5;
|
||
font-size: 14px;
|
||
}
|
||
.custom-tag-suggestion:hover {
|
||
background-color: #f5f5f5;
|
||
}
|
||
.custom-tag-suggestion.selected {
|
||
background-color: #e9f7fe;
|
||
}
|
||
.custom-tag-buttons {
|
||
text-align: right;
|
||
}
|
||
.custom-tag-btn-confirm,
|
||
.custom-tag-btn-cancel {
|
||
padding: 8px 16px;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
margin-left: 10px;
|
||
}
|
||
.custom-tag-btn-confirm {
|
||
background-color: #467B96;
|
||
color: white;
|
||
}
|
||
.custom-tag-btn-confirm:hover {
|
||
background-color: #3a6980;
|
||
}
|
||
.custom-tag-btn-cancel {
|
||
background-color: #f5f5f5;
|
||
color: #666;
|
||
}
|
||
.custom-tag-btn-cancel:hover {
|
||
background-color: #e5e5e5;
|
||
}
|
||
.custom-tag-modal-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0,0,0,0.5);
|
||
z-index: 9999;
|
||
}
|
||
.custom-tag-empty {
|
||
padding: 20px;
|
||
text-align: center;
|
||
color: #999;
|
||
font-style: italic;
|
||
}
|
||
.custom-tag-selected {
|
||
background-color: #f0f9ff;
|
||
border-left: 3px solid #467B96;
|
||
}
|
||
.dark .custom-tag-modal input[type=text]{background-color:#fff!important;color:#000!important;font-weight:none!important;}
|
||
.dark .title input[type=text]{background-color:#1f2937!important;color:#fff!important;font-weight:none!important;}
|
||
|
||
/* 深色模式适配 - 仅添加以下CSS */
|
||
.dark .custom-tag-btn {
|
||
background: #374151 !important;
|
||
border: 1px solid #4b5563 !important;
|
||
color: #d1d5db !important;
|
||
}
|
||
|
||
.dark .custom-tag-btn:hover {
|
||
background: #4b5563 !important;
|
||
}
|
||
|
||
.dark .custom-tag-modal {
|
||
background: #1f2937 !important;
|
||
border: 1px solid #374151 !important;
|
||
}
|
||
|
||
.dark .custom-tag-modal h3 {
|
||
color: #ffffff !important;
|
||
border-bottom: 1px solid #374151 !important;
|
||
}
|
||
|
||
.dark .custom-tag-input {
|
||
background: #101928 !important;
|
||
border: 1px solid #374151 !important;
|
||
color: #ffffff !important;
|
||
}
|
||
|
||
.dark .custom-tag-input:focus {
|
||
border-color: #467B96 !important;
|
||
outline: none !important;
|
||
}
|
||
|
||
.dark .custom-tag-suggestions {
|
||
background: #101928 !important;
|
||
border: 1px solid #374151 !important;
|
||
}
|
||
|
||
.dark .custom-tag-suggestion {
|
||
color: #d1d5db !important;
|
||
border-bottom: 1px solid #374151 !important;
|
||
}
|
||
|
||
.dark .custom-tag-suggestion:hover {
|
||
background-color: #1f2937 !important;
|
||
}
|
||
|
||
.dark .custom-tag-suggestion.selected {
|
||
background-color: rgba(70, 123, 150, 0.2) !important;
|
||
}
|
||
|
||
.dark .custom-tag-suggestion strong {
|
||
color: #467B96 !important;
|
||
}
|
||
|
||
.dark .custom-tag-btn-confirm {
|
||
background-color: #467B96 !important;
|
||
color: #ffffff !important;
|
||
}
|
||
|
||
.dark .custom-tag-btn-confirm:hover {
|
||
background-color: #3a6980 !important;
|
||
}
|
||
|
||
.dark .custom-tag-btn-cancel {
|
||
background-color: #374151 !important;
|
||
color: #d1d5db !important;
|
||
}
|
||
|
||
.dark .custom-tag-btn-cancel:hover {
|
||
background-color: #4b5563 !important;
|
||
}
|
||
|
||
.dark .custom-tag-empty {
|
||
color: #9ca3af !important;
|
||
}
|
||
|
||
.dark .custom-tag-selected {
|
||
background-color: rgba(70, 123, 150, 0.1) !important;
|
||
border-left: 3px solid #467B96 !important;
|
||
}
|
||
|
||
.dark #custom-tag-button span {
|
||
color: #d1d5db !important;
|
||
}
|
||
|
||
.dark .custom-tag-input::placeholder {
|
||
color: #9ca3af !important;
|
||
}
|
||
</style>';
|
||
|
||
// 在JavaScript中嵌入标签数据
|
||
echo '<script>var CUSTOM_TAGS_ALL_TAGS = ' . $tagsJson . ';</script>';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 添加编辑器按钮 - 带标签联想搜索(本地搜索版)
|
||
*/
|
||
public static function addEditorButton()
|
||
{
|
||
?>
|
||
<style>
|
||
.dark body{color:#000!important;}
|
||
</style>
|
||
<script>
|
||
(function() {
|
||
// 等待编辑器加载完成
|
||
var initInterval = setInterval(function() {
|
||
var wmdButtonRow = document.querySelector('.wmd-button-row');
|
||
if (wmdButtonRow) {
|
||
clearInterval(initInterval);
|
||
addTagButton(wmdButtonRow);
|
||
}
|
||
}, 300);
|
||
|
||
function addTagButton(toolbar) {
|
||
// 检查是否已经添加过按钮
|
||
if (document.getElementById('custom-tag-button')) {
|
||
return;
|
||
}
|
||
|
||
// 创建按钮容器
|
||
var buttonContainer = document.createElement('li');
|
||
buttonContainer.id = 'custom-tag-button';
|
||
buttonContainer.className = 'wmd-button';
|
||
buttonContainer.style.cssText = 'width: 20px; height: 20px; position: relative;';
|
||
buttonContainer.title = '插入标签';
|
||
|
||
// 创建按钮内容
|
||
var buttonSpan = document.createElement('span');
|
||
buttonSpan.style.cssText = 'display: block; width: 100%; height: 100%; text-align: center; line-height: 20px; font-weight: bold; color: #AAA; cursor: pointer;';
|
||
buttonSpan.textContent = '#';
|
||
|
||
// 添加到容器
|
||
buttonContainer.appendChild(buttonSpan);
|
||
|
||
// 添加到工具栏末尾
|
||
toolbar.appendChild(buttonContainer);
|
||
|
||
// 添加点击事件
|
||
buttonContainer.onclick = function(e) {
|
||
e.preventDefault();
|
||
showTagSelector();
|
||
return false;
|
||
};
|
||
}
|
||
|
||
// 在本地搜索标签
|
||
function searchTags(keyword) {
|
||
var allTags = window.CUSTOM_TAGS_ALL_TAGS || [];
|
||
if (!keyword) {
|
||
// 如果没有关键词,返回最近使用的标签(这里简化,返回前20个)
|
||
return allTags.slice(0, 20);
|
||
}
|
||
|
||
keyword = keyword.toLowerCase();
|
||
var results = [];
|
||
|
||
// 搜索包含关键词的标签
|
||
for (var i = 0; i < allTags.length; i++) {
|
||
if (allTags[i].toLowerCase().indexOf(keyword) !== -1) {
|
||
results.push(allTags[i]);
|
||
if (results.length >= 20) {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
// 显示标签选择器
|
||
function showTagSelector() {
|
||
// 创建遮罩层
|
||
var overlay = document.createElement('div');
|
||
overlay.className = 'custom-tag-modal-overlay';
|
||
|
||
// 创建弹窗
|
||
var modal = document.createElement('div');
|
||
modal.className = 'custom-tag-modal';
|
||
modal.innerHTML = '<h3>选择或输入标签</h3>' +
|
||
'<input type="text" class="custom-tag-input" placeholder="输入标签名称搜索..." autocomplete="off">' +
|
||
'<div class="custom-tag-suggestions"></div>' +
|
||
'<div class="custom-tag-buttons">' +
|
||
'<button type="button" class="custom-tag-btn-cancel">取消</button>' +
|
||
'<button type="button" class="custom-tag-btn-confirm">插入标签</button>' +
|
||
'</div>';
|
||
|
||
// 添加到页面
|
||
document.body.appendChild(overlay);
|
||
document.body.appendChild(modal);
|
||
|
||
// 获取DOM元素
|
||
var input = modal.querySelector('.custom-tag-input');
|
||
var suggestions = modal.querySelector('.custom-tag-suggestions');
|
||
var confirmBtn = modal.querySelector('.custom-tag-btn-confirm');
|
||
var cancelBtn = modal.querySelector('.custom-tag-btn-cancel');
|
||
|
||
var selectedTag = '';
|
||
var selectedIndex = -1;
|
||
var suggestionItems = [];
|
||
|
||
// 聚焦输入框
|
||
setTimeout(function() {
|
||
input.focus();
|
||
}, 100);
|
||
|
||
// 加载初始标签列表
|
||
updateSuggestions('');
|
||
|
||
// 输入框输入事件
|
||
var searchTimer;
|
||
input.addEventListener('input', function() {
|
||
clearTimeout(searchTimer);
|
||
searchTimer = setTimeout(function() {
|
||
updateSuggestions(input.value.trim());
|
||
}, 200);
|
||
});
|
||
|
||
// 输入框键盘事件
|
||
input.addEventListener('keydown', function(e) {
|
||
if (e.key === 'ArrowDown') {
|
||
e.preventDefault();
|
||
selectNext();
|
||
} else if (e.key === 'ArrowUp') {
|
||
e.preventDefault();
|
||
selectPrev();
|
||
} else if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
if (selectedIndex >= 0 && suggestionItems[selectedIndex]) {
|
||
selectTag(suggestionItems[selectedIndex].dataset.tag);
|
||
} else if (input.value.trim()) {
|
||
insertTag(input.value.trim());
|
||
}
|
||
} else if (e.key === 'Escape') {
|
||
e.preventDefault();
|
||
closeModal();
|
||
}
|
||
});
|
||
|
||
// 确认按钮点击
|
||
confirmBtn.addEventListener('click', function() {
|
||
if (selectedTag) {
|
||
insertTag(selectedTag);
|
||
} else if (input.value.trim()) {
|
||
insertTag(input.value.trim());
|
||
} else {
|
||
alert('请输入标签名称!');
|
||
}
|
||
});
|
||
|
||
// 取消按钮点击
|
||
cancelBtn.addEventListener('click', closeModal);
|
||
|
||
// 点击遮罩层关闭
|
||
overlay.addEventListener('click', closeModal);
|
||
|
||
// 更新建议列表
|
||
function updateSuggestions(keyword) {
|
||
var tags = searchTags(keyword);
|
||
suggestions.innerHTML = '';
|
||
suggestionItems = [];
|
||
selectedIndex = -1;
|
||
|
||
if (tags.length === 0) {
|
||
if (keyword) {
|
||
suggestions.innerHTML = '<div class="custom-tag-empty">没有找到匹配的标签</div>';
|
||
} else {
|
||
suggestions.innerHTML = '<div class="custom-tag-empty">暂无标签,请输入新标签</div>';
|
||
}
|
||
return;
|
||
}
|
||
|
||
tags.forEach(function(tag, index) {
|
||
var item = document.createElement('div');
|
||
item.className = 'custom-tag-suggestion';
|
||
item.textContent = tag;
|
||
item.dataset.tag = tag;
|
||
|
||
// 高亮匹配的关键字
|
||
if (keyword) {
|
||
var escapedKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\\\$&');
|
||
var regex = new RegExp('(' + escapedKeyword + ')', 'gi');
|
||
item.innerHTML = tag.replace(regex, '<strong>$1</strong>');
|
||
}
|
||
|
||
item.addEventListener('click', function() {
|
||
selectTag(tag);
|
||
});
|
||
|
||
item.addEventListener('mouseenter', function() {
|
||
clearSelection();
|
||
selectedIndex = index;
|
||
item.classList.add('selected');
|
||
});
|
||
|
||
suggestions.appendChild(item);
|
||
suggestionItems.push(item);
|
||
});
|
||
|
||
// 如果有建议,默认选择第一个
|
||
if (suggestionItems.length > 0) {
|
||
selectedIndex = 0;
|
||
suggestionItems[0].classList.add('selected');
|
||
}
|
||
}
|
||
|
||
// 选择下一个
|
||
function selectNext() {
|
||
if (suggestionItems.length === 0) return;
|
||
|
||
clearSelection();
|
||
selectedIndex = (selectedIndex + 1) % suggestionItems.length;
|
||
suggestionItems[selectedIndex].classList.add('selected');
|
||
scrollToSelected();
|
||
}
|
||
|
||
// 选择上一个
|
||
function selectPrev() {
|
||
if (suggestionItems.length === 0) return;
|
||
|
||
clearSelection();
|
||
selectedIndex = selectedIndex <= 0 ? suggestionItems.length - 1 : selectedIndex - 1;
|
||
suggestionItems[selectedIndex].classList.add('selected');
|
||
scrollToSelected();
|
||
}
|
||
|
||
// 清除选择
|
||
function clearSelection() {
|
||
suggestionItems.forEach(function(item) {
|
||
item.classList.remove('selected');
|
||
});
|
||
}
|
||
|
||
// 滚动到选中的项目
|
||
function scrollToSelected() {
|
||
if (selectedIndex >= 0 && suggestionItems[selectedIndex]) {
|
||
suggestionItems[selectedIndex].scrollIntoView({
|
||
block: 'nearest',
|
||
behavior: 'smooth'
|
||
});
|
||
}
|
||
}
|
||
|
||
// 选择标签
|
||
function selectTag(tag) {
|
||
selectedTag = tag;
|
||
input.value = tag;
|
||
|
||
// 高亮显示选中的标签
|
||
suggestionItems.forEach(function(item) {
|
||
item.classList.remove('custom-tag-selected');
|
||
if (item.dataset.tag === tag) {
|
||
item.classList.add('custom-tag-selected');
|
||
}
|
||
});
|
||
}
|
||
|
||
// 插入标签
|
||
function insertTag(tagName) {
|
||
// 验证标签名称
|
||
tagName = tagName.trim();
|
||
if (tagName.length < 2 || tagName.length > 20) {
|
||
alert('标签名称长度应在2-20个字符之间!');
|
||
return;
|
||
}
|
||
|
||
if (!/^[\u4e00-\u9fa5a-zA-Z0-9_\-\s]+$/.test(tagName)) {
|
||
alert('标签名称只能包含中文、英文、数字、下划线和减号!');
|
||
return;
|
||
}
|
||
|
||
// 创建短代码
|
||
var shortcode = '[tag]' + tagName + '[/tag]';
|
||
|
||
// 获取编辑器textarea
|
||
var textarea = document.getElementById('text');
|
||
if (!textarea) {
|
||
alert('无法找到编辑器!');
|
||
return;
|
||
}
|
||
|
||
// 获取光标位置
|
||
var startPos = textarea.selectionStart;
|
||
var endPos = textarea.selectionEnd;
|
||
|
||
// 检查光标前的内容,避免与Markdown标题混淆
|
||
var beforeText = textarea.value.substring(0, startPos);
|
||
var lines = beforeText.split('\\n');
|
||
var currentLine = lines[lines.length - 1];
|
||
|
||
// 如果当前行以#开头且没有空格,提示用户
|
||
var trimmedLine = currentLine.trim();
|
||
if (trimmedLine.match(/^#+[^#\\s]/)) {
|
||
if (!confirm('检测到当前位置可能在Markdown标题中,插入标签可能会影响标题显示。确定要插入吗?')) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 插入短代码
|
||
textarea.value = textarea.value.substring(0, startPos) +
|
||
shortcode +
|
||
textarea.value.substring(endPos);
|
||
|
||
// 移动光标到短代码后面
|
||
textarea.selectionStart = textarea.selectionEnd = startPos + shortcode.length;
|
||
textarea.focus();
|
||
|
||
// 触发input事件
|
||
var event = new Event('input', { bubbles: true });
|
||
textarea.dispatchEvent(event);
|
||
|
||
// 如果是Markdown编辑器,触发预览更新
|
||
if (typeof window.convertMarkdown === 'function') {
|
||
window.convertMarkdown();
|
||
}
|
||
|
||
// 关闭弹窗
|
||
closeModal();
|
||
}
|
||
|
||
// 关闭弹窗
|
||
function closeModal() {
|
||
if (overlay.parentNode) {
|
||
document.body.removeChild(overlay);
|
||
}
|
||
if (modal.parentNode) {
|
||
document.body.removeChild(modal);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 添加快捷键支持
|
||
document.addEventListener('keydown', function(e) {
|
||
// Ctrl+Alt+T 插入标签
|
||
if (e.ctrlKey && e.altKey && e.keyCode === 84) { // T键
|
||
e.preventDefault();
|
||
showTagSelector();
|
||
}
|
||
});
|
||
|
||
// 重新检查是否加载了工具栏(有些主题可能动态加载)
|
||
setTimeout(function() {
|
||
var wmdButtonRow = document.querySelector('.wmd-button-row');
|
||
if (wmdButtonRow && !document.getElementById('custom-tag-button')) {
|
||
addTagButton(wmdButtonRow);
|
||
}
|
||
}, 1000);
|
||
|
||
})();
|
||
</script>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* 解析内容中的标签短代码
|
||
*/
|
||
public static function parseContent($content, $widget, $lastResult)
|
||
{
|
||
$content = empty($lastResult) ? $content : $lastResult;
|
||
|
||
// 如果是在后台列表或编辑页面,不转换
|
||
if ($widget instanceof Widget_Archive && !$widget->is('single')) {
|
||
return $content;
|
||
}
|
||
|
||
// 获取插件配置
|
||
$options = Helper::options()->plugin('CustomInlineTags');
|
||
$prefix = $options->tagPrefix ?: '#';
|
||
$style = $options->tagStyle ?: "display: inline-block; background: #f0f0f0; color: #333; padding: 2px 8px; margin: 0 4px; border-radius: 12px; font-size: 0.9em; font-weight: normal; text-decoration: none;";
|
||
$className = 'custom-inline-tag';
|
||
$enableLink = isset($options->enableLink) ? $options->enableLink : '1';
|
||
$tagUrlFormat = isset($options->tagUrlFormat) ? $options->tagUrlFormat : '/tag-{slug}.html';
|
||
$openNewWindow = isset($options->openNewWindow) ? $options->openNewWindow : '1';
|
||
|
||
// 替换短代码为HTML
|
||
$content = preg_replace_callback(
|
||
'/\[tag\]([^\[\]]+?)\[\/tag\]/',
|
||
function($matches) use ($prefix, $style, $className, $enableLink, $tagUrlFormat, $openNewWindow, $widget) {
|
||
$tagName = htmlspecialchars(trim($matches[1]), ENT_QUOTES, 'UTF-8');
|
||
$displayName = $prefix . $tagName;
|
||
|
||
if ($enableLink == '1' && $widget instanceof Widget_Archive) {
|
||
// 获取标签链接
|
||
$tagUrl = '';
|
||
try {
|
||
$db = Typecho_Db::get();
|
||
$tag = $db->fetchRow($db->select()
|
||
->from('table.metas')
|
||
->where('type = ?', 'tag')
|
||
->where('name = ?', $tagName)
|
||
->limit(1));
|
||
|
||
if ($tag) {
|
||
// 使用Typecho的标签链接生成方法
|
||
if (method_exists($widget, 'permalink')) {
|
||
// 创建临时对象获取标签链接
|
||
$reflection = new ReflectionClass($widget);
|
||
$params = $reflection->getProperty('_params');
|
||
$params->setAccessible(true);
|
||
$widgetParams = $params->getValue($widget);
|
||
|
||
// 临时修改参数获取标签链接
|
||
$originalParams = $widgetParams;
|
||
$widgetParams['type'] = 'tag';
|
||
$widgetParams['slug'] = $tag['slug'];
|
||
|
||
// 尝试获取标签链接
|
||
try {
|
||
$tagUrl = $widget->permalink;
|
||
} catch (Exception $e) {
|
||
// 如果失败,使用自定义格式
|
||
$tagSlug = urlencode($tag['slug']);
|
||
$tagUrl = str_replace('{slug}', $tagSlug, $tagUrlFormat);
|
||
$tagUrl = Typecho_Common::url($tagUrl, Helper::options()->index);
|
||
}
|
||
|
||
// 恢复原始参数
|
||
$params->setValue($widget, $originalParams);
|
||
} else {
|
||
// 使用自定义格式
|
||
$tagSlug = urlencode($tag['slug']);
|
||
$tagUrl = str_replace('{slug}', $tagSlug, $tagUrlFormat);
|
||
$tagUrl = Typecho_Common::url($tagUrl, Helper::options()->index);
|
||
}
|
||
}
|
||
} catch (Exception $e) {
|
||
// 出错时不添加链接
|
||
error_log('CustomInlineTags Error: ' . $e->getMessage());
|
||
}
|
||
|
||
if ($tagUrl) {
|
||
// 构建链接属性
|
||
$linkAttributes = sprintf('href="%s" class="%s" data-tag="%s" style="%s" title="查看标签相关文章"',
|
||
htmlspecialchars($tagUrl),
|
||
$className,
|
||
$tagName,
|
||
$style
|
||
);
|
||
|
||
// 如果启用了新窗口打开,添加target="_blank"
|
||
if ($openNewWindow == '1') {
|
||
$linkAttributes .= ' target="_blank"';
|
||
}
|
||
|
||
return sprintf('<a %s>%s</a>',
|
||
$linkAttributes,
|
||
$displayName
|
||
);
|
||
}
|
||
}
|
||
|
||
// 如果不启用链接或找不到标签
|
||
return sprintf(
|
||
'<span class="%s" data-tag="%s" style="%s">%s</span>',
|
||
$className,
|
||
$tagName,
|
||
$style,
|
||
$displayName
|
||
);
|
||
},
|
||
$content
|
||
);
|
||
|
||
return $content;
|
||
}
|
||
|
||
/**
|
||
* 保存文章时提取标签并保存到数据库
|
||
*/
|
||
public static function parseTagsOnSave($contents, $widget)
|
||
{
|
||
$content = $contents['text'];
|
||
$tags = array();
|
||
|
||
// 从短代码中提取标签
|
||
preg_match_all('/\[tag\]([^\[\]]+?)\[\/tag\]/', $content, $matches);
|
||
|
||
if (!empty($matches[1])) {
|
||
foreach ($matches[1] as $tag) {
|
||
$tag = trim($tag);
|
||
if (!empty($tag) && !in_array($tag, $tags)) {
|
||
$tags[] = $tag;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果找到了标签,添加到文章的标签字段
|
||
if (!empty($tags)) {
|
||
// 获取现有的标签
|
||
$existingTags = isset($contents['tags']) ? $contents['tags'] : '';
|
||
$existingTagsArray = array_filter(array_map('trim', explode(',', $existingTags)));
|
||
|
||
// 合并标签并去重
|
||
$allTags = array_unique(array_merge($existingTagsArray, $tags));
|
||
|
||
// 更新标签字段
|
||
$contents['tags'] = implode(',', $allTags);
|
||
}
|
||
|
||
return $contents;
|
||
}
|
||
}
|