您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Rive编辑器自定义中文术语词典,目前支持:词条分组管理/编辑删除/导入导出/搜索/组内拖拽/分组拖拽/蓝色插入线/删空组/面板收起成可拖拽按钮;如果需要支持其他网站,自主进行代码更改,或留言添加
// ==UserScript== // @name Rive 中文术语管理 // @namespace http://tampermonkey.net/ // @version 1.5.2 // @description Rive编辑器自定义中文术语词典,目前支持:词条分组管理/编辑删除/导入导出/搜索/组内拖拽/分组拖拽/蓝色插入线/删空组/面板收起成可拖拽按钮;如果需要支持其他网站,自主进行代码更改,或留言添加 // @match https://editor.rive.app/* // @match https://app.rive.app/* // @match https://rive.app/* // @match https://*.rive.app/* // @run-at document-start // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; var DEBUG = false; function log(){ if (DEBUG) try{ console.log('[Rive-I18N]', ...arguments);}catch(_){} } var STORAGE_KEY = 'rive_terms_dict_shadow_v11'; var UI_STATE_KEY = 'rive_panel_ui_shadow_v11'; var uidSeed = 1; function uid(){ return 'u' + Date.now().toString(36) + (uidSeed++).toString(36); } var DEFAULT_DICT = [ { uid: uid(), group: '动画', items: [{ uid: uid(), key: 'Animation', value: '动画' }, { uid: uid(), key: 'Timeline', value: '时间轴' }] }, { uid: uid(), group: '状态机', items: [{ uid: uid(), key: 'State Machine', value: '状态机' }, { uid: uid(), key: 'Transition', value: '过渡' }] } ]; function loadDict(){ try{ var raw = localStorage.getItem(STORAGE_KEY); var d = raw ? JSON.parse(raw) : DEFAULT_DICT; d.forEach(function(g){ if(!g.uid) g.uid = uid(); if(!Array.isArray(g.items)) g.items=[]; g.items.forEach(function(it){ if(!it.uid) it.uid=uid(); }); }); return d; }catch(_){ return DEFAULT_DICT; } } function saveDict(){ localStorage.setItem(STORAGE_KEY, JSON.stringify(dict)); } function loadUIState(){ try{ var raw = localStorage.getItem(UI_STATE_KEY); var s = raw ? JSON.parse(raw) : {}; return { minimized: !!s.minimized, panelPos: s.panelPos || null, miniPos: s.miniPos || null, collapsed: s.collapsed || {} }; }catch(_){ return { minimized:false, panelPos:null, miniPos:null, collapsed:{} }; } } function saveUIState(){ localStorage.setItem(UI_STATE_KEY, JSON.stringify(uiState)); } var dict = loadDict(); var uiState = loadUIState(); var hostEl=null, shadow=null, root=null, panelEl=null, miniEl=null, groupsEl=null; function setFixed(el,x,y){ el.style.left=x+'px'; el.style.top=y+'px'; el.style.right='auto'; } function clamp(n,a,b){ return Math.max(a, Math.min(b,n)); } function restoreFixed(box,pos){ if(!box) return; var w=box.offsetWidth||0, h=box.offsetHeight||0; var vx=window.innerWidth, vy=window.innerHeight; var x=(pos&&Number.isFinite(pos.x))?pos.x:(vx-w-20); var y=(pos&&Number.isFinite(pos.y))?pos.y:80; x=Math.max(0, Math.min(vx-w,x)); y=Math.max(0, Math.min(vy-h,y)); setFixed(box,x,y); } var CSS = '' + '#rive-root, #rive-panel, #rive-mini{font-family: Inter, ui-sans-serif, system-ui, Arial, sans-serif; line-height: 1.4;}' + '#rive-panel{display:block; position:fixed; top:80px; right:20px; width:440px; background:#3D3D3D; color:#FFFFFF; border:1px solid #3A3A3A; border-radius:10px; font-size:13px; box-shadow:0 6px 18px rgba(0,0,0,.6); overflow:hidden; z-index:2147483647;}' + '#rive-panel *{box-sizing:border-box;}' + '#rive-header{background:#474747;color:#FFFFFF;display:flex;align-items:center;justify-content:space-between;padding:12px;user-select:none;cursor:move;}' + '#rive-title{margin-left:12px;font-weight:700;}' + '#rive-header .right{display:inline-flex;align-items:center;column-gap:12px;margin-right:12px;}' + '#rive-header .btn{border:1px solid #666;background:#5a5a5a;color:#fff;border-radius:6px;padding:2px 10px;cursor:pointer;}' + '#rive-header .btn:hover{filter:brightness(1.06);}' + '#rive-inner{padding:12px;display:flex;flex-direction:column;gap:12px;background:#3D3D3D;}' + '.rive-search-wrap{background:rgba(0,0,0,0.15);border:1px solid #3A3A3A;border-radius:8px;padding:8px;display:flex;align-items:center;gap:8px;}' + '.rive-search-wrap .icon{opacity:.65;}' + '#rive-search{flex:1;background:transparent;border:none;outline:none;color:#FFFFFFCC;font-size:13px;}' + '#rive-search::placeholder{color:#FFFFFF99;opacity:.8;}' + '#rive-groups{max-height:560px;overflow:auto;position:relative;border-radius:8px;}' + '.rive-group{background:#3D3D3D;border-radius:8px;margin:0 0px 12px 0px;}' + '.rive-group:last-child{margin-bottom:4px;}' + '.rive-group-header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 10px;border:1px solid #454545;border-radius:6px;background:#3D3D3D;}' + '.rive-group-header .left{display:inline-flex;align-items:center;gap:8px;}' + '.rive-toggle{width:20px;height:20px;display:inline-flex;align-items:center;justify-content:center;border:1px solid #555;border-radius:4px;background:#2F2F2F;color:#fff;cursor:pointer;}' + '.rive-group-title{font-weight:600;color:#FFFFFF;}' + '.rive-group-handle{cursor:grab;color:#FFFFFF99;user-select:none;padding:2px 6px;border-radius:4px;}' + '.rive-group-handle:hover{background:rgba(255,255,255,.08);}' + '.rive-items{padding-top:8px;}' + '.rive-row{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:4px 0;padding:8px 10px;cursor:grab;border:1px solid #3a3a3a;border-radius:6px;background:#FFFFFF0D;transition:background .12s ease,border-color .12s ease;}' + '.rive-row:first-child{margin-top:0;}.rive-row:last-child{margin-bottom:0;}' + '.rive-row:hover{background:#FFFFFF1A;border-color:#4a4a4a;}' + '.rive-row .term{font-weight:400;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:70%;}' + '.rive-row .actions{display:inline-flex;align-items:center;column-gap:12px;}' + '.rive-row .act{color:#7ECBFF;cursor:pointer;font-weight:600;text-decoration:none;}' + '.rive-row .act:hover{text-decoration:underline;}' + '.dragging{opacity:.85;}' + '#rive-insert-indicator{position:absolute;left:8px;right:8px;height:2px;background:#2AA0FF;box-shadow:0 0 0 1px rgba(42,160,255,.25);pointer-events:none;display:none;}' + '#rive-footer{display:flex;align-items:center;justify-content:center;gap:12px;background:#3D3D3D;border-radius:8px;padding:8px;}' + '.rive-btn{border:1px solid #888;background:#5A5A5A;color:#fff;border-radius:8px;padding:8px 12px;cursor:pointer;}' + '.rive-btn:hover{filter:brightness(1.06);}' + '#rive-mini{position:fixed;top:80px;right:20px;background:#474747;color:#fff;border:1px solid #3A3A3A;border-radius:20px;padding:8px 12px;cursor:move;user-select:none;box-shadow:0 6px 18px rgba(0,0,0,.4);font-size:13px;font-weight:600;z-index:2147483647;}' + '#rive-mini .tap{cursor:pointer;}' + '.hidden{display:none !important;}'; function injectStylesSafe(shadowRoot, cssText){ try{ if ('adoptedStyleSheets' in shadowRoot && 'CSSStyleSheet' in window) { var sheet = new CSSStyleSheet(); sheet.replaceSync(cssText); shadowRoot.adoptedStyleSheets = (shadowRoot.adoptedStyleSheets || []).concat(sheet); log('styles: adoptedStyleSheets'); return 'adopted'; } }catch(e1){ log('adopted failed', e1); } try{ var blob = new Blob([cssText], {type:'text/css'}); var link = document.createElement('link'); link.rel='stylesheet'; link.href=URL.createObjectURL(blob); shadowRoot.appendChild(link); window.addEventListener('unload', function(){ try{ URL.revokeObjectURL(link.href);}catch(e){} }); log('styles: blob link'); return 'blob'; }catch(e2){ log('blob failed', e2); } try{ var style = document.createElement('style'); style.textContent = cssText; shadowRoot.appendChild(style); log('styles: inline style'); return 'inline'; }catch(e3){ log('inline failed', e3); } return 'none'; } function applyEmergencyPanelStyle(el){ if(!el) return; el.style.cssText += ';position:fixed;top:80px;right:20px;width:520px;display:block;background:#3D3D3D;color:#fff;border:1px solid #3A3A3A;border-radius:10px;font:13px Inter,ui-sans-serif,system-ui,Arial,sans-serif;box-shadow:0 6px 18px rgba(0,0,0,.6);z-index:2147483647;'; } function applyEmergencyMiniStyle(el){ if(!el) return; el.style.cssText += ';position:fixed;top:80px;right:20px;background:#474747;color:#fff;border:1px solid #3A3A3A;border-radius:20px;padding:8px 12px;font:600 13px Inter,ui-sans-serif,system-ui,Arial,sans-serif;box-shadow:0 6px 18px rgba(0,0,0,.4);z-index:2147483647;cursor:move;'; } function waitForDomReady(cb, tries){ tries = tries || 0; if (document.body || document.documentElement) { cb(); return; } if (tries > 300) { cb(); return; } requestAnimationFrame(function(){ waitForDomReady(cb, tries+1); }); } function makeDraggable(box, handle, onMove){ var down=false, dx=0, dy=0; handle.addEventListener('mousedown', function(e){ var t = e.target; var isBtn = t && (t.id==='rive-close' || t.id==='rive-collapse' || t.classList.contains('tap')); if (isBtn) return; down=true; var r=box.getBoundingClientRect(); dx=e.clientX-r.left; dy=e.clientY-r.top; }); document.addEventListener('mouseup', function(){ down=false; }); document.addEventListener('mousemove', function(e){ if(!down) return; var x = clamp(e.clientX - dx, 0, window.innerWidth - box.offsetWidth); var y = clamp(e.clientY - dy, 0, window.innerHeight - box.offsetHeight); setFixed(box, x, y); if (typeof onMove==='function') onMove(x,y); }); } var DND = { mode:null, dragGroupEl:null, dragItemEl:null, indicator:null, tgtGroupIdx:null, tgtItemIdx:null }; function onGroupHandleDragStart(ev){ ev.dataTransfer.effectAllowed='move'; DND.mode='group'; DND.dragGroupEl = ev.currentTarget.closest('.rive-group'); } function onGroupHandleDragEnd(){ DND.mode=null; DND.dragGroupEl=null; hideIndicator(); } function onRowDragStart(e){ DND.mode='item'; DND.dragItemEl=e.currentTarget; e.currentTarget.classList.add('dragging'); e.dataTransfer.effectAllowed='move'; } function onRowDragEnd(e){ e.currentTarget.classList.remove('dragging'); DND.mode=null; DND.dragItemEl=null; DND.tgtItemIdx=null; hideIndicator(); } function onGroupsClick(e){ var t=e.target; if(!t) return; if (t.classList.contains('rive-toggle')){ var gid=t.dataset.uid; var curOpen=!uiState.collapsed[gid]; uiState.collapsed[gid] = curOpen ? true : false; saveUIState(); renderGroups(shadow.getElementById('rive-search').value.trim().toLowerCase()); return; } if (t.classList.contains('act')){ var action=t.dataset.action; var row=t.closest('.rive-row'); var group=t.closest('.rive-group'); if (!row || !group) return; var gUid=group.dataset.uid, itUid=row.dataset.uid; var gIndex = dict.findIndex(function(g){ return g.uid===gUid; }); if (gIndex<0) return; var iIndex = dict[gIndex].items.findIndex(function(it){ return it.uid===itUid; }); if (iIndex<0) return; if (action==='edit'){ var cur=dict[gIndex].items[iIndex]; var nk=window.prompt('编辑英文术语', cur.key); if(!nk) return; var nv=window.prompt('编辑中文翻译', cur.value); if(nv===null) return; dict[gIndex].items[iIndex].key=nk; dict[gIndex].items[iIndex].value=nv; saveDict(); renderGroups(shadow.getElementById('rive-search').value.trim().toLowerCase()); } else if (action==='del'){ if(!window.confirm('确认删除此词条?')) return; dict[gIndex].items.splice(iIndex,1); if (dict[gIndex].items.length===0) dict.splice(gIndex,1); saveDict(); renderGroups(shadow.getElementById('rive-search').value.trim().toLowerCase()); } } } function ensureHost(){ var existed=document.getElementById('rive-i18n-host'); if (existed){ hostEl=existed; shadow=hostEl.shadowRoot || hostEl.attachShadow({mode:'open'}); root=shadow.getElementById('rive-root'); if(!root){ root=document.createElement('div'); root.id='rive-root'; shadow.appendChild(root); } return true; } hostEl=document.createElement('div'); hostEl.id='rive-i18n-host'; hostEl.style.cssText='position:fixed;left:0;top:0;width:0;height:0;z-index:2147483647;'; (document.body||document.documentElement).appendChild(hostEl); shadow=hostEl.attachShadow({mode:'open'}); root=document.createElement('div'); root.id='rive-root'; shadow.appendChild(root); return true; } function buildPanel(){ ensureHost(); if(!shadow||!root) return; root.innerHTML=''; injectStylesSafe(shadow, CSS); panelEl=document.createElement('div'); panelEl.id='rive-panel'; panelEl.innerHTML = '<div id="rive-header">'+ '<div id="rive-title">Rive 中文术语管理</div>'+ '<div class="right">'+ '<button id="rive-collapse" class="btn" title="收起">收起</button>'+ '<button id="rive-close" class="btn" title="关闭">✖</button>'+ '</div>'+ '</div>'+ '<div id="rive-inner">'+ '<div class="rive-search-wrap"><span class="icon">🔍</span><input id="rive-search" type="text" placeholder="搜索英文或中文..."></div>'+ '<div id="rive-groups"></div>'+ '<div id="rive-footer">'+ '<button id="btn-add" class="rive-btn">添加新词条</button>'+ '<button id="btn-import" class="rive-btn">导入词条 JSON</button>'+ '<button id="btn-export" class="rive-btn">导出词条 JSON</button>'+ '</div>'+ '</div>'; root.appendChild(panelEl); var headerEl=panelEl.querySelector('#rive-header'); groupsEl=panelEl.querySelector('#rive-groups'); panelEl.querySelector('#rive-close').addEventListener('click', function(){ root.innerHTML=''; panelEl=null; miniEl=null; }); panelEl.querySelector('#rive-collapse').addEventListener('click', function(){ toMini(); }); panelEl.querySelector('#rive-search').addEventListener('input', function(){ renderGroups(this.value.trim().toLowerCase()); }); makeDraggable(panelEl, headerEl, function(x,y){ uiState.panelPos={x:x,y:y}; saveUIState(); }); setTimeout(function(){ if(uiState.panelPos&&uiState.panelPos.x!=null&&uiState.panelPos.y!=null){ restoreFixed(panelEl, uiState.panelPos); } },0); groupsEl.addEventListener('click', onGroupsClick); renderGroups(''); bindFooter(); setTimeout(function(){ try{ var cs=getComputedStyle(panelEl); var bad=!cs || cs.position!=='fixed' || cs.backgroundColor==='rgba(0, 0, 0, 0)' || panelEl.offsetWidth===0; if (bad) applyEmergencyPanelStyle(panelEl); }catch(_){ applyEmergencyPanelStyle(panelEl); } if (uiState.minimized) toMini(); },120); } function toMini(){ if(!shadow||!root) return; if(panelEl) panelEl.classList.add('hidden'); if(!miniEl){ injectStylesSafe(shadow, CSS); miniEl=document.createElement('div'); miniEl.id='rive-mini'; miniEl.innerHTML='<span class="tap">Rive中文</span>'; root.appendChild(miniEl); miniEl.querySelector('.tap').addEventListener('click', function(){ if(panelEl) panelEl.classList.remove('hidden'); if(miniEl) miniEl.remove(); miniEl=null; uiState.minimized=false; saveUIState(); }); makeDraggable(miniEl, miniEl, function(x,y){ uiState.miniPos={x:x,y:y}; saveUIState(); }); setTimeout(function(){ try{ var cs=getComputedStyle(miniEl); var bad=!cs||cs.position!=='fixed'||miniEl.offsetWidth===0; if(bad) applyEmergencyMiniStyle(miniEl); } catch(_){ applyEmergencyMiniStyle(miniEl); } },50); } uiState.minimized=true; saveUIState(); setTimeout(function(){ restoreFixed(miniEl, uiState.miniPos); },0); } function ensureIndicator(){ if (DND.indicator && groupsEl && groupsEl.contains(DND.indicator)) return DND.indicator; var line=document.createElement('div'); line.id='rive-insert-indicator'; groupsEl.appendChild(line); DND.indicator=line; return line; } function showIndicator(y){ var line=ensureIndicator(); line.style.top=y+'px'; line.style.display='block'; } function hideIndicator(){ if (DND.indicator) DND.indicator.style.display='none'; } function renderGroups(filter){ groupsEl.innerHTML=''; hideIndicator(); for (var gi=0; gi<dict.length; gi++){ var grp=dict[gi]; var card=document.createElement('div'); card.className='rive-group'; card.dataset.uid=grp.uid; var gh=document.createElement('div'); gh.className='rive-group-header'; var left=document.createElement('div'); left.className='left'; var open=!uiState.collapsed[grp.uid]; var btn=document.createElement('button'); btn.className='rive-toggle'; btn.textContent=open?'▾':'▸'; btn.dataset.uid=grp.uid; var title=document.createElement('div'); title.className='rive-group-title'; title.textContent=grp.group+' ('+grp.items.length+')'; left.appendChild(btn); left.appendChild(title); var handle=document.createElement('div'); handle.className='rive-group-handle'; handle.title='拖拽排序此分组'; handle.textContent='⠿'; handle.setAttribute('draggable','true'); handle.addEventListener('dragstart', onGroupHandleDragStart); handle.addEventListener('dragend', onGroupHandleDragEnd); gh.appendChild(left); gh.appendChild(handle); card.appendChild(gh); var items=document.createElement('div'); items.className='rive-items'; if(!open) items.classList.add('hidden'); for (var ii=0; ii<grp.items.length; ii++){ var it=grp.items[ii]; if (filter){ var ok=(it.key||'').toLowerCase().includes(filter) || (it.value||'').includes(filter); if (!ok) continue; } var row=document.createElement('div'); row.className='rive-row'; row.setAttribute('draggable','true'); row.dataset.uid=it.uid; var term=document.createElement('div'); term.className='term'; term.textContent=it.key+' → '+it.value; var actions=document.createElement('div'); actions.className='actions'; var aEdit=document.createElement('a'); aEdit.className='act'; aEdit.href='javascript:void(0);'; aEdit.textContent='编辑'; aEdit.dataset.action='edit'; var aDel=document.createElement('a'); aDel.className='act'; aDel.href='javascript:void(0);'; aDel.textContent='删除'; aDel.dataset.action='del'; actions.appendChild(aEdit); actions.appendChild(aDel); row.appendChild(term); row.appendChild(actions); items.appendChild(row); row.addEventListener('dragstart', onRowDragStart); row.addEventListener('dragend', onRowDragEnd); } card.appendChild(items); groupsEl.appendChild(card); } bindDnDContainer(); } function bindDnDContainer(){ if (!groupsEl) return; groupsEl.onDragOverBound && groupsEl.removeEventListener('dragover', groupsEl.onDragOverBound); groupsEl.onDropBound && groupsEl.removeEventListener('drop', groupsEl.onDropBound); var onDragOver=function(e){ e.preventDefault(); if(!DND.mode) return; var y = e.clientY + (shadow.host.getBoundingClientRect().top + window.scrollY); var rect=groupsEl.getBoundingClientRect(); var baseY = rect.top + window.scrollY - groupsEl.scrollTop; if (DND.mode==='group' && DND.dragGroupEl){ var groups = Array.prototype.slice.call(groupsEl.querySelectorAll('.rive-group')); var insertAt = groups.length; for (var i=0;i<groups.length;i++){ var r=groups[i].getBoundingClientRect(); var mid=r.top + window.scrollY + r.height/2; if (y < mid){ insertAt=i; break; } } var lineTop; if (groups.length===0) lineTop=0; else if (insertAt===0) lineTop=groups[0].getBoundingClientRect().top + window.scrollY - baseY; else if (insertAt>=groups.length) lineTop=groups[groups.length-1].getBoundingClientRect().bottom + window.scrollY - baseY; else lineTop=groups[insertAt-1].getBoundingClientRect().bottom + window.scrollY - baseY; DND.tgtGroupIdx=insertAt; showIndicator(lineTop); } if (DND.mode==='item' && DND.dragItemEl){ var groupEl = DND.dragItemEl.closest('.rive-group'); if(!groupEl) return; var rows = Array.prototype.slice.call(groupEl.querySelectorAll('.rive-items > .rive-row')); if (rows.length===0) return; var insertAt2 = rows.length; for (var j=0;j<rows.length;j++){ var rr=rows[j].getBoundingClientRect(); var mid2=rr.top + window.scrollY + rr.height/2; if (y < mid2){ insertAt2=j; break; } } var lineTop2; if (insertAt2===0) lineTop2=rows[0].getBoundingClientRect().top + window.scrollY - baseY; else if (insertAt2>=rows.length) lineTop2=rows[rows.length-1].getBoundingClientRect().bottom + window.scrollY - baseY; else lineTop2=rows[insertAt2-1].getBoundingClientRect().bottom + window.scrollY - baseY; DND.tgtItemIdx=insertAt2; showIndicator(lineTop2); } }; var onDrop=function(e){ e.preventDefault(); if(!DND.mode) return; // 分组拖拽:修正目标索引;更新数据后直接重渲染,避免 DOM 快照错位 if (DND.mode==='group' && DND.dragGroupEl){ var groups = Array.prototype.slice.call(groupsEl.querySelectorAll('.rive-group')); var src = groups.indexOf(DND.dragGroupEl); var dst = DND.tgtGroupIdx != null ? DND.tgtGroupIdx : src; if (src < dst && dst < groups.length) dst = dst - 1; // 非末尾插入才补偿 if (src !== dst && dst >= 0){ var moved = dict.splice(src,1)[0]; if (dst > dict.length) dst = dict.length; // 允许 append dict.splice(dst,0,moved); saveDict(); // 关键改动:用 renderGroups() 统一刷新,避免使用旧 rows/refs 造成 off-by-one var q1 = shadow.getElementById('rive-search')?.value.trim().toLowerCase() || ''; renderGroups(q1); } } // 词条拖拽:同理,更新数据后整体重渲染 if (DND.mode==='item' && DND.dragItemEl){ var groupEl = DND.dragItemEl.closest('.rive-group'); if(!groupEl) return; var gUid = groupEl.dataset.uid; var gIdx = dict.findIndex(function(x){ return x.uid===gUid; }); if (gIdx<0) return; var rows = Array.prototype.slice.call(groupEl.querySelectorAll('.rive-items > .rive-row')); var src2 = rows.indexOf(DND.dragItemEl); var dst2 = DND.tgtItemIdx != null ? DND.tgtItemIdx : src2; if (src2 < dst2 && dst2 < rows.length) dst2 = dst2 - 1; // 非末尾插入才补偿 if (src2 !== dst2 && dst2 >= 0){ var moved2 = dict[gIdx].items.splice(src2,1)[0]; if (dst2 > dict[gIdx].items.length) dst2 = dict[gIdx].items.length; // 允许 append dict[gIdx].items.splice(dst2,0,moved2); saveDict(); var q2 = shadow.getElementById('rive-search')?.value.trim().toLowerCase() || ''; renderGroups(q2); } } DND.mode=null; DND.dragGroupEl=null; DND.dragItemEl=null; DND.tgtGroupIdx=null; DND.tgtItemIdx=null; hideIndicator(); }; groupsEl.onDragOverBound=onDragOver; groupsEl.onDropBound=onDrop; groupsEl.addEventListener('dragover', onDragOver); groupsEl.addEventListener('drop', onDrop); } function bindFooter(){ var add=shadow.getElementById('btn-add'); var imp=shadow.getElementById('btn-import'); var exp=shadow.getElementById('btn-export'); add.addEventListener('click', function(){ var g=window.prompt('分组名',''); if(!g) return; var k=window.prompt('英文术语',''); if(!k) return; var v=window.prompt('中文翻译',''); if(v===null||v==='') return; var grp=dict.find(function(x){ return x.group===g; }); if(!grp){ grp={uid:uid(), group:g, items:[]}; dict.push(grp); } grp.items.push({uid:uid(), key:k, value:v}); saveDict(); var q=shadow.getElementById('rive-search').value.trim().toLowerCase(); renderGroups(q); }); exp.addEventListener('click', function(){ var data=JSON.stringify(dict.map(function(g){ return { group:g.group, items:g.items.map(function(it){ return {key:it.key, value:it.value}; }) }; }), null, 2); var blob=new Blob([data], {type:'application/json'}); var url=URL.createObjectURL(blob); var a=document.createElement('a'); a.href=url; a.download='rive_terms.json'; a.click(); URL.revokeObjectURL(url); }); imp.addEventListener('click', function(){ var input=document.createElement('input'); input.type='file'; input.accept='.json,application/json'; input.onchange=function(e){ var file=e.target.files && e.target.files[0]; if(!file) return; var reader=new FileReader(); reader.onload=function(evt){ try{ var json=JSON.parse(evt.target.result); if(!Array.isArray(json)){ window.alert('JSON 结构应为数组'); return; } dict=json.map(function(g){ return { uid:uid(), group:g.group, items:Array.isArray(g.items)?g.items.map(function(it){ return {uid:uid(), key:it.key, value:it.value}; }):[] }; }); saveDict(); var q=shadow.getElementById('rive-search').value.trim().toLowerCase(); renderGroups(q); }catch(_){ window.alert('JSON 解析失败'); } }; reader.readAsText(file); }; input.click(); }); } var lastUrl=location.href; function ensurePanel(){ ensureHost(); if(!panelEl && !miniEl) buildPanel(); if(uiState.minimized && panelEl && !miniEl) toMini(); } function boot(){ try{ ensurePanel(); }catch(_){ setTimeout(boot,300); } } document.addEventListener('keydown', function(e){ if(e.altKey && e.shiftKey && (e.key==='R' || e.key==='r')){ try{ ensureHost(); if(!panelEl) buildPanel(); if(panelEl) panelEl.classList.remove('hidden'); if(miniEl){ miniEl.remove(); miniEl=null; } uiState.minimized=false; saveUIState(); setTimeout(function(){ applyEmergencyPanelStyle(panelEl); },50); }catch(_){} } }); setInterval(function(){ if(lastUrl!==location.href){ lastUrl=location.href; setTimeout(ensurePanel,400); } }, 800); var mo=new MutationObserver(function(){ if(!document.getElementById('rive-i18n-host')){ setTimeout(function(){ buildPanel(); },300); } }); function startObserver(){ if(!document.documentElement){ setTimeout(startObserver,200); return; } mo.observe(document.documentElement,{childList:true,subtree:true}); } document.addEventListener('visibilitychange', function(){ if(!document.hidden) setTimeout(ensurePanel,400); }); setInterval(ensurePanel, 3000); if(document.readyState==='loading'){ document.addEventListener('DOMContentLoaded', function(){ waitForDomReady(boot); }); waitForDomReady(boot); } else { waitForDomReady(boot); } startObserver(); })();