Danbooru提示词提取器 - ⚙️设置(自选图标/过滤规则NAI·Anima·自定义/通配符匹配)
// ==UserScript==
// @name Danbooru Prompt Extractor
// @namespace danbooru-prompt-extractor
// @version 1.1.1
// @description Danbooru提示词提取器 - ⚙️设置(自选图标/过滤规则NAI·Anima·自定义/通配符匹配)
// @match https://danbooru.donmai.us/posts/*
// @match https://danbooru.donmai.us/posts?*
// @grant none
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// ===== 内置过滤规则预设 =====
// NAI规则:只过滤马赛克/审查类(保留 pubic_hair 不删)
var RULE_NAI = ['mosaic', 'mosaic_censoring', 'censored', 'censor', 'censoring',
'bar_censor', 'black_bar', 'pixelated', 'pixelation',
'patchwork_censor', 'spotlight_censor', 'blur', 'blurred', 'covering'];
// Anima规则:审查类 + commentary类(*支持前后缀通配:commentary*=开头,*commentary=结尾)
var RULE_ANIMA = RULE_NAI.concat(['commentary*', '*commentary', 'translated*',
'speech_bubble', 'text*', 'high_score_dialogue']);
// ===== localStorage 状态存储 =====
function lsGet(k, d) { try { var v = localStorage.getItem('dpe_' + k); return v === null ? d : JSON.parse(v); } catch (e) { return d; } }
function lsSet(k, v) { try { localStorage.setItem('dpe_' + k, JSON.stringify(v)); return true; } catch (e) { return false; } }
var rule = lsGet('rule', 'nai'); // nai | anima | custom
var customWords = lsGet('customWords', null); // 自定义词表(初始=Anima复制)
if (!customWords) { customWords = RULE_ANIMA.slice(); lsSet('customWords', customWords); }
var icons = lsGet('icons', []); // [{name, data(base64)}]
var activeIcon = lsGet('activeIcon', -1); // -1 = 默认🏷
function toast(msg) {
var o = document.getElementById('dpe-toast'); if (o) o.remove();
var t = document.createElement('div'); t.id = 'dpe-toast'; t.textContent = msg;
document.body.appendChild(t);
setTimeout(function() { t.style.opacity = '0'; setTimeout(function() { t.remove(); }, 300); }, 1600);
}
function currentWords() { return rule === 'custom' ? customWords : (rule === 'anima' ? RULE_ANIMA : RULE_NAI); }
var css =
'#dpe-wrap{position:fixed;left:20px;top:300px;width:50px;height:50px;z-index:2147483647;font-family:-apple-system,BlinkMacSystemFont,sans-serif}' +
'#dpe-toggle{width:50px;height:50px;border-radius:50%;background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;display:flex;align-items:center;justify-content:center;font-size:24px;box-shadow:0 4px 16px rgba(102,126,234,0.5);cursor:pointer;user-select:none;-webkit-user-select:none;-webkit-touch-callout:none;touch-action:none;background-size:cover;background-position:center}' +
'#dpe-panel{display:none;flex-direction:column;position:fixed;background:#fff;border-radius:12px;box-shadow:0 12px 40px rgba(0,0,0,0.25);overflow:hidden;z-index:2147483647}' +
'#dpe-head{padding:10px 12px;background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;display:flex;align-items:center;justify-content:space-between;font-size:14px;font-weight:600;flex-shrink:0;gap:6px}' +
'#dpe-tools{display:flex;gap:4px;align-items:center}' +
'#dpe-tools button{background:rgba(255,255,255,0.2);border:none;color:#fff;font-size:14px;padding:3px 8px;border-radius:6px;cursor:pointer;line-height:1.4}' +
'#dpe-tools button.on{background:#ff5252}' +
'#dpe-close{background:rgba(255,255,255,0.2);border:none;color:#fff;font-size:14px;padding:3px 9px;border-radius:6px;cursor:pointer;line-height:1.4;margin-left:2px}' +
'#dpe-search{display:none;padding:8px 10px;border-bottom:1px solid #eee;flex-shrink:0}' +
'#dpe-search input{width:100%;box-sizing:border-box;padding:7px 10px;border:1px solid #ddd;border-radius:6px;font-size:13px;outline:none}' +
'#dpe-body{flex:1;overflow-y:auto;padding:10px;-webkit-overflow-scrolling:touch;min-height:0;touch-action:pan-y}' +
'.dpe-cat{margin-bottom:10px}' +
'.dpe-cat-t{font-size:12px;font-weight:600;color:#888;margin-bottom:5px}' +
'.dpe-cat-c{background:#eee;color:#666;padding:1px 7px;border-radius:10px;font-size:10px;margin-left:6px}' +
'.dpe-tgs{display:flex;flex-wrap:wrap;gap:4px}' +
'.dpe-tg{background:#f3f3f3;color:#333;padding:5px 10px;border-radius:5px;font-size:12px;cursor:pointer;border:1px solid #e8e8e8;user-select:none;-webkit-user-select:none;white-space:nowrap;touch-action:pan-y}' +
'.dpe-delx{display:inline-block;margin-left:5px;color:#fff;background:#e74c3c;border-radius:50%;width:15px;height:15px;line-height:14px;text-align:center;font-size:10px;font-weight:bold;cursor:pointer;vertical-align:1px}' +
'.dpe-hl{border:2px solid #ff5252 !important;background:#ffe3e3 !important;box-shadow:0 0 6px rgba(255,60,60,.5) !important}' +
'.dpe-hl-first{border:3px solid #ff1744 !important;background:#ffd0d0 !important}' +
'#dpe-foot{padding:10px 14px;border-top:1px solid #eee;display:flex;gap:8px;align-items:stretch;flex-shrink:0}' +
'#dpe-preview{flex:1;background:#f8f8f8;border:1px solid #e0e0e0;border-radius:6px;padding:6px 8px;font-size:11px;color:#555;max-height:70px;overflow-y:auto;word-break:break-all;-webkit-overflow-scrolling:touch;touch-action:pan-y}' +
'#dpe-copy{background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;border:none;padding:8px 14px;border-radius:6px;cursor:pointer;font-size:13px;font-weight:500;white-space:nowrap}' +
// 设置面板
'#dpe-settings{display:none;padding:10px 12px;border-bottom:1px solid #eee;flex-shrink:0;max-height:46vh;overflow-y:auto;-webkit-overflow-scrolling:touch;background:#fafafa}' +
'.dpe-set-t{font-size:12px;font-weight:700;color:#667eea;margin:6px 0 6px}' +
'.dpe-icons{display:flex;flex-wrap:wrap;gap:8px}' +
'.dpe-ic{position:relative;width:52px;height:52px;border-radius:10px;border:2px solid #ddd;background-size:cover;background-position:center;background-color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:22px}' +
'.dpe-ic.active{border-color:#667eea;box-shadow:0 0 8px rgba(102,126,234,.6)}' +
'.dpe-ic .dpe-icx{position:absolute;top:-6px;right:-6px;width:16px;height:16px;background:#e74c3c;color:#fff;border-radius:50%;font-size:10px;line-height:16px;text-align:center}' +
'.dpe-ic-add{border:2px dashed #bbb;color:#999}' +
'.dpe-rules{display:flex;gap:6px;margin-bottom:6px}' +
'.dpe-rules button{flex:1;padding:6px 0;border:1px solid #ddd;background:#fff;border-radius:8px;font-size:12px;cursor:pointer}' +
'.dpe-rules button.on{background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;border-color:transparent}' +
'.dpe-words{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:6px}' +
'.dpe-w{background:#eee;color:#555;padding:3px 8px;border-radius:10px;font-size:11px;display:flex;align-items:center;gap:4px}' +
'.dpe-w .dpe-wx{color:#e74c3c;font-weight:bold;cursor:pointer}' +
'.dpe-addrow{display:flex;gap:6px}' +
'.dpe-addrow input{flex:1;padding:6px 8px;border:1px solid #ddd;border-radius:6px;font-size:12px;outline:none}' +
'.dpe-addrow button{padding:6px 12px;border:none;border-radius:6px;background:#667eea;color:#fff;font-size:12px;cursor:pointer}' +
'#dpe-toast{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(0,0,0,0.85);color:#fff;padding:10px 20px;border-radius:8px;font-size:14px;z-index:2147483648;pointer-events:none;transition:opacity 0.3s}';
var st = document.createElement('style');
st.textContent = css;
document.head.appendChild(st);
var wrapper = document.createElement('div');
wrapper.id = 'dpe-wrap';
wrapper.innerHTML =
'<div id="dpe-toggle">🏷</div>' +
'<div id="dpe-panel">' +
'<div id="dpe-head"><span>🎨 Danbooru</span><span id="dpe-tools"><button id="dpe-search-btn" title="搜索">🔍</button><button id="dpe-filter-btn" title="按当前规则过滤">🧹</button><button id="dpe-del-btn" title="删除模式">🗑️</button><button id="dpe-ok-btn" title="完成" style="display:none">✓</button><button id="dpe-set-btn" title="设置">⚙️</button><button id="dpe-refresh" title="刷新">🔄</button><button id="dpe-close" title="关闭">✕</button></span></div>' +
'<div id="dpe-settings">' +
'<div class="dpe-set-t">🖼 图标(点击切换 / × 删除 / ➕ 添加,GIF保留动画)</div>' +
'<div class="dpe-icons" id="dpe-icons"></div>' +
'<input type="file" id="dpe-file" accept="image/*" style="display:none">' +
'<div class="dpe-set-t">🧹 过滤规则(当前:<span id="dpe-rule-name"></span>)</div>' +
'<div class="dpe-rules">' +
'<button data-r="nai">NAI</button><button data-r="anima">Anima</button><button data-r="custom">自定义</button>' +
'</div>' +
'<div class="dpe-words" id="dpe-words"></div>' +
'<div class="dpe-addrow" id="dpe-addrow"><input id="dpe-word-in" placeholder="输入过滤词,如 mosaic_gag"/><button id="dpe-word-add">添加</button></div>' +
'</div>' +
'<div id="dpe-search"><input type="text" placeholder="输入关键词搜索标签..."/></div>' +
'<div id="dpe-body"></div>' +
'<div id="dpe-foot"><div id="dpe-preview"></div><button id="dpe-copy">📋 复制</button></div>' +
'</div>';
document.body.appendChild(wrapper);
var panel = wrapper.querySelector('#dpe-panel');
var pbody = wrapper.querySelector('#dpe-body');
var preview = wrapper.querySelector('#dpe-preview');
var copyBtn = wrapper.querySelector('#dpe-copy');
var btn = wrapper.querySelector('#dpe-toggle');
var refreshBtn = wrapper.querySelector('#dpe-refresh');
var delBtn = wrapper.querySelector('#dpe-del-btn');
var okBtn = wrapper.querySelector('#dpe-ok-btn');
var filterBtn = wrapper.querySelector('#dpe-filter-btn');
var searchBtn = wrapper.querySelector('#dpe-search-btn');
var setBtn = wrapper.querySelector('#dpe-set-btn');
var setPanel = wrapper.querySelector('#dpe-settings');
var closeBtn = wrapper.querySelector('#dpe-close');
var searchBox = wrapper.querySelector('#dpe-search');
var searchInput = wrapper.querySelector('#dpe-search input');
var iconsBox = wrapper.querySelector('#dpe-icons');
var fileInput = wrapper.querySelector('#dpe-file');
var wordsBox = wrapper.querySelector('#dpe-words');
var addRow = wrapper.querySelector('#dpe-addrow');
var wordIn = wrapper.querySelector('#dpe-word-in');
var wordAdd = wrapper.querySelector('#dpe-word-add');
var ruleNameEl = wrapper.querySelector('#dpe-rule-name');
var isOpen = false, data = null;
var deleteMode = false, lastKeyword = '';
var psx = 0, psy = 0, baseL = 20, baseT = 300, moved = false, dragging = false;
var touchLock = false;
// ==== 图标 ====
function applyMainIcon() {
if (activeIcon >= 0 && icons[activeIcon]) {
btn.style.backgroundImage = 'url("' + icons[activeIcon].data + '")';
btn.textContent = '';
} else {
btn.style.backgroundImage = '';
btn.textContent = '🏷';
}
}
function renderIcons() {
iconsBox.innerHTML = '';
var def = document.createElement('div');
def.className = 'dpe-ic' + (activeIcon === -1 ? ' active' : '');
def.textContent = '🏷';
def.title = '默认图标';
def.addEventListener('click', function() { activeIcon = -1; lsSet('activeIcon', -1); applyMainIcon(); renderIcons(); toast('已恢复默认图标'); });
iconsBox.appendChild(def);
icons.forEach(function(ic, i) {
var d = document.createElement('div');
d.className = 'dpe-ic' + (i === activeIcon ? ' active' : '');
d.style.backgroundImage = 'url("' + ic.data + '")';
var x = document.createElement('span');
x.className = 'dpe-icx'; x.textContent = '×';
x.addEventListener('click', function(e) {
e.stopPropagation();
icons.splice(i, 1);
if (activeIcon === i) activeIcon = -1; else if (activeIcon > i) activeIcon--;
lsSet('icons', icons); lsSet('activeIcon', activeIcon);
applyMainIcon(); renderIcons(); toast('已删除图标');
});
d.appendChild(x);
d.addEventListener('click', function() {
activeIcon = i; lsSet('activeIcon', i);
applyMainIcon(); renderIcons(); toast('已切换图标');
});
iconsBox.appendChild(d);
});
var add = document.createElement('div');
add.className = 'dpe-ic dpe-ic-add';
add.textContent = '➕';
add.addEventListener('click', function() { fileInput.click(); });
iconsBox.appendChild(add);
}
function compress96(file, cb) {
var img = new Image();
img.onload = function() {
var c = document.createElement('canvas'); c.width = 96; c.height = 96;
var ctx = c.getContext('2d');
var s = Math.min(img.width, img.height);
ctx.drawImage(img, (img.width - s) / 2, (img.height - s) / 2, s, s, 0, 0, 96, 96);
cb(c.toDataURL('image/png'));
};
img.onerror = function() { toast('图片读取失败'); };
img.src = URL.createObjectURL(file);
}
fileInput.addEventListener('change', function() {
var f = fileInput.files[0]; if (!f) return;
fileInput.value = '';
if (f.type === 'image/gif') {
var r = new FileReader();
r.onload = function() {
if (r.result.length > 3 * 1024 * 1024) { toast('GIF太大(>3MB),请先压缩'); return; }
icons.push({ name: f.name, data: r.result });
if (!lsSet('icons', icons)) { icons.pop(); toast('存储空间不足'); return; }
renderIcons(); toast('GIF已添加(保留动画)');
};
r.readAsDataURL(f);
} else {
compress96(f, function(data) {
icons.push({ name: f.name, data: data });
if (!lsSet('icons', icons)) { icons.pop(); toast('存储空间不足'); return; }
renderIcons(); toast('图标已添加');
});
}
});
// ==== 过滤规则 UI ====
function renderWords() {
var ws = currentWords();
ruleNameEl.textContent = rule === 'nai' ? 'NAI(保留pubic_hair)' : rule === 'anima' ? 'Anima(含commentary类)' : '自定义';
wordsBox.innerHTML = '';
ws.forEach(function(w, i) {
var s = document.createElement('span');
s.className = 'dpe-w';
s.innerHTML = w.replace(/</g, '<') + (rule === 'custom' ? ' <span class="dpe-wx">×</span>' : '');
if (rule === 'custom') {
s.querySelector('.dpe-wx').addEventListener('click', function() {
customWords.splice(i, 1); lsSet('customWords', customWords); renderWords();
});
}
wordsBox.appendChild(s);
});
if (!ws.length) wordsBox.innerHTML = '<span style="font-size:11px;color:#999">词表为空,请添加</span>';
addRow.style.display = rule === 'custom' ? 'flex' : 'none';
wrapper.querySelectorAll('.dpe-rules button').forEach(function(b) {
b.classList.toggle('on', b.getAttribute('data-r') === rule);
});
}
wrapper.querySelectorAll('.dpe-rules button').forEach(function(b) {
b.addEventListener('click', function() {
rule = b.getAttribute('data-r');
lsSet('rule', rule);
renderWords();
toast('已切换规则:' + (rule === 'nai' ? 'NAI' : rule === 'anima' ? 'Anima' : '自定义') + ',点🧹生效');
});
});
wordAdd.addEventListener('click', function() {
var w = wordIn.value.trim().replace(/\s+/g, '_').toLowerCase();
if (!w) return;
if (customWords.indexOf(w) > -1) { toast('已存在'); return; }
customWords.push(w); lsSet('customWords', customWords);
wordIn.value = ''; renderWords(); toast('已添加: ' + w);
});
// ==== 定位/开合 ====
function setPos(l, t) {
var vw = window.innerWidth, vh = window.innerHeight;
l = Math.max(0, Math.min(vw - 50, l));
t = Math.max(0, Math.min(vh - 50, t));
wrapper.style.left = l + 'px';
wrapper.style.top = t + 'px';
}
function getL() { return parseInt(wrapper.style.left) || 20; }
function getT() { return parseInt(wrapper.style.top) || 300; }
setPos(20, 300);
function openPanel() {
var vw = window.innerWidth, vh = window.innerHeight;
var pw = Math.min(380, vw - 16), ph = Math.min(Math.floor(vh * 0.7), 480);
var r = wrapper.getBoundingClientRect();
// 面板改为 fixed 定位:基于按钮实际屏幕位置,且始终夹在可视区内
var left = Math.min(Math.max(8, r.left), Math.max(8, vw - pw - 8));
var top;
if (r.bottom + 8 + ph <= vh) {
top = r.bottom + 8; // 默认在按钮下方
} else if (r.top - 8 - ph >= 0) {
top = r.top - 8 - ph; // 放不下则整体翻到按钮上方
} else {
top = Math.max(8, (vh - ph) / 2); // 都放不下就垂直居中
}
panel.style.width = pw + 'px';
panel.style.maxHeight = ph + 'px';
panel.style.left = left + 'px';
panel.style.top = top + 'px';
panel.style.right = 'auto';
panel.style.bottom = 'auto';
panel.style.display = 'flex';
if (!data) load();
}
function closePanel() { isOpen = false; panel.style.display = 'none'; }
function togglePanel() { isOpen ? closePanel() : (isOpen = true, openPanel()); }
// ==== 拖拽 ====
function dragStart(x, y, target) {
if (panel.contains(target)) return;
psx = x; psy = y;
baseL = getL(); baseT = getT();
moved = false;
dragging = true;
}
function dragMove(x, y, ev) {
if (!dragging) return;
var dx = x - psx, dy = y - psy;
if (!moved && (Math.abs(dx) > 8 || Math.abs(dy) > 8)) moved = true;
if (moved) {
if (ev && ev.cancelable) ev.preventDefault();
setPos(baseL + dx, baseT + dy);
}
}
function dragEnd() {
var wasMoved = moved;
dragging = false; moved = false;
if (wasMoved && isOpen) openPanel();
}
wrapper.addEventListener('touchstart', function(e) {
touchLock = true;
dragStart(e.touches[0].clientX, e.touches[0].clientY, e.target);
}, { passive: true });
document.addEventListener('touchmove', function(e) {
if (!dragging) return;
dragMove(e.touches[0].clientX, e.touches[0].clientY, e);
}, { passive: false });
document.addEventListener('touchend', function() {
dragEnd();
setTimeout(function() { touchLock = false; }, 350);
});
wrapper.addEventListener('mousedown', function(e) {
if (touchLock) return;
dragStart(e.clientX, e.clientY, e.target);
});
document.addEventListener('mousemove', function(e) {
if (touchLock || !dragging) return;
dragMove(e.clientX, e.clientY, null);
});
document.addEventListener('mouseup', function() {
if (touchLock) return;
dragEnd();
});
btn.addEventListener('click', function(e) {
if (moved) { e.preventDefault(); e.stopPropagation(); return; }
togglePanel();
});
panel.addEventListener('click', function(e) { e.stopPropagation(); });
document.addEventListener('click', function(e) {
if (!isOpen) return;
if (e.target === document.body || e.target === document.documentElement) closePanel();
});
// ==== 工具函数 ====
function getPostId() { var m = window.location.pathname.match(/\/posts\/(\d+)/); return m ? m[1] : null; }
function esc(s) { return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); }
function fbCopy(text) {
var ta = document.createElement('textarea');
ta.value = text; ta.style.cssText = 'position:fixed;opacity:0;top:-100px';
document.body.appendChild(ta); ta.select();
document.execCommand('copy'); ta.remove();
toast('已复制: ' + text);
}
async function load() {
var id = getPostId();
if (!id) { pbody.innerHTML = '<div style="text-align:center;padding:20px;color:#e74c3c">未获取到帖子ID</div>'; return; }
pbody.innerHTML = '<div style="text-align:center;padding:20px;color:#999">加载中...</div>';
var tags;
try {
var r = await fetch('https://danbooru.donmai.us/posts/' + id + '.json');
if (!r.ok) throw 0;
var d = await r.json(); var p = d.post || d;
tags = {
general: (p.tag_string_general || '').split(' ').filter(Boolean),
artist: (p.tag_string_artist || '').split(' ').filter(Boolean),
copyright: (p.tag_string_copyright || '').split(' ').filter(Boolean),
character: (p.tag_string_character || '').split(' ').filter(Boolean),
meta: (p.tag_string_meta || '').split(' ').filter(Boolean)
};
} catch (e) { tags = parseDom(); }
data = tags; render(tags);
}
function parseDom() {
var t = { general: [], artist: [], copyright: [], character: [], meta: [] };
document.querySelectorAll('a').forEach(function(a) {
var tx = a.textContent.trim(), hf = a.getAttribute('href') || '';
if (!tx || tx.length > 50 || hf.indexOf('/tags/') !== 0) return;
var p = a.closest('.tag-type-general,.tag-type-artist,.tag-type-copyright,.tag-type-character,.tag-type-meta');
var c = 'general';
if (p) {
if (p.classList.contains('tag-type-artist')) c = 'artist';
else if (p.classList.contains('tag-type-copyright')) c = 'copyright';
else if (p.classList.contains('tag-type-character')) c = 'character';
else if (p.classList.contains('tag-type-meta')) c = 'meta';
}
if (t[c].indexOf(tx) === -1) t[c].push(tx);
});
return t;
}
function removeTag(tag) {
for (var k in data) {
var i = data[k].indexOf(tag);
if (i > -1) { data[k].splice(i, 1); return true; }
}
return false;
}
// 按当前规则过滤(通配符:commentary*=前缀匹配,*commentary=后缀匹配)
function wordMatch(tag, w) {
var star = w.indexOf('*');
if (star === -1) return tag === w; // 精确匹配
if (star === w.length - 1) return tag.indexOf(w.slice(0, -1)) === 0; // 前缀:xxx*
if (star === 0) return tag.slice(-w.length + 1) === w.slice(1); // 后缀:*xxx
return false;
}
function applyFilter() {
if (!data) return;
var ws = currentWords(), removed = [];
for (var k in data) {
data[k] = data[k].filter(function(tag) {
var hit = null;
for (var i = 0; i < ws.length; i++) {
if (wordMatch(tag, ws[i])) { hit = ws[i]; break; }
}
if (hit) { removed.push(tag + '(' + hit + ')'); return false; }
return true;
});
}
render(data);
if (removed.length) toast('[' + rule.toUpperCase() + '] 已过滤 ' + removed.length + ' 个: ' + removed.join(', '));
else toast('没有需要过滤的标签');
}
function doSearch(kw) {
var tgs = pbody.querySelectorAll('.dpe-tg');
tgs.forEach(function(el) { el.classList.remove('dpe-hl', 'dpe-hl-first'); });
if (!kw) return;
var q = kw.toLowerCase(), first = null;
tgs.forEach(function(el) {
var txt = el.childNodes[0] ? el.childNodes[0].textContent : el.textContent;
if (txt.toLowerCase().indexOf(q) > -1) {
el.classList.add('dpe-hl');
if (!first) { first = el; el.classList.add('dpe-hl-first'); }
}
});
if (first) first.scrollIntoView({ block: 'center', behavior: 'smooth' });
}
function syncPreview() {
var sep = ', ';
var all = [];
for (var k in data) {
var arr = data[k].filter(function(x) { return x && x.length > 1; });
if (arr.length) all.push(arr.join(sep));
}
if (preview) preview.textContent = all.join(sep) || '';
}
function render(tags) {
var sep = ', ';
var names = { general: '通用', artist: '画师', copyright: '版权', character: '角色', meta: '元' };
var icons2 = { general: '🏷', artist: '🎨', copyright: '©', character: '👤', meta: '⚙' };
var html = '';
for (var k in names) {
var arr = tags[k].filter(function(x) { return x && x.length > 1; });
if (!arr.length) continue;
html += '<div class="dpe-cat"><div class="dpe-cat-t">' + icons2[k] + ' ' + names[k] + ' <span class="dpe-cat-c">' + arr.length + '</span></div><div class="dpe-tgs">' +
arr.map(function(x) {
return '<span class="dpe-tg" data-v="' + esc(x) + '">' + esc(x) + (deleteMode ? '<span class="dpe-delx">×</span>' : '') + '</span>';
}).join('') +
'</div></div>';
}
if (!html) html = '<div style="text-align:center;padding:20px;color:#e74c3c">未找到标签</div>';
pbody.innerHTML = html;
syncPreview();
pbody.querySelectorAll('.dpe-delx').forEach(function(x) {
x.addEventListener('click', function(e) {
e.stopPropagation();
var tg = x.closest('.dpe-tg');
if (tg) { removeTag(tg.getAttribute('data-v')); render(data); }
});
});
pbody.querySelectorAll('.dpe-tg').forEach(function(el) {
el.addEventListener('click', function() {
if (deleteMode) return;
fbCopy(el.getAttribute('data-v'));
});
});
if (lastKeyword) doSearch(lastKeyword);
}
// ==== 工具栏事件 ====
delBtn.addEventListener('click', function() {
deleteMode = !deleteMode;
delBtn.classList.toggle('on', deleteMode);
okBtn.style.display = deleteMode ? '' : 'none';
toast(deleteMode ? '删除模式开启,点 × 删除' : '删除模式已关闭');
render(data);
});
okBtn.addEventListener('click', function() {
deleteMode = false;
delBtn.classList.remove('on');
okBtn.style.display = 'none';
toast('已确认删除');
render(data);
});
filterBtn.addEventListener('click', applyFilter);
searchBtn.addEventListener('click', function() {
var show = searchBox.style.display !== 'block';
searchBox.style.display = show ? 'block' : 'none';
if (show) { searchInput.focus(); } else { searchInput.value = ''; lastKeyword = ''; doSearch(''); }
});
searchInput.addEventListener('input', function() {
lastKeyword = this.value.trim();
doSearch(lastKeyword);
});
setBtn.addEventListener('click', function() {
var show = setPanel.style.display !== 'block';
setPanel.style.display = show ? 'block' : 'none';
if (show) { renderIcons(); renderWords(); }
});
closeBtn.addEventListener('click', function() { closePanel(); });
refreshBtn.addEventListener('click', function() { data = null; load(); });
copyBtn.addEventListener('click', function() {
var txt = preview.textContent;
if (!txt) { toast('没有内容'); return; }
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(txt).then(function() {
copyBtn.textContent = '✅ 已复制'; toast('已复制!');
setTimeout(function() { copyBtn.textContent = '📋 复制'; }, 1800);
}).catch(function() { fbCopy(txt); });
} else { fbCopy(txt); }
});
applyMainIcon();
console.log('[PromptExtractor] v1.1.0 loaded');
})();