您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Save memo for user at skeb.jp.
// ==UserScript== // @name SkebMemo // @namespace http://tampermonkey.net/ // @version 1.2.3 // @description Save memo for user at skeb.jp. // @author A. A. // @match *://skeb.jp/* // @icon https://www.google.com/s2/favicons?sz=64&domain=skeb.jp // @grant GM_registerMenuCommand // @license MIT // ==/UserScript== (function () { 'use strict'; let fontCJE = 'Microsoft Yahei, SimHei, Noto Sans JP, Arial, sans-serif'; function handleExportNotes() { let notes = JSON.parse(localStorage.getItem('notes') || '{}'); let dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(notes, null, 2)); let downloadAnchorNode = document.createElement('a'); downloadAnchorNode.setAttribute("href", dataStr); downloadAnchorNode.setAttribute("download", "skeb_memo.json"); document.body.appendChild(downloadAnchorNode); downloadAnchorNode.click(); downloadAnchorNode.remove(); }; function handleImportNotes() { let input = document.createElement('input'); let notes = JSON.parse(localStorage.getItem('notes') || '{}'); input.type = 'file'; input.accept = '.json'; input.style.display = 'none'; input.addEventListener('change', function (event) { let file = event.target.files[0]; if (file) { let reader = new FileReader(); reader.onload = function (e) { try { let importedNotes = JSON.parse(e.target.result); // Merge imported notes with existing notes notes = { ...notes, ...importedNotes }; localStorage.setItem('notes', JSON.stringify(notes)); alert(languages[currentLanguage].importSuccess); notes = JSON.parse(localStorage.getItem('notes') || '{}'); } catch (error) { console.error('SkebMemo: Error parsing imported JSON.', error); alert(languages[currentLanguage].importError); } }; reader.readAsText(file); } }); input.click(); }; const languages = { en: { viewNotes: 'Show All Notes', exportNotes: 'Export', importNotes: 'Import', clearNotes: 'Remove All', notesList: 'Notes List', searchNotes: 'Search Notes', delete: 'Delete', confirmClear: 'Are you sure you want to remove all notes? This action cannot be undone.', notesCleared: 'All notes have been removed.', importSuccess: 'Notes imported successfully!', importError: 'Import failed, please check if the file format is correct.', settings: 'Settings', language: 'Language', notesPerPage: 'Notes per page', firstPage: 'First', lastPage: 'Last', boxComment: 'The memo will be saved for this work.' }, cn: { viewNotes: '查看所有笔记', exportNotes: '导出笔记', importNotes: '导入笔记', clearNotes: '清空笔记', notesList: '笔记列表', searchNotes: '搜索笔记', delete: '删除', confirmClear: '确定要清空所有笔记吗?此操作不可恢复。', notesCleared: '所有笔记已清空。', importSuccess: '笔记导入成功!', importError: '导入失败,请检查文件格式是否正确。', settings: '设置', language: '语言', notesPerPage: '每页显示笔记数', firstPage: '首', lastPage: '末', boxComment: '笔记内容将为此稿件保存。' }, jp: { viewNotes: 'メモ一覧', exportNotes: 'エクスポート', importNotes: 'インポート', clearNotes: 'メモ一括削除', notesList: 'メモ一覧', searchNotes: '検索', delete: '削除', confirmClear: 'すべてのメモを削除しますか? この操作は元に戻せません。', notesCleared: 'すべてのメモが削除されました。', importSuccess: 'メモがインポートされました!', importError: 'インポートに失敗しました。ファイル形式が正しいか確認してください。', settings: '設定', language: '言語', notesPerPage: 'ページごとのメモ数', firstPage: '最初', lastPage: '最後', boxComment: 'このメモはイラストに保存されます。' } }; function openSettings() { if (document.querySelector('.SkebMemoSettings')) { return; } let settingsDiv = document.createElement("div"); settingsDiv.className = 'SkebMemoSettings'; settingsDiv.style.position = "fixed"; settingsDiv.style.top = "50%"; settingsDiv.style.left = "50%"; settingsDiv.style.transform = "translate(-50%, -50%)"; settingsDiv.style.backgroundColor = "#fff"; settingsDiv.style.padding = "30px"; settingsDiv.style.border = "1px solid #ccc"; settingsDiv.style.zIndex = "9999"; let header = document.createElement('h2'); header.textContent = languages[currentLanguage].settings; header.style.textAlign = 'center'; header.style.fontFamily = fontCJE; settingsDiv.appendChild(header); let languageLabel = document.createElement('label'); languageLabel.textContent = languages[currentLanguage].language; languageLabel.style.display = 'block'; languageLabel.style.marginBottom = '10px'; languageLabel.style.fontFamily = fontCJE; settingsDiv.appendChild(languageLabel); let cnRadio = document.createElement('input'); cnRadio.type = 'radio'; cnRadio.name = 'SkebMemoLang'; cnRadio.value = 'cn'; cnRadio.id = 'cnRadio'; let cnLabel = document.createElement('label'); cnLabel.textContent = '中文'; cnLabel.style.marginRight = '10px'; cnLabel.htmlFor = 'cnRadio'; cnLabel.style.fontFamily = 'Microsoft Yahei, SimHei, sans-serif'; let enRadio = document.createElement('input'); enRadio.type = 'radio'; enRadio.name = 'SkebMemoLang'; enRadio.value = 'en'; enRadio.id = 'enRadio'; let enLabel = document.createElement('label'); enLabel.textContent = 'English'; enLabel.style.marginRight = '10px'; enLabel.htmlFor = 'enRadio'; enLabel.style.fontFamily = 'Arial, sans-serif'; let jpRadio = document.createElement('input'); jpRadio.type = 'radio'; jpRadio.name = 'SkebMemoLang'; jpRadio.value = 'jp'; jpRadio.id = 'jpRadio'; let jpLabel = document.createElement('label'); jpLabel.textContent = '日本語'; jpLabel.style.marginRight = '10px'; jpLabel.htmlFor = 'jpRadio'; jpLabel.style.fontFamily = 'Noto Sans JP, sans-serif'; settingsDiv.appendChild(cnRadio); settingsDiv.appendChild(cnLabel); settingsDiv.appendChild(enRadio); settingsDiv.appendChild(enLabel); settingsDiv.appendChild(jpRadio); settingsDiv.appendChild(jpLabel); let notesPerPageLabel = document.createElement('label'); notesPerPageLabel.textContent = languages[currentLanguage].notesPerPage; notesPerPageLabel.style.display = 'block'; notesPerPageLabel.style.marginTop = '20px'; notesPerPageLabel.style.fontFamily = fontCJE; settingsDiv.appendChild(notesPerPageLabel); let notesPerPageInput = document.createElement('input'); notesPerPageInput.type = 'number'; notesPerPageInput.value = notesPerPage || '10'; notesPerPageInput.min = '1'; notesPerPageInput.style.width = '100%'; notesPerPageInput.style.marginBottom = '10px'; notesPerPageInput.style.marginRight = '10px'; settingsDiv.appendChild(notesPerPageInput); let exportNotesButton = document.createElement('button'); exportNotesButton.textContent = languages[currentLanguage].exportNotes; exportNotesButton.style.marginBottom = '10px'; exportNotesButton.style.marginRight = '10px'; exportNotesButton.style.fontFamily = fontCJE; settingsDiv.appendChild(exportNotesButton); let importNotesButton = document.createElement('button'); importNotesButton.textContent = languages[currentLanguage].importNotes; importNotesButton.style.marginBottom = '10px'; importNotesButton.style.marginRight = '10px'; importNotesButton.style.fontFamily = fontCJE; settingsDiv.appendChild(importNotesButton); let clearNotesButton = document.createElement('button'); clearNotesButton.textContent = languages[currentLanguage].clearNotes; clearNotesButton.style.marginBottom = '10px'; clearNotesButton.style.fontFamily = fontCJE; settingsDiv.appendChild(clearNotesButton); document.body.appendChild(settingsDiv); exportNotesButton.addEventListener('click', handleExportNotes); importNotesButton.addEventListener('click', handleImportNotes); clearNotesButton.addEventListener('click', function () { let confirmClear = confirm(languages[currentLanguage].confirmClear); if (confirmClear) { localStorage.removeItem('notes'); notes = {}; alert(languages[currentLanguage].notesCleared); } }); // container.appendChild(document.createElement("br")); let closeButton = document.createElement("span"); closeButton.textContent = "×"; closeButton.style.position = "absolute"; closeButton.style.top = "5px"; closeButton.style.right = "5px"; closeButton.style.fontSize = "20px"; closeButton.style.cursor = "pointer"; closeButton.addEventListener("click", function () { document.body.removeChild(settingsDiv); }); settingsDiv.appendChild(closeButton); cnRadio.addEventListener('change', function () { localStorage.setItem('SkebMemoLang', 'cn'); // location.reload(); }); jpRadio.addEventListener('change', function () { localStorage.setItem('SkebMemoLang', 'jp'); // location.reload(); }); enRadio.addEventListener('change', function () { localStorage.setItem('SkebMemoLang', 'en'); // location.reload(); }); let savedLanguge = localStorage.getItem('SkebMemoLang'); if (savedLanguge === 'cn') { cnRadio.checked = true; currentLanguage = 'cn'; } else if (savedLanguge === 'jp') { jpRadio.checked = true; currentLanguage = 'jp'; } else { enRadio.checked = true; currentLanguage = 'en'; }; notesPerPageInput.addEventListener("input", function () { localStorage.setItem("SkebMemoN", notesPerPageInput.value); }); } var notesPerPage = parseInt(localStorage.getItem('SkebMemoN')) || 10; var currentLanguage = localStorage.getItem('SkebMemoLang') || 'en'; GM_registerMenuCommand(languages[currentLanguage].settings, openSettings); function note_func() { if (document.querySelector('.memobox')) { return; } let urlPath = window.location.pathname; // Find user name let segments = urlPath.split('/'); let pageID = '' let authorID = segments.find(segment => segment.startsWith('@')); if (!authorID) { console.warn('SkebMemo: Not a user page or work page.'); return; } else { pageID = authorID; } if (window.location.pathname.includes('/works/')) { pageID = authorID + '/works/' + segments[3]; } let nickname = ''; setTimeout(function() { try { if (window.location.pathname.includes('/works/')) { nickname = document.querySelector('.title.is-5').textContent.trim() + '/' + segments[3]; } else { nickname = document.querySelector('.title.is-4').textContent.trim(); } } catch (error) { console.error('SkebMemo: Error extracting nickname:', error); nickname = ''; } }, 100); // Initialize notes object let notes = JSON.parse(localStorage.getItem('notes') || '{}'); // Find info box let targetDiv = document.querySelector('.is-box'); if (!targetDiv) { console.error('SkebMemo: .is-box not found.'); return; } // let divbox = document.createElement('div'); // divbox.className = 'is-box'; let container = document.createElement('div'); container.style.marginTop = '20px'; container.className = 'is-box'; container.classList.add('memobox'); // divbox.appendChild(container); targetDiv.parentNode.insertBefore(container, targetDiv.nextSibling); // Create text box let textBox = document.createElement('textarea'); textBox.id = 'myTextBox'; textBox.style.width = '100%'; textBox.style.height = '200px'; textBox.style.marginBottom = '10px'; textBox.style.resize = 'vertical'; textBox.style.fontFamily = fontCJE; textBox.style.fontSize = '15px'; container.appendChild(textBox); let viewNotesButton = document.createElement('button'); viewNotesButton.textContent = languages[currentLanguage].viewNotes;; viewNotesButton.style.fontFamily = fontCJE; viewNotesButton.style.fontSize = '15px'; viewNotesButton.style.marginBottom = '0px'; viewNotesButton.style.backgroundColor = '#28837f'; viewNotesButton.style.padding = '3px 1em' viewNotesButton.style.borderColor = 'transparent'; viewNotesButton.style.borderRadius = '4px'; viewNotesButton.style.color = 'white'; viewNotesButton.addEventListener('mouseover', function() { viewNotesButton.style.backgroundColor = '#257976'; }); viewNotesButton.addEventListener('mouseout', function() { viewNotesButton.style.backgroundColor = '#28837f'; }); viewNotesButton.addEventListener('mousedown', function() { viewNotesButton.style.backgroundColor = '#226F6C'; }); viewNotesButton.addEventListener('mouseup', function() { viewNotesButton.style.backgroundColor = '#28837f'; }); container.appendChild(viewNotesButton); if (window.location.pathname.includes('/works/')) { container.style.position = 'relative' let explaination = document.createElement('div'); explaination.textContent = languages[currentLanguage].boxComment; explaination.style.fontFamily = fontCJE; explaination.style.fontSize = '13px'; explaination.style.color = '#c5c5c5'; explaination.style.position = 'absolute'; explaination.style.bottom = '30px'; explaination.style.left = '20px'; container.appendChild(explaination); } // Use flexbox to align items container.style.display = 'flex'; container.style.flexDirection = 'column'; container.style.alignItems = 'flex-end'; // Load notes from local storage try { textBox.value = notes[pageID].memo || ''; } catch (error) { textBox.value = ''; // No memo } // Save notes to local storage // textBox.addEventListener('input', function() { // notes[pageID] = textBox.value; // localStorage.setItem('notes', JSON.stringify(notes)); // }); textBox.addEventListener('input', function () { if (textBox.value.trim() === '') { delete notes[pageID]; } else { notes[pageID] = { memo: textBox.value, name: nickname }; } localStorage.setItem('notes', JSON.stringify(notes)); }); // View all notes viewNotesButton.addEventListener('click', function () { if (document.querySelector('.notesList')) { return; } let notesList = document.createElement('div'); notesList.className = 'notesList'; notesList.style.position = 'fixed'; notesList.style.top = '50%'; notesList.style.left = '50%'; notesList.style.transform = 'translate(-50%, -50%)'; notesList.style.width = '80%'; notesList.style.height = '80%'; notesList.style.overflow = 'auto'; notesList.style.backgroundColor = 'white'; notesList.style.zIndex = '1000'; notesList.style.border = '1px solid black'; notesList.style.padding = '20px'; notesList.style.boxShadow = '0px 0px 10px rgba(0,0,0,0.5)'; notesList.style.fontFamily = fontCJE; notesList.id = 'notesList'; let header = document.createElement('h2'); header.textContent = languages[currentLanguage].notesList; header.style.textAlign = 'center'; header.style.fontFamily = fontCJE; notesList.appendChild(header); let searchInput = document.createElement('input'); searchInput.type = 'text'; searchInput.placeholder = languages[currentLanguage].searchNotes; searchInput.style.width = '100%'; searchInput.style.marginBottom = '10px'; searchInput.style.fontFamily = fontCJE; searchInput.style.border = '1px solid #ccc'; searchInput.style.padding = '5px'; notesList.appendChild(searchInput); let notesContainer = document.createElement('div'); notesContainer.style.display = 'grid'; notesContainer.style.gridTemplateColumns = '3fr 3fr 12fr 1fr'; notesContainer.style.gap = '10px'; notesContainer.style.fontFamily = fontCJE; notesList.appendChild(notesContainer); let buttonContainer = document.createElement('div'); buttonContainer.style.display = 'flex'; buttonContainer.style.position = 'absolute'; buttonContainer.style.top = '10px'; buttonContainer.style.left = '10px'; notesList.appendChild(buttonContainer); // Export notes button let exportNotesButton = document.createElement('button'); exportNotesButton.textContent = languages[currentLanguage].exportNotes; exportNotesButton.style.marginRight = '10px'; exportNotesButton.style.fontFamily = fontCJE; buttonContainer.appendChild(exportNotesButton); // Import notes button let importNotesButton = document.createElement('button'); importNotesButton.textContent = languages[currentLanguage].importNotes; importNotesButton.style.marginRight = '10px'; importNotesButton.style.fontFamily = fontCJE; buttonContainer.appendChild(importNotesButton); // Remove all notes button let clearNotesButton = document.createElement('button'); clearNotesButton.textContent = languages[currentLanguage].clearNotes; clearNotesButton.style.marginRight = '10px'; clearNotesButton.style.fontFamily = fontCJE; buttonContainer.appendChild(clearNotesButton); // Setting button let settingButton = document.createElement('button'); settingButton.textContent = languages[currentLanguage].settings; settingButton.style.marginRight = '10px'; settingButton.style.fontFamily = fontCJE; buttonContainer.appendChild(settingButton); let closeButton = document.createElement("span"); closeButton.textContent = "×"; closeButton.style.position = "absolute"; closeButton.style.top = "5px"; closeButton.style.right = "15px"; closeButton.style.fontSize = "20px"; closeButton.style.cursor = "pointer"; closeButton.addEventListener('click', function () { document.body.removeChild(notesList); }); notesList.appendChild(closeButton); let paginationContainer = document.createElement('div'); paginationContainer.style.display = 'flex'; paginationContainer.style.justifyContent = 'center'; paginationContainer.style.marginTop = '10px'; paginationContainer.style.fontFamily = fontCJE; notesList.appendChild(paginationContainer); document.body.appendChild(notesList); let currentPage = 1; let filteredNotes = Object.keys(notes).filter(id => notes[id].memo.includes('')); let maxVisiblePages = 7; function renderNotes(filter = '') { notesContainer.innerHTML = ''; paginationContainer.innerHTML = ''; filteredNotes = Object.keys(notes).filter(id => notes[id].memo.includes(filter)); let totalPages = Math.ceil(filteredNotes.length / notesPerPage); function createPageButton(page, text) { let pageButton = document.createElement('button'); pageButton.textContent = text; pageButton.style.margin = '0 5px'; pageButton.style.fontFamily = fontCJE; pageButton.style.border = 'none'; // Remove border if (page === currentPage) { pageButton.disabled = true; pageButton.style.fontWeight = 'bold'; } else { pageButton.addEventListener('click', function () { currentPage = page; renderNotes(filter); }); } paginationContainer.appendChild(pageButton); } if (totalPages > 1) { createPageButton(1, languages[currentLanguage].firstPage); if (currentPage > 1) { createPageButton(currentPage - 1, '<'); } let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2)); let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1); if (endPage - startPage + 1 < maxVisiblePages) { startPage = Math.max(1, endPage - maxVisiblePages + 1); } for (let i = startPage; i <= endPage; i++) { createPageButton(i, i); } if (currentPage < totalPages) { createPageButton(currentPage + 1, '>'); } createPageButton(totalPages, languages[currentLanguage].lastPage); } let start = (currentPage - 1) * notesPerPage; let end = start + notesPerPage; let notesToDisplay = filteredNotes.slice(start, end).reverse(); // Sort notes from newest to oldest for (let id of notesToDisplay) { let noteItem = document.createElement('div'); noteItem.style.display = 'contents'; let noteID = document.createElement('a'); noteID.href = `https://skeb.jp/${id}`; noteID.textContent = id; noteID.target = '_blank'; noteID.style.textDecoration = 'none'; noteID.style.color = 'blue'; noteID.style.fontFamily = fontCJE; notesContainer.appendChild(noteID); let noteName = document.createElement('a'); noteName.href = `https://skeb.jp/${id}`; noteName.textContent = notes[id].name; noteName.target = '_blank'; noteName.style.textDecoration = 'none'; noteName.style.fontFamily = fontCJE; notesContainer.appendChild(noteName); let noteText = document.createElement('div'); noteText.textContent = notes[id].memo; noteText.style.fontFamily = fontCJE; noteText.style.whiteSpace = 'pre-wrap'; notesContainer.appendChild(noteText); let deleteButton = document.createElement('button'); deleteButton.textContent = languages[currentLanguage].delete; deleteButton.style.marginLeft = '10px'; deleteButton.style.float = 'right'; deleteButton.style.fontFamily = fontCJE; deleteButton.style.padding = '1px 1px'; deleteButton.style.fontWeight = 'bold'; deleteButton.style.alignSelf = 'center' deleteButton.addEventListener('click', function (id) { return function () { delete notes[id]; localStorage.setItem('notes', JSON.stringify(notes)); renderNotes(filter); }; }(id)); notesContainer.appendChild(deleteButton); } } searchInput.addEventListener('input', function () { currentPage = 1; renderNotes(searchInput.value); }); renderNotes(); exportNotesButton.addEventListener('click', handleExportNotes); importNotesButton.addEventListener('click', function () { let input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.style.display = 'none'; input.addEventListener('change', function (event) { let file = event.target.files[0]; if (file) { let reader = new FileReader(); reader.onload = function (e) { try { let importedNotes = JSON.parse(e.target.result); // Merge imported notes with existing notes notes = { ...notes, ...importedNotes }; localStorage.setItem('notes', JSON.stringify(notes)); alert(languages[currentLanguage].importSuccess); // notes = JSON.parse(localStorage.getItem('notes') || '{}'); document.body.removeChild(notesList); viewNotesButton.click(); } catch (error) { console.error('SkebMemo: Error parsing imported JSON.', error); alert(languages[currentLanguage].importError); } }; reader.readAsText(file); } }); input.click(); }); clearNotesButton.addEventListener('click', function () { let confirmClear = confirm(languages[currentLanguage].confirmClear); if (confirmClear) { localStorage.removeItem('notes'); notes = {}; alert(languages[currentLanguage].notesCleared); document.body.removeChild(notesList); viewNotesButton.click(); } }); settingButton.addEventListener('click', openSettings); }); } function add_observer() { let body = document.body; let observer = new MutationObserver(mutations => { note_func(); }); observer.observe(body, { childList: true, subtree: true }); } add_observer(); })();