feat: 添加文本标签支持并优化资源加载
- 在雪花效果中添加文本标签支持,可配置文本内容和权重 - 优化图像资源加载逻辑,支持异步解码 - 添加对 prefers-reduced-motion 媒体查询的支持 - 改进小屏幕设备上的雪花数量计算 - 重构后台管理界面,统一添加卡片布局 - 添加国际化支持,准备翻译文件 - 优化脚本依赖加载,按需引入形状渲染器
This commit is contained in:
parent
00471c205f
commit
def279a87e
|
|
@ -15,16 +15,19 @@
|
||||||
var listContainer = null;
|
var listContainer = null;
|
||||||
var emojiListContainer = null;
|
var emojiListContainer = null;
|
||||||
var emojiInput = document.getElementById('yooneSnowAddEmojiInput');
|
var emojiInput = document.getElementById('yooneSnowAddEmojiInput');
|
||||||
var emojiAddButton = document.getElementById('yooneSnowAddEmoji');
|
var emojiAddButton = null;
|
||||||
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 shapeAddButton = document.getElementById('yooneSnowAddShapeBtn');
|
var shapeAddButton = null;
|
||||||
var emojiSelect = document.getElementById('yooneSnowEmojiSelect');
|
var emojiSelect = document.getElementById('yooneSnowEmojiSelect');
|
||||||
var typeSelect = document.getElementById('yooneSnowAddTypeSelect');
|
var typeSelect = document.getElementById('yooneSnowAddTypeSelect');
|
||||||
var paneDefault = document.getElementById('yooneSnowAddDefaultPane');
|
var paneDefault = document.getElementById('yooneSnowAddDefaultPane');
|
||||||
var paneEmoji = document.getElementById('yooneSnowAddEmojiPane');
|
var paneEmoji = document.getElementById('yooneSnowAddEmojiPane');
|
||||||
var paneMedia = document.getElementById('yooneSnowAddMediaPane');
|
var paneMedia = document.getElementById('yooneSnowAddMediaPane');
|
||||||
|
var unifiedAddCard = document.getElementById('yooneSnowAddUnified');
|
||||||
|
var paneText = document.getElementById('yooneSnowAddTextPane');
|
||||||
|
var textInput = document.getElementById('yooneSnowAddTextInput');
|
||||||
// 统一列表容器为形状列表容器 旧容器不再使用
|
// 统一列表容器为形状列表容器 旧容器不再使用
|
||||||
listContainer = shapeListContainer;
|
listContainer = shapeListContainer;
|
||||||
emojiListContainer = shapeListContainer;
|
emojiListContainer = shapeListContainer;
|
||||||
|
|
@ -77,7 +80,7 @@
|
||||||
var baseSize = previewBaseSizeMap[shapeKey] || 3.2;
|
var baseSize = previewBaseSizeMap[shapeKey] || 3.2;
|
||||||
// 调用渲染器 使用画布中心作为位置
|
// 调用渲染器 使用画布中心作为位置
|
||||||
try { renderer(ctx, 16, 16, baseSize); } catch(e){}
|
try { renderer(ctx, 16, 16, baseSize); } catch(e){}
|
||||||
// 对基于圖像的形狀進行補繪 當圖像加載完成時重試一次
|
// 对基于图像的形状进行补绘 当图像加载完成时重试一次
|
||||||
var assets = (window.YooneSnowSettings && window.YooneSnowSettings.assetsMap) ? window.YooneSnowSettings.assetsMap : {};
|
var assets = (window.YooneSnowSettings && window.YooneSnowSettings.assetsMap) ? window.YooneSnowSettings.assetsMap : {};
|
||||||
var url = assets[shapeKey];
|
var url = assets[shapeKey];
|
||||||
if (url && typeof window.YooneSnowGetOrLoadImage === 'function'){
|
if (url && typeof window.YooneSnowGetOrLoadImage === 'function'){
|
||||||
|
|
@ -197,7 +200,11 @@
|
||||||
wrapper.appendChild(weightInput);
|
wrapper.appendChild(weightInput);
|
||||||
wrapper.appendChild(input);
|
wrapper.appendChild(input);
|
||||||
wrapper.appendChild(cancelBtn);
|
wrapper.appendChild(cancelBtn);
|
||||||
shapeListContainer.appendChild(wrapper);
|
if (unifiedAddCard && unifiedAddCard.parentNode === shapeListContainer){
|
||||||
|
shapeListContainer.insertBefore(wrapper, unifiedAddCard);
|
||||||
|
} else {
|
||||||
|
shapeListContainer.appendChild(wrapper);
|
||||||
|
}
|
||||||
var previewEl = createShapePreviewElement(key);
|
var previewEl = createShapePreviewElement(key);
|
||||||
if (previewEl) { previewHost.appendChild(previewEl); }
|
if (previewEl) { previewHost.appendChild(previewEl); }
|
||||||
}
|
}
|
||||||
|
|
@ -216,7 +223,7 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 綁定移除 emoji 按鈕事件 使用事件委託處理動態元素
|
// 绑定移除 emoji 按钮事件 使用事件委托处理动态元素
|
||||||
if (shapeListContainer){
|
if (shapeListContainer){
|
||||||
shapeListContainer.addEventListener('click', function(event){
|
shapeListContainer.addEventListener('click', function(event){
|
||||||
var target = event.target;
|
var target = event.target;
|
||||||
|
|
@ -282,14 +289,18 @@
|
||||||
wrapper.appendChild(weightInput);
|
wrapper.appendChild(weightInput);
|
||||||
wrapper.appendChild(input);
|
wrapper.appendChild(input);
|
||||||
wrapper.appendChild(removeBtn);
|
wrapper.appendChild(removeBtn);
|
||||||
listContainer.appendChild(wrapper);
|
if (unifiedAddCard && unifiedAddCard.parentNode === listContainer){
|
||||||
|
listContainer.insertBefore(wrapper, unifiedAddCard);
|
||||||
|
} else {
|
||||||
|
listContainer.appendChild(wrapper);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
frame.open();
|
frame.open();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emoji 别名映射 用於文本搜索到 emoji 字符
|
// Emoji 别名映射 用于文本搜索到 emoji 字符
|
||||||
var emojiAliasMap = {
|
var emojiAliasMap = {
|
||||||
smile: '🙂',
|
smile: '🙂',
|
||||||
happy: '😊',
|
happy: '😊',
|
||||||
|
|
@ -321,10 +332,10 @@
|
||||||
berry: '🍓'
|
berry: '🍓'
|
||||||
};
|
};
|
||||||
|
|
||||||
// 填充下拉選單 以別名和字符組合顯示
|
// 填充下拉菜单 以别名和字符组合显示
|
||||||
function populateEmojiSelect(){
|
function populateEmojiSelect(){
|
||||||
if (!emojiSelect) { return; }
|
if (!emojiSelect) { return; }
|
||||||
// 清空除第一個提示項之外的選項
|
// 清空除第一个提示项之外的选项
|
||||||
while (emojiSelect.options.length > 1){ emojiSelect.remove(1); }
|
while (emojiSelect.options.length > 1){ emojiSelect.remove(1); }
|
||||||
for (var key in emojiAliasMap){
|
for (var key in emojiAliasMap){
|
||||||
var ch = emojiAliasMap[key];
|
var ch = emojiAliasMap[key];
|
||||||
|
|
@ -335,7 +346,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 綁定下拉選擇事件 選擇後添加對應 emoji 並重置選中
|
// 绑定下拉选择事件 选择后添加对应 emoji 并重置选中
|
||||||
if (emojiSelect){
|
if (emojiSelect){
|
||||||
populateEmojiSelect();
|
populateEmojiSelect();
|
||||||
emojiSelect.addEventListener('change', function(){
|
emojiSelect.addEventListener('change', function(){
|
||||||
|
|
@ -347,7 +358,7 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 顯示建議列表 根據查詢文本匹配別名
|
// 显示建议列表 根据查询文本匹配别名
|
||||||
function showEmojiSuggestions(query){
|
function showEmojiSuggestions(query){
|
||||||
if (!emojiSuggestBox) { return; }
|
if (!emojiSuggestBox) { return; }
|
||||||
var q = String(query || '').toLowerCase().trim();
|
var q = String(query || '').toLowerCase().trim();
|
||||||
|
|
@ -371,7 +382,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 將 emoji 字符添加到列表 並創建隱藏輸入
|
// 将 emoji 字符添加到列表 并创建隐藏输入
|
||||||
function addEmojiByChar(ch){
|
function addEmojiByChar(ch){
|
||||||
if (!shapeListContainer) { return; }
|
if (!shapeListContainer) { return; }
|
||||||
var key = String(ch);
|
var key = String(ch);
|
||||||
|
|
@ -418,25 +429,55 @@
|
||||||
wrapper.appendChild(weightInput);
|
wrapper.appendChild(weightInput);
|
||||||
wrapper.appendChild(input);
|
wrapper.appendChild(input);
|
||||||
wrapper.appendChild(removeBtn);
|
wrapper.appendChild(removeBtn);
|
||||||
shapeListContainer.appendChild(wrapper);
|
if (unifiedAddCard && unifiedAddCard.parentNode === shapeListContainer){
|
||||||
|
shapeListContainer.insertBefore(wrapper, unifiedAddCard);
|
||||||
|
} else {
|
||||||
|
shapeListContainer.appendChild(wrapper);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 綁定 emoji 添加按鈕 點擊後優先按別名匹配 否則直接添加字符
|
// 绑定 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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 監聽輸入變化 顯示建議列表 支持即時搜索
|
// 监听输入变化 显示建议列表 支持即时搜索
|
||||||
|
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){
|
if (emojiInput){
|
||||||
emojiInput.addEventListener('input', function(){
|
emojiInput.addEventListener('input', function(){
|
||||||
showEmojiSuggestions(emojiInput.value);
|
showEmojiSuggestions(emojiInput.value);
|
||||||
|
|
@ -448,10 +489,31 @@
|
||||||
if (q !== ''){
|
if (q !== ''){
|
||||||
var lower = q.toLowerCase();
|
var lower = q.toLowerCase();
|
||||||
if (emojiAliasMap[lower]){ addEmojiByChar(emojiAliasMap[lower]); }
|
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){
|
if (shapeListContainer){
|
||||||
|
|
@ -477,27 +539,97 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shapeAddButton){
|
|
||||||
shapeAddButton.addEventListener('click', function(){
|
|
||||||
var val = shapeAddSelect ? String(shapeAddSelect.value || '') : '';
|
// 切换类型面板 显示对应控件 其他隐藏
|
||||||
if (val.trim() !== ''){
|
function updateTypePane(){
|
||||||
addShapeBox(val);
|
var t = typeSelect ? String(typeSelect.value || '') : '';
|
||||||
shapeAddSelect.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 = ''; }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切換類型面板 顯示對應控件 其他隱藏
|
if (shapeListContainer){
|
||||||
function updateTypePane(){
|
shapeListContainer.addEventListener('click', function(event){
|
||||||
var t = typeSelect ? String(typeSelect.value || '') : '';
|
var target = event.target;
|
||||||
if (!paneDefault || !paneEmoji || !paneMedia) { return; }
|
if (target && target.classList.contains('yoone-snow-remove-text')){
|
||||||
paneDefault.style.display = (t === 'default') ? 'flex' : 'none';
|
var item = target.closest('.yoone-snow-text-item');
|
||||||
paneEmoji.style.display = (t === 'emoji') ? 'flex' : 'none';
|
if (item){ item.remove(); }
|
||||||
paneMedia.style.display = (t === 'media') ? 'flex' : 'none';
|
}
|
||||||
}
|
});
|
||||||
if (typeSelect){
|
|
||||||
typeSelect.addEventListener('change', updateTypePane);
|
|
||||||
updateTypePane();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,12 @@
|
||||||
const record = { img: img, ready: false };
|
const record = { img: img, ready: false };
|
||||||
window.YooneSnowImageCache[imageUrl] = record;
|
window.YooneSnowImageCache[imageUrl] = record;
|
||||||
img.onload = function(){
|
img.onload = function(){
|
||||||
// 加载成功 标记为可用
|
var decoder = img.decode && typeof img.decode === 'function' ? img.decode() : null;
|
||||||
record.ready = true;
|
if (decoder && typeof decoder.then === 'function'){
|
||||||
|
decoder.then(function(){ record.ready = true; }).catch(function(){ record.ready = true; });
|
||||||
|
} else {
|
||||||
|
record.ready = true;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
img.onerror = function(){
|
img.onerror = function(){
|
||||||
// 加载失败 从缓存移除避免重复错误
|
// 加载失败 从缓存移除避免重复错误
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
const canvas = document.getElementById('effectiveAppsSnow');
|
const canvas = document.getElementById('effectiveAppsSnow');
|
||||||
// 条件判断 如果未找到画布元素则不执行
|
// 条件判断 如果未找到画布元素则不执行
|
||||||
if (!canvas) return;
|
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');
|
const context = canvas.getContext('2d');
|
||||||
let viewportWidth = window.innerWidth;
|
let viewportWidth = window.innerWidth;
|
||||||
let viewportHeight = window.innerHeight;
|
let viewportHeight = window.innerHeight;
|
||||||
|
|
@ -27,6 +29,9 @@
|
||||||
const emojiItems = (window.YooneSnowSettings && Array.isArray(window.YooneSnowSettings.emojiItems))
|
const emojiItems = (window.YooneSnowSettings && Array.isArray(window.YooneSnowSettings.emojiItems))
|
||||||
? 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 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
|
||||||
|
|
@ -37,6 +42,9 @@
|
||||||
const emojiWeightsRaw = (window.YooneSnowSettings && window.YooneSnowSettings.emojiWeights && typeof window.YooneSnowSettings.emojiWeights === 'object')
|
const emojiWeightsRaw = (window.YooneSnowSettings && window.YooneSnowSettings.emojiWeights && typeof window.YooneSnowSettings.emojiWeights === 'object')
|
||||||
? window.YooneSnowSettings.emojiWeights
|
? window.YooneSnowSettings.emojiWeights
|
||||||
: {};
|
: {};
|
||||||
|
const textWeightsRaw = (window.YooneSnowSettings && window.YooneSnowSettings.textWeights && typeof window.YooneSnowSettings.textWeights === 'object')
|
||||||
|
? window.YooneSnowSettings.textWeights
|
||||||
|
: {};
|
||||||
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];
|
||||||
|
|
@ -87,7 +95,8 @@
|
||||||
const maxCountRaw = (window.YooneSnowSettings && typeof window.YooneSnowSettings.maxCount !== 'undefined')
|
const maxCountRaw = (window.YooneSnowSettings && typeof window.YooneSnowSettings.maxCount !== 'undefined')
|
||||||
? parseInt(window.YooneSnowSettings.maxCount, 10)
|
? parseInt(window.YooneSnowSettings.maxCount, 10)
|
||||||
: 0;
|
: 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 snowflakesTargetCount = Math.max(1, (isNaN(maxCountRaw) || maxCountRaw <= 0) ? computedAutoCount : maxCountRaw);
|
||||||
const snowflakes = [];
|
const snowflakes = [];
|
||||||
// 定义连续生成控制参数 使用时间积累的方式平滑新增
|
// 定义连续生成控制参数 使用时间积累的方式平滑新增
|
||||||
|
|
@ -117,6 +126,15 @@
|
||||||
if (ew > 0){ items.push({ kind: 'emoji', text: ch, weight: ew }); }
|
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++){
|
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;
|
||||||
|
|
@ -145,7 +163,11 @@
|
||||||
if (items[i].kind === 'media'){
|
if (items[i].kind === 'media'){
|
||||||
return { type: 'media_image', url: items[i].url, text: null };
|
return { type: 'media_image', url: items[i].url, text: null };
|
||||||
} else {
|
} 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();
|
context.restore();
|
||||||
continue;
|
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 registry = window.YooneSnowShapeRenderers || {};
|
||||||
const renderer = registry[flake.shapeType] || registry['dot'];
|
const renderer = registry[flake.shapeType] || registry['dot'];
|
||||||
|
|
|
||||||
227
yoone-snow.php
227
yoone-snow.php
|
|
@ -16,55 +16,74 @@ function yoone_snow_enqueue_assets() {
|
||||||
if (!yoone_snow_is_enabled()) { return; }
|
if (!yoone_snow_is_enabled()) { return; }
|
||||||
$style_handle = 'yoone-snow-style';
|
$style_handle = 'yoone-snow-style';
|
||||||
$style_src = plugins_url('css/snow.css', __FILE__);
|
$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);
|
wp_enqueue_style($style_handle);
|
||||||
|
|
||||||
// 注册形状渲染脚本 保证主脚本之前加载
|
// 注册形状渲染脚本 保证主脚本之前加载
|
||||||
$shape_index_handle = 'yoone-snow-shapes-index';
|
$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';
|
$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';
|
$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';
|
$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';
|
$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';
|
$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';
|
$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';
|
$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';
|
$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';
|
$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';
|
$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';
|
$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_handle = 'yoone-snow-script';
|
||||||
$script_src = plugins_url('js/snow-canvas.js', __FILE__);
|
$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(
|
wp_register_script(
|
||||||
$script_handle,
|
$script_handle,
|
||||||
$script_src,
|
$script_src,
|
||||||
array(
|
$deps,
|
||||||
$shape_index_handle,
|
$script_ver ? (string)$script_ver : '1.1.0',
|
||||||
$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',
|
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
wp_enqueue_script($script_handle);
|
wp_enqueue_script($script_handle);
|
||||||
|
|
@ -168,6 +187,30 @@ function yoone_snow_enqueue_assets() {
|
||||||
}
|
}
|
||||||
return $clean;
|
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 渲染脚本 以供预览复用
|
// 在后台也注册并加载 shapes 渲染脚本 以供预览复用
|
||||||
// 形状索引与工具脚本 提供全局渲染器与图像加载能力
|
// 形状索引与工具脚本 提供全局渲染器与图像加载能力
|
||||||
$shape_index_handle = 'yoone-snow-shapes-index';
|
$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';
|
$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';
|
$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';
|
$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';
|
$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';
|
$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';
|
$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';
|
$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';
|
$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';
|
$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';
|
$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';
|
$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 相关脚本 以便预览复用前端实现
|
// 在后台本页入队 shapes 相关脚本 以便预览复用前端实现
|
||||||
wp_enqueue_script($shape_index_handle);
|
wp_enqueue_script($shape_index_handle);
|
||||||
|
|
@ -252,7 +307,7 @@ function yoone_snow_admin_enqueue($hook) {
|
||||||
$shape_reindeer_handle,
|
$shape_reindeer_handle,
|
||||||
$shape_berry_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
|
true
|
||||||
);
|
);
|
||||||
// 传递资源映射用于后台形状预览 保持与前端一致
|
// 传递资源映射用于后台形状预览 保持与前端一致
|
||||||
|
|
@ -304,10 +359,9 @@ function yoone_snow_register_settings() {
|
||||||
// 添加设置分区 标题为 Snow Settings
|
// 添加设置分区 标题为 Snow Settings
|
||||||
add_settings_section(
|
add_settings_section(
|
||||||
'yoone_snow_section',
|
'yoone_snow_section',
|
||||||
'Snow Settings',
|
esc_html__('Snow Settings', 'yoone-snow'),
|
||||||
function() {
|
function() {
|
||||||
// 输出分区描述 使用英文标点保证兼容
|
echo '<p>' . esc_html__('Configure snow appearance', 'yoone-snow') . '</p>';
|
||||||
echo '<p>Configure snow appearance</p>';
|
|
||||||
},
|
},
|
||||||
'yoone_snow'
|
'yoone_snow'
|
||||||
);
|
);
|
||||||
|
|
@ -341,6 +395,8 @@ function yoone_snow_register_settings() {
|
||||||
if (!is_array($current_media)) { $current_media = array(); }
|
if (!is_array($current_media)) { $current_media = array(); }
|
||||||
$current_emojis = get_option('yoone_snow_emoji_items', array());
|
$current_emojis = get_option('yoone_snow_emoji_items', array());
|
||||||
if (!is_array($current_emojis)) { $current_emojis = 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());
|
$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(); }
|
||||||
|
|
@ -348,6 +404,8 @@ function yoone_snow_register_settings() {
|
||||||
if (!is_array($media_weights_current)) { $media_weights_current = array(); }
|
if (!is_array($media_weights_current)) { $media_weights_current = array(); }
|
||||||
$emoji_weights_current = get_option('yoone_snow_emoji_weights', array());
|
$emoji_weights_current = get_option('yoone_snow_emoji_weights', array());
|
||||||
if (!is_array($emoji_weights_current)) { $emoji_weights_current = 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 '<div id="yooneSnowShapeList" style="display:flex;flex-wrap:wrap;gap:12px;">';
|
echo '<div id="yooneSnowShapeList" style="display:flex;flex-wrap:wrap;gap:12px;">';
|
||||||
// 渲染默认形状卡片
|
// 渲染默认形状卡片
|
||||||
|
|
@ -388,42 +446,49 @@ function yoone_snow_register_settings() {
|
||||||
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;">Remove</button>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
}
|
}
|
||||||
echo '</div>';
|
// 文本卡片
|
||||||
|
foreach ($current_texts as $text_item) {
|
||||||
// 统一添加控件 包含类型下拉与三类子控件
|
$label = trim((string)$text_item);
|
||||||
echo '<div id="yooneSnowAddUnified" style="margin-top:8px;display:flex;flex-direction:column;gap:8px;">';
|
if ($label === '') { continue; }
|
||||||
|
echo '<div class="yoone-snow-text-item" data-text-label="' . esc_attr($label) . '" style="border:1px solid #ddd;padding:8px;display:flex;flex-direction:column;align-items:center;min-width:96px;">';
|
||||||
|
echo '<div style="font-size:14px;line-height:18px;margin-bottom:6px;width:120px;min-height:32px;display:flex;align-items:center;justify-content:center;background-color:#e6e6e6;border:1px solid #ddd;border-radius:4px;word-break:break-word;text-align:center;">' . esc_html($label) . '</div>';
|
||||||
|
$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="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 '</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 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>Type <select id="yooneSnowAddTypeSelect" style="min-width:180px;">';
|
||||||
echo '<option value="">Select type</option>';
|
echo '<option value="default" selected>Default</option>';
|
||||||
echo '<option value="default">Default</option>';
|
|
||||||
echo '<option value="emoji">Emoji</option>';
|
echo '<option value="emoji">Emoji</option>';
|
||||||
echo '<option value="media">Media</option>';
|
echo '<option value="media">Media</option>';
|
||||||
|
echo '<option value="text">Text</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:none;gap:8px;align-items:center;">';
|
|
||||||
echo '<select id="yooneSnowAddShapeSelect" style="min-width:240px;"><option value="">Select shape</option>';
|
echo '<select id="yooneSnowAddShapeSelect" style="min-width:240px;"><option value="">Select shape</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 '<button type="button" class="button" id="yooneSnowAddShapeBtn">Add Shape</button>';
|
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
// Emoji 子面板
|
|
||||||
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="">Select emoji</option></select>';
|
||||||
echo '<input type="text" id="yooneSnowAddEmojiInput" placeholder="Type emoji or alias" style="width:240px;" />';
|
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 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">Add Images</button>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
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 '</div>';
|
||||||
echo '<p class="description">Add shapes by type all in one list</p>';
|
echo '<p class="description">Add shapes by type all in one list</p>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
echo '</div>';
|
||||||
// 权重输入已集成到各类型卡片之中 无需单独权重区域
|
|
||||||
},
|
},
|
||||||
'yoone_snow',
|
'yoone_snow',
|
||||||
'yoone_snow_section'
|
'yoone_snow_section'
|
||||||
|
|
@ -555,6 +620,41 @@ function yoone_snow_register_settings() {
|
||||||
'default' => array(),
|
'default' => array(),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
register_setting('yoone_snow_options', 'yoone_snow_text_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(),
|
||||||
|
));
|
||||||
|
|
||||||
|
register_setting('yoone_snow_options', 'yoone_snow_text_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',
|
||||||
|
|
@ -710,14 +810,14 @@ function yoone_snow_register_settings() {
|
||||||
// 添加设置页面到后台菜单 条目在设置菜单下
|
// 添加设置页面到后台菜单 条目在设置菜单下
|
||||||
function yoone_snow_add_settings_page() {
|
function yoone_snow_add_settings_page() {
|
||||||
add_options_page(
|
add_options_page(
|
||||||
'Yoone Snow',
|
esc_html__('Yoone Snow', 'yoone-snow'),
|
||||||
'Yoone Snow',
|
esc_html__('Yoone Snow', 'yoone-snow'),
|
||||||
'manage_options',
|
'manage_options',
|
||||||
'yoone_snow',
|
'yoone_snow',
|
||||||
function() {
|
function() {
|
||||||
// 渲染设置页面 表单提交到 options.php
|
// 渲染设置页面 表单提交到 options.php
|
||||||
echo '<div class="wrap">';
|
echo '<div class="wrap">';
|
||||||
echo '<h1>Yoone Snow</h1>';
|
echo '<h1>' . esc_html__('Yoone Snow', 'yoone-snow') . '</h1>';
|
||||||
echo '<form method="post" action="options.php">';
|
echo '<form method="post" action="options.php">';
|
||||||
settings_fields('yoone_snow_options');
|
settings_fields('yoone_snow_options');
|
||||||
do_settings_sections('yoone_snow');
|
do_settings_sections('yoone_snow');
|
||||||
|
|
@ -737,7 +837,7 @@ add_action('admin_enqueue_scripts', 'yoone_snow_admin_enqueue');
|
||||||
function yoone_snow_plugin_action_links($links) {
|
function yoone_snow_plugin_action_links($links) {
|
||||||
// 构造设置页面链接 使用 admin_url 保证后台路径正确
|
// 构造设置页面链接 使用 admin_url 保证后台路径正确
|
||||||
$settingsUrl = admin_url('options-general.php?page=yoone_snow');
|
$settingsUrl = admin_url('options-general.php?page=yoone_snow');
|
||||||
$settingsLink = '<a href="' . esc_url($settingsUrl) . '">Settings</a>';
|
$settingsLink = '<a href="' . esc_url($settingsUrl) . '">' . esc_html__('Settings', 'yoone-snow') . '</a>';
|
||||||
// 将设置链接插入到最前面 便于用户点击
|
// 将设置链接插入到最前面 便于用户点击
|
||||||
array_unshift($links, $settingsLink);
|
array_unshift($links, $settingsLink);
|
||||||
return $links;
|
return $links;
|
||||||
|
|
@ -745,3 +845,8 @@ function yoone_snow_plugin_action_links($links) {
|
||||||
|
|
||||||
// 绑定到当前插件的 action links 钩子 使用 plugin_basename 计算插件标识
|
// 绑定到当前插件的 action links 钩子 使用 plugin_basename 计算插件标识
|
||||||
add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'yoone_snow_plugin_action_links');
|
add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'yoone_snow_plugin_action_links');
|
||||||
|
|
||||||
|
function yoone_snow_load_textdomain() {
|
||||||
|
load_plugin_textdomain('yoone-snow', false, dirname(plugin_basename(__FILE__)) . '/languages');
|
||||||
|
}
|
||||||
|
add_action('plugins_loaded', 'yoone_snow_load_textdomain');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue