// ==UserScript== // @name WIKIWIKI 編集ソース保存ツール // @namespace local.wikiwiki.backup // @version 1.0 // @description wikiwiki.jpの編集画面(::cmd/edit)にあるテキストエリアの中身(wikiwiki記法ソース)を、ボタン一つでtxtファイルとして保存します。 // @match https://wikiwiki.jp/* // @run-at document-idle // @grant none // ==/UserScript== (function () { 'use strict'; // 編集画面かどうかを判定 // 例: https://wikiwiki.jp/omngtkwn/::cmd/edit?page=クロ定型文 function isEditPage() { return /::cmd\/edit/.test(location.pathname) || /cmd=edit/.test(location.search); } if (!isEditPage()) { return; } // URLからページ名(page=の値)を取り出す。取れない場合はdocument.titleなどから推測。 function getPageNameFromUrl() { const url = new URL(location.href); const pageParam = url.searchParams.get('page'); if (pageParam) { return decodeURIComponent(pageParam); } // ::cmd/edit?page=xxx 形式だが、pathnameの末尾に付くケースも一応拾う const match = decodeURIComponent(location.href).match(/[?&]page=([^&]+)/); if (match) { return match[1]; } return 'untitled'; } // ファイル名として使えない文字を置換 function sanitizeFileName(name) { return name .replace(/[\\/:*?"<>|]/g, '_') .trim() || 'untitled'; } // 編集用テキストエリアを探す。 // PukiWiki系wikiは name="msg" のtextareaが本文ソースであることが多いが、 // 念のため複数候補から一番大きい(=本文らしい)textareaを選ぶフォールバックも用意する。 function findSourceTextarea() { const candidates = Array.from(document.querySelectorAll('textarea')); if (candidates.length === 0) return null; // よくある name/id を優先的に探す const preferredNames = ['msg', 'contents', 'text', 'body']; for (const n of preferredNames) { const found = candidates.find( (el) => el.name === n || el.id === n ); if (found) return found; } // 優先候補が無ければ、最も文字数が多い(=本文の可能性が高い)ものを選ぶ candidates.sort((a, b) => (b.value?.length || 0) - (a.value?.length || 0)); return candidates[0]; } function downloadTextFile(filename, text) { const blob = new Blob([text], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } function createSaveButton() { const btn = document.createElement('button'); btn.textContent = '📝 このページのソースをtxt保存'; btn.type = 'button'; btn.style.position = 'fixed'; btn.style.top = '10px'; btn.style.right = '10px'; btn.style.zIndex = '999999'; btn.style.padding = '10px 14px'; btn.style.background = '#2d7dd2'; btn.style.color = '#fff'; btn.style.border = 'none'; btn.style.borderRadius = '6px'; btn.style.fontSize = '14px'; btn.style.cursor = 'pointer'; btn.style.boxShadow = '0 2px 6px rgba(0,0,0,0.3)'; btn.addEventListener('click', () => { const textarea = findSourceTextarea(); if (!textarea) { alert('本文のテキストエリアが見つかりませんでした。ページ構造が想定と異なる可能性があります。'); return; } const pageName = sanitizeFileName(getPageNameFromUrl()); const content = textarea.value; downloadTextFile(`${pageName}.txt`, content); }); document.body.appendChild(btn); } createSaveButton(); })();