您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Auto-fills tags for Iromirai & Chichi-pui.
// ==UserScript== // @name Auto Tags Filler for Iromirai & Chichi-pui // @name:ja タグ入力補助 for イロミライ&ちちぷい // @description Auto-fills tags for Iromirai & Chichi-pui. // @description:ja イロミライとちちぷいのタグ入力を補助します。 // @namespace https://bsky.app/profile/neon-ai.art // @homepage https://bsky.app/profile/neon-ai.art // @icon data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⛄️</text></svg> // @version 7.3 // @author ねおん // @match https://iromirai.jp/user/post* // @match https://www.chichi-pui.com/posts/upload* // @grant GM_getValue // @grant GM_setValue // @require https://gf.qytechs.cn/scripts/28536-gm-config/code/GM_config.js?version=184529 // @license CC BY-NC 4.0 // ==/UserScript== (function() { 'use strict'; // --- 設定関連 --- // exportFileNameはGM_configには直接関係しないが、エクスポート機能のために残す const exportFileName = 'tags_filler.json'; // DOM要素はサイトごとに動的に設定 let themeTagInput; let addButton; const hostname = window.location.hostname; if (hostname.includes('iromirai.jp')) { themeTagInput = document.getElementById('themeTagText'); addButton = document.querySelector('.postmedia-mobile-addtag-button'); } else if (hostname.includes('chichi-pui.com')) { themeTagInput = document.querySelector('input.input[placeholder*="タグを入力"]'); addButton = document.querySelector('button.button.is-dark.ml-2[type="button"]'); } else { console.error('このスクリプトはIromiraiまたはChichi-puiでのみ動作します。'); return; } if (!themeTagInput || !addButton) { console.error('必要な要素が見つかりません。スクリプトを終了します。'); return; } const ENABLE_TEXTFILL_INTEGRATION = false; // TextFill連携機能の有効/無効 (true:有効, false:無効) const ENABLE_IN_SCRIPT_TAG_MANAGEMENT = true; // スクリプト内タグ管理機能の有効/無効 (true:有効, false:無効) // ユーザー定義テンプレートの初期データ(GM_configにデータがない場合にロードされる) const DEFAULT_USER_TEMPLATES = [ { name: "基本タグ", tags: "イラスト,AI生成,可愛い" }, { name: "風景タグ", tags: "風景,空,自然" }, { name: "キャラクター", tags: "女の子,オリジナル,ファンタジー" } ]; let currentTemplates = []; // 現在のテンプレートリスト let isProcessing = false; // 二重実行防止フラグ let changeTimer = null; let isButtonTriggeredTextFill = false; // ボタンからのTextFillトリガー中を示すフラグ let isComposing = false; // IME変換中フラグ // --- GM_configの初期化 --- GM_config.init({ 'id': 'IromiraiChichiPuiConfig', // 設定を保存するためのユニークなID 'title': 'テーマタグ設定', // 設定パネルのタイトル 'fields': { 'userTemplates': { 'label': 'ユーザーテンプレート', 'type': 'hidden', // UIには表示しないが、データはここに保存される 'default': JSON.stringify(DEFAULT_USER_TEMPLATES) } }, 'events': { 'save': function() { // GM_configが保存されたときに、内部のcurrentTemplatesを更新 // ただし、このスクリプトはUIから直接GM_configを操作しないため、主にGM_config.set()の後に呼ばれる想定 loadTemplatesFromGMConfig(); } } }); // --- テンプレートをグローバルに読み込む関数 --- function loadTemplatesFromGMConfig() { try { // グローバルキーを使用してテンプレートを読み込む const storedData = GM_getValue('global_user_templates', JSON.stringify(DEFAULT_USER_TEMPLATES)); const parsedData = JSON.parse(storedData); if (Array.isArray(parsedData) && parsedData.every(t => typeof t.name === 'string' && typeof t.tags === 'string')) { currentTemplates = parsedData; } else { console.warn("グローバルストレージのテンプレートデータが不正なため、デフォルトをロードします。"); currentTemplates = DEFAULT_USER_TEMPLATES; saveTemplatesToGMConfig(); // 不正な場合はデフォルトを保存し直す } } catch (e) { console.error("グローバルストレージからのテンプレート読み込みに失敗しました:", e); currentTemplates = DEFAULT_USER_TEMPLATES; saveTemplatesToGMConfig(); // エラー時はデフォルトを保存 } console.log("ロードされたテンプレート:", currentTemplates); } // --- テンプレートをグローバルに保存する関数 --- function saveTemplatesToGMConfig() { try { // グローバルキーを使用してテンプレートを保存する GM_setValue('global_user_templates', JSON.stringify(currentTemplates)); console.log("テンプレートをグローバルストレージに保存しました。"); } catch (e) { console.error("グローバルストレージへのテンプレート保存に失敗しました:", e); } } // スクリプト起動時にテンプレートをロード if (ENABLE_IN_SCRIPT_TAG_MANAGEMENT) { loadTemplatesFromGMConfig(); } // --- タグ追加処理の関数 (自動確定用) --- async function processTagsAutomatically() { if (isProcessing) { console.log('既に処理中です。'); return; } isProcessing = true; const rawTagsString = themeTagInput.value; if (!rawTagsString.trim() || rawTagsString.trim() === '/') { console.log('タグ入力フォームが空か、TextFillトリガー文字のみです。自動処理をスキップします。'); isProcessing = false; return; } let tagsToEnter = rawTagsString.split(/[、, ]+/).map(tag => tag.trim()).filter(tag => tag !== ''); if (tagsToEnter.length === 0) { console.log('有効なタグが見つかりませんでした。自動処理をスキップします。'); isProcessing = false; return; } console.log('自動タグ入力処理を開始します。'); const initialDelayTime = 200; console.log('初回処理開始前:遅延 (' + initialDelayTime + 'ms)'); await new Promise(resolve => setTimeout(resolve, initialDelayTime)); themeTagInput.value = ''; themeTagInput.dispatchEvent(new Event('input', { bubbles: true })); themeTagInput.dispatchEvent(new Event('change', { bubbles: true })); await new Promise(resolve => setTimeout(resolve, 50)); for (let i = 0; i < tagsToEnter.length; i++) { const tag = tagsToEnter[i]; themeTagInput.value = tag; themeTagInput.dispatchEvent(new Event('input', { bubbles: true })); themeTagInput.dispatchEvent(new Event('change', { bubbles: true })); addButton.click(); const loopDelayTime = 100; console.log('各タグ追加:短めの遅延 (' + loopDelayTime + 'ms)'); await new Promise(resolve => setTimeout(resolve, loopDelayTime)); } console.log('自動タグ入力処理が完了しました。'); isProcessing = false; } // --- UIコンポーネントの作成と配置 --- let triggerTextFillButton; if (ENABLE_TEXTFILL_INTEGRATION) { triggerTextFillButton = document.createElement('button'); triggerTextFillButton.textContent = 'TextFill'; triggerTextFillButton.style.cssText = 'position:fixed; bottom:50px; right:10px; z-index:9999; padding:10px; background-color:#800080; color:white; border:none; border-radius:5px; cursor:pointer; font-size: 0.9em;'; document.body.appendChild(triggerTextFillButton); } let manageTemplatesButton; let managePanel; let h3TemplateList; let filterInput; let templateItemsScrollArea; let addFormContainer; let addTemplateNameInput; let addTemplateTagsInput; let addTemplateButton; let bottomButtonContainer; let addTagButton; let exportTagsButton; let importTagsButton; if (ENABLE_IN_SCRIPT_TAG_MANAGEMENT) { manageTemplatesButton = document.createElement('button'); manageTemplatesButton.textContent = 'タグ管理'; manageTemplatesButton.style.cssText = 'position:fixed; bottom:95px; right:10px; z-index:9999; padding:10px; background-color:#337ab7; color:white; border:none; border-radius:5px; cursor:pointer; font-size: 0.9em;'; document.body.appendChild(manageTemplatesButton); managePanel = document.createElement('div'); managePanel.style.cssText = ` position:fixed; bottom:140px; right:10px; z-index:9999; background-color:white; border:1px solid #ccc; border-radius:5px; padding:15px; display:none; flex-direction:column; gap:10px; width: 350px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); max-height: calc(100vh - 150px); resize: vertical; overflow: hidden; `; document.body.appendChild(managePanel); h3TemplateList = document.createElement('h3'); h3TemplateList.textContent = 'マイテーマタグ'; managePanel.appendChild(h3TemplateList); filterInput = document.createElement('input'); filterInput.type = 'text'; filterInput.id = 'templateFilterInput'; filterInput.placeholder = 'フィルター (名前、タグ)'; filterInput.style.cssText = 'width:calc(100% - 2px); padding:8px; margin-bottom:10px; border:1px solid #ccc; border-radius:3px;'; managePanel.appendChild(filterInput); templateItemsScrollArea = document.createElement('div'); templateItemsScrollArea.style.cssText = ` flex-grow: 1; overflow-y: auto; border-bottom: 1px solid #eee; padding-bottom: 10px; margin-bottom: 10px; min-height: 50px; `; managePanel.appendChild(templateItemsScrollArea); addFormContainer = document.createElement('div'); addFormContainer.style.cssText = 'padding-top: 10px; margin-top: 10px; display: none;'; addFormContainer.innerHTML = ` <label for="addTemplateNameInput">名前:</label><br> <input type="text" id="addTemplateNameInput" style="width:100%; padding:5px; margin-bottom:5px;"><br> <label for="addTemplateTagsInput">タグ (カンマ区切り):</label><br> <input type="text" id="addTemplateTagsInput" style="width:100%; padding:5px; margin-bottom:10px;"><br> <button id="addTemplateButton" style="padding:8px 15px; background-color:#5cb85c; color:white; border:none; border-radius:3px; cursor:pointer;">追加</button> `; managePanel.appendChild(addFormContainer); addTemplateNameInput = addFormContainer.querySelector('#addTemplateNameInput'); addTemplateTagsInput = addFormContainer.querySelector('#addTemplateTagsInput'); addTemplateButton = addFormContainer.querySelector('#addTemplateButton'); bottomButtonContainer = document.createElement('div'); bottomButtonContainer.style.cssText = ` border-top: 1px solid #eee; padding-top: 10px; margin-top: 10px; display: flex; justify-content: space-between; flex-wrap: wrap; gap: 5px; `; managePanel.appendChild(bottomButtonContainer); addTagButton = document.createElement('button'); addTagButton.textContent = 'タグ追加'; addTagButton.style.cssText = 'padding:8px 15px; background-color:#6c757d; color:white; border:none; border-radius:3px; cursor:pointer; flex-grow:1; min-width:80px;'; bottomButtonContainer.appendChild(addTagButton); exportTagsButton = document.createElement('button'); exportTagsButton.textContent = 'エクスポート'; exportTagsButton.style.cssText = 'padding:8px 15px; background-color:#007bff; color:white; border:none; border-radius:3px; cursor:pointer; margin-right: 5px; flex-grow:1; min-width:80px;'; bottomButtonContainer.appendChild(exportTagsButton); importTagsButton = document.createElement('button'); importTagsButton.textContent = 'インポート'; importTagsButton.style.cssText = 'padding:8px 15px; background-color:#ffc107; color:black; border:none; border-radius:3px; cursor:pointer; flex-grow:1; min-width:80px;'; bottomButtonContainer.appendChild(importTagsButton); const importTagsFile = document.createElement('input'); importTagsFile.type = 'file'; importTagsFile.id = 'importTagsFile'; importTagsFile.accept = '.json'; importTagsFile.style.display = 'none'; bottomButtonContainer.appendChild(importTagsFile); addTagButton.addEventListener('click', () => { const isVisible = addFormContainer.style.display === 'block'; addFormContainer.style.display = isVisible ? 'none' : 'block'; addTagButton.textContent = isVisible ? 'タグ追加' : 'タグ追加 ▲'; adjustPanelHeight(); if (!isVisible) { addTemplateNameInput.focus(); } }); importTagsButton.addEventListener('click', () => { importTagsFile.click(); }); importTagsFile.addEventListener('change', (event) => { const file = event.target.files[0]; if (!file) { return; } const reader = new FileReader(); reader.onload = (e) => { try { const rawData = JSON.parse(e.target.result); let importedData = []; if (typeof rawData === 'object' && !Array.isArray(rawData) && Object.keys(rawData).every(key => key.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/))) { console.log("TextFill形式のデータを検出しました。"); importedData = Object.values(rawData).map(item => { let tags = ''; try { if (typeof item.script === 'string') { let scriptContent; try { scriptContent = JSON.parse(item.script); } catch (innerParseError) { scriptContent = item.script; } if (Array.isArray(scriptContent) && scriptContent.length > 0 && scriptContent[0] && typeof scriptContent[0].insert === 'string') { tags = scriptContent[0].insert.replace(/\\n/g, ', ').trim(); if (tags.endsWith(',')) tags = tags.slice(0, -1).trim(); } else if (typeof scriptContent === 'string') { tags = scriptContent.replace(/\\n/g, ', ').trim(); if (tags.endsWith(',')) tags = tags.slice(0, -1).trim(); } else { tags = item.script; } } else { if (Array.isArray(item.script) && item.script.length > 0 && item.script[0] && typeof item.script[0].insert === 'string') { tags = item.script[0].insert.replace(/\\n/g, ', ').trim(); if (tags.endsWith(',')) tags = tags.slice(0, -1).trim(); } else { tags = item.script; } } } catch (parseError) { console.warn(`TextFillスクリプトのパースに失敗しました (${item.label || item.command}):`, parseError); tags = item.script; } return { name: item.label || item.command || '不明なタグ', tags: tags }; }); } else if (Array.isArray(rawData) && rawData.every(t => typeof t.name === 'string' && typeof t.tags === 'string')) { console.log("iromirai形式のデータを検出しました。"); importedData = rawData; } else { throw new Error("対応していないJSON形式です。TextFillのエクスポートデータか、このスクリプトでエクスポートしたJSONファイルを指定してください。"); } const newTemplates = [...currentTemplates]; let importedCount = 0; let updatedCount = 0; importedData.forEach(importedTemplate => { const existingIndex = newTemplates.findIndex(t => t.name === importedTemplate.name); if (existingIndex !== -1) { newTemplates[existingIndex] = importedTemplate; updatedCount++; } else { newTemplates.push(importedTemplate); importedCount++; } }); currentTemplates = newTemplates; saveTemplatesToGMConfig(); renderTemplateList(); alert(`テーマタグのインポートが完了しました!\n新規追加: ${importedCount}件, 更新: ${updatedCount}件`); } catch (e) { alert('ファイルの読み込みまたはパースに失敗しました。\nエラー: ' + e.message); console.error('インポートエラー:', e); } finally { event.target.value = ''; } }; reader.readAsText(file); }); } // --- パネルの高さ調整関数 --- function adjustPanelHeight() { if (!ENABLE_IN_SCRIPT_TAG_MANAGEMENT) return; const originalDisplay = managePanel.style.display; managePanel.style.display = 'block'; managePanel.style.height = 'auto'; let fixedHeight = 0; fixedHeight += h3TemplateList.offsetHeight + parseInt(getComputedStyle(h3TemplateList).marginTop) + parseInt(getComputedStyle(h3TemplateList).marginBottom); fixedHeight += filterInput.offsetHeight + parseInt(getComputedStyle(filterInput).marginTop) + parseInt(getComputedStyle(filterInput).marginBottom); fixedHeight += bottomButtonContainer.offsetHeight + parseInt(getComputedStyle(bottomButtonContainer).marginTop) + parseInt(getComputedStyle(bottomButtonContainer).paddingTop); fixedHeight += parseInt(getComputedStyle(managePanel).paddingTop) + parseInt(getComputedStyle(managePanel).paddingBottom); if (addFormContainer.style.display === 'block') { fixedHeight += addFormContainer.offsetHeight; } const newTemplateListMaxHeight = `calc(100vh - ${fixedHeight + 140}px)`; templateItemsScrollArea.style.maxHeight = newTemplateListMaxHeight; templateItemsScrollArea.style.height = 'auto'; managePanel.style.display = originalDisplay; } // --- イベントハンドラの定義 --- // TextFillボタンクリック処理 if (ENABLE_TEXTFILL_INTEGRATION) { triggerTextFillButton.onclick = () => { isButtonTriggeredTextFill = true; themeTagInput.value = '/'; themeTagInput.dispatchEvent(new Event('input', { bubbles: true })); themeTagInput.dispatchEvent(new Event('change', { bubbles: true })); console.log('TextFillメニューをトリガーしました。手動で選択してください。'); themeTagInput.focus(); const tempInputListener = () => { if (themeTagInput.value.length > 1 && themeTagInput.value.trim() !== '/') { themeTagInput.removeEventListener('input', tempInputListener); console.log('TextFillがタグの展開を開始しました。自動確定を待機します。'); setTimeout(() => { if (!isProcessing) { processTagsAutomatically(); } isButtonTriggeredTextFill = false; }, 1000); } }; themeTagInput.addEventListener('input', tempInputListener); setTimeout(() => { if (isButtonTriggeredTextFill && themeTagInput.value.trim() === '/') { console.log("TextFillが使用されなかったため、ボタントリガーフラグをリセットします。"); isButtonTriggeredTextFill = false; themeTagInput.removeEventListener('input', tempInputListener); } }, 5000); }; } // タグ管理ボタンクリックでパネル表示/非表示 if (ENABLE_IN_SCRIPT_TAG_MANAGEMENT) { manageTemplatesButton.onclick = () => { const isPanelVisible = managePanel.style.display === 'flex'; managePanel.style.display = isPanelVisible ? 'none' : 'flex'; if (!isPanelVisible) { addFormContainer.style.display = 'none'; addTagButton.textContent = 'タグ追加'; // GM_configから最新のデータをロードしてからリストをレンダリング loadTemplatesFromGMConfig(); renderTemplateList(); resetAddForm(); adjustPanelHeight(); } }; function handleFilterInput() { if (!isComposing) { renderTemplateList(); } } function compositionStartHandler() { isComposing = true; } function compositionEndHandler() { isComposing = false; renderTemplateList(); } filterInput.addEventListener('input', handleFilterInput); filterInput.addEventListener('compositionstart', compositionStartHandler); filterInput.addEventListener('compositionend', compositionEndHandler); // ヘルパー関数:クリックされた要素が特定のコントロールボタンかどうかを判定 function isControlButtonClicked(targetElement) { // 共通のコントロール let isCommonControl = targetElement.closest('.save-in-line-edit-btn') || targetElement.closest('.cancel-in-line-edit-btn') || targetElement.closest('.delete-template-btn') || targetElement.closest('#exportTagsButton') || targetElement.closest('#importTagsButton') || targetElement.closest('.apply-template-btn'); if (isCommonControl) return true; // サイト固有の追加ボタン if (hostname.includes('iromirai.jp')) { return targetElement.closest('.postmedia-mobile-addtag-button'); } else if (hostname.includes('chichi-pui.com')) { return targetElement.closest('button.button.is-dark.ml-2[type="button"]'); } return false; } // ドキュメント全体へのクリックリスナー document.addEventListener('click', (event) => { if (managePanel.style.display === 'flex' && !managePanel.contains(event.target) && event.target !== manageTemplatesButton && event.target !== addTagButton && !addFormContainer.contains(event.target) && !isControlButtonClicked(event.target)) { managePanel.style.display = 'none'; } }); } // --- テンプレート一覧をレンダリングする関数 --- let draggedItem = null; function renderTemplateList() { if (!ENABLE_IN_SCRIPT_TAG_MANAGEMENT) return; let wasFilterInputFocused = (document.activeElement === filterInput); let selectionStart = 0; let selectionEnd = 0; if (wasFilterInputFocused) { selectionStart = filterInput.selectionStart; selectionEnd = filterInput.selectionEnd; } templateItemsScrollArea.innerHTML = ''; if (wasFilterInputFocused) { filterInput.focus(); filterInput.setSelectionRange(selectionStart, selectionEnd); } const filterText = filterInput.value; const lowerCaseFilter = filterText.toLowerCase(); const filteredTemplates = currentTemplates.filter(template => { return template.name.toLowerCase().includes(lowerCaseFilter) || template.tags.toLowerCase().includes(lowerCaseFilter); }); if (filteredTemplates.length === 0) { const noMatchP = document.createElement('p'); noMatchP.textContent = '条件に一致するテーマタグが見つかりません。'; templateItemsScrollArea.appendChild(noMatchP); if (currentTemplates.length === 0 && !filterText) { const addPromptP = document.createElement('p'); addPromptP.textContent = '以下の「タグ追加」ボタンから追加してください。'; templateItemsScrollArea.appendChild(addPromptP); } } else { const ul = document.createElement('ul'); ul.style.cssText = 'list-style:none; padding:0;'; filteredTemplates.forEach((template, index) => { const originalIndex = currentTemplates.findIndex(t => t.name === template.name && t.tags === template.tags); if (originalIndex === -1) return; const li = document.createElement('li'); li.setAttribute('draggable', 'true'); li.dataset.index = originalIndex; li.style.cssText = ` margin-bottom:8px; padding:8px; border:1px solid #eee; border-radius:5px; display:flex; flex-direction:column; gap:5px; background-color: #f9f9f9; cursor: grab; `; const nameDisplay = document.createElement('div'); nameDisplay.className = 'template-name-display'; nameDisplay.style.fontWeight = 'bold'; nameDisplay.textContent = template.name; const nameInput = document.createElement('input'); nameInput.type = 'text'; nameInput.className = 'template-name-input'; nameInput.style.cssText = 'width:100%; padding:5px; margin-bottom:5px; display:none;'; nameInput.value = template.name; const tagsDisplay = document.createElement('div'); tagsDisplay.className = 'template-tags-display'; tagsDisplay.style.cssText = 'font-size:0.9em; color:#555; display:none;'; tagsDisplay.textContent = template.tags; const tagsInput = document.createElement('input'); tagsInput.type = 'text'; tagsInput.className = 'template-tags-input'; tagsInput.style.cssText = 'width:100%; padding:5px; margin-bottom:10px; display:none;'; tagsInput.value = template.tags; const buttonContainer = document.createElement('div'); buttonContainer.style.textAlign = 'right'; const applyButton = document.createElement('button'); applyButton.textContent = '適用'; applyButton.className = 'apply-template-btn'; applyButton.style.cssText = 'padding:5px 10px; background-color:#28a745; color:white; border:none; border-radius:3px; cursor:pointer; font-size:0.8em; margin-right:5px;'; const editButton = document.createElement('button'); editButton.textContent = '編集'; editButton.className = 'edit-template-btn'; editButton.style.cssText = 'padding:5px 10px; background-color:#0275d8; color:white; border:none; border-radius:3px; cursor:pointer; font-size:0.8em;'; const saveButton = document.createElement('button'); saveButton.textContent = '保存'; saveButton.className = 'save-in-line-edit-btn'; saveButton.style.cssText = 'padding:5px 10px; background-color:#5cb85c; color:white; border:none; border-radius:3px; cursor:pointer; font-size:0.8em; margin-right:5px; display:none;'; const cancelButton = document.createElement('button'); cancelButton.textContent = 'キャンセル'; cancelButton.className = 'cancel-in-line-edit-btn'; cancelButton.style.cssText = 'padding:5px 10px; background-color:#f0ad4e; color:white; border:none; border-radius:3px; cursor:pointer; font-size:0.8em; margin-right:5px; display:none;'; const deleteButton = document.createElement('button'); deleteButton.textContent = '削除'; deleteButton.className = 'delete-template-btn'; deleteButton.style.cssText = 'padding:5px 10px; background-color:#d9534f; color:white; border:none; border-radius:3px; cursor:pointer; font-size:0.8em; margin-left:5px;'; li.appendChild(nameDisplay); li.appendChild(nameInput); li.appendChild(tagsDisplay); li.appendChild(tagsInput); buttonContainer.appendChild(applyButton); buttonContainer.appendChild(editButton); buttonContainer.appendChild(saveButton); buttonContainer.appendChild(cancelButton); buttonContainer.appendChild(deleteButton); li.appendChild(buttonContainer); ul.appendChild(li); }); templateItemsScrollArea.appendChild(ul); ul.querySelectorAll('.apply-template-btn').forEach(button => { button.onclick = (e) => applyTemplate(parseInt(e.target.closest('li').dataset.index)); }); ul.querySelectorAll('.delete-template-btn').forEach(button => { button.onclick = (e) => deleteTemplate(parseInt(e.target.closest('li').dataset.index)); }); ul.querySelectorAll('.edit-template-btn').forEach(button => { button.onclick = (e) => { const li = e.target.closest('li'); const index = parseInt(li.dataset.index); const template = currentTemplates[index]; li.querySelector('.template-name-display').style.display = 'none'; li.querySelector('.template-tags-display').style.display = 'none'; li.querySelector('.template-name-input').style.display = 'block'; li.querySelector('.template-tags-input').style.display = 'block'; li.querySelector('.template-name-input').value = template.name; li.querySelector('.template-tags-input').value = template.tags; li.querySelector('.apply-template-btn').style.display = 'none'; li.querySelector('.edit-template-btn').style.display = 'none'; li.querySelector('.delete-template-btn').style.display = 'none'; li.querySelector('.save-in-line-edit-btn').style.display = 'inline-block'; li.querySelector('.cancel-in-line-edit-btn').style.display = 'inline-block'; li.querySelector('.template-name-input').focus(); }; }); ul.querySelectorAll('.save-in-line-edit-btn').forEach(button => { button.onclick = (e) => { const li = e.target.closest('li'); const index = parseInt(li.dataset.index); const newName = li.querySelector('.template-name-input').value.trim(); const newTags = li.querySelector('.template-tags-input').value.trim(); if (!newName || !newTags) { alert('テーマタグ名とタグを入力してください。'); return; } if (currentTemplates.some((t, i) => i !== index && t.name === newName)) { alert('同じテーマタグ名が既に存在します。別の名前を入力してください。'); return; } currentTemplates[index] = { name: newName, tags: newTags }; saveTemplatesToGMConfig(); renderTemplateList(); }; }); ul.querySelectorAll('.cancel-in-line-edit-btn').forEach(button => { button.onclick = (e) => { renderTemplateList(); }; }); ul.querySelectorAll('li').forEach(item => { item.addEventListener('dragstart', (e) => { if (item.querySelector('.template-name-input').style.display === 'block') { e.preventDefault(); return; } draggedItem = item; e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/html', item.innerHTML); setTimeout(() => { item.style.opacity = '0.5'; }, 0); }); item.addEventListener('dragover', (e) => { e.preventDefault(); if (item !== draggedItem) { const bounding = item.getBoundingClientRect(); const offset = bounding.y + (bounding.height / 2); if (e.clientY > offset) { item.style.borderBottom = '2px solid blue'; item.style.borderTop = ''; } else { item.style.borderTop = '2px solid blue'; item.style.borderBottom = ''; } } }); item.addEventListener('dragleave', () => { item.style.borderTop = ''; item.style.borderBottom = ''; }); item.addEventListener('drop', (e) => { e.preventDefault(); if (item !== draggedItem) { const draggedIndex = parseInt(draggedItem.dataset.index); const targetIndex = parseInt(item.dataset.index); const [removed] = currentTemplates.splice(draggedIndex, 1); const bounding = item.getBoundingClientRect(); const offset = bounding.y + (bounding.height / 2); let insertIndex; if (e.clientY > offset) { insertIndex = targetIndex + (draggedIndex < targetIndex ? 0 : 1); } else { insertIndex = targetIndex + (draggedIndex < targetIndex ? 0 : 0); } currentTemplates.splice(insertIndex, 0, removed); saveTemplatesToGMConfig(); renderTemplateList(); } }); item.addEventListener('dragend', () => { ul.querySelectorAll('li').forEach(li => { li.style.opacity = '1'; li.style.borderTop = ''; li.style.borderBottom = ''; }); draggedItem = null; }); }); } adjustPanelHeight(); } // --- テンプレートの適用関数 --- function applyTemplate(index) { if (!ENABLE_IN_SCRIPT_TAG_MANAGEMENT) return; const template = currentTemplates[index]; themeTagInput.value = template.tags; if (!isProcessing) { processTagsAutomatically(); } } // --- 新規テンプレートの追加関数 --- if (ENABLE_IN_SCRIPT_TAG_MANAGEMENT) { addTemplateButton.onclick = () => { const name = addTemplateNameInput.value.trim(); const tags = addTemplateTagsInput.value.trim(); if (!name || !tags) { alert('テーマタグ名とタグを入力してください。'); return; } if (currentTemplates.some(t => t.name === name)) { alert('同じテーマタグ名が既に存在します。別の名前を入力してください。'); return; } currentTemplates.push({ name, tags }); console.log("新規テーマタグを追加しました:", { name, tags }); saveTemplatesToGMConfig(); renderTemplateList(); resetAddForm(); const lastItem = templateItemsScrollArea.querySelector('ul > li:last-child'); if (lastItem) { lastItem.scrollIntoView({ behavior: 'smooth', block: 'end' }); } }; } // --- 追加フォームをリセットする関数 --- function resetAddForm() { if (!ENABLE_IN_SCRIPT_TAG_MANAGEMENT) return; addTemplateNameInput.value = ''; addTemplateTagsInput.value = ''; } // --- テンプレートを削除する関数 --- function deleteTemplate(index) { if (!ENABLE_IN_SCRIPT_TAG_MANAGEMENT) return; if (confirm(`「${currentTemplates[index].name}」を削除してもよろしいですか?`)) { currentTemplates.splice(index, 1); saveTemplatesToGMConfig(); renderTemplateList(); console.log("テーマタグを削除しました。"); adjustPanelHeight(); } } // --- エクスポート機能 --- if (ENABLE_IN_SCRIPT_TAG_MANAGEMENT) { exportTagsButton.onclick = () => { const dataStr = JSON.stringify(currentTemplates, null, 2); const blob = new Blob([dataStr], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = exportFileName; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }; } // --- その他のイベントリスナー --- // themeTagInput の change イベントを監視 (手動入力や、TextFillがchangeを発火した場合用) themeTagInput.addEventListener('change', () => { if (isButtonTriggeredTextFill) { console.log("Changeイベント: ボタンからのTextFillトリガー中のため、changeイベントの自動確定はスキップします。"); return; } if (changeTimer) clearTimeout(changeTimer); changeTimer = setTimeout(() => { if (themeTagInput.value.trim() === '/' || themeTagInput.value.trim() === '') { console.log("Changeイベント: TextFillトリガー文字または空値が検出されました。自動確定はスキップします."); return; } if (!isProcessing) { processTagsAutomatically(); } }, 100); }); // ページロード時に既に値が入っている場合(TextFillが自動で展開するなど) if (themeTagInput.value.trim() !== '') { setTimeout(() => { if (themeTagInput.value.trim() !== '/') { if (!isProcessing) { processTagsAutomatically(); } } }, 500); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址