优化评论区细节
This commit is contained in:
+9
-1
@@ -307,8 +307,14 @@ const config: AskyConfig = {
|
||||
avatarUrl: 'https://cravatar.cn/avatar/',
|
||||
// 头像服务前缀(Gravatar 镜像)
|
||||
|
||||
showUa: true
|
||||
showUa: true,
|
||||
// 是否显示评论者的 UA 图标(OS / 浏览器)
|
||||
|
||||
showEmotion: true,
|
||||
// 是否启用 Twikoo 表情面板
|
||||
|
||||
emotionCdn: ''
|
||||
// Twikoo 表情 CDN,多个用英文逗号分隔(留空时读取 Twikoo 后台 EMOTION_CDN,仍为空则使用默认 OwO)
|
||||
},
|
||||
|
||||
/* ===== 杂七杂八 ===== */
|
||||
@@ -465,6 +471,8 @@ export interface AskyConfig {
|
||||
region?: string;
|
||||
avatarUrl?: string;
|
||||
showUa?: boolean;
|
||||
showEmotion?: boolean;
|
||||
emotionCdn?: string;
|
||||
};
|
||||
|
||||
/* ===== 杂七杂八 ===== */
|
||||
|
||||
+183
-3
@@ -3031,7 +3031,7 @@ a.page-numbers:hover{
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.comments .commentwrap {
|
||||
width: 69.076%;
|
||||
width: 100%;
|
||||
max-width: 860px;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
@@ -3220,14 +3220,172 @@ img.wp-smiley {
|
||||
}
|
||||
|
||||
.smilies-box {
|
||||
clear: both;
|
||||
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 {
|
||||
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 {
|
||||
-webkit-animation: btn-pudding 1s linear;
|
||||
@@ -3242,13 +3400,35 @@ animation: btn-pudding 1s linear;
|
||||
}
|
||||
|
||||
.smli-button{
|
||||
width:2rem;
|
||||
height:2rem;
|
||||
width:40px;
|
||||
height:40px;
|
||||
float:right;
|
||||
position:relative;
|
||||
cursor:pointer;
|
||||
font-size:20px;
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ const envId = (config.twikoo?.envId || '').replace(/\/$/, '');
|
||||
const avatarBase = config.twikoo?.avatarUrl || 'https://cravatar.cn/avatar/';
|
||||
const showUa = config.twikoo?.showUa !== false;
|
||||
const showPrivate = isOptionOn('open_private_message');
|
||||
const localShowEmotion = config.twikoo?.showEmotion !== false;
|
||||
const localEmotionCdn = config.twikoo?.emotionCdn || '';
|
||||
---
|
||||
|
||||
{open && (
|
||||
@@ -81,7 +83,7 @@ const showPrivate = isOptionOn('open_private_message');
|
||||
<span class="siren-is-private-checkbox siren-checkbox-radioInput"></span>私密评论
|
||||
</label>
|
||||
)}
|
||||
<a class="smli-button">😊</a>
|
||||
<button type="button" class="smli-button" title="插入表情" aria-label="插入表情">😊</button>
|
||||
</div>
|
||||
<div class="comment_bottom">
|
||||
<input class="submit" name="submit" type="submit" id="submit" tabindex={5} value="发表评论" />
|
||||
@@ -95,7 +97,7 @@ const showPrivate = isOptionOn('open_private_message');
|
||||
</section>
|
||||
)}
|
||||
|
||||
<script is:inline define:vars={{ envId, avatarBase, showUa }}>
|
||||
<script is:inline define:vars={{ envId, avatarBase, showUa, localShowEmotion, localEmotionCdn }}>
|
||||
(function () {
|
||||
if (!envId) {
|
||||
var loading = document.getElementById('loading-comments');
|
||||
@@ -213,14 +215,199 @@ const showPrivate = isOptionOn('open_private_message');
|
||||
}
|
||||
|
||||
var masterTag = '博主';
|
||||
var emotionMap = {};
|
||||
var emotionEnabled = localShowEmotion;
|
||||
var emotionCdn = localEmotionCdn || '';
|
||||
var defaultEmotionCdn = 'https://owo.imaegoo.com/owo.json';
|
||||
|
||||
function loadConfig() {
|
||||
return callApi('GET_CONFIG').then(function (res) {
|
||||
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 () {});
|
||||
}
|
||||
|
||||
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 ============ */
|
||||
function renderCommentItem(c) {
|
||||
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');
|
||||
@@ -345,7 +535,7 @@ const showPrivate = isOptionOn('open_private_message');
|
||||
var oldVal = submitBtn.value;
|
||||
submitBtn.value = '提交中...';
|
||||
|
||||
var commentHtml = '<p>' + escapeHtml(comment).replace(/\n/g, '<br>') + '</p>';
|
||||
var commentHtml = formatCommentHtml(comment);
|
||||
|
||||
callApi('COMMENT_SUBMIT', {
|
||||
url: path,
|
||||
|
||||
Reference in New Issue
Block a user