优化评论区细节

This commit is contained in:
浪子
2026-05-17 17:00:28 +08:00
parent b23ac50817
commit ee4dd9973e
3 changed files with 387 additions and 9 deletions
+9 -1
View File
@@ -307,8 +307,14 @@ const config: AskyConfig = {
avatarUrl: 'https://cravatar.cn/avatar/', avatarUrl: 'https://cravatar.cn/avatar/',
// 头像服务前缀(Gravatar 镜像) // 头像服务前缀(Gravatar 镜像)
showUa: true showUa: true,
// 是否显示评论者的 UA 图标(OS / 浏览器) // 是否显示评论者的 UA 图标(OS / 浏览器)
showEmotion: true,
// 是否启用 Twikoo 表情面板
emotionCdn: ''
// Twikoo 表情 CDN,多个用英文逗号分隔(留空时读取 Twikoo 后台 EMOTION_CDN,仍为空则使用默认 OwO
}, },
/* ===== 杂七杂八 ===== */ /* ===== 杂七杂八 ===== */
@@ -465,6 +471,8 @@ export interface AskyConfig {
region?: string; region?: string;
avatarUrl?: string; avatarUrl?: string;
showUa?: boolean; showUa?: boolean;
showEmotion?: boolean;
emotionCdn?: string;
}; };
/* ===== 杂七杂八 ===== */ /* ===== 杂七杂八 ===== */
+183 -3
View File
@@ -3031,7 +3031,7 @@ a.page-numbers:hover{
margin-bottom: 10px; margin-bottom: 10px;
} }
.comments .commentwrap { .comments .commentwrap {
width: 69.076%; width: 100%;
max-width: 860px; max-width: 860px;
margin: 0 auto; margin: 0 auto;
padding: 0; padding: 0;
@@ -3220,14 +3220,172 @@ img.wp-smiley {
} }
.smilies-box { .smilies-box {
clear: both;
padding-bottom:5px; padding-bottom:5px;
position: relative;
} }
.twikoo-emotions {
overflow: hidden;
margin: 8px 0 10px;
border: 1px solid #DDE6EA;
border-radius: 8px;
background: #fff;
box-shadow: 0 8px 24px rgba(0,0,0,.08);
}
.twikoo-emotion-tabs {
display: flex;
overflow-x: auto;
border-bottom: 1px solid #EEF2F5;
}
.twikoo-emotion-tab {
display: flex;
align-items: center;
min-height: 40px;
padding: 0 12px;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
color: #6f7680;
cursor: pointer;
font-size: 12px;
line-height: 1;
white-space: nowrap;
appearance: none;
-webkit-appearance: none;
transition-property: background-color, color;
transition-duration: .15s;
transition-timing-function: ease-out;
}
.twikoo-emotion-tab.is-active,
.twikoo-emotion-tab:hover {
background: #F5F8FA;
box-shadow: none;
color: #e2684a;
}
.twikoo-emotion-tab:focus,
.twikoo-emotion-item:focus,
.smli-button:focus {
box-shadow: none;
outline: 1px solid #DDE6EA;
outline-offset: -1px;
}
.twikoo-emotion-tab:active {
box-shadow: none;
}
.twikoo-emotion-panel {
display: none;
max-height: 210px;
overflow-y: auto;
padding: 8px;
}
.twikoo-emotion-panel.is-active {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));
gap: 4px;
}
.twikoo-emotion-panel-emoticon.is-active {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
gap: 4px;
}
.twikoo-emotion-panel-image.is-active {
grid-template-columns: repeat(auto-fill, minmax(44px, 1fr));
}
.twikoo-emotion-item {
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
min-height: 40px;
padding: 4px;
border: 0;
border-radius: 6px;
background: transparent;
box-shadow: none;
cursor: pointer;
font-size: 18px;
line-height: 1;
appearance: none;
-webkit-appearance: none;
transition-property: background-color, transform;
transition-duration: .15s;
transition-timing-function: ease-out;
}
.twikoo-emotion-panel-emoticon .twikoo-emotion-item {
width: auto;
max-width: 100%;
min-height: 32px;
padding: 0 8px;
font-size: 13px;
line-height: 1.2;
font-family: PingFang SC,Hiragino Sans GB,Microsoft YaHei,STHeiti,WenQuanYi Micro Hei,Helvetica,Arial,sans-serif;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.twikoo-emotion-panel-emoji .twikoo-emotion-item {
font-size: 20px;
}
.twikoo-emotion-panel-image .twikoo-emotion-item {
min-width: 44px;
min-height: 44px;
}
.twikoo-emotion-item:hover {
background: rgba(144,147,153,.13);
box-shadow: none;
}
.twikoo-emotion-item:active {
box-shadow: none;
transform: scale(.96);
}
.twikoo-emotion-status {
margin: 8px 0 10px;
padding: 12px;
border: 1px solid #DDE6EA;
border-radius: 8px;
color: #8a8f96;
font-size: 12px;
text-align: center;
}
.comment .body .tk-owo-emotion {
width: 3em;
height: auto;
border-radius: 0;
vertical-align: middle;
}
.smilies-box img { .smilies-box img {
width:26px; width:26px;
} }
.smilies-box .twikoo-emotion-item img {
width:28px;
max-height:28px;
height:auto;
border-radius:0;
object-fit:contain;
vertical-align:middle;
}
.smilies-box img:hover { .smilies-box img:hover {
-webkit-animation: btn-pudding 1s linear; -webkit-animation: btn-pudding 1s linear;
@@ -3242,13 +3400,35 @@ animation: btn-pudding 1s linear;
} }
.smli-button{ .smli-button{
width:2rem; width:40px;
height:2rem; height:40px;
float:right; float:right;
position:relative; position:relative;
cursor:pointer; cursor:pointer;
font-size:20px; font-size:20px;
font-family: EmojiMart, "Segoe UI Emoji", "Segoe UI Symbol", "Segoe UI", "Apple Color Emoji", "Twemoji Mozilla", "Noto Color Emoji", "Android Emoji"; font-family: EmojiMart, "Segoe UI Emoji", "Segoe UI Symbol", "Segoe UI", "Apple Color Emoji", "Twemoji Mozilla", "Noto Color Emoji", "Android Emoji";
border: 0;
border-radius: 8px;
background: transparent;
box-shadow: none;
line-height: 40px;
padding: 0;
text-align: center;
appearance: none;
-webkit-appearance: none;
transition-property: background-color, transform;
transition-duration: .15s;
transition-timing-function: ease-out;
}
.smli-button:hover {
background: rgba(144,147,153,.13);
box-shadow: none;
}
.smli-button:active {
box-shadow: none;
transform: scale(.96);
} }
+195 -5
View File
@@ -19,6 +19,8 @@ const envId = (config.twikoo?.envId || '').replace(/\/$/, '');
const avatarBase = config.twikoo?.avatarUrl || 'https://cravatar.cn/avatar/'; const avatarBase = config.twikoo?.avatarUrl || 'https://cravatar.cn/avatar/';
const showUa = config.twikoo?.showUa !== false; const showUa = config.twikoo?.showUa !== false;
const showPrivate = isOptionOn('open_private_message'); const showPrivate = isOptionOn('open_private_message');
const localShowEmotion = config.twikoo?.showEmotion !== false;
const localEmotionCdn = config.twikoo?.emotionCdn || '';
--- ---
{open && ( {open && (
@@ -81,7 +83,7 @@ const showPrivate = isOptionOn('open_private_message');
<span class="siren-is-private-checkbox siren-checkbox-radioInput"></span>私密评论 <span class="siren-is-private-checkbox siren-checkbox-radioInput"></span>私密评论
</label> </label>
)} )}
<a class="smli-button">😊</a> <button type="button" class="smli-button" title="插入表情" aria-label="插入表情">😊</button>
</div> </div>
<div class="comment_bottom"> <div class="comment_bottom">
<input class="submit" name="submit" type="submit" id="submit" tabindex={5} value="发表评论" /> <input class="submit" name="submit" type="submit" id="submit" tabindex={5} value="发表评论" />
@@ -95,7 +97,7 @@ const showPrivate = isOptionOn('open_private_message');
</section> </section>
)} )}
<script is:inline define:vars={{ envId, avatarBase, showUa }}> <script is:inline define:vars={{ envId, avatarBase, showUa, localShowEmotion, localEmotionCdn }}>
(function () { (function () {
if (!envId) { if (!envId) {
var loading = document.getElementById('loading-comments'); var loading = document.getElementById('loading-comments');
@@ -213,14 +215,199 @@ const showPrivate = isOptionOn('open_private_message');
} }
var masterTag = '博主'; var masterTag = '博主';
var emotionMap = {};
var emotionEnabled = localShowEmotion;
var emotionCdn = localEmotionCdn || '';
var defaultEmotionCdn = 'https://owo.imaegoo.com/owo.json';
function loadConfig() { function loadConfig() {
return callApi('GET_CONFIG').then(function (res) { return callApi('GET_CONFIG').then(function (res) {
if (!res || (res.code && res.code !== 0)) return; if (!res || (res.code && res.code !== 0)) return;
if (res.config && res.config.MASTER_TAG) masterTag = res.config.MASTER_TAG; if (!res.config) return;
if (res.config.MASTER_TAG) masterTag = res.config.MASTER_TAG;
if (!emotionCdn && res.config.EMOTION_CDN) emotionCdn = res.config.EMOTION_CDN;
if (String(res.config.SHOW_EMOTION).toLowerCase() === 'false') emotionEnabled = false;
}).catch(function () {}); }).catch(function () {});
} }
function safeEmotionSrc(src) {
var value = String(src || '').trim();
if (/^(https?:)?\/\//i.test(value) || value.charAt(0) === '/') return value;
return '';
}
function extractEmotionImageSrc(icon) {
var template = document.createElement('template');
template.innerHTML = String(icon || '').trim();
var img = template.content.querySelector('img');
return img ? safeEmotionSrc(img.getAttribute('src')) : '';
}
function fetchJson(url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState !== 4) return;
if (xhr.status >= 200 && xhr.status < 300) {
try {
resolve(JSON.parse(xhr.responseText));
} catch (e) {
reject(e);
}
} else {
reject(new Error('HTTP ' + xhr.status));
}
};
xhr.open('GET', url);
xhr.send();
});
}
function mergeEmotionData(target, data) {
if (!data || typeof data !== 'object') return target;
Object.keys(data).forEach(function (name) {
if (!target[name]) {
target[name] = data[name];
return;
}
var current = target[name].container || [];
var incoming = data[name].container || [];
target[name].container = current.concat(incoming);
});
return target;
}
function insertTextAtCursor(textarea, text) {
if (!textarea || !text) return;
var start = textarea.selectionStart || 0;
var end = textarea.selectionEnd || 0;
textarea.setRangeText(text, start, end, 'end');
textarea.dispatchEvent(new InputEvent('input'));
requestAnimationFrame(function () {
textarea.focus();
});
}
function setEmotionIcon(button, item, imageSrc) {
if (imageSrc) {
var img = document.createElement('img');
img.src = imageSrc;
img.loading = 'lazy';
img.alt = item.text || 'emotion';
button.appendChild(img);
return;
}
button.textContent = String(item.icon || '');
}
function activateEmotionTab(root, index) {
root.querySelectorAll('.twikoo-emotion-tab').forEach(function (tab, i) {
tab.classList.toggle('is-active', i === index);
});
root.querySelectorAll('.twikoo-emotion-panel').forEach(function (panel, i) {
panel.classList.toggle('is-active', i === index);
});
}
function renderEmotionPicker(data) {
var box = document.querySelector('.smilies-box');
var trigger = document.querySelector('.smli-button');
if (!box || !trigger) return;
var packageNames = Object.keys(data || {}).filter(function (name) {
return data[name] && Array.isArray(data[name].container) && data[name].container.length;
});
if (!packageNames.length) {
trigger.style.display = 'none';
return;
}
box.innerHTML = '';
box.style.display = 'none';
var root = document.createElement('div');
root.className = 'twikoo-emotions';
var tabs = document.createElement('div');
tabs.className = 'twikoo-emotion-tabs';
var panels = document.createElement('div');
panels.className = 'twikoo-emotion-panels';
packageNames.forEach(function (name, index) {
var pack = data[name];
var packType = String(pack.type || 'text').replace(/[^a-z0-9_-]/gi, '').toLowerCase() || 'text';
var tab = document.createElement('button');
tab.type = 'button';
tab.className = 'twikoo-emotion-tab';
tab.textContent = name;
tab.addEventListener('click', function () {
activateEmotionTab(root, index);
});
tabs.appendChild(tab);
var panel = document.createElement('div');
panel.className = 'twikoo-emotion-panel twikoo-emotion-panel-' + packType;
pack.container.forEach(function (item) {
var imageSrc = extractEmotionImageSrc(item.icon);
if (imageSrc && item.text) emotionMap[item.text] = imageSrc;
var insertText = imageSrc && item.text ? ':' + item.text + ': ' : String(item.icon || '');
if (!insertText || /<img/i.test(insertText)) return;
var button = document.createElement('button');
button.type = 'button';
button.className = 'twikoo-emotion-item twikoo-emotion-item-' + (imageSrc ? 'image' : packType);
button.title = item.text || name;
setEmotionIcon(button, item, imageSrc);
button.addEventListener('click', function () {
insertTextAtCursor(form.comment, insertText);
box.style.display = 'none';
});
panel.appendChild(button);
});
panels.appendChild(panel);
});
root.appendChild(tabs);
root.appendChild(panels);
box.appendChild(root);
activateEmotionTab(root, 0);
}
function initEmotions() {
var box = document.querySelector('.smilies-box');
var trigger = document.querySelector('.smli-button');
if (!emotionEnabled) {
if (trigger) trigger.style.display = 'none';
if (box) box.style.display = 'none';
return Promise.resolve();
}
if (box) box.innerHTML = '<div class="twikoo-emotion-status">表情加载中...</div>';
var cdns = String(emotionCdn || defaultEmotionCdn)
.split(',')
.map(function (item) { return item.trim(); })
.filter(Boolean);
return Promise.all(cdns.map(function (cdn) {
return fetchJson(cdn).catch(function () { return null; });
})).then(function (items) {
var data = {};
items.forEach(function (item) {
mergeEmotionData(data, item);
});
renderEmotionPicker(data);
});
}
function formatCommentHtml(text) {
var html = escapeHtml(text).replace(/\r\n?/g, '\n');
html = html.replace(/:([^:\n<>]{1,64}):/g, function (match, name) {
var src = emotionMap[name];
if (!src) return match;
return '<img class="tk-owo-emotion" src="' + escapeHtml(src) + '" alt=":' + escapeHtml(name) + ':">';
});
return '<p>' + html.replace(/\n/g, '<br>') + '</p>';
}
/* ============ 渲染单条评论 —— 同 akina_comment_format ============ */ /* ============ 渲染单条评论 —— 同 akina_comment_format ============ */
function renderCommentItem(c) { function renderCommentItem(c) {
var url = c.link ? escapeHtml(c.link) : 'javascript:;'; var url = c.link ? escapeHtml(c.link) : 'javascript:;';
@@ -315,7 +502,10 @@ const showPrivate = isOptionOn('open_private_message');
}); });
} }
loadConfig().then(refresh); loadConfig().then(function () {
initEmotions();
return refresh();
});
/* ============ 提交评论 ============ */ /* ============ 提交评论 ============ */
var form = document.getElementById('twikoo-form'); var form = document.getElementById('twikoo-form');
@@ -345,7 +535,7 @@ const showPrivate = isOptionOn('open_private_message');
var oldVal = submitBtn.value; var oldVal = submitBtn.value;
submitBtn.value = '提交中...'; submitBtn.value = '提交中...';
var commentHtml = '<p>' + escapeHtml(comment).replace(/\n/g, '<br>') + '</p>'; var commentHtml = formatCommentHtml(comment);
callApi('COMMENT_SUBMIT', { callApi('COMMENT_SUBMIT', {
url: path, url: path,