Compare commits

..

3 Commits

Author SHA1 Message Date
tikkhun a64c19750f feat(i18n): 添加中文翻译文件并国际化形状标签
refactor(admin): 使用国际化函数包装形状标签和设置字段
2025-12-12 11:21:06 +08:00
tikkhun 0855b22974 feat(i18n): 实现管理界面国际化支持并优化形状选择交互
添加多语言支持,将管理界面中的静态文本替换为可翻译字符串
优化形状选择交互,添加原生下拉列表和预览功能
补充形状描述的国际化文本和工具提示
2025-12-12 11:16:04 +08:00
tikkhun 7ce8be8bf9 feat(admin): 添加形状选择预览功能并优化交互
在形状选择下拉框旁添加预览区域,显示当前选中形状的预览图像
优化类型切换时的预览容器状态管理
2025-12-12 11:06:04 +08:00
4 changed files with 345 additions and 49 deletions

View File

@ -19,6 +19,8 @@
var emojiSuggestBox = document.getElementById('yooneSnowEmojiSuggest'); var emojiSuggestBox = document.getElementById('yooneSnowEmojiSuggest');
var shapeListContainer = document.getElementById('yooneSnowShapeList'); var shapeListContainer = document.getElementById('yooneSnowShapeList');
var shapeAddSelect = document.getElementById('yooneSnowAddShapeSelect'); var shapeAddSelect = document.getElementById('yooneSnowAddShapeSelect');
var shapeLabelPreviewHost = document.getElementById('yooneSnowAddShapeLabelPreview');
var shapeNativeOverlay = document.getElementById('yooneSnowShapeNativeOverlay');
var shapeAddButton = null; var shapeAddButton = null;
var emojiSelect = document.getElementById('yooneSnowEmojiSelect'); var emojiSelect = document.getElementById('yooneSnowEmojiSelect');
var typeSelect = document.getElementById('yooneSnowAddTypeSelect'); var typeSelect = document.getElementById('yooneSnowAddTypeSelect');
@ -36,7 +38,7 @@
// 权重输入已整合到卡片内部 旧容器不再使用 // 权重输入已整合到卡片内部 旧容器不再使用
// 形状标签映射用于显示友好名称 // 形状标签映射用于显示友好名称
var shapeLabelsMap = { var shapeLabelsMap = (typeof window !== 'undefined' && window.YooneSnowAdmin && window.YooneSnowAdmin.shapeLabels) ? window.YooneSnowAdmin.shapeLabels : {
dot: 'Dot', dot: 'Dot',
flake: 'Snowflake', flake: 'Snowflake',
yuanbao: 'Yuanbao', yuanbao: 'Yuanbao',
@ -193,7 +195,7 @@
var cancelBtn = document.createElement('button'); var cancelBtn = document.createElement('button');
cancelBtn.type = 'button'; cancelBtn.type = 'button';
cancelBtn.className = 'button yoone-snow-cancel-shape'; cancelBtn.className = 'button yoone-snow-cancel-shape';
cancelBtn.textContent = 'Cancel'; cancelBtn.textContent = t('cancel', 'Cancel');
cancelBtn.style.marginTop = '6px'; cancelBtn.style.marginTop = '6px';
wrapper.appendChild(previewHost); wrapper.appendChild(previewHost);
wrapper.appendChild(nameSpan); wrapper.appendChild(nameSpan);
@ -244,7 +246,7 @@
// 条件判断 如果 wp.media 不可用则终止 // 条件判断 如果 wp.media 不可用则终止
if (typeof wp === 'undefined' || !wp.media) { return; } if (typeof wp === 'undefined' || !wp.media) { return; }
var frame = wp.media({ var frame = wp.media({
title: 'Select images or SVG', title: t('select_images_or_svg', 'Select images or SVG'),
multiple: true, multiple: true,
library: { type: [ 'image', 'image/svg+xml' ] } library: { type: [ 'image', 'image/svg+xml' ] }
}); });
@ -283,7 +285,7 @@
var removeBtn = document.createElement('button'); var removeBtn = document.createElement('button');
removeBtn.type = 'button'; removeBtn.type = 'button';
removeBtn.className = 'button yoone-snow-remove-media'; removeBtn.className = 'button yoone-snow-remove-media';
removeBtn.textContent = 'Remove'; removeBtn.textContent = t('remove', 'Remove');
removeBtn.style.marginTop = '6px'; removeBtn.style.marginTop = '6px';
wrapper.appendChild(img); wrapper.appendChild(img);
wrapper.appendChild(weightInput); wrapper.appendChild(weightInput);
@ -423,7 +425,7 @@
var removeBtn = document.createElement('button'); var removeBtn = document.createElement('button');
removeBtn.type = 'button'; removeBtn.type = 'button';
removeBtn.className = 'button yoone-snow-remove-emoji'; removeBtn.className = 'button yoone-snow-remove-emoji';
removeBtn.textContent = 'Cancel'; removeBtn.textContent = t('cancel', 'Cancel');
removeBtn.style.marginTop = '6px'; removeBtn.style.marginTop = '6px';
wrapper.appendChild(preview); wrapper.appendChild(preview);
wrapper.appendChild(weightInput); wrapper.appendChild(weightInput);
@ -530,13 +532,33 @@
} }
if (shapeAddSelect){ if (shapeAddSelect){
// 监听默认形状下拉变化 用于更新左侧图像预览并添加卡片
shapeAddSelect.addEventListener('change', function(){ shapeAddSelect.addEventListener('change', function(){
var val = shapeAddSelect.value; var val = shapeAddSelect.value;
if (String(val).trim() !== ''){ if (String(val).trim() !== ''){
// 更新左侧预览 显示当前选择形状的图像或渲染预览
if (shapeLabelPreviewHost){
while (shapeLabelPreviewHost.firstChild){ shapeLabelPreviewHost.removeChild(shapeLabelPreviewHost.firstChild); }
var previewEl = createShapePreviewElement(val);
if (previewEl){
try { previewEl.style.backgroundColor = 'transparent'; } catch(e){}
try { previewEl.style.border = 'none'; } catch(e){}
try { previewEl.style.borderRadius = '0'; } catch(e){}
shapeLabelPreviewHost.style.display = 'flex';
shapeLabelPreviewHost.appendChild(previewEl);
}
}
// 添加形状卡片到列表
addShapeBox(val); addShapeBox(val);
// 重置下拉并清空预览 保持与未选中状态一致
shapeAddSelect.value = ''; shapeAddSelect.value = '';
if (shapeLabelPreviewHost){
while (shapeLabelPreviewHost.firstChild){ shapeLabelPreviewHost.removeChild(shapeLabelPreviewHost.firstChild); }
shapeLabelPreviewHost.style.display = 'none';
}
} }
}); });
// 僅使用原生下拉 不再彈出自定義覆蓋列表
} }
@ -549,6 +571,11 @@
paneEmoji.style.display = (t === 'emoji') ? 'flex' : 'none'; paneEmoji.style.display = (t === 'emoji') ? 'flex' : 'none';
paneMedia.style.display = (t === 'media') ? 'flex' : 'none'; paneMedia.style.display = (t === 'media') ? 'flex' : 'none';
paneText.style.display = (t === 'text') ? 'flex' : 'none'; paneText.style.display = (t === 'text') ? 'flex' : 'none';
if (shapeLabelPreviewHost){
// 切換類型時預覽容器默認隱藏 僅在選中形狀時顯示
shapeLabelPreviewHost.style.display = 'none';
while (shapeLabelPreviewHost.firstChild){ shapeLabelPreviewHost.removeChild(shapeLabelPreviewHost.firstChild); }
}
} }
if (typeSelect){ if (typeSelect){
typeSelect.value = 'default'; typeSelect.value = 'default';
@ -599,7 +626,7 @@
var removeBtn = document.createElement('button'); var removeBtn = document.createElement('button');
removeBtn.type = 'button'; removeBtn.type = 'button';
removeBtn.className = 'button yoone-snow-remove-text'; removeBtn.className = 'button yoone-snow-remove-text';
removeBtn.textContent = 'Cancel'; removeBtn.textContent = t('cancel', 'Cancel');
removeBtn.style.marginTop = '6px'; removeBtn.style.marginTop = '6px';
wrapper.appendChild(preview); wrapper.appendChild(preview);
wrapper.appendChild(weightInput); wrapper.appendChild(weightInput);
@ -640,3 +667,54 @@
initAdminMedia(); initAdminMedia();
} }
})(); })();
// 初始化內嵌下拉的預覽與點擊交互
(function(){
if (!shapeNativeOverlay) { return; }
var nodes = shapeNativeOverlay.querySelectorAll('.yoone-snow-shape-native-item');
nodes.forEach(function(btn){
var key = btn.getAttribute('data-shape-key');
var host = btn.querySelector('.yoone-snow-shape-native-preview');
if (host && host.childNodes && host.childNodes.length === 0){
var previewEl = createShapePreviewElement(key);
if (previewEl){
try { previewEl.style.backgroundColor = 'transparent'; } catch(e){}
try { previewEl.style.border = 'none'; } catch(e){}
try { previewEl.style.borderRadius = '0'; } catch(e){}
host.appendChild(previewEl);
}
}
btn.addEventListener('click', function(){
// 先更新左側預覽 再添加卡片 並關閉內嵌下拉
if (shapeLabelPreviewHost){
while (shapeLabelPreviewHost.firstChild){ shapeLabelPreviewHost.removeChild(shapeLabelPreviewHost.firstChild); }
var previewEl2 = createShapePreviewElement(key);
if (previewEl2){
try { previewEl2.style.backgroundColor = 'transparent'; } catch(e){}
try { previewEl2.style.border = 'none'; } catch(e){}
try { previewEl2.style.borderRadius = '0'; } catch(e){}
shapeLabelPreviewHost.style.display = 'flex';
shapeLabelPreviewHost.appendChild(previewEl2);
}
}
addShapeBox(key);
if (shapeNativeOverlay){ shapeNativeOverlay.style.display = 'none'; }
// 重置原生下拉為未選中 清空左側預覽 保持簡潔
if (shapeAddSelect){ shapeAddSelect.value = ''; }
if (shapeLabelPreviewHost){
while (shapeLabelPreviewHost.firstChild){ shapeLabelPreviewHost.removeChild(shapeLabelPreviewHost.firstChild); }
shapeLabelPreviewHost.style.display = 'none';
}
});
});
// 外部點擊時關閉內嵌下拉
document.addEventListener('click', function(e){
var t = e.target;
var inside = (t === shapeNativeOverlay || shapeNativeOverlay.contains(t) || (shapeAddSelect && (t === shapeAddSelect || shapeAddSelect.contains(t))));
if (!inside){ shapeNativeOverlay.style.display = 'none'; }
});
})();
function t(key, fallback){
var dict = (typeof window !== 'undefined' && window.YooneSnowAdmin && window.YooneSnowAdmin.i18n) ? window.YooneSnowAdmin.i18n : {};
var val = dict[key];
return String(val || fallback || '');
}

Binary file not shown.

View File

@ -0,0 +1,168 @@
msgid ""
msgstr ""
"Project-Id-Version: yoone-snow\n"
"Language: zh_CN\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=1; plural=0;\n"
# Plugin and menu
msgid "Yoone Snow"
msgstr "Yoone Snow"
msgid "Settings"
msgstr "设置"
# Section and fields
msgid "Snow Settings"
msgstr "雪花设置"
msgid "Configure snow appearance"
msgstr "配置雪花外观"
msgid "Shapes"
msgstr "形状"
msgid "Type"
msgstr "类型"
msgid "Default"
msgstr "默认"
msgid "Emoji"
msgstr "Emoji"
msgid "Media"
msgstr "媒体"
msgid "Text"
msgstr "文本"
msgid "Select shape"
msgstr "选择形状"
msgid "Select emoji"
msgstr "选择 Emoji"
msgid "Type emoji or alias"
msgstr "输入 Emoji 或别名"
msgid "Add Images"
msgstr "添加图片"
msgid "Remove"
msgstr "移除"
msgid "Cancel"
msgstr "取消"
msgid "Add shapes by type all in one list"
msgstr "按类型统一添加到列表"
msgid "Add different types of shapes here"
msgstr "在此添加不同类型的形状"
# Shape labels
msgid "Dot"
msgstr "圆点"
msgid "Snowflake"
msgstr "雪花"
msgid "Yuanbao"
msgstr "元宝"
msgid "Coin"
msgstr "金币"
msgid "Santa Hat"
msgstr "圣诞帽"
msgid "Candy Cane"
msgstr "拐杖糖"
msgid "Christmas Sock"
msgstr "圣诞袜"
msgid "Christmas Tree"
msgstr "圣诞树"
msgid "Reindeer"
msgstr "麋鹿"
msgid "Christmas Berry"
msgstr "冬青果"
# Shape descriptions
msgid "Basic dot shape simple and lightweight"
msgstr "基础圆点形状 简洁轻量"
msgid "Snowflake shape more decorative"
msgstr "雪花形状 更具装饰性"
msgid "Yuanbao shape festive theme"
msgstr "元宝形状 节日气氛"
msgid "Coin shape festive theme"
msgstr "金币形状 节日气氛"
msgid "Santa hat shape seasonal theme"
msgstr "圣诞帽形状 应季主题"
msgid "Candy cane shape seasonal theme"
msgstr "拐杖糖形状 应季主题"
msgid "Christmas sock shape seasonal theme"
msgstr "圣诞袜形状 应季主题"
msgid "Christmas tree shape seasonal theme"
msgstr "圣诞树形状 应季主题"
msgid "Reindeer shape seasonal theme"
msgstr "麋鹿形状 应季主题"
msgid "Christmas berry shape seasonal theme"
msgstr "冬青果形状 应季主题"
# Weight explanation
msgid "Weight controls relative probability Weight is a non negative integer Weight 0 disables a shape Probability equals shape weight divided by the sum of all shape weights Example dot 1 flake 4 flake has about four times the chance of dot"
msgstr "权重用于控制随机生成的相对概率 权重为非负整数 权重为 0 表示禁用 某形状概率等于其权重除以所有形状权重之和 例如 dot 1 flake 4 flake 概率约为 dot 的四倍"
# Other settings
msgid "Home Display Duration Seconds"
msgstr "首页显示时长 秒"
msgid "Duration in seconds for snow on home 0 means infinite"
msgstr "首页雪花显示时长 单位秒 0 表示无限"
msgid "Max Snowflakes On Screen"
msgstr "在屏最大雪花数量"
msgid "0 means auto based on viewport area upper bound 1000"
msgstr "0 表示根据视口面积自动 上限 1000"
msgid "Snow Size"
msgstr "雪花尺寸"
msgid "Random radius in [min max] single option"
msgstr "随机半径在最小与最大之间 单一选项保存"
msgid "Min"
msgstr "最小"
msgid "Max"
msgstr "最大"
msgid "Drift Speed Random Range"
msgstr "漂移速度随机范围"
msgid "Random vertical drift speed base in [min max]"
msgstr "垂直漂移速度基值在最小与最大之间随机"
msgid "Swing Amplitude Random Range"
msgstr "摆动幅度随机范围"
msgid "Random horizontal swing amplitude base in [min max] before offset scale"
msgstr "水平摆动幅度基值在最小与最大之间随机 应用偏移缩放前"
msgid "Select images or SVG"
msgstr "选择图片或 SVG"

View File

@ -113,6 +113,9 @@ function yoone_snow_enqueue_assets() {
$media_weights_by_url[$url] = $w; $media_weights_by_url[$url] = $w;
} }
} }
// 形状权重默认值 映射为形状键到非负整数权重
// 权重用于控制每次生成时的相对概率 权重越大被选中越频繁
// 权重为 0 表示可选但不参与随机生成
$default_weights = array( $default_weights = array(
'dot' => 1, 'dot' => 1,
'flake' => 4, 'flake' => 4,
@ -127,6 +130,7 @@ function yoone_snow_enqueue_assets() {
); );
$saved_weights = get_option('yoone_snow_shape_weights', $default_weights); $saved_weights = get_option('yoone_snow_shape_weights', $default_weights);
if (!is_array($saved_weights)) { $saved_weights = array(); } if (!is_array($saved_weights)) { $saved_weights = array(); }
// 合并用户保存的权重与默认值 并规范为非负整数
$shape_weights = array(); $shape_weights = array();
foreach ($default_weights as $k => $v) { foreach ($default_weights as $k => $v) {
$val = isset($saved_weights[$k]) ? intval($saved_weights[$k]) : intval($v); $val = isset($saved_weights[$k]) ? intval($saved_weights[$k]) : intval($v);
@ -319,6 +323,35 @@ function yoone_snow_admin_enqueue($hook) {
'christmas_tree' => plugins_url('assets/圣诞树.svg', __FILE__), 'christmas_tree' => plugins_url('assets/圣诞树.svg', __FILE__),
'reindeer' => plugins_url('assets/圣诞麋鹿.svg', __FILE__), 'reindeer' => plugins_url('assets/圣诞麋鹿.svg', __FILE__),
'christmas_berry' => plugins_url('assets/圣诞果.svg', __FILE__), 'christmas_berry' => plugins_url('assets/圣诞果.svg', __FILE__),
),
'shapeLabels' => array(
'dot' => esc_html__('Dot', 'yoone-snow'),
'flake' => esc_html__('Snowflake', 'yoone-snow'),
'yuanbao' => esc_html__('Yuanbao', 'yoone-snow'),
'coin' => esc_html__('Coin', 'yoone-snow'),
'santa_hat' => esc_html__('Santa Hat', 'yoone-snow'),
'candy_cane' => esc_html__('Candy Cane', 'yoone-snow'),
'christmas_sock' => esc_html__('Christmas Sock', 'yoone-snow'),
'christmas_tree' => esc_html__('Christmas Tree', 'yoone-snow'),
'reindeer' => esc_html__('Reindeer', 'yoone-snow'),
'christmas_berry' => esc_html__('Christmas Berry', 'yoone-snow'),
),
'i18n' => array(
'cancel' => esc_html__('Cancel', 'yoone-snow'),
'remove' => esc_html__('Remove', 'yoone-snow'),
'select_images_or_svg' => esc_html__('Select images or SVG', 'yoone-snow'),
'type' => esc_html__('Type', 'yoone-snow'),
'default' => esc_html__('Default', 'yoone-snow'),
'emoji' => esc_html__('Emoji', 'yoone-snow'),
'media' => esc_html__('Media', 'yoone-snow'),
'text' => esc_html__('Text', 'yoone-snow'),
'select_shape' => esc_html__('Select shape', 'yoone-snow'),
'select_emoji' => esc_html__('Select emoji', 'yoone-snow'),
'add_images' => esc_html__('Add Images', 'yoone-snow'),
'type_text' => esc_html__('Type text', 'yoone-snow'),
'type_emoji_or_alias' => esc_html__('Type emoji or alias', 'yoone-snow'),
'add_shapes_all_in_one' => esc_html__('Add shapes by type all in one list', 'yoone-snow'),
'shapes' => esc_html__('Shapes', 'yoone-snow')
) )
)); ));
wp_enqueue_script($admin_script_handle); wp_enqueue_script($admin_script_handle);
@ -370,7 +403,7 @@ function yoone_snow_register_settings() {
add_settings_field( add_settings_field(
'yoone_snow_mixed_items', 'yoone_snow_mixed_items',
'Shapes', esc_html__('Shapes', 'yoone-snow'),
function() { function() {
// 读取默认形状列表 用于初始渲染卡片 // 读取默认形状列表 用于初始渲染卡片
$current_list = get_option('yoone_snow_mixed_items', array('dot','flake')); $current_list = get_option('yoone_snow_mixed_items', array('dot','flake'));
@ -379,16 +412,29 @@ function yoone_snow_register_settings() {
} }
if (!is_array($current_list)) { $current_list = array('dot','flake'); } if (!is_array($current_list)) { $current_list = array('dot','flake'); }
$options = array( $options = array(
'dot' => 'Dot', 'dot' => esc_html__('Dot', 'yoone-snow'),
'flake' => 'Snowflake', 'flake' => esc_html__('Snowflake', 'yoone-snow'),
'yuanbao' => 'Yuanbao', 'yuanbao' => esc_html__('Yuanbao', 'yoone-snow'),
'coin' => 'Coin', 'coin' => esc_html__('Coin', 'yoone-snow'),
'santa_hat' => 'Santa Hat', 'santa_hat' => esc_html__('Santa Hat', 'yoone-snow'),
'candy_cane' => 'Candy Cane', 'candy_cane' => esc_html__('Candy Cane', 'yoone-snow'),
'christmas_sock' => 'Christmas Sock', 'christmas_sock' => esc_html__('Christmas Sock', 'yoone-snow'),
'christmas_tree' => 'Christmas Tree', 'christmas_tree' => esc_html__('Christmas Tree', 'yoone-snow'),
'reindeer' => 'Reindeer', 'reindeer' => esc_html__('Reindeer', 'yoone-snow'),
'christmas_berry' => 'Christmas Berry', 'christmas_berry' => esc_html__('Christmas Berry', 'yoone-snow'),
);
// 形状描述映射 用于在界面提示 使用国际化函数
$shape_descriptions = array(
'dot' => __('Basic dot shape simple and lightweight', 'yoone-snow'),
'flake' => __('Snowflake shape more decorative', 'yoone-snow'),
'yuanbao' => __('Yuanbao shape festive theme', 'yoone-snow'),
'coin' => __('Coin shape festive theme', 'yoone-snow'),
'santa_hat' => __('Santa hat shape seasonal theme', 'yoone-snow'),
'candy_cane' => __('Candy cane shape seasonal theme', 'yoone-snow'),
'christmas_sock' => __('Christmas sock shape seasonal theme', 'yoone-snow'),
'christmas_tree' => __('Christmas tree shape seasonal theme', 'yoone-snow'),
'reindeer' => __('Reindeer shape seasonal theme', 'yoone-snow'),
'christmas_berry' => __('Christmas berry shape seasonal theme', 'yoone-snow'),
); );
// 读取已选择的媒体与 emoji 列表 用于统一卡片渲染 // 读取已选择的媒体与 emoji 列表 用于统一卡片渲染
$current_media = get_option('yoone_snow_media_items', array()); $current_media = get_option('yoone_snow_media_items', array());
@ -411,13 +457,13 @@ function yoone_snow_register_settings() {
// 渲染默认形状卡片 // 渲染默认形状卡片
foreach ($current_list as $key) { foreach ($current_list as $key) {
if (!isset($options[$key])) { continue; } if (!isset($options[$key])) { continue; }
echo '<div class="yoone-snow-shape-item" data-shape-key="' . esc_attr($key) . '" style="border:1px solid #ddd;padding:8px;display:flex;flex-direction:column;align-items:center;min-width:96px;">'; echo '<div class="yoone-snow-shape-item" data-shape-key="' . esc_attr($key) . '" title="' . esc_attr(isset($shape_descriptions[$key]) ? $shape_descriptions[$key] : '') . '" style="border:1px solid #ddd;padding:8px;display:flex;flex-direction:column;align-items:center;min-width:96px;">';
echo '<div class="yoone-snow-shape-preview" style="width:32px;height:32px;margin-bottom:6px;background-color:#e6e6e6;border:1px solid #ddd;border-radius:4px;"></div>'; echo '<div class="yoone-snow-shape-preview" style="width:32px;height:32px;margin-bottom:6px;background-color:#e6e6e6;border:1px solid #ddd;border-radius:4px;"></div>';
echo '<span>' . esc_html($options[$key]) . '</span>'; echo '<span>' . esc_html($options[$key]) . '</span>';
$shapeWeightVal = isset($shape_weights_current[$key]) ? intval($shape_weights_current[$key]) : 1; $shapeWeightVal = isset($shape_weights_current[$key]) ? intval($shape_weights_current[$key]) : 1;
echo '<input type="number" min="0" name="yoone_snow_shape_weights[' . esc_attr($key) . ']" value="' . esc_attr($shapeWeightVal) . '" style="width:120px;margin-top:6px;" />'; echo '<input type="number" min="0" name="yoone_snow_shape_weights[' . esc_attr($key) . ']" value="' . esc_attr($shapeWeightVal) . '" style="width:120px;margin-top:6px;" />';
echo '<input type="hidden" name="yoone_snow_mixed_items[]" value="' . esc_attr($key) . '" />'; echo '<input type="hidden" name="yoone_snow_mixed_items[]" value="' . esc_attr($key) . '" />';
echo '<button type="button" class="button yoone-snow-cancel-shape" style="margin-top:6px;">Cancel</button>'; echo '<button type="button" class="button yoone-snow-cancel-shape" style="margin-top:6px;">' . esc_html__('Cancel', 'yoone-snow') . '</button>';
echo '</div>'; echo '</div>';
} }
// 渲染 emoji 卡片 // 渲染 emoji 卡片
@ -429,7 +475,7 @@ function yoone_snow_register_settings() {
$emojiWeightVal = isset($emoji_weights_current[$label]) ? intval($emoji_weights_current[$label]) : 1; $emojiWeightVal = isset($emoji_weights_current[$label]) ? intval($emoji_weights_current[$label]) : 1;
echo '<input type="number" min="0" name="yoone_snow_emoji_weights[' . esc_attr($label) . ']" value="' . esc_attr($emojiWeightVal) . '" style="width:120px;margin-top:6px;" />'; echo '<input type="number" min="0" name="yoone_snow_emoji_weights[' . esc_attr($label) . ']" value="' . esc_attr($emojiWeightVal) . '" style="width:120px;margin-top:6px;" />';
echo '<input type="hidden" name="yoone_snow_emoji_items[]" value="' . esc_attr($label) . '" />'; echo '<input type="hidden" name="yoone_snow_emoji_items[]" value="' . esc_attr($label) . '" />';
echo '<button type="button" class="button yoone-snow-remove-emoji" style="margin-top:6px;">Cancel</button>'; echo '<button type="button" class="button yoone-snow-remove-emoji" style="margin-top:6px;">' . esc_html__('Cancel', 'yoone-snow') . '</button>';
echo '</div>'; echo '</div>';
} }
// 渲染媒体卡片 // 渲染媒体卡片
@ -443,7 +489,7 @@ function yoone_snow_register_settings() {
$mediaWeightVal = isset($media_weights_current[$safe_id]) ? intval($media_weights_current[$safe_id]) : 1; $mediaWeightVal = isset($media_weights_current[$safe_id]) ? intval($media_weights_current[$safe_id]) : 1;
echo '<input type="number" min="0" name="yoone_snow_media_weights[' . $safe_id . ']" value="' . esc_attr($mediaWeightVal) . '" style="width:120px;margin-top:6px;" />'; echo '<input type="number" min="0" name="yoone_snow_media_weights[' . $safe_id . ']" value="' . esc_attr($mediaWeightVal) . '" style="width:120px;margin-top:6px;" />';
echo '<input type="hidden" name="yoone_snow_media_items[]" value="' . $safe_id . '" />'; echo '<input type="hidden" name="yoone_snow_media_items[]" value="' . $safe_id . '" />';
echo '<button type="button" class="button yoone-snow-remove-media" style="margin-top:6px;">Remove</button>'; echo '<button type="button" class="button yoone-snow-remove-media" style="margin-top:6px;">' . esc_html__('Remove', 'yoone-snow') . '</button>';
echo '</div>'; echo '</div>';
} }
// 文本卡片 // 文本卡片
@ -455,40 +501,44 @@ function yoone_snow_register_settings() {
$textWeightVal = isset($text_weights_current[$label]) ? intval($text_weights_current[$label]) : 1; $textWeightVal = isset($text_weights_current[$label]) ? intval($text_weights_current[$label]) : 1;
echo '<input type="number" min="0" name="yoone_snow_text_weights[' . esc_attr($label) . ']" value="' . esc_attr($textWeightVal) . '" style="width:120px;margin-top:6px;" />'; echo '<input type="number" min="0" name="yoone_snow_text_weights[' . esc_attr($label) . ']" value="' . esc_attr($textWeightVal) . '" style="width:120px;margin-top:6px;" />';
echo '<input type="hidden" name="yoone_snow_text_items[]" value="' . esc_attr($label) . '" />'; echo '<input type="hidden" name="yoone_snow_text_items[]" value="' . esc_attr($label) . '" />';
echo '<button type="button" class="button yoone-snow-remove-text" style="margin-top:6px;">Cancel</button>'; echo '<button type="button" class="button yoone-snow-remove-text" style="margin-top:6px;">' . esc_html__('Cancel', 'yoone-snow') . '</button>';
echo '</div>'; echo '</div>';
} }
echo '<div id="yooneSnowAddUnified" class="yoone-snow-shape-item" style="border:1px solid #ddd;padding:8px;display:flex;flex-direction:column;align-items:center;gap:8px;min-width:96px;">'; echo '<div id="yooneSnowAddUnified" class="yoone-snow-shape-item" style="border:1px solid #ddd;padding:8px;display:flex;flex-direction:column;align-items:center;gap:8px;min-width:96px;">';
echo '<div style="display:flex;gap:8px;align-items:center;">'; echo '<div style="display:flex;gap:8px;align-items:center;">';
echo '<label>Type <select id="yooneSnowAddTypeSelect" style="min-width:180px;">'; echo '<label>' . esc_html__('Type', 'yoone-snow') . ' <select id="yooneSnowAddTypeSelect" style="min-width:180px;">';
echo '<option value="default" selected>Default</option>'; echo '<option value="default" selected>' . esc_html__('Default', 'yoone-snow') . '</option>';
echo '<option value="emoji">Emoji</option>'; echo '<option value="emoji">' . esc_html__('Emoji', 'yoone-snow') . '</option>';
echo '<option value="media">Media</option>'; echo '<option value="media">' . esc_html__('Media', 'yoone-snow') . '</option>';
echo '<option value="text">Text</option>'; echo '<option value="text">' . esc_html__('Text', 'yoone-snow') . '</option>';
echo '</select></label>'; echo '</select></label>';
echo '</div>'; echo '</div>';
echo '<div id="yooneSnowAddDefaultPane" style="display:flex;gap:8px;align-items:center;position:relative;">'; echo '<div id="yooneSnowAddDefaultPane" style="display:flex;gap:8px;align-items:center;position:relative;">';
echo '<select id="yooneSnowAddShapeSelect" style="min-width:240px;"><option value="">Select shape</option>'; echo '<div id="yooneSnowAddShapeLabelPreview" style="width:32px;height:32px;display:none;align-items:center;justify-content:center;"></div>';
echo '<select id="yooneSnowAddShapeSelect" style="min-width:240px;"><option value="">' . esc_html__('Select shape', 'yoone-snow') . '</option>';
foreach ($options as $key => $label) { foreach ($options as $key => $label) {
echo '<option value="' . esc_attr($key) . '">' . esc_html($label) . '</option>'; echo '<option value="' . esc_attr($key) . '">' . esc_html($label) . '</option>';
} }
echo '</select>'; echo '</select>';
echo '</div>'; echo '</div>';
echo '<div id="yooneSnowAddEmojiPane" style="display:none;gap:8px;align-items:center;">'; echo '<div id="yooneSnowAddEmojiPane" style="display:none;gap:8px;align-items:center;">';
echo '<select id="yooneSnowEmojiSelect" style="min-width:240px;"><option value="">Select emoji</option></select>'; echo '<select id="yooneSnowEmojiSelect" style="min-width:240px;"><option value="">' . esc_html__('Select emoji', 'yoone-snow') . '</option></select>';
echo '<input type="text" id="yooneSnowAddEmojiInput" placeholder="Type emoji or alias" style="width:240px;" />'; echo '<input type="text" id="yooneSnowAddEmojiInput" placeholder="' . esc_attr__('Type emoji or alias', 'yoone-snow') . '" style="width:240px;" />';
echo '<div id="yooneSnowEmojiSuggest" style="margin-top:6px;display:flex;flex-wrap:wrap;gap:6px;"></div>'; echo '<div id="yooneSnowEmojiSuggest" style="margin-top:6px;display:flex;flex-wrap:wrap;gap:6px;"></div>';
echo '</div>'; echo '</div>';
echo '<div id="yooneSnowAddMediaPane" style="display:none;gap:8px;align-items:center;">'; echo '<div id="yooneSnowAddMediaPane" style="display:none;gap:8px;align-items:center;">';
echo '<button type="button" class="button button-primary" id="yooneSnowAddMediaUnified">Add Images</button>'; echo '<button type="button" class="button button-primary" id="yooneSnowAddMediaUnified">' . esc_html__('Add Images', 'yoone-snow') . '</button>';
echo '</div>'; echo '</div>';
echo '<div id="yooneSnowAddTextPane" style="display:none;gap:8px;align-items:center;">'; echo '<div id="yooneSnowAddTextPane" style="display:none;gap:8px;align-items:center;">';
echo '<input type="text" id="yooneSnowAddTextInput" placeholder="Type text" style="width:240px;" />'; echo '<input type="text" id="yooneSnowAddTextInput" placeholder="' . esc_attr__('Type text', 'yoone-snow') . '" style="width:240px;" />';
echo '</div>'; echo '</div>';
echo '<p class="description">Add shapes by type all in one list</p>'; // 权重说明 提示用户权重影响概率且为非负整数
echo '<p class="description">' . esc_html__('Add shapes by type all in one list', 'yoone-snow') . '</p>';
echo '</div>'; echo '</div>';
echo '</div>'; echo '</div>';
echo '<p class="description">' . esc_html__('Weight controls relative probability Weight is a non negative integer Weight 0 disables a shape Probability equals shape weight divided by the sum of all shape weights Example dot 1 flake 4 flake has about four times the chance of dot', 'yoone-snow') . '</p>';
}, },
'yoone_snow', 'yoone_snow',
'yoone_snow_section' 'yoone_snow_section'
@ -670,12 +720,12 @@ function yoone_snow_register_settings() {
// 添加首页显示时长字段 输入为数字最小值为 0 // 添加首页显示时长字段 输入为数字最小值为 0
add_settings_field( add_settings_field(
'yoone_snow_home_duration', 'yoone_snow_home_duration',
'Home Display Duration Seconds', esc_html__('Home Display Duration Seconds', 'yoone-snow'),
function() { function() {
// 读取当前设置值 并渲染数字输入框 // 读取当前设置值 并渲染数字输入框
$current = intval(get_option('yoone_snow_home_duration', 0)); $current = intval(get_option('yoone_snow_home_duration', 0));
echo '<input type="number" min="0" name="yoone_snow_home_duration" value="' . esc_attr($current) . '" style="width:120px;" />'; echo '<input type="number" min="0" name="yoone_snow_home_duration" value="' . esc_attr($current) . '" style="width:120px;" />';
echo '<p class="description">Duration in seconds for snow on home 0 means infinite</p>'; echo '<p class="description">' . esc_html__('Duration in seconds for snow on home 0 means infinite', 'yoone-snow') . '</p>';
}, },
'yoone_snow', 'yoone_snow',
'yoone_snow_section' 'yoone_snow_section'
@ -697,11 +747,11 @@ function yoone_snow_register_settings() {
// 添加输入字段 用于设置在屏最大数量 0 表示自动 // 添加输入字段 用于设置在屏最大数量 0 表示自动
add_settings_field( add_settings_field(
'yoone_snow_max_count', 'yoone_snow_max_count',
'Max Snowflakes On Screen', esc_html__('Max Snowflakes On Screen', 'yoone-snow'),
function() { function() {
$current = intval(get_option('yoone_snow_max_count', 0)); $current = intval(get_option('yoone_snow_max_count', 0));
echo '<input type="number" min="0" step="1" name="yoone_snow_max_count" value="' . esc_attr($current) . '" style="width:120px;" />'; echo '<input type="number" min="0" step="1" name="yoone_snow_max_count" value="' . esc_attr($current) . '" style="width:120px;" />';
echo '<p class="description">0 means auto based on viewport area upper bound 1000</p>'; echo '<p class="description">' . esc_html__('0 means auto based on viewport area upper bound 1000', 'yoone-snow') . '</p>';
}, },
'yoone_snow', 'yoone_snow',
'yoone_snow_section' 'yoone_snow_section'
@ -724,15 +774,15 @@ function yoone_snow_register_settings() {
add_settings_field( add_settings_field(
'yoone_snow_size', 'yoone_snow_size',
'Snow Size', esc_html__('Snow Size', 'yoone-snow'),
function() { function() {
// 渲染组合输入 使用同一选项保存最小与最大半径 // 渲染组合输入 使用同一选项保存最小与最大半径
$grp = get_option('yoone_snow_size', array('min' => 1.0, 'max' => 3.0)); $grp = get_option('yoone_snow_size', array('min' => 1.0, 'max' => 3.0));
$min = isset($grp['min']) ? floatval($grp['min']) : 1.0; $min = isset($grp['min']) ? floatval($grp['min']) : 1.0;
$max = isset($grp['max']) ? floatval($grp['max']) : 3.0; $max = isset($grp['max']) ? floatval($grp['max']) : 3.0;
echo '<label style="margin-right:12px;">Min <input type="number" min="0" step="0.1" name="yoone_snow_size[min]" value="' . esc_attr($min) . '" style="width:120px;" /></label>'; echo '<label style="margin-right:12px;">' . esc_html__('Min', 'yoone-snow') . ' <input type="number" min="0" step="0.1" name="yoone_snow_size[min]" value="' . esc_attr($min) . '" style="width:120px;" /></label>';
echo '<label>Max <input type="number" min="0" step="0.1" name="yoone_snow_size[max]" value="' . esc_attr($max) . '" style="width:120px;" /></label>'; echo '<label>' . esc_html__('Max', 'yoone-snow') . ' <input type="number" min="0" step="0.1" name="yoone_snow_size[max]" value="' . esc_attr($max) . '" style="width:120px;" /></label>';
echo '<p class="description">Random radius in [min max] single option</p>'; echo '<p class="description">' . esc_html__('Random radius in [min max] single option', 'yoone-snow') . '</p>';
}, },
'yoone_snow', 'yoone_snow',
'yoone_snow_section' 'yoone_snow_section'
@ -761,13 +811,13 @@ function yoone_snow_register_settings() {
)); ));
add_settings_field( add_settings_field(
'yoone_snow_drift_range', 'yoone_snow_drift_range',
'Drift Speed Random Range', esc_html__('Drift Speed Random Range', 'yoone-snow'),
function() { function() {
$min = floatval(get_option('yoone_snow_drift_min', 0.4)); $min = floatval(get_option('yoone_snow_drift_min', 0.4));
$max = floatval(get_option('yoone_snow_drift_max', 1.0)); $max = floatval(get_option('yoone_snow_drift_max', 1.0));
echo '<label style="margin-right:12px;">Min <input type="number" min="0" step="0.1" name="yoone_snow_drift_min" value="' . esc_attr($min) . '" style="width:120px;" /></label>'; echo '<label style="margin-right:12px;">' . esc_html__('Min', 'yoone-snow') . ' <input type="number" min="0" step="0.1" name="yoone_snow_drift_min" value="' . esc_attr($min) . '" style="width:120px;" /></label>';
echo '<label>Max <input type="number" min="0" step="0.1" name="yoone_snow_drift_max" value="' . esc_attr($max) . '" style="width:120px;" /></label>'; echo '<label>' . esc_html__('Max', 'yoone-snow') . ' <input type="number" min="0" step="0.1" name="yoone_snow_drift_max" value="' . esc_attr($max) . '" style="width:120px;" /></label>';
echo '<p class="description">Random vertical drift speed base in [min max]</p>'; echo '<p class="description">' . esc_html__('Random vertical drift speed base in [min max]', 'yoone-snow') . '</p>';
}, },
'yoone_snow', 'yoone_snow',
'yoone_snow_section' 'yoone_snow_section'
@ -794,13 +844,13 @@ function yoone_snow_register_settings() {
)); ));
add_settings_field( add_settings_field(
'yoone_snow_swing_range', 'yoone_snow_swing_range',
'Swing Amplitude Random Range', esc_html__('Swing Amplitude Random Range', 'yoone-snow'),
function() { function() {
$min = floatval(get_option('yoone_snow_swing_min', 0.2)); $min = floatval(get_option('yoone_snow_swing_min', 0.2));
$max = floatval(get_option('yoone_snow_swing_max', 1.0)); $max = floatval(get_option('yoone_snow_swing_max', 1.0));
echo '<label style="margin-right:12px;">Min <input type="number" min="0" step="0.1" name="yoone_snow_swing_min" value="' . esc_attr($min) . '" style="width:120px;" /></label>'; echo '<label style="margin-right:12px;">' . esc_html__('Min', 'yoone-snow') . ' <input type="number" min="0" step="0.1" name="yoone_snow_swing_min" value="' . esc_attr($min) . '" style="width:120px;" /></label>';
echo '<label>Max <input type="number" min="0" step="0.1" name="yoone_snow_swing_max" value="' . esc_attr($max) . '" style="width:120px;" /></label>'; echo '<label>' . esc_html__('Max', 'yoone-snow') . ' <input type="number" min="0" step="0.1" name="yoone_snow_swing_max" value="' . esc_attr($max) . '" style="width:120px;" /></label>';
echo '<p class="description">Random horizontal swing amplitude base in [min max] before offset scale</p>'; echo '<p class="description">' . esc_html__('Random horizontal swing amplitude base in [min max] before offset scale', 'yoone-snow') . '</p>';
}, },
'yoone_snow', 'yoone_snow',
'yoone_snow_section' 'yoone_snow_section'