您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Alt+Sで検索プリセット。日本語除外は自動で"引用"。グループ・空グループ作成・カードD&D、カード右端の編集⇄更新トグル、URL全幅+クエリ/除外同列、バックアップ▼(エクスポート/インポート)、テーマ追従。ストレージは searchPresets.vN を使用し、最新のみ読み込み・保存時は新規vN作成、古い版は5件残して削除。
当前为
// ==UserScript== // @name X/Twitter 検索プリセット起動 (Alt+S) — 完全版 v2.3(バージョン管理・最新5件保持) // @namespace preset-search-launcher // @version 2.3.0 // @author Dondoco // @license MIT // @description Alt+Sで検索プリセット。日本語除外は自動で"引用"。グループ・空グループ作成・カードD&D、カード右端の編集⇄更新トグル、URL全幅+クエリ/除外同列、バックアップ▼(エクスポート/インポート)、テーマ追従。ストレージは searchPresets.vN を使用し、最新のみ読み込み・保存時は新規vN作成、古い版は5件残して削除。 // @match https://x.com/* // @match https://twitter.com/* // @run-at document-idle // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @grant GM_listValues // @grant GM_deleteValue // ==/UserScript== (() => { 'use strict'; /** ============== 設定 ============== */ const KEEP_VERSIONS = 5; // 何世代残すか const EXPANDED_KEY = 'searchPresets.expGroups.v1'; // 開閉状態 const GROUPS_KEY = 'searchPresets.customGroups.v1'; // カスタムグループ const OPEN_IN_NEW_TAB = false; // true で新タブ /** ================================== */ // 初期値(初回のみ) const DEFAULTS = [ { title: 'Twitter 落ちた', group: '未分類', q: ['Twitter', '落ちた'], exclude: ['エックスくん', 'Twix'], lang: 'ja', live: true }, ]; /* ================= バージョン管理 ================= */ function listPresetVersions(){ const keys = (typeof GM_listValues === 'function') ? GM_listValues() : []; return keys.map(k => { const m = /^searchPresets\.v(\d+)$/.exec(k); return m ? { key: k, ver: parseInt(m[1], 10) } : null; }).filter(Boolean).sort((a,b)=> b.ver - a.ver); // 降順 } function loadLatestPresets(){ const vers = listPresetVersions(); if (vers.length === 0) { // 初回保存 v1 GM_setValue('searchPresets.v1', JSON.stringify(DEFAULTS)); return DEFAULTS.slice(); } const latestKey = vers[0].key; try { const raw = GM_getValue(latestKey, '[]'); const data = typeof raw === 'string' ? JSON.parse(raw) : raw; return Array.isArray(data) ? data : DEFAULTS.slice(); } catch(e){ console.warn('[PSL] preset parse failed for', latestKey, e); return DEFAULTS.slice(); } } function saveNewVersion(presets){ const vers = listPresetVersions(); const nextVer = vers.length ? (vers[0].ver + 1) : 1; const key = `searchPresets.v${nextVer}`; GM_setValue(key, JSON.stringify(presets)); pruneOldVersions(); } function pruneOldVersions(){ const vers = listPresetVersions(); // 降順 const toDelete = vers.slice(KEEP_VERSIONS); // KEEP_VERSIONS より古いものを削除 toDelete.forEach(v => GM_deleteValue(v.key)); } // 便利関数 const esc = (s)=> (s||'').replace(/[&<>"']/g, c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); const enc = (s)=> encodeURIComponent(s); const loadExpanded = ()=> { try { return new Set(JSON.parse(GM_getValue(EXPANDED_KEY,'[]'))); } catch { return new Set(); } }; const saveExpanded = (set)=> GM_setValue(EXPANDED_KEY, JSON.stringify(Array.from(set))); const loadGroups = ()=> { try { return new Set(JSON.parse(GM_getValue(GROUPS_KEY,'[]'))); } catch { return new Set(); } }; const saveGroups = (set)=> GM_setValue(GROUPS_KEY, JSON.stringify(Array.from(set))); // 日本語判定 function isJapanese(str=''){ return /[\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Han}ー-]/u.test(str); } // 検索URL合成 function buildSearchUrl(p){ if (p.url) return p.url; const parts=[]; if (Array.isArray(p.q)&&p.q.length){ const hh=p.q.map(line=>{ line=(line||'').trim(); if(!line) return ''; if(isJapanese(line)) return `"${line}"`; // 英語等:スペースはAND扱い。演算子含む場合は丸括弧で保護 return /\s|OR|AND|NOT|-|:|\(|\)/i.test(line)?`(${line})`:line; }).filter(Boolean).join(' OR '); if(hh) parts.push(hh); } if (Array.isArray(p.exclude)&&p.exclude.length){ const ex=p.exclude.map(w=>(w||'').trim()).filter(Boolean).map(w=>isJapanese(w)?`-"${w}"`:`-${w}`).join(' '); if(ex) parts.push(ex); } if (p.lang) parts.push(`lang:${p.lang}`); const qStr = parts.join(' ').trim(); const params=['q='+enc(qStr),'src=typed_query']; if(p.live) params.push('f=live'); return `https://x.com/search?${params.join('&')}`; } // 表示用クエリ function buildQueryText(p){ if ((p.q&&p.q.length)||(p.exclude&&p.exclude.length)||p.lang||p.live){ const parts=[]; if (Array.isArray(p.q)&&p.q.length){ const hh=p.q.map(line=>{ line=(line||'').trim(); if(!line) return ''; if(isJapanese(line)) return `"${line}"`; return /\s|OR|AND|NOT|-|:|\(|\)/i.test(line)?`(${line})`:line; }).filter(Boolean).join(' OR '); if(hh) parts.push(hh); } if (Array.isArray(p.exclude)&&p.exclude.length){ const ex=p.exclude.map(w=>(w||'').trim()).filter(Boolean).map(w=>isJapanese(w)?`-"${w}"`:`-${w}`).join(' '); if(ex) parts.push(ex); } if (p.lang) parts.push(`lang:${p.lang}`); if (p.live) parts.push('[最新]'); return parts.join(' '); } if (p.url) { try { const u=new URL(p.url); let t = u.searchParams.get('q') ? decodeURIComponent(u.searchParams.get('q')) : p.url; if (u.searchParams.get('f')==='live') t+=' [最新]'; return t; } catch { return p.url; } } return ''; } // テーマ function detectTheme(){ const bg=getComputedStyle(document.body).backgroundColor||'rgb(255,255,255)'; const m=bg.match(/\d+/g); if(!m) return 'dim'; const [r,g,b]=m.map(Number); const max=Math.max(r,g,b), min=Math.min(r,g,b); const l=(max+min)/510; if(l>0.82) return 'light'; if(l<0.10) return 'lightsout'; return 'dim'; } const palettes={ light:{bg:'#fff',card:'#fff',border:'#eff3f4',text:'#0f1419',subtle:'#536471',hover:'rgba(15,20,25,.06)'}, dim:{bg:'#15202b',card:'#192734',border:'#22303c',text:'#e7e9ea',subtle:'#8899a6',hover:'rgba(29,155,240,.12)'}, lightsout:{bg:'#000',card:'#0a0a0a',border:'#2f3336',text:'#e7e9ea',subtle:'#8b98a5',hover:'rgba(29,155,240,.15)'} }; const pal=palettes[detectTheme()]; GM_addStyle(` .psl-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:2147483646;display:none} .psl-modal{position:fixed;left:50%;top:10%;transform:translateX(-50%); width:min(900px,94vw);background:${pal.card};border:1px solid ${pal.border};border-radius:12px; box-shadow:0 8px 28px rgba(0,0,0,.45);z-index:2147483647;color:${pal.text}; font:14px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,"Apple Color Emoji","Segoe UI Emoji"} .psl-modal, .psl-modal * { box-sizing:border-box; font:inherit; color:inherit; } .psl-head{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-bottom:1px solid ${pal.border};font-weight:700} .psl-small{opacity:.7;font-size:12px;color:${pal.subtle}} .psl-kbd{background:${pal.border};border-radius:6px;padding:0 6px;margin-left:8px;font-size:12px} .psl-list{max-height:48vh;overflow:auto;padding:6px 8px} .psl-group{border:1px solid ${pal.border};border-radius:10px;margin:8px 0;overflow:hidden;background:${pal.bg}} .psl-group-header{display:flex;align-items:center;gap:8px;padding:10px 12px;cursor:pointer;user-select:none} .psl-caret{transition:transform .15s} .psl-group-header:hover{background:${pal.hover}} .psl-group-title{font-weight:700} .psl-group-body{display:none;padding:6px;min-height:36px} .psl-group.open .psl-group-body{display:block} .psl-group.open .psl-caret{transform:rotate(90deg)} .psl-group.empty .psl-group-body{display:block} .psl-group.empty .psl-group-body::after{content:'(空のグループ)ここにドラッグで追加';display:block;color:${pal.subtle};font-size:12px;padding:8px} .psl-card{display:flex;align-items:center;gap:10px;padding:10px;border:1px solid ${pal.border};border-radius:10px;background:${pal.card};margin:6px 4px;cursor:pointer} .psl-card[aria-selected="true"], .psl-card:hover{background:${pal.hover}} .psl-handle{flex:0 0 18px;display:flex;align-items:center;justify-content:center;cursor:grab;user-select:none;opacity:.6} .psl-handle::before{content:"⋮⋮";line-height:1} .psl-card.dragging{opacity:.6} .psl-card-main{display:flex;flex-direction:column;flex:1;min-width:0} .psl-title{font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} .psl-sub{opacity:.8;font-size:12px;color:${pal.subtle};white-space:nowrap;overflow:hidden;text-overflow:ellipsis} .psl-card-actions{display:flex;gap:6px} .psl-mini{padding:6px 8px;border:1px solid ${pal.border};border-radius:8px;background:${pal.bg};color:${pal.text};cursor:pointer;font-size:12px} .psl-mini:hover{filter:brightness(1.06)} .psl-actions{display:flex;gap:8px;padding:10px 14px;justify-content:flex-end;border-top:1px solid ${pal.border};flex-wrap:wrap;position:relative} .psl-btn{padding:8px 10px;border:1px solid ${pal.border};border-radius:8px;background:${pal.bg};color:${pal.text};cursor:pointer} .psl-btn[disabled]{opacity:.5;cursor:not-allowed} .psl-btn:hover:not([disabled]){filter:brightness(1.06)} /* バックアップ▼のメニュー */ .psl-menu{position:absolute;right:14px;bottom:46px;background:${pal.card};border:1px solid ${pal.border};border-radius:8px;box-shadow:0 6px 20px rgba(0,0,0,.35);display:none;min-width:180px;z-index:10} .psl-menu.open{display:block} .psl-menu button{display:block;width:100%;text-align:left;border:0;border-bottom:1px solid ${pal.border};background:transparent;padding:10px} .psl-menu button:last-child{border-bottom:0} .psl-menu button:hover{background:${pal.hover}} /* 入力域:2カラム固定。URLは全幅(2カラム跨ぎ)。クエリと除外は同列 */ .psl-inputs{display:grid;grid-template-columns:1fr 1fr;gap:10px 12px;padding:10px 14px} .psl-row{display:flex;flex-direction:column;gap:6px} .psl-label{font-size:12px;color:${pal.subtle}} .psl-span2{grid-column:1 / -1;} .psl-inputs input,.psl-inputs textarea,.psl-inputs select{ padding:8px 10px;border:1px solid ${pal.border};border-radius:8px;background:${pal.bg};color:${pal.text};width:100%; } .psl-inputs textarea{min-height:74px;resize:vertical} .psl-inputs input::placeholder, .psl-inputs textarea::placeholder { color:${pal.subtle}; opacity:.8; } `); // DOM const backdrop=document.createElement('div'); backdrop.className='psl-backdrop'; const modal=document.createElement('div'); modal.className='psl-modal'; backdrop.appendChild(modal); document.body.appendChild(backdrop); // 状態 let presets=loadLatestPresets(); let selectedGlobalIndex=0; // ↑↓操作は実装しない(クリック選択のみ) let editIndex=-1; let expandedGroups=loadExpanded(); let customGroups=loadGroups(); const groupOf=(p)=> (p.group||'未分類'); function labelWrap(text, control, span2=false){ const wrap=document.createElement('div'); wrap.className='psl-row' + (span2 ? ' psl-span2' : ''); const lab=document.createElement('div'); lab.className='psl-label'; lab.textContent=text; wrap.appendChild(lab); wrap.appendChild(control); return wrap; } function btn(label,onClick){ const b=document.createElement('button'); b.className='psl-btn'; b.textContent=label; b.addEventListener('click',onClick); return b; } function mini(label,onClick){ const b=document.createElement('button'); b.className='psl-mini'; b.textContent=label; b.addEventListener('click',(e)=>{ e.stopPropagation(); onClick(e); }); return b; } function rebuildPresetsFromDOM(listEl){ const used=new Set(); const newArr=[]; const groups=[...listEl.querySelectorAll('.psl-group')]; groups.forEach(gEl=>{ const gname=gEl.getAttribute('data-group-name')||'未分類'; const cards=[...gEl.querySelectorAll('.psl-card')]; cards.forEach(card=>{ const idx=Number(card.getAttribute('data-index')); if (Number.isInteger(idx) && presets[idx]){ const obj=Object.assign({}, presets[idx]); obj.group=gname; newArr.push(obj); used.add(idx); } }); }); presets.forEach((p,i)=>{ if(!used.has(i)) newArr.push(p); }); presets=newArr; saveNewVersion(presets); } function render(){ modal.innerHTML=''; const head=document.createElement('div'); head.className='psl-head'; head.innerHTML=`<div>検索プリセット</div> <div class="psl-small">Alt+S<span class="psl-kbd">Alt+S</span> / Esc</div>`; modal.appendChild(head); // グループ集合 const setNames=new Set(['未分類']); customGroups.forEach(n=>setNames.add(n)); presets.forEach(p=> setNames.add(groupOf(p))); const groupNames=[...setNames]; const list=document.createElement('div'); list.className='psl-list'; modal.appendChild(list); groupNames.forEach(gname=>{ const groupEl=document.createElement('div'); groupEl.className='psl-group'; groupEl.setAttribute('data-group-name', gname); const header=document.createElement('div'); header.className='psl-group-header'; header.innerHTML=`<span class="psl-caret">▶</span><span class="psl-group-title">${esc(gname)}</span>`; header.addEventListener('click',()=>{ if (groupEl.classList.toggle('open')) expandedGroups.add(gname); else expandedGroups.delete(gname); saveExpanded(expandedGroups); }); if (expandedGroups.has(gname)) groupEl.classList.add('open'); list.appendChild(groupEl); groupEl.appendChild(header); const body=document.createElement('div'); body.className='psl-group-body'; groupEl.appendChild(body); // ドロップ受け入れ(空でもOK) body.addEventListener('dragover',(e)=>{ e.preventDefault(); const dragging=list.querySelector('.psl-card.dragging'); if (dragging){ const after=getDragAfterElement(body, e.clientY); if (after==null) body.appendChild(dragging); else body.insertBefore(dragging, after); } }); body.addEventListener('drop',(e)=>{ e.preventDefault(); rebuildPresetsFromDOM(list); render(); }); const items=presets.map((p,i)=>({p,i})).filter(x=> groupOf(x.p)===gname); if (items.length===0) groupEl.classList.add('empty'); else groupEl.classList.remove('empty'); items.forEach(({p,i})=>{ const card=document.createElement('div'); card.className='psl-card'; card.setAttribute('data-index', String(i)); card.setAttribute('data-group', gname); card.setAttribute('draggable','true'); const handle=document.createElement('div'); handle.className='psl-handle'; card.appendChild(handle); let dragEnabled=false; handle.addEventListener('mousedown',()=>{ dragEnabled=true; }); handle.addEventListener('mouseup',()=>{ dragEnabled=false; }); card.addEventListener('dragstart',(e)=>{ if(!dragEnabled){ e.preventDefault(); return; } card.classList.add('dragging'); e.dataTransfer.effectAllowed='move'; }); card.addEventListener('dragend',()=>{ card.classList.remove('dragging'); }); const main=document.createElement('div'); main.className='psl-card-main'; const title=p.title?.trim()||'(no title)'; const sub=buildQueryText(p); main.innerHTML=`<div class="psl-title">${esc(title)}</div><div class="psl-sub">${esc(sub)}</div>`; card.appendChild(main); const right=document.createElement('div'); right.className='psl-card-actions'; const editLabel = (editIndex===i) ? '更新' : '編集'; const editB=mini(editLabel,()=>{ if (editIndex===i){ // 更新保存 const pNew=formToPreset(); if(!pNew.url && (!pNew.q||pNew.q.length===0)) return; presets[i]=pNew; saveNewVersion(presets); customGroups.add(groupOf(pNew)); saveGroups(customGroups); editIndex=-1; render(); } else { // 編集開始 editIndex=i; render(); } }); const delB=mini('削除',()=>{ presets.splice(i,1); if(selectedGlobalIndex>=presets.length) selectedGlobalIndex=presets.length-1; saveNewVersion(presets); render(); }); right.append(editB, delB); card.appendChild(right); if (i===selectedGlobalIndex) card.setAttribute('aria-selected','true'); card.addEventListener('click',(e)=>{ if(e.target===handle) return; openPreset(i); }); card.addEventListener('mouseenter',()=>{ selectedGlobalIndex=i; updateSelection(); }); body.appendChild(card); }); }); // 入力フォーム(URL全幅、クエリと除外は同列) const inputs=document.createElement('div'); inputs.className='psl-inputs'; const t=document.createElement('input'), g=document.createElement('input'), u=document.createElement('input'), q=document.createElement('textarea'), ex=document.createElement('textarea'), lang=document.createElement('input'), live=document.createElement('select'); t.placeholder='タイトル(例: Twitter 落ちた)'; g.placeholder='グループ(例: 障害情報)'; u.placeholder='検索URL(https://x.com/search?...) ※URL保存ならこちらに記入'; q.placeholder='#タグかキーワードを行区切りで。日本語は自動で""囲まれます(URLを使うなら空でOK)'; ex.placeholder='除外ワード(行区切り)'; lang.placeholder='言語コード(例: ja)'; live.innerHTML=`<option value="">並べ替え既定</option><option value="1">最新 (f=live)</option>`; if (editIndex>=0){ const p=presets[editIndex]; t.value=p.title||''; g.value=p.group||''; u.value=p.url||''; // 改行を実際の改行文字にして代入 q.value=Array.isArray(p.q)?p.q.join("\n"):''; ex.value=Array.isArray(p.exclude)?p.exclude.join("\n"):''; lang.value=p.lang||''; live.value=p.live?'1':''; } inputs.append( labelWrap('タイトル',t,false), labelWrap('グループ',g,false), labelWrap('検索URL',u,true), labelWrap('クエリ(行区切り / OR 連結)',q,false), labelWrap('除外(行区切り / 日本語は自動で引用)',ex,false), labelWrap('言語',lang,false), labelWrap('最新タブ',live,false), ); modal.appendChild(inputs); // アクション(追加 / 編集(更新) / 削除 / バックアップ▼ / グループ追加) const actions=document.createElement('div'); actions.className='psl-actions'; const addBtn=btn('追加',()=>{ const p=formToPreset(); if(!p.url && (!p.q||p.q.length===0)) return; presets.push(p); saveNewVersion(presets); selectedGlobalIndex=presets.length-1; editIndex=-1; customGroups.add(groupOf(p)); saveGroups(customGroups); render(); }); const editToggleBtn=btn(editIndex>=0?'更新':'編集',()=>{ if (editIndex<0){ if(presets.length===0) return; editIndex=selectedGlobalIndex; render(); } else { const p=formToPreset(); if(!p.url && (!p.q||p.q.length===0)) return; presets[editIndex]=p; saveNewVersion(presets); customGroups.add(groupOf(p)); saveGroups(customGroups); editIndex=-1; render(); } }); const delBtn=btn('削除',()=>{ if(presets.length===0) return; presets.splice(selectedGlobalIndex,1); if(selectedGlobalIndex>=presets.length) selectedGlobalIndex=presets.length-1; saveNewVersion(presets); render(); }); const backupBtn=btn('バックアップ▼',()=>{ menu.classList.toggle('open'); }); const menu=document.createElement('div'); menu.className='psl-menu'; const exportItem=document.createElement('button'); exportItem.textContent='エクスポート (.json)'; exportItem.addEventListener('click',()=>{ const blob=new Blob([JSON.stringify(presets,null,2)],{type:'application/json'}); const a=Object.assign(document.createElement('a'),{href:URL.createObjectURL(blob),download:'x-search-presets.json'}); document.body.appendChild(a); a.click(); a.remove(); menu.classList.remove('open'); }); const importItem=document.createElement('button'); importItem.textContent='インポート (.json)'; importItem.addEventListener('click',()=>{ const input=Object.assign(document.createElement('input'),{type:'file',accept:'.json,application/json'}); input.onchange=async()=>{ try{ const text=await input.files[0].text(); presets=JSON.parse(text); saveNewVersion(presets); render(); }catch{} }; input.click(); menu.classList.remove('open'); }); menu.append(exportItem, importItem); actions.append(menu); document.addEventListener('click',(e)=>{ if (!actions.contains(e.target)) menu.classList.remove('open'); }); const newGroupBtn=btn('グループ追加',()=>{ const name=prompt('新しいグループ名を入力してください','新規グループ'); if(!name) return; customGroups.add(name); saveGroups(customGroups); expandedGroups.add(name); saveExpanded(expandedGroups); render(); }); actions.append(addBtn, editToggleBtn, delBtn, backupBtn, newGroupBtn); modal.appendChild(actions); function formToPreset(){ const obj={ title:(t.value||'').trim(), group:(g.value||'').trim()||'未分類' }; const urlVal=(u.value||'').trim(); if(urlVal) obj.url=urlVal; // 改行で配列化(空行は除外) const qLines=(q.value||'').split(/\r?\n/).map(s=>s.trim()).filter(Boolean); const exLines=(ex.value||'').split(/\r?\n/).map(s=>s.trim()).filter(Boolean); if(qLines.length) obj.q=qLines; if(exLines.length) obj.exclude=exLines; const langVal=(lang.value||'').trim(); if(langVal) obj.lang=langVal; obj.live=!!live.value; return obj; } } function updateSelection(){ modal.querySelectorAll('.psl-card').forEach(el=>{ const idx=Number(el.getAttribute('data-index')); if(idx===selectedGlobalIndex) el.setAttribute('aria-selected','true'); else el.removeAttribute('aria-selected'); }); } function openPreset(i){ const p=presets[i]; if(!p) return; const url=buildSearchUrl(p); if(OPEN_IN_NEW_TAB) window.open(url,'_blank'); else location.href=url; close(); } function show(){ selectedGlobalIndex=Math.min(selectedGlobalIndex, Math.max(0, presets.length-1)); render(); backdrop.style.display='block'; document.documentElement.style.overflow='hidden'; } function close(){ backdrop.style.display='none'; editIndex=-1; document.documentElement.style.overflow=''; } function getDragAfterElement(container,y){ const els=[...container.querySelectorAll('.psl-card:not(.dragging)')]; return els.reduce((closest,child)=>{ const box=child.getBoundingClientRect(); const offset=y - box.top - box.height/2; if(offset<0 && offset>closest.offset) return {offset, element: child}; else return closest; }, {offset:Number.NEGATIVE_INFINITY}).element; } // キーイベント:モーダル中はXショートカット抑止、Alt+Sトグル/ESCのみ通す const isModalOpen=()=> backdrop.style.display==='block'; ['keydown','keypress','keyup'].forEach((type)=>{ window.addEventListener(type,(e)=>{ const isAltS = e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey && e.code==='KeyS'; const isEsc = e.key==='Escape'; if (type==='keydown'){ if (!isModalOpen() && isAltS){ e.preventDefault(); e.stopImmediatePropagation(); show(); return; } if (isModalOpen() && isAltS){ e.preventDefault(); e.stopImmediatePropagation(); close(); return; } if (isModalOpen() && isEsc){ e.preventDefault(); e.stopImmediatePropagation(); close(); return; } } if (isModalOpen()){ if (!modal.contains(e.target)) e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); } }, true); }); // 背景クリックで閉じる backdrop.addEventListener('click',(e)=>{ if(e.target===backdrop) close(); }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址