diff --git a/js/admin-media.js b/js/admin-media.js index f41922a..c50a27e 100644 --- a/js/admin-media.js +++ b/js/admin-media.js @@ -15,16 +15,19 @@ var listContainer = null; var emojiListContainer = null; var emojiInput = document.getElementById('yooneSnowAddEmojiInput'); - var emojiAddButton = document.getElementById('yooneSnowAddEmoji'); + var emojiAddButton = null; var emojiSuggestBox = document.getElementById('yooneSnowEmojiSuggest'); var shapeListContainer = document.getElementById('yooneSnowShapeList'); var shapeAddSelect = document.getElementById('yooneSnowAddShapeSelect'); - var shapeAddButton = document.getElementById('yooneSnowAddShapeBtn'); + var shapeAddButton = null; var emojiSelect = document.getElementById('yooneSnowEmojiSelect'); var typeSelect = document.getElementById('yooneSnowAddTypeSelect'); var paneDefault = document.getElementById('yooneSnowAddDefaultPane'); var paneEmoji = document.getElementById('yooneSnowAddEmojiPane'); var paneMedia = document.getElementById('yooneSnowAddMediaPane'); + var unifiedAddCard = document.getElementById('yooneSnowAddUnified'); + var paneText = document.getElementById('yooneSnowAddTextPane'); + var textInput = document.getElementById('yooneSnowAddTextInput'); // 统一列表容器为形状列表容器 旧容器不再使用 listContainer = shapeListContainer; emojiListContainer = shapeListContainer; @@ -77,7 +80,7 @@ var baseSize = previewBaseSizeMap[shapeKey] || 3.2; // 调用渲染器 使用画布中心作为位置 try { renderer(ctx, 16, 16, baseSize); } catch(e){} - // 对基于圖像的形狀進行補繪 當圖像加載完成時重試一次 + // 对基于图像的形状进行补绘 当图像加载完成时重试一次 var assets = (window.YooneSnowSettings && window.YooneSnowSettings.assetsMap) ? window.YooneSnowSettings.assetsMap : {}; var url = assets[shapeKey]; if (url && typeof window.YooneSnowGetOrLoadImage === 'function'){ @@ -197,7 +200,11 @@ wrapper.appendChild(weightInput); wrapper.appendChild(input); wrapper.appendChild(cancelBtn); - shapeListContainer.appendChild(wrapper); + if (unifiedAddCard && unifiedAddCard.parentNode === shapeListContainer){ + shapeListContainer.insertBefore(wrapper, unifiedAddCard); + } else { + shapeListContainer.appendChild(wrapper); + } var previewEl = createShapePreviewElement(key); if (previewEl) { previewHost.appendChild(previewEl); } } @@ -216,7 +223,7 @@ } }); - // 綁定移除 emoji 按鈕事件 使用事件委託處理動態元素 + // 绑定移除 emoji 按钮事件 使用事件委托处理动态元素 if (shapeListContainer){ shapeListContainer.addEventListener('click', function(event){ var target = event.target; @@ -282,14 +289,18 @@ wrapper.appendChild(weightInput); wrapper.appendChild(input); wrapper.appendChild(removeBtn); - listContainer.appendChild(wrapper); + if (unifiedAddCard && unifiedAddCard.parentNode === listContainer){ + listContainer.insertBefore(wrapper, unifiedAddCard); + } else { + listContainer.appendChild(wrapper); + } }); }); frame.open(); }); } - // Emoji 别名映射 用於文本搜索到 emoji 字符 + // Emoji 别名映射 用于文本搜索到 emoji 字符 var emojiAliasMap = { smile: '🙂', happy: '😊', @@ -321,10 +332,10 @@ berry: '🍓' }; - // 填充下拉選單 以別名和字符組合顯示 + // 填充下拉菜单 以别名和字符组合显示 function populateEmojiSelect(){ if (!emojiSelect) { return; } - // 清空除第一個提示項之外的選項 + // 清空除第一个提示项之外的选项 while (emojiSelect.options.length > 1){ emojiSelect.remove(1); } for (var key in emojiAliasMap){ var ch = emojiAliasMap[key]; @@ -335,7 +346,7 @@ } } - // 綁定下拉選擇事件 選擇後添加對應 emoji 並重置選中 + // 绑定下拉选择事件 选择后添加对应 emoji 并重置选中 if (emojiSelect){ populateEmojiSelect(); emojiSelect.addEventListener('change', function(){ @@ -347,7 +358,7 @@ }); } - // 顯示建議列表 根據查詢文本匹配別名 + // 显示建议列表 根据查询文本匹配别名 function showEmojiSuggestions(query){ if (!emojiSuggestBox) { return; } var q = String(query || '').toLowerCase().trim(); @@ -371,7 +382,7 @@ } } - // 將 emoji 字符添加到列表 並創建隱藏輸入 + // 将 emoji 字符添加到列表 并创建隐藏输入 function addEmojiByChar(ch){ if (!shapeListContainer) { return; } var key = String(ch); @@ -418,25 +429,55 @@ wrapper.appendChild(weightInput); wrapper.appendChild(input); wrapper.appendChild(removeBtn); - shapeListContainer.appendChild(wrapper); + if (unifiedAddCard && unifiedAddCard.parentNode === shapeListContainer){ + shapeListContainer.insertBefore(wrapper, unifiedAddCard); + } else { + shapeListContainer.appendChild(wrapper); + } } - // 綁定 emoji 添加按鈕 點擊後優先按別名匹配 否則直接添加字符 - if (emojiAddButton){ - emojiAddButton.addEventListener('click', function(){ - var q = emojiInput ? String(emojiInput.value || '') : ''; - var trimmed = q.trim(); - if (trimmed === '') { return; } - var lower = trimmed.toLowerCase(); - if (emojiAliasMap[lower]){ - addEmojiByChar(emojiAliasMap[lower]); - } else { - addEmojiByChar(trimmed); - } - }); - } + // 绑定 emoji 添加按钮 点击后优先按别名匹配 否则直接添加字符 + - // 監聽輸入變化 顯示建議列表 支持即時搜索 + // 监听输入变化 显示建议列表 支持即时搜索 + function segmentGraphemes(text){ + // 将输入参数转为字符串 防止出现非字符串类型 + var segments = []; + // 初始化结果数组 用于存放分割后的字符簇 + var s = String(text || ''); + // 如果当前环境支持 Intl.Segmenter 优先使用字符簇级别的分段 + if (typeof Intl !== 'undefined' && Intl.Segmenter){ + try { + // 创建针对简体中文环境的分段器 按 grapheme 粒度分段 + var sg = new Intl.Segmenter('zh-Hans', { granularity: 'grapheme' }); + // 执行分段操作 并提取每个分段的文本内容 + var it = sg.segment(s); + it.forEach(function(rec){ segments.push(rec.segment); }); + // 分段成功后返回结果数组 + return segments; + } catch(err){} + } + // 如果不支持 Intl.Segmenter 或发生异常 使用正则作为后备方案 + try { + // 正则匹配常见的 emoji 字符簇 包含 ZWJ 组合和区域指示符 + var re = /(?:\p{Extended_Pictographic}\uFE0F?(?:\u200D\p{Extended_Pictographic}\uFE0F?)*)|(?:\p{Regional_Indicator}{2})|(?:[#*0-9]\uFE0F?\u20E3)/gu; + var m = s.match(re); + // 如果匹配到至少一个字符簇 则直接返回匹配结果 + if (m && m.length > 0){ return m; } + } catch(err){} + // 若未匹配到任何字符簇 且原始字符串非空 则返回包含原字符串的数组 否则返回空数组 + return s ? [s] : []; + } + function isEmojiCluster(cluster){ + var str = String(cluster || ''); + if (str.trim() === ''){ return false; } + try { if (/\p{Extended_Pictographic}/u.test(str)){ return true; } } catch(err){} + try { if (/[\u{1F1E6}-\u{1F1FF}]{2}/u.test(str)){ return true; } } catch(err){} + if (/(?:[#*0-9]\uFE0F?\u20E3)/u.test(str)){ return true; } + if (/\u200D/.test(str)){ return true; } + if (/\uFE0F/.test(str)){ return true; } + return false; + } if (emojiInput){ emojiInput.addEventListener('input', function(){ showEmojiSuggestions(emojiInput.value); @@ -448,10 +489,31 @@ if (q !== ''){ var lower = q.toLowerCase(); if (emojiAliasMap[lower]){ addEmojiByChar(emojiAliasMap[lower]); } - else { addEmojiByChar(q); } + else { + var clusters = segmentGraphemes(q); + for (var i = 0; i < clusters.length; i++){ + if (isEmojiCluster(clusters[i])){ addEmojiByChar(clusters[i]); } + } + } + emojiInput.value = ''; + emojiSuggestBox && (emojiSuggestBox.innerHTML = ''); } } }); + emojiInput.addEventListener('paste', function(e){ + var txt = ''; + if (e && e.clipboardData){ txt = String(e.clipboardData.getData('text') || ''); } + if (txt.trim() === ''){ return; } + setTimeout(function(){ + var q = String(emojiInput.value || txt).trim(); + var clusters = segmentGraphemes(q); + for (var i = 0; i < clusters.length; i++){ + if (isEmojiCluster(clusters[i])){ addEmojiByChar(clusters[i]); } + } + emojiInput.value = ''; + emojiSuggestBox && (emojiSuggestBox.innerHTML = ''); + }, 0); + }); } if (shapeListContainer){ @@ -477,27 +539,97 @@ }); } - if (shapeAddButton){ - shapeAddButton.addEventListener('click', function(){ - var val = shapeAddSelect ? String(shapeAddSelect.value || '') : ''; - if (val.trim() !== ''){ - addShapeBox(val); - shapeAddSelect.value = ''; + + + // 切换类型面板 显示对应控件 其他隐藏 + function updateTypePane(){ + var t = typeSelect ? String(typeSelect.value || '') : ''; + if (!paneDefault || !paneEmoji || !paneMedia || !paneText) { return; } + paneDefault.style.display = (t === 'default') ? 'flex' : 'none'; + paneEmoji.style.display = (t === 'emoji') ? 'flex' : 'none'; + paneMedia.style.display = (t === 'media') ? 'flex' : 'none'; + paneText.style.display = (t === 'text') ? 'flex' : 'none'; + } + if (typeSelect){ + typeSelect.value = 'default'; + typeSelect.addEventListener('change', updateTypePane); + updateTypePane(); + } + + function addTextBox(textLabel){ + if (!shapeListContainer) { return; } + var key = String(textLabel).trim(); + if (key === '') { return; } + var exist = shapeListContainer.querySelector('.yoone-snow-text-item[data-text-label="' + key + '"]'); + if (exist) { return; } + var wrapper = document.createElement('div'); + wrapper.className = 'yoone-snow-text-item'; + wrapper.setAttribute('data-text-label', key); + wrapper.style.border = '1px solid #ddd'; + wrapper.style.padding = '8px'; + wrapper.style.display = 'flex'; + wrapper.style.flexDirection = 'column'; + wrapper.style.alignItems = 'center'; + wrapper.style.minWidth = '96px'; + var preview = document.createElement('div'); + preview.textContent = key; + preview.style.fontSize = '14px'; + preview.style.lineHeight = '18px'; + preview.style.width = '120px'; + preview.style.minHeight = '32px'; + preview.style.display = 'flex'; + preview.style.alignItems = 'center'; + preview.style.justifyContent = 'center'; + preview.style.backgroundColor = '#e6e6e6'; + preview.style.border = '1px solid #ddd'; + preview.style.borderRadius = '4px'; + preview.style.wordBreak = 'break-word'; + preview.style.textAlign = 'center'; + var weightInput = document.createElement('input'); + weightInput.type = 'number'; + weightInput.min = '0'; + weightInput.name = 'yoone_snow_text_weights[' + key + ']'; + weightInput.value = '1'; + weightInput.style.width = '120px'; + weightInput.style.marginTop = '6px'; + var input = document.createElement('input'); + input.type = 'hidden'; + input.name = 'yoone_snow_text_items[]'; + input.value = key; + var removeBtn = document.createElement('button'); + removeBtn.type = 'button'; + removeBtn.className = 'button yoone-snow-remove-text'; + removeBtn.textContent = 'Cancel'; + removeBtn.style.marginTop = '6px'; + wrapper.appendChild(preview); + wrapper.appendChild(weightInput); + wrapper.appendChild(input); + wrapper.appendChild(removeBtn); + if (unifiedAddCard && unifiedAddCard.parentNode === shapeListContainer){ + shapeListContainer.insertBefore(wrapper, unifiedAddCard); + } else { + shapeListContainer.appendChild(wrapper); + } + } + + if (textInput){ + textInput.addEventListener('keydown', function(e){ + if (e && e.key === 'Enter'){ + e.preventDefault(); + var val = String(textInput.value || '').trim(); + if (val !== ''){ addTextBox(val); textInput.value = ''; } } }); } - // 切換類型面板 顯示對應控件 其他隱藏 - function updateTypePane(){ - var t = typeSelect ? String(typeSelect.value || '') : ''; - if (!paneDefault || !paneEmoji || !paneMedia) { return; } - paneDefault.style.display = (t === 'default') ? 'flex' : 'none'; - paneEmoji.style.display = (t === 'emoji') ? 'flex' : 'none'; - paneMedia.style.display = (t === 'media') ? 'flex' : 'none'; - } - if (typeSelect){ - typeSelect.addEventListener('change', updateTypePane); - updateTypePane(); + if (shapeListContainer){ + shapeListContainer.addEventListener('click', function(event){ + var target = event.target; + if (target && target.classList.contains('yoone-snow-remove-text')){ + var item = target.closest('.yoone-snow-text-item'); + if (item){ item.remove(); } + } + }); } } diff --git a/js/shapes/utils.js b/js/shapes/utils.js index d8bf255..fe527de 100644 --- a/js/shapes/utils.js +++ b/js/shapes/utils.js @@ -22,8 +22,12 @@ const record = { img: img, ready: false }; window.YooneSnowImageCache[imageUrl] = record; img.onload = function(){ - // 加载成功 标记为可用 - record.ready = true; + var decoder = img.decode && typeof img.decode === 'function' ? img.decode() : null; + if (decoder && typeof decoder.then === 'function'){ + decoder.then(function(){ record.ready = true; }).catch(function(){ record.ready = true; }); + } else { + record.ready = true; + } }; img.onerror = function(){ // 加载失败 从缓存移除避免重复错误 diff --git a/js/snow-canvas.js b/js/snow-canvas.js index 653ff00..7e08a45 100644 --- a/js/snow-canvas.js +++ b/js/snow-canvas.js @@ -4,6 +4,8 @@ const canvas = document.getElementById('effectiveAppsSnow'); // 条件判断 如果未找到画布元素则不执行 if (!canvas) return; + const prefersReducedMotion = (typeof window.matchMedia === 'function') && window.matchMedia('(prefers-reduced-motion: reduce)').matches; + if (prefersReducedMotion) { canvas.style.display = 'none'; return; } const context = canvas.getContext('2d'); let viewportWidth = window.innerWidth; let viewportHeight = window.innerHeight; @@ -27,6 +29,9 @@ const emojiItems = (window.YooneSnowSettings && Array.isArray(window.YooneSnowSettings.emojiItems)) ? window.YooneSnowSettings.emojiItems : []; + const textItems = (window.YooneSnowSettings && Array.isArray(window.YooneSnowSettings.textItems)) + ? window.YooneSnowSettings.textItems + : []; const defaultShapeWeights = { dot: 1, flake: 4, yuanbao: 1, coin: 1, santa_hat: 1, candy_cane: 1, christmas_sock: 1, christmas_tree: 1, reindeer: 1, christmas_berry: 1 }; const shapeWeightsRaw = (window.YooneSnowSettings && window.YooneSnowSettings.shapeWeights && typeof window.YooneSnowSettings.shapeWeights === 'object') ? window.YooneSnowSettings.shapeWeights @@ -37,6 +42,9 @@ const emojiWeightsRaw = (window.YooneSnowSettings && window.YooneSnowSettings.emojiWeights && typeof window.YooneSnowSettings.emojiWeights === 'object') ? window.YooneSnowSettings.emojiWeights : {}; + const textWeightsRaw = (window.YooneSnowSettings && window.YooneSnowSettings.textWeights && typeof window.YooneSnowSettings.textWeights === 'object') + ? window.YooneSnowSettings.textWeights + : {}; const shapeWeights = {}; for (let key in defaultShapeWeights){ const val = typeof shapeWeightsRaw[key] !== 'undefined' ? parseInt(shapeWeightsRaw[key], 10) : defaultShapeWeights[key]; @@ -87,7 +95,8 @@ const maxCountRaw = (window.YooneSnowSettings && typeof window.YooneSnowSettings.maxCount !== 'undefined') ? parseInt(window.YooneSnowSettings.maxCount, 10) : 0; - const computedAutoCount = Math.floor(Math.min(400, Math.max(120, (viewportWidth * viewportHeight) / 12000))); + const isSmallScreen = Math.min(viewportWidth, viewportHeight) <= 768; + const computedAutoCount = Math.floor(Math.min(isSmallScreen ? 240 : 400, Math.max(isSmallScreen ? 80 : 120, (viewportWidth * viewportHeight) / (isSmallScreen ? 24000 : 12000)))); const snowflakesTargetCount = Math.max(1, (isNaN(maxCountRaw) || maxCountRaw <= 0) ? computedAutoCount : maxCountRaw); const snowflakes = []; // 定义连续生成控制参数 使用时间积累的方式平滑新增 @@ -117,6 +126,15 @@ if (ew > 0){ items.push({ kind: 'emoji', text: ch, weight: ew }); } } } + if (textItems.length > 0){ + for (let tIndex = 0; tIndex < textItems.length; tIndex++){ + const tx = String(textItems[tIndex] || '').trim(); + if (tx === ''){ continue; } + const twRaw = typeof textWeightsRaw[tx] !== 'undefined' ? parseInt(textWeightsRaw[tx], 10) : 1; + const tw = isNaN(twRaw) ? 1 : Math.max(0, twRaw); + if (tw > 0){ items.push({ kind: 'text', text: tx, weight: tw }); } + } + } for (let mIndex = 0; mIndex < mediaItems.length; mIndex++){ const mediaUrl = mediaItems[mIndex]; const mediaWeight = typeof mediaWeightsRaw[mediaUrl] !== 'undefined' ? parseInt(mediaWeightsRaw[mediaUrl], 10) : 1; @@ -145,7 +163,11 @@ if (items[i].kind === 'media'){ return { type: 'media_image', url: items[i].url, text: null }; } else { - return { type: 'emoji_text', url: null, text: items[i].text }; + if (items[i].kind === 'emoji'){ + return { type: 'emoji_text', url: null, text: items[i].text }; + } else { + return { type: 'text_label', url: null, text: items[i].text }; + } } } } @@ -275,6 +297,17 @@ context.restore(); continue; } + if (flake.shapeType === 'text_label' && flake.emojiText){ + context.save(); + const fontSize = Math.max(12, flake.radius * 5.5); + context.font = String(Math.floor(fontSize)) + 'px system-ui, -apple-system, Segoe UI, Roboto, Noto Sans'; + context.textAlign = 'center'; + context.textBaseline = 'middle'; + context.fillStyle = 'rgba(255,255,255,0.9)'; + context.fillText(String(flake.emojiText), flake.positionX, flake.positionY); + context.restore(); + continue; + } // 否则执行注册表中的形状渲染函数 const registry = window.YooneSnowShapeRenderers || {}; const renderer = registry[flake.shapeType] || registry['dot']; diff --git a/yoone-snow.php b/yoone-snow.php index f193066..c0e7534 100644 --- a/yoone-snow.php +++ b/yoone-snow.php @@ -16,55 +16,74 @@ function yoone_snow_enqueue_assets() { if (!yoone_snow_is_enabled()) { return; } $style_handle = 'yoone-snow-style'; $style_src = plugins_url('css/snow.css', __FILE__); - wp_register_style($style_handle, $style_src, array(), '1.1.0', 'all'); + $style_ver = @filemtime(plugin_dir_path(__FILE__) . 'css/snow.css'); + wp_register_style($style_handle, $style_src, array(), $style_ver ? (string)$style_ver : '1.1.0', 'all'); wp_enqueue_style($style_handle); // 注册形状渲染脚本 保证主脚本之前加载 $shape_index_handle = 'yoone-snow-shapes-index'; - wp_register_script($shape_index_handle, plugins_url('js/shapes/index.js', __FILE__), array(), '1.1.0', true); + $ver_shapes_index = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/index.js'); + wp_register_script($shape_index_handle, plugins_url('js/shapes/index.js', __FILE__), array(), $ver_shapes_index ? (string)$ver_shapes_index : '1.1.0', true); $shape_utils_handle = 'yoone-snow-shapes-utils'; - wp_register_script($shape_utils_handle, plugins_url('js/shapes/utils.js', __FILE__), array($shape_index_handle), '1.1.0', true); + $ver_shapes_utils = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/utils.js'); + wp_register_script($shape_utils_handle, plugins_url('js/shapes/utils.js', __FILE__), array($shape_index_handle), $ver_shapes_utils ? (string)$ver_shapes_utils : '1.1.0', true); $shape_dot_handle = 'yoone-snow-shapes-dot'; - wp_register_script($shape_dot_handle, plugins_url('js/shapes/dot.js', __FILE__), array($shape_index_handle), '1.1.0', true); + $ver_shapes_dot = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/dot.js'); + wp_register_script($shape_dot_handle, plugins_url('js/shapes/dot.js', __FILE__), array($shape_index_handle), $ver_shapes_dot ? (string)$ver_shapes_dot : '1.1.0', true); $shape_flake_handle = 'yoone-snow-shapes-flake'; - wp_register_script($shape_flake_handle, plugins_url('js/shapes/flake.js', __FILE__), array($shape_index_handle), '1.1.0', true); + $ver_shapes_flake = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/flake.js'); + wp_register_script($shape_flake_handle, plugins_url('js/shapes/flake.js', __FILE__), array($shape_index_handle), $ver_shapes_flake ? (string)$ver_shapes_flake : '1.1.0', true); $shape_yuanbao_handle = 'yoone-snow-shapes-yuanbao'; - wp_register_script($shape_yuanbao_handle, plugins_url('js/shapes/yuanbao.js', __FILE__), array($shape_index_handle), '1.1.0', true); + $ver_shapes_yuanbao = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/yuanbao.js'); + wp_register_script($shape_yuanbao_handle, plugins_url('js/shapes/yuanbao.js', __FILE__), array($shape_index_handle), $ver_shapes_yuanbao ? (string)$ver_shapes_yuanbao : '1.1.0', true); $shape_coin_handle = 'yoone-snow-shapes-coin'; - wp_register_script($shape_coin_handle, plugins_url('js/shapes/coin.js', __FILE__), array($shape_index_handle), '1.1.0', true); + $ver_shapes_coin = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/coin.js'); + wp_register_script($shape_coin_handle, plugins_url('js/shapes/coin.js', __FILE__), array($shape_index_handle), $ver_shapes_coin ? (string)$ver_shapes_coin : '1.1.0', true); $shape_santa_handle = 'yoone-snow-shapes-santa-hat'; - wp_register_script($shape_santa_handle, plugins_url('js/shapes/santa_hat.js', __FILE__), array($shape_utils_handle), '1.1.0', true); + $ver_shapes_santa = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/santa_hat.js'); + wp_register_script($shape_santa_handle, plugins_url('js/shapes/santa_hat.js', __FILE__), array($shape_utils_handle), $ver_shapes_santa ? (string)$ver_shapes_santa : '1.1.0', true); $shape_cane_handle = 'yoone-snow-shapes-candy-cane'; - wp_register_script($shape_cane_handle, plugins_url('js/shapes/candy_cane.js', __FILE__), array($shape_utils_handle), '1.1.0', true); + $ver_shapes_cane = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/candy_cane.js'); + wp_register_script($shape_cane_handle, plugins_url('js/shapes/candy_cane.js', __FILE__), array($shape_utils_handle), $ver_shapes_cane ? (string)$ver_shapes_cane : '1.1.0', true); $shape_sock_handle = 'yoone-snow-shapes-christmas-sock'; - wp_register_script($shape_sock_handle, plugins_url('js/shapes/christmas_sock.js', __FILE__), array($shape_utils_handle), '1.1.0', true); + $ver_shapes_sock = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/christmas_sock.js'); + wp_register_script($shape_sock_handle, plugins_url('js/shapes/christmas_sock.js', __FILE__), array($shape_utils_handle), $ver_shapes_sock ? (string)$ver_shapes_sock : '1.1.0', true); $shape_tree_handle = 'yoone-snow-shapes-christmas-tree'; - wp_register_script($shape_tree_handle, plugins_url('js/shapes/christmas_tree.js', __FILE__), array($shape_utils_handle), '1.1.0', true); + $ver_shapes_tree = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/christmas_tree.js'); + wp_register_script($shape_tree_handle, plugins_url('js/shapes/christmas_tree.js', __FILE__), array($shape_utils_handle), $ver_shapes_tree ? (string)$ver_shapes_tree : '1.1.0', true); $shape_reindeer_handle = 'yoone-snow-shapes-reindeer'; - wp_register_script($shape_reindeer_handle, plugins_url('js/shapes/reindeer.js', __FILE__), array($shape_utils_handle), '1.1.0', true); + $ver_shapes_reindeer = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/reindeer.js'); + wp_register_script($shape_reindeer_handle, plugins_url('js/shapes/reindeer.js', __FILE__), array($shape_utils_handle), $ver_shapes_reindeer ? (string)$ver_shapes_reindeer : '1.1.0', true); $shape_berry_handle = 'yoone-snow-shapes-christmas-berry'; - wp_register_script($shape_berry_handle, plugins_url('js/shapes/christmas_berry.js', __FILE__), array($shape_utils_handle), '1.1.0', true); + $ver_shapes_berry = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/christmas_berry.js'); + wp_register_script($shape_berry_handle, plugins_url('js/shapes/christmas_berry.js', __FILE__), array($shape_utils_handle), $ver_shapes_berry ? (string)$ver_shapes_berry : '1.1.0', true); // 注册并加载主脚本 设置依赖确保顺序正确 $script_handle = 'yoone-snow-script'; $script_src = plugins_url('js/snow-canvas.js', __FILE__); + $script_ver = @filemtime(plugin_dir_path(__FILE__) . 'js/snow-canvas.js'); + $deps = array($shape_index_handle); + $needs_utils = false; + foreach ($mixed_items_sanitized as $key) { + if ($key === 'dot') { $deps[] = $shape_dot_handle; } + if ($key === 'flake') { $deps[] = $shape_flake_handle; } + if ($key === 'yuanbao') { $deps[] = $shape_yuanbao_handle; } + if ($key === 'coin') { $deps[] = $shape_coin_handle; } + if ($key === 'santa_hat') { $deps[] = $shape_santa_handle; $needs_utils = true; } + if ($key === 'candy_cane') { $deps[] = $shape_cane_handle; $needs_utils = true; } + if ($key === 'christmas_sock') { $deps[] = $shape_sock_handle; $needs_utils = true; } + if ($key === 'christmas_tree') { $deps[] = $shape_tree_handle; $needs_utils = true; } + if ($key === 'reindeer') { $deps[] = $shape_reindeer_handle; $needs_utils = true; } + if ($key === 'christmas_berry') { $deps[] = $shape_berry_handle; $needs_utils = true; } + } + if (!empty($media_urls)) { $needs_utils = true; } + if ($needs_utils) { $deps[] = $shape_utils_handle; } + $deps = array_values(array_unique($deps)); wp_register_script( $script_handle, $script_src, - array( - $shape_index_handle, - $shape_dot_handle, - $shape_flake_handle, - $shape_yuanbao_handle, - $shape_coin_handle, - $shape_santa_handle, - $shape_cane_handle, - $shape_sock_handle, - $shape_tree_handle, - $shape_reindeer_handle, - $shape_berry_handle - ), - '1.1.0', + $deps, + $script_ver ? (string)$script_ver : '1.1.0', true ); wp_enqueue_script($script_handle); @@ -168,6 +187,30 @@ function yoone_snow_enqueue_assets() { } return $clean; })(), + 'textItems' => (function(){ + $items = get_option('yoone_snow_text_items', array()); + if (!is_array($items)) { $items = array(); } + $clean = array(); + foreach ($items as $it) { + $s = trim((string)$it); + if ($s !== '') { $clean[] = $s; } + } + return $clean; + })(), + 'textWeights' => (function(){ + $map = get_option('yoone_snow_text_weights', array()); + if (!is_array($map)) { $map = array(); } + $clean = array(); + foreach ($map as $k => $v) { + $key = trim((string)$k); + $num = intval($v); + if ($key !== '') { + if ($num < 0) { $num = 0; } + $clean[$key] = $num; + } + } + return $clean; + })(), )); } @@ -181,30 +224,42 @@ function yoone_snow_admin_enqueue($hook) { // 在后台也注册并加载 shapes 渲染脚本 以供预览复用 // 形状索引与工具脚本 提供全局渲染器与图像加载能力 $shape_index_handle = 'yoone-snow-shapes-index'; - wp_register_script($shape_index_handle, plugins_url('js/shapes/index.js', __FILE__), array(), '1.1.0', true); + $ver_shapes_index = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/index.js'); + wp_register_script($shape_index_handle, plugins_url('js/shapes/index.js', __FILE__), array(), $ver_shapes_index ? (string)$ver_shapes_index : '1.1.0', true); $shape_utils_handle = 'yoone-snow-shapes-utils'; - wp_register_script($shape_utils_handle, plugins_url('js/shapes/utils.js', __FILE__), array($shape_index_handle), '1.1.0', true); + $ver_shapes_utils = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/utils.js'); + wp_register_script($shape_utils_handle, plugins_url('js/shapes/utils.js', __FILE__), array($shape_index_handle), $ver_shapes_utils ? (string)$ver_shapes_utils : '1.1.0', true); // 各具体形状脚本 注册为依赖以保证顺序 $shape_dot_handle = 'yoone-snow-shapes-dot'; - wp_register_script($shape_dot_handle, plugins_url('js/shapes/dot.js', __FILE__), array($shape_index_handle), '1.1.0', true); + $ver_shapes_dot = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/dot.js'); + wp_register_script($shape_dot_handle, plugins_url('js/shapes/dot.js', __FILE__), array($shape_index_handle), $ver_shapes_dot ? (string)$ver_shapes_dot : '1.1.0', true); $shape_flake_handle = 'yoone-snow-shapes-flake'; - wp_register_script($shape_flake_handle, plugins_url('js/shapes/flake.js', __FILE__), array($shape_index_handle), '1.1.0', true); + $ver_shapes_flake = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/flake.js'); + wp_register_script($shape_flake_handle, plugins_url('js/shapes/flake.js', __FILE__), array($shape_index_handle), $ver_shapes_flake ? (string)$ver_shapes_flake : '1.1.0', true); $shape_yuanbao_handle = 'yoone-snow-shapes-yuanbao'; - wp_register_script($shape_yuanbao_handle, plugins_url('js/shapes/yuanbao.js', __FILE__), array($shape_index_handle), '1.1.0', true); + $ver_shapes_yuanbao = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/yuanbao.js'); + wp_register_script($shape_yuanbao_handle, plugins_url('js/shapes/yuanbao.js', __FILE__), array($shape_index_handle), $ver_shapes_yuanbao ? (string)$ver_shapes_yuanbao : '1.1.0', true); $shape_coin_handle = 'yoone-snow-shapes-coin'; - wp_register_script($shape_coin_handle, plugins_url('js/shapes/coin.js', __FILE__), array($shape_index_handle), '1.1.0', true); + $ver_shapes_coin = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/coin.js'); + wp_register_script($shape_coin_handle, plugins_url('js/shapes/coin.js', __FILE__), array($shape_index_handle), $ver_shapes_coin ? (string)$ver_shapes_coin : '1.1.0', true); $shape_santa_handle = 'yoone-snow-shapes-santa-hat'; - wp_register_script($shape_santa_handle, plugins_url('js/shapes/santa_hat.js', __FILE__), array($shape_utils_handle), '1.1.0', true); + $ver_shapes_santa = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/santa_hat.js'); + wp_register_script($shape_santa_handle, plugins_url('js/shapes/santa_hat.js', __FILE__), array($shape_utils_handle), $ver_shapes_santa ? (string)$ver_shapes_santa : '1.1.0', true); $shape_cane_handle = 'yoone-snow-shapes-candy-cane'; - wp_register_script($shape_cane_handle, plugins_url('js/shapes/candy_cane.js', __FILE__), array($shape_utils_handle), '1.1.0', true); + $ver_shapes_cane = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/candy_cane.js'); + wp_register_script($shape_cane_handle, plugins_url('js/shapes/candy_cane.js', __FILE__), array($shape_utils_handle), $ver_shapes_cane ? (string)$ver_shapes_cane : '1.1.0', true); $shape_sock_handle = 'yoone-snow-shapes-christmas-sock'; - wp_register_script($shape_sock_handle, plugins_url('js/shapes/christmas_sock.js', __FILE__), array($shape_utils_handle), '1.1.0', true); + $ver_shapes_sock = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/christmas_sock.js'); + wp_register_script($shape_sock_handle, plugins_url('js/shapes/christmas_sock.js', __FILE__), array($shape_utils_handle), $ver_shapes_sock ? (string)$ver_shapes_sock : '1.1.0', true); $shape_tree_handle = 'yoone-snow-shapes-christmas-tree'; - wp_register_script($shape_tree_handle, plugins_url('js/shapes/christmas_tree.js', __FILE__), array($shape_utils_handle), '1.1.0', true); + $ver_shapes_tree = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/christmas_tree.js'); + wp_register_script($shape_tree_handle, plugins_url('js/shapes/christmas_tree.js', __FILE__), array($shape_utils_handle), $ver_shapes_tree ? (string)$ver_shapes_tree : '1.1.0', true); $shape_reindeer_handle = 'yoone-snow-shapes-reindeer'; - wp_register_script($shape_reindeer_handle, plugins_url('js/shapes/reindeer.js', __FILE__), array($shape_utils_handle), '1.1.0', true); + $ver_shapes_reindeer = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/reindeer.js'); + wp_register_script($shape_reindeer_handle, plugins_url('js/shapes/reindeer.js', __FILE__), array($shape_utils_handle), $ver_shapes_reindeer ? (string)$ver_shapes_reindeer : '1.1.0', true); $shape_berry_handle = 'yoone-snow-shapes-christmas-berry'; - wp_register_script($shape_berry_handle, plugins_url('js/shapes/christmas_berry.js', __FILE__), array($shape_utils_handle), '1.1.0', true); + $ver_shapes_berry = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/christmas_berry.js'); + wp_register_script($shape_berry_handle, plugins_url('js/shapes/christmas_berry.js', __FILE__), array($shape_utils_handle), $ver_shapes_berry ? (string)$ver_shapes_berry : '1.1.0', true); // 在后台本页入队 shapes 相关脚本 以便预览复用前端实现 wp_enqueue_script($shape_index_handle); @@ -252,7 +307,7 @@ function yoone_snow_admin_enqueue($hook) { $shape_reindeer_handle, $shape_berry_handle ), - '1.1.0', + (@filemtime(plugin_dir_path(__FILE__) . 'js/admin-media.js') ? (string)@filemtime(plugin_dir_path(__FILE__) . 'js/admin-media.js') : '1.1.0'), true ); // 传递资源映射用于后台形状预览 保持与前端一致 @@ -304,10 +359,9 @@ function yoone_snow_register_settings() { // 添加设置分区 标题为 Snow Settings add_settings_section( 'yoone_snow_section', - 'Snow Settings', + esc_html__('Snow Settings', 'yoone-snow'), function() { - // 输出分区描述 使用英文标点保证兼容 - echo '
Configure snow appearance
'; + echo '' . esc_html__('Configure snow appearance', 'yoone-snow') . '
'; }, 'yoone_snow' ); @@ -341,6 +395,8 @@ function yoone_snow_register_settings() { if (!is_array($current_media)) { $current_media = array(); } $current_emojis = get_option('yoone_snow_emoji_items', array()); if (!is_array($current_emojis)) { $current_emojis = array(); } + $current_texts = get_option('yoone_snow_text_items', array()); + if (!is_array($current_texts)) { $current_texts = array(); } // 读取当前权重值 用于在卡片中预填 $shape_weights_current = get_option('yoone_snow_shape_weights', array()); if (!is_array($shape_weights_current)) { $shape_weights_current = array(); } @@ -348,6 +404,8 @@ function yoone_snow_register_settings() { if (!is_array($media_weights_current)) { $media_weights_current = array(); } $emoji_weights_current = get_option('yoone_snow_emoji_weights', array()); if (!is_array($emoji_weights_current)) { $emoji_weights_current = array(); } + $text_weights_current = get_option('yoone_snow_text_weights', array()); + if (!is_array($text_weights_current)) { $text_weights_current = array(); } echo '