feat(雪花效果): 新增emoji表情支持并优化形状选择界面
添加emoji表情作为新的雪花形状选项,支持通过别名搜索和添加emoji 重构形状选择界面,增加图形预览和更直观的添加/删除操作 在后台设置页面新增emoji管理区域,支持设置emoji权重 优化前端渲染逻辑,独立处理emoji与其他形状的显示
This commit is contained in:
parent
3a41a164d3
commit
17083c23bd
|
|
@ -1,15 +1,33 @@
|
||||||
(function(){
|
(function(){
|
||||||
// 后台媒体选择交互脚本 用于在设置页选择媒体项目
|
// 后台媒体选择交互脚本 用于在设置页选择媒体项目
|
||||||
function initAdminMedia(){
|
function initAdminMedia(){
|
||||||
|
// 将后台资源映射注入到前端设置对象 以便 shapes 脚本复用
|
||||||
|
if (typeof window !== 'undefined'){
|
||||||
|
// 条件判断 如果前端设置对象不存在则创建
|
||||||
|
window.YooneSnowSettings = window.YooneSnowSettings || {};
|
||||||
|
// 条件判断 若未设置 assetsMap 且后台提供则注入
|
||||||
|
if (!window.YooneSnowSettings.assetsMap && window.YooneSnowAdmin && window.YooneSnowAdmin.assetsMap){
|
||||||
|
window.YooneSnowSettings.assetsMap = window.YooneSnowAdmin.assetsMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
// 条件判断 如果没有媒体按钮则不执行
|
// 条件判断 如果没有媒体按钮则不执行
|
||||||
var addButton = document.getElementById('yooneSnowAddMedia');
|
var addButton = document.getElementById('yooneSnowAddMedia');
|
||||||
var listContainer = document.getElementById('yooneSnowMediaList');
|
var listContainer = document.getElementById('yooneSnowMediaList');
|
||||||
|
var emojiListContainer = document.getElementById('yooneSnowEmojiList');
|
||||||
|
var emojiInput = document.getElementById('yooneSnowAddEmojiInput');
|
||||||
|
var emojiAddButton = document.getElementById('yooneSnowAddEmoji');
|
||||||
|
var emojiSuggestBox = document.getElementById('yooneSnowEmojiSuggest');
|
||||||
|
var shapeListContainer = document.getElementById('yooneSnowShapeList');
|
||||||
|
var shapeAddSelect = document.getElementById('yooneSnowAddShapeSelect');
|
||||||
|
var shapeAddButton = document.getElementById('yooneSnowAddShapeBtn');
|
||||||
|
var emojiSelect = document.getElementById('yooneSnowEmojiSelect');
|
||||||
if (!addButton || !listContainer) { return; }
|
if (!addButton || !listContainer) { return; }
|
||||||
|
|
||||||
// 形状与媒体权重容器元素 获取以便动态更新
|
// 形状与媒体权重容器元素 获取以便动态更新
|
||||||
var weightsContainer = document.getElementById('yooneSnowWeightsContainer');
|
var weightsContainer = document.getElementById('yooneSnowWeightsContainer');
|
||||||
var shapeWeightsBox = document.getElementById('yooneSnowShapeWeights');
|
var shapeWeightsBox = document.getElementById('yooneSnowShapeWeights');
|
||||||
var mediaWeightsBox = document.getElementById('yooneSnowMediaWeights');
|
var mediaWeightsBox = document.getElementById('yooneSnowMediaWeights');
|
||||||
|
var emojiWeightsBox = document.getElementById('yooneSnowEmojiWeights');
|
||||||
|
|
||||||
// 形状标签映射用于显示友好名称
|
// 形状标签映射用于显示友好名称
|
||||||
var shapeLabelsMap = {
|
var shapeLabelsMap = {
|
||||||
|
|
@ -25,47 +43,151 @@
|
||||||
christmas_berry: 'Christmas Berry'
|
christmas_berry: 'Christmas Berry'
|
||||||
};
|
};
|
||||||
|
|
||||||
// 同步形状权重输入 根据勾选状态增删对应输入
|
// 创建形状预览元素 直接复用 shapes 渲染器 实现一致预览
|
||||||
function ensureShapeWeightInputs(){
|
function createShapePreviewElement(shapeKey){
|
||||||
// 条件判断 权重容器不存在则跳过
|
var registry = (typeof window !== 'undefined' && window.YooneSnowShapeRenderers) ? window.YooneSnowShapeRenderers : {};
|
||||||
if (!shapeWeightsBox) { return; }
|
var renderer = registry[shapeKey];
|
||||||
var checkboxes = document.querySelectorAll('input[name="yoone_snow_mixed_items[]"]');
|
// 条件判断 如果找到渲染器则使用 canvas 调用渲染函数
|
||||||
var presentKeys = {};
|
if (typeof renderer === 'function'){
|
||||||
// 收集已存在的标签记录用于对比
|
var canvas = document.createElement('canvas');
|
||||||
var existingLabels = shapeWeightsBox.querySelectorAll('label[data-shape-key]');
|
canvas.width = 32;
|
||||||
existingLabels.forEach(function(el){
|
canvas.height = 32;
|
||||||
presentKeys[el.getAttribute('data-shape-key')] = el;
|
canvas.style.marginRight = '6px';
|
||||||
});
|
// 为白色形状提供可见的暗底与边框 提升可读性
|
||||||
checkboxes.forEach(function(cb){
|
canvas.style.background = '#2d3748';
|
||||||
var key = cb.value;
|
canvas.style.border = '1px solid #ddd';
|
||||||
if (cb.checked){
|
canvas.style.borderRadius = '4px';
|
||||||
// 条件判断 如果不存在对应输入则创建 默认权重值为 1
|
var ctx = canvas.getContext('2d');
|
||||||
if (!presentKeys[key]){
|
// 根据形状选择更合适的基础尺寸 保证预览清晰
|
||||||
var lab = document.createElement('label');
|
var previewBaseSizeMap = {
|
||||||
lab.setAttribute('data-shape-key', key);
|
dot: 6,
|
||||||
lab.style.display = 'flex';
|
flake: 6,
|
||||||
lab.style.flexDirection = 'column';
|
yuanbao: 3,
|
||||||
lab.style.minWidth = '160px';
|
coin: 3.5,
|
||||||
var title = document.createElement('span');
|
santa_hat: 2.8,
|
||||||
title.textContent = shapeLabelsMap[key] || key;
|
candy_cane: 2.8,
|
||||||
var input = document.createElement('input');
|
christmas_sock: 2.8,
|
||||||
input.type = 'number';
|
christmas_tree: 2.8,
|
||||||
input.min = '0';
|
reindeer: 2.8,
|
||||||
input.name = 'yoone_snow_shape_weights[' + key + ']';
|
christmas_berry: 2.8
|
||||||
input.value = '1';
|
};
|
||||||
input.style.width = '120px';
|
var baseSize = previewBaseSizeMap[shapeKey] || 3.2;
|
||||||
lab.appendChild(title);
|
// 调用渲染器 使用画布中心作为位置
|
||||||
lab.appendChild(input);
|
try { renderer(ctx, 16, 16, baseSize); } catch(e){}
|
||||||
shapeWeightsBox.appendChild(lab);
|
// 对基于圖像的形狀進行補繪 當圖像加載完成時重試一次
|
||||||
|
var assets = (window.YooneSnowSettings && window.YooneSnowSettings.assetsMap) ? window.YooneSnowSettings.assetsMap : {};
|
||||||
|
var url = assets[shapeKey];
|
||||||
|
if (url && typeof window.YooneSnowGetOrLoadImage === 'function'){
|
||||||
|
var record = window.YooneSnowGetOrLoadImage(url);
|
||||||
|
if (!record || !record.ready){
|
||||||
|
var tries = 0;
|
||||||
|
var timer = setInterval(function(){
|
||||||
|
tries++;
|
||||||
|
var check = window.YooneSnowGetOrLoadImage(url);
|
||||||
|
if (check && check.ready){
|
||||||
|
try {
|
||||||
|
ctx.clearRect(0,0,canvas.width,canvas.height);
|
||||||
|
renderer(ctx, 16, 16, 2.5);
|
||||||
|
} catch(err){}
|
||||||
|
clearInterval(timer);
|
||||||
|
}
|
||||||
|
if (tries > 50){ clearInterval(timer); }
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// 条件判断 如果取消勾选则移除对应输入
|
return canvas;
|
||||||
var exist = shapeWeightsBox.querySelector('label[data-shape-key="' + key + '"]');
|
}
|
||||||
if (exist) { exist.remove(); }
|
// 条件判断 若无渲染器则回退为资源图预览 保证可见性
|
||||||
|
if (typeof window !== 'undefined' && window.YooneSnowAdmin && window.YooneSnowAdmin.assetsMap && window.YooneSnowAdmin.assetsMap[shapeKey]){
|
||||||
|
var img = document.createElement('img');
|
||||||
|
img.src = String(window.YooneSnowAdmin.assetsMap[shapeKey]);
|
||||||
|
img.alt = shapeKey;
|
||||||
|
img.style.width = '28px';
|
||||||
|
img.style.height = '28px';
|
||||||
|
img.style.objectFit = 'contain';
|
||||||
|
img.style.marginRight = '6px';
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
// 默认降级 使用文本首字母表示
|
||||||
|
var fallback = document.createElement('canvas');
|
||||||
|
fallback.width = 32;
|
||||||
|
fallback.height = 32;
|
||||||
|
fallback.style.marginRight = '6px';
|
||||||
|
var fctx = fallback.getContext('2d');
|
||||||
|
fctx.fillStyle = '#555';
|
||||||
|
fctx.font = '12px system-ui';
|
||||||
|
fctx.textAlign = 'center';
|
||||||
|
fctx.textBaseline = 'middle';
|
||||||
|
var txt = (shapeKey || 'S').substring(0, 1).toUpperCase();
|
||||||
|
fctx.fillText(txt, 16, 16);
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为形状复选框添加图形预览 替代纯文字展示
|
||||||
|
function renderShapeCheckboxPreviews(){
|
||||||
|
// 查找所有形状复选框 遍历其父级标签容器
|
||||||
|
var inputs = document.querySelectorAll('input[name="yoone_snow_mixed_items[]"]');
|
||||||
|
inputs.forEach(function(cb){
|
||||||
|
var label = cb.closest('label');
|
||||||
|
if (!label) { return; }
|
||||||
|
// 条件判断 若已绘制过预览则跳过
|
||||||
|
if (label.querySelector('.yoone-shape-preview')) { return; }
|
||||||
|
var key = cb.value;
|
||||||
|
// 创建预览节点并插入在文本前
|
||||||
|
var wrap = document.createElement('span');
|
||||||
|
wrap.className = 'yoone-shape-preview';
|
||||||
|
wrap.style.display = 'inline-flex';
|
||||||
|
wrap.style.alignItems = 'center';
|
||||||
|
wrap.style.marginRight = '6px';
|
||||||
|
var previewEl = createShapePreviewElement(key);
|
||||||
|
wrap.appendChild(previewEl);
|
||||||
|
// 插入到复选框后的位置便于对齐
|
||||||
|
cb.insertAdjacentElement('afterend', wrap);
|
||||||
|
// 同步更新文字标签 保留友好名称
|
||||||
|
var nameSpan = label.querySelector('span');
|
||||||
|
if (!nameSpan){
|
||||||
|
nameSpan = document.createElement('span');
|
||||||
|
nameSpan.textContent = shapeLabelsMap[key] || key;
|
||||||
|
nameSpan.style.marginLeft = '4px';
|
||||||
|
wrap.insertAdjacentElement('afterend', nameSpan);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 同步形状权重输入 根据勾选状态增删对应输入
|
||||||
|
function ensureShapeWeightInputs(){
|
||||||
|
if (!shapeWeightsBox) { return; }
|
||||||
|
var presentKeys = {};
|
||||||
|
var existingLabels = shapeWeightsBox.querySelectorAll('label[data-shape-key]');
|
||||||
|
existingLabels.forEach(function(el){ presentKeys[el.getAttribute('data-shape-key')] = el; });
|
||||||
|
var items = shapeListContainer ? shapeListContainer.querySelectorAll('.yoone-snow-shape-item') : [];
|
||||||
|
items.forEach(function(item){
|
||||||
|
var key = item.getAttribute('data-shape-key');
|
||||||
|
if (!presentKeys[key]){
|
||||||
|
var lab = document.createElement('label');
|
||||||
|
lab.setAttribute('data-shape-key', key);
|
||||||
|
lab.style.display = 'flex';
|
||||||
|
lab.style.flexDirection = 'column';
|
||||||
|
lab.style.minWidth = '160px';
|
||||||
|
var title = document.createElement('span');
|
||||||
|
title.textContent = shapeLabelsMap[key] || key;
|
||||||
|
var input = document.createElement('input');
|
||||||
|
input.type = 'number';
|
||||||
|
input.min = '0';
|
||||||
|
input.name = 'yoone_snow_shape_weights[' + key + ']';
|
||||||
|
input.value = '1';
|
||||||
|
input.style.width = '120px';
|
||||||
|
lab.appendChild(title);
|
||||||
|
lab.appendChild(input);
|
||||||
|
shapeWeightsBox.appendChild(lab);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (var k in presentKeys){
|
||||||
|
var stillSelected = shapeListContainer && shapeListContainer.querySelector('.yoone-snow-shape-item[data-shape-key="' + k + '"]');
|
||||||
|
if (!stillSelected){ presentKeys[k].remove(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 添加媒体权重输入 根据附件 ID 创建输入 默认值为 1
|
// 添加媒体权重输入 根据附件 ID 创建输入 默认值为 1
|
||||||
function addMediaWeightInput(attachmentId){
|
function addMediaWeightInput(attachmentId){
|
||||||
if (!mediaWeightsBox) { return; }
|
if (!mediaWeightsBox) { return; }
|
||||||
|
|
@ -89,6 +211,39 @@
|
||||||
mediaWeightsBox.appendChild(lab);
|
mediaWeightsBox.appendChild(lab);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加 emoji 權重輸入 根據字符創建輸入 默認值為 1
|
||||||
|
function addEmojiWeightInput(emojiChar){
|
||||||
|
if (!emojiWeightsBox) { return; }
|
||||||
|
var key = String(emojiChar);
|
||||||
|
if (key.trim() === '') { return; }
|
||||||
|
var exist = emojiWeightsBox.querySelector('label[data-emoji-char="' + key + '"]');
|
||||||
|
if (exist) { return; }
|
||||||
|
var lab = document.createElement('label');
|
||||||
|
lab.setAttribute('data-emoji-char', key);
|
||||||
|
lab.style.display = 'flex';
|
||||||
|
lab.style.flexDirection = 'column';
|
||||||
|
lab.style.minWidth = '160px';
|
||||||
|
var title = document.createElement('span');
|
||||||
|
title.textContent = key;
|
||||||
|
var input = document.createElement('input');
|
||||||
|
input.type = 'number';
|
||||||
|
input.min = '0';
|
||||||
|
input.name = 'yoone_snow_emoji_weights[' + key + ']';
|
||||||
|
input.value = '1';
|
||||||
|
input.style.width = '120px';
|
||||||
|
lab.appendChild(title);
|
||||||
|
lab.appendChild(input);
|
||||||
|
emojiWeightsBox.appendChild(lab);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除 emoji 權重輸入 根據字符刪除對應輸入
|
||||||
|
function removeEmojiWeightInput(emojiChar){
|
||||||
|
if (!emojiWeightsBox) { return; }
|
||||||
|
var key = String(emojiChar);
|
||||||
|
var exist = emojiWeightsBox.querySelector('label[data-emoji-char="' + key + '"]');
|
||||||
|
if (exist) { exist.remove(); }
|
||||||
|
}
|
||||||
|
|
||||||
// 移除媒体权重输入 根据附件 ID 删除对应输入
|
// 移除媒体权重输入 根据附件 ID 删除对应输入
|
||||||
function removeMediaWeightInput(attachmentId){
|
function removeMediaWeightInput(attachmentId){
|
||||||
if (!mediaWeightsBox) { return; }
|
if (!mediaWeightsBox) { return; }
|
||||||
|
|
@ -98,12 +253,59 @@
|
||||||
|
|
||||||
// 初始化时同步一次形状权重输入
|
// 初始化时同步一次形状权重输入
|
||||||
ensureShapeWeightInputs();
|
ensureShapeWeightInputs();
|
||||||
|
// 初始化时为形状复选框添加图形预览
|
||||||
|
renderShapeCheckboxPreviews();
|
||||||
|
ensureShapeWeightInputs();
|
||||||
|
|
||||||
|
// 添加形状卡片 并插入预览与隐藏输入 用于保存选择
|
||||||
|
function addShapeBox(shapeKey){
|
||||||
|
if (!shapeListContainer) { return; }
|
||||||
|
var key = String(shapeKey);
|
||||||
|
if (key.trim() === '') { return; }
|
||||||
|
var exist = shapeListContainer.querySelector('.yoone-snow-shape-item[data-shape-key="' + key + '"]');
|
||||||
|
if (exist) { return; }
|
||||||
|
var wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'yoone-snow-shape-item';
|
||||||
|
wrapper.setAttribute('data-shape-key', 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 previewHost = document.createElement('div');
|
||||||
|
previewHost.className = 'yoone-snow-shape-preview';
|
||||||
|
previewHost.style.width = '32px';
|
||||||
|
previewHost.style.height = '32px';
|
||||||
|
previewHost.style.marginBottom = '6px';
|
||||||
|
var nameSpan = document.createElement('span');
|
||||||
|
nameSpan.textContent = shapeLabelsMap[key] || key;
|
||||||
|
var input = document.createElement('input');
|
||||||
|
input.type = 'hidden';
|
||||||
|
input.name = 'yoone_snow_mixed_items[]';
|
||||||
|
input.value = key;
|
||||||
|
var cancelBtn = document.createElement('button');
|
||||||
|
cancelBtn.type = 'button';
|
||||||
|
cancelBtn.className = 'button yoone-snow-cancel-shape';
|
||||||
|
cancelBtn.textContent = 'Cancel';
|
||||||
|
cancelBtn.style.marginTop = '6px';
|
||||||
|
wrapper.appendChild(previewHost);
|
||||||
|
wrapper.appendChild(nameSpan);
|
||||||
|
wrapper.appendChild(input);
|
||||||
|
wrapper.appendChild(cancelBtn);
|
||||||
|
shapeListContainer.appendChild(wrapper);
|
||||||
|
var previewEl = createShapePreviewElement(key);
|
||||||
|
if (previewEl) { previewHost.appendChild(previewEl); }
|
||||||
|
ensureShapeWeightInputs();
|
||||||
|
}
|
||||||
|
|
||||||
// 监听形状复选框变化事件 动态刷新权重输入
|
// 监听形状复选框变化事件 动态刷新权重输入
|
||||||
document.addEventListener('change', function(event){
|
document.addEventListener('change', function(event){
|
||||||
var t = event.target;
|
var t = event.target;
|
||||||
if (t && t.name === 'yoone_snow_mixed_items[]'){
|
if (t && t.name === 'yoone_snow_mixed_items[]'){
|
||||||
ensureShapeWeightInputs();
|
ensureShapeWeightInputs();
|
||||||
|
// 复选状态变更后也尝试补齐预览
|
||||||
|
renderShapeCheckboxPreviews();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -122,6 +324,22 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 綁定移除 emoji 按鈕事件 使用事件委託處理動態元素
|
||||||
|
if (emojiListContainer){
|
||||||
|
emojiListContainer.addEventListener('click', function(event){
|
||||||
|
var target = event.target;
|
||||||
|
if (target && target.classList.contains('yoone-snow-remove-emoji')){
|
||||||
|
var item = target.closest('.yoone-snow-emoji-item');
|
||||||
|
if (item){
|
||||||
|
var ch = item.getAttribute('data-emoji-char');
|
||||||
|
item.remove();
|
||||||
|
// 同步移除對應 emoji 權重輸入
|
||||||
|
if (ch) { removeEmojiWeightInput(ch); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 打开媒体选择器 支持多选 图片和 SVG
|
// 打开媒体选择器 支持多选 图片和 SVG
|
||||||
addButton.addEventListener('click', function(){
|
addButton.addEventListener('click', function(){
|
||||||
// 条件判断 如果 wp.media 不可用则终止
|
// 条件判断 如果 wp.media 不可用则终止
|
||||||
|
|
@ -171,6 +389,199 @@
|
||||||
});
|
});
|
||||||
frame.open();
|
frame.open();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Emoji 别名映射 用於文本搜索到 emoji 字符
|
||||||
|
var emojiAliasMap = {
|
||||||
|
smile: '🙂',
|
||||||
|
happy: '😊',
|
||||||
|
grin: '😁',
|
||||||
|
laugh: '😂',
|
||||||
|
joy: '😂',
|
||||||
|
wink: '😉',
|
||||||
|
blush: '☺️',
|
||||||
|
heart: '❤️',
|
||||||
|
love: '❤️',
|
||||||
|
star: '⭐',
|
||||||
|
fire: '🔥',
|
||||||
|
cool: '😎',
|
||||||
|
cry: '😢',
|
||||||
|
sad: '🙁',
|
||||||
|
angry: '😠',
|
||||||
|
thumbs_up: '👍',
|
||||||
|
ok: '👌',
|
||||||
|
clap: '👏',
|
||||||
|
pray: '🙏',
|
||||||
|
tada: '🎉',
|
||||||
|
gift: '🎁',
|
||||||
|
snow: '❄️',
|
||||||
|
tree: '🌲',
|
||||||
|
bell: '🔔',
|
||||||
|
candy: '🍬',
|
||||||
|
sock: '🧦',
|
||||||
|
deer: '🦌',
|
||||||
|
berry: '🍓'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 填充下拉選單 以別名和字符組合顯示
|
||||||
|
function populateEmojiSelect(){
|
||||||
|
if (!emojiSelect) { return; }
|
||||||
|
// 清空除第一個提示項之外的選項
|
||||||
|
while (emojiSelect.options.length > 1){ emojiSelect.remove(1); }
|
||||||
|
for (var key in emojiAliasMap){
|
||||||
|
var ch = emojiAliasMap[key];
|
||||||
|
var opt = document.createElement('option');
|
||||||
|
opt.value = ch;
|
||||||
|
opt.textContent = ch + ' ' + key;
|
||||||
|
emojiSelect.appendChild(opt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 綁定下拉選擇事件 選擇後添加對應 emoji 並重置選中
|
||||||
|
if (emojiSelect){
|
||||||
|
populateEmojiSelect();
|
||||||
|
emojiSelect.addEventListener('change', function(){
|
||||||
|
var val = emojiSelect.value;
|
||||||
|
if (String(val).trim() !== ''){
|
||||||
|
addEmojiByChar(val);
|
||||||
|
emojiSelect.value = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 顯示建議列表 根據查詢文本匹配別名
|
||||||
|
function showEmojiSuggestions(query){
|
||||||
|
if (!emojiSuggestBox) { return; }
|
||||||
|
var q = String(query || '').toLowerCase().trim();
|
||||||
|
emojiSuggestBox.innerHTML = '';
|
||||||
|
if (q === '') { return; }
|
||||||
|
var max = 12;
|
||||||
|
var count = 0;
|
||||||
|
for (var key in emojiAliasMap){
|
||||||
|
if (key.indexOf(q) !== -1){
|
||||||
|
var btn = document.createElement('button');
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.className = 'button';
|
||||||
|
btn.textContent = emojiAliasMap[key] + ' ' + key;
|
||||||
|
(function(ch){
|
||||||
|
btn.addEventListener('click', function(){ addEmojiByChar(ch); });
|
||||||
|
})(emojiAliasMap[key]);
|
||||||
|
emojiSuggestBox.appendChild(btn);
|
||||||
|
count++;
|
||||||
|
if (count >= max){ break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 將 emoji 字符添加到列表 並創建隱藏輸入
|
||||||
|
function addEmojiByChar(ch){
|
||||||
|
if (!emojiListContainer) { return; }
|
||||||
|
var key = String(ch);
|
||||||
|
if (key.trim() === '') { return; }
|
||||||
|
var exist = emojiListContainer.querySelector('.yoone-snow-emoji-item[data-emoji-char="' + key + '"]');
|
||||||
|
if (exist) { return; }
|
||||||
|
var wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'yoone-snow-emoji-item';
|
||||||
|
wrapper.setAttribute('data-emoji-char', key);
|
||||||
|
wrapper.style.border = '1px solid #ddd';
|
||||||
|
wrapper.style.padding = '8px';
|
||||||
|
wrapper.style.display = 'flex';
|
||||||
|
wrapper.style.flexDirection = 'column';
|
||||||
|
wrapper.style.alignItems = 'center';
|
||||||
|
var preview = document.createElement('div');
|
||||||
|
preview.textContent = key;
|
||||||
|
preview.style.fontSize = '28px';
|
||||||
|
preview.style.lineHeight = '32px';
|
||||||
|
var input = document.createElement('input');
|
||||||
|
input.type = 'hidden';
|
||||||
|
input.name = 'yoone_snow_emoji_items[]';
|
||||||
|
input.value = key;
|
||||||
|
var removeBtn = document.createElement('button');
|
||||||
|
removeBtn.type = 'button';
|
||||||
|
removeBtn.className = 'button yoone-snow-remove-emoji';
|
||||||
|
removeBtn.textContent = 'Cancel';
|
||||||
|
removeBtn.style.marginTop = '6px';
|
||||||
|
wrapper.appendChild(preview);
|
||||||
|
wrapper.appendChild(input);
|
||||||
|
wrapper.appendChild(removeBtn);
|
||||||
|
emojiListContainer.appendChild(wrapper);
|
||||||
|
// 同步添加 emoji 權重輸入 默認權重為 1
|
||||||
|
addEmojiWeightInput(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 綁定 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 監聽輸入變化 顯示建議列表 支持即時搜索
|
||||||
|
if (emojiInput){
|
||||||
|
emojiInput.addEventListener('input', function(){
|
||||||
|
showEmojiSuggestions(emojiInput.value);
|
||||||
|
});
|
||||||
|
emojiInput.addEventListener('keydown', function(e){
|
||||||
|
if (e && e.key === 'Enter'){
|
||||||
|
e.preventDefault();
|
||||||
|
var q = String(emojiInput.value || '').trim();
|
||||||
|
if (q !== ''){
|
||||||
|
var lower = q.toLowerCase();
|
||||||
|
if (emojiAliasMap[lower]){ addEmojiByChar(emojiAliasMap[lower]); }
|
||||||
|
else { addEmojiByChar(q); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化時為現有 emoji 列表補齊權重輸入
|
||||||
|
if (emojiListContainer){
|
||||||
|
var existing = emojiListContainer.querySelectorAll('.yoone-snow-emoji-item');
|
||||||
|
existing.forEach(function(node){
|
||||||
|
var ch = node.getAttribute('data-emoji-char');
|
||||||
|
if (ch) { addEmojiWeightInput(ch); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (shapeListContainer){
|
||||||
|
shapeListContainer.addEventListener('click', function(event){
|
||||||
|
var target = event.target;
|
||||||
|
if (target && target.classList.contains('yoone-snow-cancel-shape')){
|
||||||
|
var item = target.closest('.yoone-snow-shape-item');
|
||||||
|
if (item){
|
||||||
|
var key = item.getAttribute('data-shape-key');
|
||||||
|
item.remove();
|
||||||
|
ensureShapeWeightInputs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shapeAddSelect){
|
||||||
|
shapeAddSelect.addEventListener('change', function(){
|
||||||
|
var val = shapeAddSelect.value;
|
||||||
|
if (String(val).trim() !== ''){
|
||||||
|
addShapeBox(val);
|
||||||
|
shapeAddSelect.value = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shapeAddButton){
|
||||||
|
shapeAddButton.addEventListener('click', function(){
|
||||||
|
var val = shapeAddSelect ? String(shapeAddSelect.value || '') : '';
|
||||||
|
if (val.trim() !== ''){
|
||||||
|
addShapeBox(val);
|
||||||
|
shapeAddSelect.value = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 条件判断 如果文档尚未加载则等待 DOMContentLoaded 事件
|
// 条件判断 如果文档尚未加载则等待 DOMContentLoaded 事件
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,9 @@
|
||||||
const mediaItems = (window.YooneSnowSettings && Array.isArray(window.YooneSnowSettings.mediaItems))
|
const mediaItems = (window.YooneSnowSettings && Array.isArray(window.YooneSnowSettings.mediaItems))
|
||||||
? window.YooneSnowSettings.mediaItems
|
? window.YooneSnowSettings.mediaItems
|
||||||
: [];
|
: [];
|
||||||
|
const emojiItems = (window.YooneSnowSettings && Array.isArray(window.YooneSnowSettings.emojiItems))
|
||||||
|
? window.YooneSnowSettings.emojiItems
|
||||||
|
: [];
|
||||||
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 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')
|
const shapeWeightsRaw = (window.YooneSnowSettings && window.YooneSnowSettings.shapeWeights && typeof window.YooneSnowSettings.shapeWeights === 'object')
|
||||||
? window.YooneSnowSettings.shapeWeights
|
? window.YooneSnowSettings.shapeWeights
|
||||||
|
|
@ -31,6 +34,9 @@
|
||||||
const mediaWeightsRaw = (window.YooneSnowSettings && window.YooneSnowSettings.mediaWeights && typeof window.YooneSnowSettings.mediaWeights === 'object')
|
const mediaWeightsRaw = (window.YooneSnowSettings && window.YooneSnowSettings.mediaWeights && typeof window.YooneSnowSettings.mediaWeights === 'object')
|
||||||
? window.YooneSnowSettings.mediaWeights
|
? window.YooneSnowSettings.mediaWeights
|
||||||
: {};
|
: {};
|
||||||
|
const emojiWeightsRaw = (window.YooneSnowSettings && window.YooneSnowSettings.emojiWeights && typeof window.YooneSnowSettings.emojiWeights === 'object')
|
||||||
|
? window.YooneSnowSettings.emojiWeights
|
||||||
|
: {};
|
||||||
const shapeWeights = {};
|
const shapeWeights = {};
|
||||||
for (let key in defaultShapeWeights){
|
for (let key in defaultShapeWeights){
|
||||||
const val = typeof shapeWeightsRaw[key] !== 'undefined' ? parseInt(shapeWeightsRaw[key], 10) : defaultShapeWeights[key];
|
const val = typeof shapeWeightsRaw[key] !== 'undefined' ? parseInt(shapeWeightsRaw[key], 10) : defaultShapeWeights[key];
|
||||||
|
|
@ -97,9 +103,20 @@
|
||||||
const shapeKey = selectedShapes[sIndex];
|
const shapeKey = selectedShapes[sIndex];
|
||||||
const weightVal = typeof shapeWeights[shapeKey] !== 'undefined' ? shapeWeights[shapeKey] : 1;
|
const weightVal = typeof shapeWeights[shapeKey] !== 'undefined' ? shapeWeights[shapeKey] : 1;
|
||||||
if (weightVal > 0){
|
if (weightVal > 0){
|
||||||
|
// 条件判断 普通形状直接加入 不含 emoji
|
||||||
items.push({ kind: 'shape', key: shapeKey, weight: weightVal });
|
items.push({ kind: 'shape', key: shapeKey, weight: weightVal });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Emoji 候選獨立加入 不依賴 Shapes 是否包含 emoji
|
||||||
|
if (emojiItems.length > 0){
|
||||||
|
for (let eIndex = 0; eIndex < emojiItems.length; eIndex++){
|
||||||
|
const ch = String(emojiItems[eIndex] || '').trim();
|
||||||
|
if (ch === ''){ continue; }
|
||||||
|
const ewRaw = typeof emojiWeightsRaw[ch] !== 'undefined' ? parseInt(emojiWeightsRaw[ch], 10) : 1;
|
||||||
|
const ew = isNaN(ewRaw) ? 1 : Math.max(0, ewRaw);
|
||||||
|
if (ew > 0){ items.push({ kind: 'emoji', text: ch, weight: ew }); }
|
||||||
|
}
|
||||||
|
}
|
||||||
for (let mIndex = 0; mIndex < mediaItems.length; mIndex++){
|
for (let mIndex = 0; mIndex < mediaItems.length; mIndex++){
|
||||||
const mediaUrl = mediaItems[mIndex];
|
const mediaUrl = mediaItems[mIndex];
|
||||||
const mediaWeight = typeof mediaWeightsRaw[mediaUrl] !== 'undefined' ? parseInt(mediaWeightsRaw[mediaUrl], 10) : 1;
|
const mediaWeight = typeof mediaWeightsRaw[mediaUrl] !== 'undefined' ? parseInt(mediaWeightsRaw[mediaUrl], 10) : 1;
|
||||||
|
|
@ -110,7 +127,7 @@
|
||||||
}
|
}
|
||||||
// 条件判断 如果没有可选项则回退为点形状
|
// 条件判断 如果没有可选项则回退为点形状
|
||||||
if (items.length === 0){
|
if (items.length === 0){
|
||||||
return { type: 'dot', url: null };
|
return { type: 'dot', url: null, text: null };
|
||||||
}
|
}
|
||||||
let totalWeight = 0;
|
let totalWeight = 0;
|
||||||
for (let i = 0; i < items.length; i++){
|
for (let i = 0; i < items.length; i++){
|
||||||
|
|
@ -123,18 +140,23 @@
|
||||||
// 条件判断 如果随机值落在当前累计权重内则选择该项
|
// 条件判断 如果随机值落在当前累计权重内则选择该项
|
||||||
if (r <= acc){
|
if (r <= acc){
|
||||||
if (items[i].kind === 'shape'){
|
if (items[i].kind === 'shape'){
|
||||||
return { type: items[i].key, url: null };
|
return { type: items[i].key, url: null, text: null };
|
||||||
} else {
|
} else {
|
||||||
return { type: 'media_image', url: items[i].url };
|
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 };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { type: 'dot', url: null };
|
return { type: 'dot', url: null, text: null };
|
||||||
}
|
}
|
||||||
function createSnowflake(preferredX, preferredY){
|
function createSnowflake(preferredX, preferredY){
|
||||||
const picked = selectWeightedItem();
|
const picked = selectWeightedItem();
|
||||||
let chosenType = picked.type;
|
let chosenType = picked.type;
|
||||||
let chosenImageUrl = picked.url;
|
let chosenImageUrl = picked.url;
|
||||||
|
let chosenEmojiText = picked.text;
|
||||||
return {
|
return {
|
||||||
positionX: typeof preferredX === 'number' ? preferredX : Math.random() * viewportWidth,
|
positionX: typeof preferredX === 'number' ? preferredX : Math.random() * viewportWidth,
|
||||||
positionY: typeof preferredY === 'number' ? preferredY : (-1 - Math.random() * 4),
|
positionY: typeof preferredY === 'number' ? preferredY : (-1 - Math.random() * 4),
|
||||||
|
|
@ -145,6 +167,7 @@
|
||||||
swingAmplitude: (Math.random() * (swingMax - swingMin) + swingMin) * swingMin,
|
swingAmplitude: (Math.random() * (swingMax - swingMin) + swingMin) * swingMin,
|
||||||
shapeType: chosenType,
|
shapeType: chosenType,
|
||||||
imageUrl: chosenImageUrl,
|
imageUrl: chosenImageUrl,
|
||||||
|
emojiText: chosenEmojiText,
|
||||||
// 标记该粒子是否已经移出视口 用于停止后清理
|
// 标记该粒子是否已经移出视口 用于停止后清理
|
||||||
outOfView: false
|
outOfView: false
|
||||||
};
|
};
|
||||||
|
|
@ -240,6 +263,18 @@
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// 条件判断 如果是 emoji 文本类型則使用文本繪制
|
||||||
|
if (flake.shapeType === 'emoji_text' && flake.emojiText){
|
||||||
|
context.save();
|
||||||
|
// 設置字體大小與居中對齊 基於半徑縮放
|
||||||
|
const fontSize = Math.max(12, flake.radius * 6);
|
||||||
|
context.font = String(Math.floor(fontSize)) + 'px system-ui, Apple Color Emoji, Segoe UI Emoji, Noto Color Emoji';
|
||||||
|
context.textAlign = 'center';
|
||||||
|
context.textBaseline = 'middle';
|
||||||
|
context.fillText(String(flake.emojiText), flake.positionX, flake.positionY);
|
||||||
|
context.restore();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// 否则执行注册表中的形状渲染函数
|
// 否则执行注册表中的形状渲染函数
|
||||||
const registry = window.YooneSnowShapeRenderers || {};
|
const registry = window.YooneSnowShapeRenderers || {};
|
||||||
const renderer = registry[flake.shapeType] || registry['dot'];
|
const renderer = registry[flake.shapeType] || registry['dot'];
|
||||||
|
|
|
||||||
207
yoone-snow.php
207
yoone-snow.php
|
|
@ -142,6 +142,32 @@ function yoone_snow_enqueue_assets() {
|
||||||
),
|
),
|
||||||
'shapeWeights' => $shape_weights,
|
'shapeWeights' => $shape_weights,
|
||||||
'mediaWeights' => $media_weights_by_url,
|
'mediaWeights' => $media_weights_by_url,
|
||||||
|
'emojiItems' => (function(){
|
||||||
|
// 读取已保存的 emoji 列表 返回字符串数组
|
||||||
|
$items = get_option('yoone_snow_emoji_items', array());
|
||||||
|
if (!is_array($items)) { $items = array(); }
|
||||||
|
$clean = array();
|
||||||
|
foreach ($items as $it) {
|
||||||
|
$s = trim((string)$it);
|
||||||
|
if ($s !== '') { $clean[] = $s; }
|
||||||
|
}
|
||||||
|
return $clean;
|
||||||
|
})(),
|
||||||
|
'emojiWeights' => (function(){
|
||||||
|
// 读取已保存的 emoji 权重 映射为字符到权重
|
||||||
|
$map = get_option('yoone_snow_emoji_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;
|
||||||
|
})(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,9 +177,75 @@ function yoone_snow_admin_enqueue($hook) {
|
||||||
if ($hook !== 'settings_page_yoone_snow') { return; }
|
if ($hook !== 'settings_page_yoone_snow') { return; }
|
||||||
// 加载媒体库脚本 以便使用 wp.media 选择器
|
// 加载媒体库脚本 以便使用 wp.media 选择器
|
||||||
wp_enqueue_media();
|
wp_enqueue_media();
|
||||||
// 注册并加载后台交互脚本
|
|
||||||
|
// 在后台也注册并加载 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);
|
||||||
|
$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);
|
||||||
|
// 各具体形状脚本 注册为依赖以保证顺序
|
||||||
|
$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);
|
||||||
|
$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);
|
||||||
|
$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);
|
||||||
|
$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);
|
||||||
|
$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);
|
||||||
|
$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);
|
||||||
|
$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);
|
||||||
|
$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);
|
||||||
|
$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);
|
||||||
|
$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);
|
||||||
|
|
||||||
|
// 在后台本页入队 shapes 相关脚本 以便预览复用前端实现
|
||||||
|
wp_enqueue_script($shape_index_handle);
|
||||||
|
wp_enqueue_script($shape_utils_handle);
|
||||||
|
wp_enqueue_script($shape_dot_handle);
|
||||||
|
wp_enqueue_script($shape_flake_handle);
|
||||||
|
wp_enqueue_script($shape_yuanbao_handle);
|
||||||
|
wp_enqueue_script($shape_coin_handle);
|
||||||
|
wp_enqueue_script($shape_santa_handle);
|
||||||
|
wp_enqueue_script($shape_cane_handle);
|
||||||
|
wp_enqueue_script($shape_sock_handle);
|
||||||
|
wp_enqueue_script($shape_tree_handle);
|
||||||
|
wp_enqueue_script($shape_reindeer_handle);
|
||||||
|
wp_enqueue_script($shape_berry_handle);
|
||||||
|
|
||||||
|
// 为后台也提供 YooneSnowSettings 的 assetsMap 以满足形状脚本的资源需求
|
||||||
|
wp_localize_script($shape_utils_handle, 'YooneSnowSettings', array(
|
||||||
|
'assetsMap' => array(
|
||||||
|
'santa_hat' => plugins_url('assets/圣诞雪帽.svg', __FILE__),
|
||||||
|
'candy_cane' => plugins_url('assets/圣诞拐杖.svg', __FILE__),
|
||||||
|
'christmas_sock' => plugins_url('assets/圣诞袜子.svg', __FILE__),
|
||||||
|
'christmas_tree' => plugins_url('assets/圣诞树.svg', __FILE__),
|
||||||
|
'reindeer' => plugins_url('assets/圣诞麋鹿.svg', __FILE__),
|
||||||
|
'christmas_berry' => plugins_url('assets/圣诞果.svg', __FILE__),
|
||||||
|
)
|
||||||
|
));
|
||||||
|
|
||||||
|
// 注册并加载后台交互脚本 该脚本复用 shapes 渲染器进行预览
|
||||||
$admin_script_handle = 'yoone-snow-admin-media';
|
$admin_script_handle = 'yoone-snow-admin-media';
|
||||||
wp_register_script($admin_script_handle, plugins_url('js/admin-media.js', __FILE__), array(), '1.1.0', true);
|
wp_register_script($admin_script_handle, plugins_url('js/admin-media.js', __FILE__), array($shape_index_handle, $shape_utils_handle), '1.1.0', true);
|
||||||
|
// 传递资源映射用于后台形状预览 保持与前端一致
|
||||||
|
wp_localize_script($admin_script_handle, 'YooneSnowAdmin', array(
|
||||||
|
'assetsMap' => array(
|
||||||
|
'santa_hat' => plugins_url('assets/圣诞雪帽.svg', __FILE__),
|
||||||
|
'candy_cane' => plugins_url('assets/圣诞拐杖.svg', __FILE__),
|
||||||
|
'christmas_sock' => plugins_url('assets/圣诞袜子.svg', __FILE__),
|
||||||
|
'christmas_tree' => plugins_url('assets/圣诞树.svg', __FILE__),
|
||||||
|
'reindeer' => plugins_url('assets/圣诞麋鹿.svg', __FILE__),
|
||||||
|
'christmas_berry' => plugins_url('assets/圣诞果.svg', __FILE__),
|
||||||
|
)
|
||||||
|
));
|
||||||
wp_enqueue_script($admin_script_handle);
|
wp_enqueue_script($admin_script_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,7 +294,6 @@ function yoone_snow_register_settings() {
|
||||||
|
|
||||||
// 移除下拉字段 保留复选框作为唯一选择入口
|
// 移除下拉字段 保留复选框作为唯一选择入口
|
||||||
|
|
||||||
// 添加形状复选集合 用于选择参与渲染的形状
|
|
||||||
add_settings_field(
|
add_settings_field(
|
||||||
'yoone_snow_mixed_items',
|
'yoone_snow_mixed_items',
|
||||||
'Shapes',
|
'Shapes',
|
||||||
|
|
@ -211,6 +302,7 @@ function yoone_snow_register_settings() {
|
||||||
if (is_string($current_list)) {
|
if (is_string($current_list)) {
|
||||||
$current_list = array_filter(array_map('trim', explode(',', $current_list)));
|
$current_list = array_filter(array_map('trim', explode(',', $current_list)));
|
||||||
}
|
}
|
||||||
|
if (!is_array($current_list)) { $current_list = array('dot','flake'); }
|
||||||
$options = array(
|
$options = array(
|
||||||
'dot' => 'Dot',
|
'dot' => 'Dot',
|
||||||
'flake' => 'Snowflake',
|
'flake' => 'Snowflake',
|
||||||
|
|
@ -223,16 +315,62 @@ function yoone_snow_register_settings() {
|
||||||
'reindeer' => 'Reindeer',
|
'reindeer' => 'Reindeer',
|
||||||
'christmas_berry' => 'Christmas Berry',
|
'christmas_berry' => 'Christmas Berry',
|
||||||
);
|
);
|
||||||
foreach ($options as $key => $label) {
|
echo '<div id="yooneSnowShapeList" style="display:flex;flex-wrap:wrap;gap:12px;">';
|
||||||
$checked = in_array($key, $current_list, true) ? 'checked' : '';
|
foreach ($current_list as $key) {
|
||||||
echo '<label style="margin-right:12px;"><input type="checkbox" name="yoone_snow_mixed_items[]" value="' . esc_attr($key) . '" ' . $checked . ' /> ' . esc_html($label) . '</label>';
|
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-preview" style="width:32px;height:32px;margin-bottom:6px;"></div>';
|
||||||
|
echo '<span>' . esc_html($options[$key]) . '</span>';
|
||||||
|
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 '</div>';
|
||||||
}
|
}
|
||||||
|
echo '</div>';
|
||||||
|
echo '<div style="margin-top:8px;display:flex;gap:8px;align-items:center;">';
|
||||||
|
echo '<select id="yooneSnowAddShapeSelect" style="min-width:240px;"><option value="">Select shape</option>';
|
||||||
|
foreach ($options as $key => $label) {
|
||||||
|
echo '<option value="' . esc_attr($key) . '">' . esc_html($label) . '</option>';
|
||||||
|
}
|
||||||
|
echo '</select>';
|
||||||
|
echo '<button type="button" class="button" id="yooneSnowAddShapeBtn">Add Shape</button>';
|
||||||
|
echo '</div>';
|
||||||
echo '<p class="description">Choose shapes to render</p>';
|
echo '<p class="description">Choose shapes to render</p>';
|
||||||
},
|
},
|
||||||
'yoone_snow',
|
'yoone_snow',
|
||||||
'yoone_snow_section'
|
'yoone_snow_section'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 添加 emoji 形狀選擇區域 放置在 Media Shapes 之前
|
||||||
|
add_settings_field(
|
||||||
|
'yoone_snow_emoji_items',
|
||||||
|
'Emoji Shapes',
|
||||||
|
function() {
|
||||||
|
// 渲染當前 emoji 列表與添加搜索框
|
||||||
|
$current_emojis = get_option('yoone_snow_emoji_items', array());
|
||||||
|
if (!is_array($current_emojis)) { $current_emojis = array(); }
|
||||||
|
echo '<div id="yooneSnowEmojiList" style="display:flex;flex-wrap:wrap;gap:12px;align-items:flex-start;">';
|
||||||
|
foreach ($current_emojis as $emoji_char) {
|
||||||
|
$label = trim((string)$emoji_char);
|
||||||
|
if ($label === '') { continue; }
|
||||||
|
echo '<div class="yoone-snow-emoji-item" data-emoji-char="' . esc_attr($label) . '" style="border:1px solid #ddd;padding:8px;display:flex;flex-direction:column;align-items:center;">';
|
||||||
|
echo '<div style="font-size:28px;line-height:32px;">' . esc_html($label) . '</div>';
|
||||||
|
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 '</div>';
|
||||||
|
}
|
||||||
|
echo '</div>';
|
||||||
|
echo '<div style="margin-top:8px;display:flex;gap:8px;align-items:center;">';
|
||||||
|
echo '<select id="yooneSnowEmojiSelect" style="min-width:240px;"><option value="">Select emoji</option></select>';
|
||||||
|
echo '<input type="text" id="yooneSnowAddEmojiInput" placeholder="Type emoji or alias" style="width:240px;" />';
|
||||||
|
echo '<button type="button" class="button" id="yooneSnowAddEmoji">Add Emoji</button>';
|
||||||
|
echo '</div>';
|
||||||
|
echo '<div id="yooneSnowEmojiSuggest" style="margin-top:6px;display:flex;flex-wrap:wrap;gap:6px;"></div>';
|
||||||
|
echo '<p class="description">Search by alias or paste emoji character then add</p>';
|
||||||
|
},
|
||||||
|
'yoone_snow',
|
||||||
|
'yoone_snow_section'
|
||||||
|
);
|
||||||
|
|
||||||
register_setting('yoone_snow_options', 'yoone_snow_shape_weights', array(
|
register_setting('yoone_snow_options', 'yoone_snow_shape_weights', array(
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'sanitize_callback' => function($value) {
|
'sanitize_callback' => function($value) {
|
||||||
|
|
@ -363,6 +501,7 @@ function yoone_snow_register_settings() {
|
||||||
'christmas_tree' => 'Christmas Tree',
|
'christmas_tree' => 'Christmas Tree',
|
||||||
'reindeer' => 'Reindeer',
|
'reindeer' => 'Reindeer',
|
||||||
'christmas_berry' => 'Christmas Berry',
|
'christmas_berry' => 'Christmas Berry',
|
||||||
|
'emoji' => 'Emoji',
|
||||||
);
|
);
|
||||||
$shape_weights_current = get_option('yoone_snow_shape_weights', array());
|
$shape_weights_current = get_option('yoone_snow_shape_weights', array());
|
||||||
if (!is_array($shape_weights_current)) { $shape_weights_current = array(); }
|
if (!is_array($shape_weights_current)) { $shape_weights_current = array(); }
|
||||||
|
|
@ -393,6 +532,22 @@ function yoone_snow_register_settings() {
|
||||||
echo '</label>';
|
echo '</label>';
|
||||||
}
|
}
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
// 渲染 emoji 权重 输入为字符键 默认 1
|
||||||
|
$emoji_items_current = get_option('yoone_snow_emoji_items', array());
|
||||||
|
if (!is_array($emoji_items_current)) { $emoji_items_current = array(); }
|
||||||
|
$emoji_weights_current = get_option('yoone_snow_emoji_weights', array());
|
||||||
|
if (!is_array($emoji_weights_current)) { $emoji_weights_current = array(); }
|
||||||
|
echo '<div id="yooneSnowEmojiWeights" style="display:flex;flex-wrap:wrap;gap:12px;margin-top:12px;">';
|
||||||
|
foreach ($emoji_items_current as $emojiChar) {
|
||||||
|
$label = trim((string)$emojiChar);
|
||||||
|
if ($label === '') { continue; }
|
||||||
|
$val = isset($emoji_weights_current[$label]) ? intval($emoji_weights_current[$label]) : 1;
|
||||||
|
echo '<label data-emoji-char="' . esc_attr($label) . '" style="display:flex;flex-direction:column;min-width:160px;">';
|
||||||
|
echo '<span>' . esc_html($label) . '</span>';
|
||||||
|
echo '<input type="number" min="0" name="yoone_snow_emoji_weights[' . esc_attr($label) . ']" value="' . esc_attr($val) . '" style="width:120px;" />';
|
||||||
|
echo '</label>';
|
||||||
|
}
|
||||||
|
echo '</div>';
|
||||||
echo '<p class="description">Higher value increases selection probability 0 disables default is 1</p>';
|
echo '<p class="description">Higher value increases selection probability 0 disables default is 1</p>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
// 绑定交互脚本 通过已有 admin 脚本实现动态刷新
|
// 绑定交互脚本 通过已有 admin 脚本实现动态刷新
|
||||||
|
|
@ -401,6 +556,46 @@ function yoone_snow_register_settings() {
|
||||||
'yoone_snow_section'
|
'yoone_snow_section'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 注册 emoji 列表设置项 保存为字符串数组
|
||||||
|
register_setting('yoone_snow_options', 'yoone_snow_emoji_items', array(
|
||||||
|
'type' => 'array',
|
||||||
|
'sanitize_callback' => function($value) {
|
||||||
|
// 将输入统一为非空字符串数组 并去重
|
||||||
|
if (is_string($value)) {
|
||||||
|
$value = array_filter(array_map('trim', explode(',', $value)));
|
||||||
|
}
|
||||||
|
if (!is_array($value)) { $value = array(); }
|
||||||
|
$out = array();
|
||||||
|
foreach ($value as $item) {
|
||||||
|
$s = trim((string)$item);
|
||||||
|
if ($s !== '') { $out[] = $s; }
|
||||||
|
}
|
||||||
|
return array_values(array_unique($out));
|
||||||
|
},
|
||||||
|
'default' => array(),
|
||||||
|
));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 注册 emoji 权重设置项 映射字符到非负整数权重
|
||||||
|
register_setting('yoone_snow_options', 'yoone_snow_emoji_weights', array(
|
||||||
|
'type' => 'array',
|
||||||
|
'sanitize_callback' => function($value) {
|
||||||
|
if (!is_array($value)) { $value = array(); }
|
||||||
|
$clean = array();
|
||||||
|
foreach ($value as $ch => $num) {
|
||||||
|
$key = trim((string)$ch);
|
||||||
|
$weight = intval($num);
|
||||||
|
if ($key !== '') {
|
||||||
|
if ($weight < 0) { $weight = 0; }
|
||||||
|
$clean[$key] = $weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $clean;
|
||||||
|
},
|
||||||
|
'default' => array(),
|
||||||
|
));
|
||||||
|
|
||||||
// 注册首页显示时长设置 项为整数秒 0 表示无限
|
// 注册首页显示时长设置 项为整数秒 0 表示无限
|
||||||
register_setting('yoone_snow_options', 'yoone_snow_home_duration', array(
|
register_setting('yoone_snow_options', 'yoone_snow_home_duration', array(
|
||||||
'type' => 'integer',
|
'type' => 'integer',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue