您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Mark post read on dev.to.
// Q:Why I want to write this script? // A: I often read articles on dev.to site, dev.to provides three buttons, namely like, unicorn and readinglist. But I want a button to indicate whether the current article has been read, similar to the read function of mailing lists and RSS. // ==UserScript== // @name Mark post read on dev.to // @namespace http://tampermonkey.net/ // @version 0.1 // @description Mark post read on dev.to. // @author Du Lin // @match https://dev.to/* // @grant none // @require https://cdnjs.cloudflare.com/ajax/libs/localforage/1.9.0/localforage.min.js // ==/UserScript== (function () { const buttonTexts = { unread: 'Mark As Read', read: 'Read', }; function bindEvent() { const widget = document.querySelector('#marked-as-read-menu'); const readButton = document.querySelector('#marked-as-read-button'); const exportButton = document.querySelector('#marked-as-read-export-button'); const closeMenuButton = document.querySelector('#marked-as-read-close-button'); const exportReadListCloseButton = document.querySelector('#marked-as-read-export-read-list-close-button'); readButton.addEventListener('click', onReadButtonClick, false); widget.addEventListener('click', onWidgetClick, false); exportButton.addEventListener('click', onExportButtonClick, false); closeMenuButton.addEventListener('click', onCloseMenuButtonClick, false); exportReadListCloseButton.addEventListener('click', onExportReadListCloseButtonClick, false); } async function initStorage() { localforage.config({ driver: [localforage.INDEXEDDB, localforage.WEBSQL], name: 'mark-post-read-on-devto', }); try { const list = await getReadListFromStorage(); if (!list) { await localforage.setItem('list', []); } } catch (error) { console.error('intialize error, ', error); } } function createReadButton() { const button = document.createElement('button'); button.id = 'marked-as-read-button'; button.classList.add('crayons-reaction'); button.textContent = buttonTexts.unread; button.style.cssText = 'border: 1px solid rgb(53, 53, 53);'; return button; } function createExportButton() { const button = document.createElement('button'); button.id = 'marked-as-read-export-button'; button.textContent = 'export'; button.style.marginRight = '10px'; return button; } function createImportButton() { const button = document.createElement('button'); button.id = 'marked-as-read-import-button'; button.textContent = 'import'; button.style.marginRight = '10px'; return button; } function createCloseMenuButton() { const button = document.createElement('button'); button.id = 'marked-as-read-close-button'; button.textContent = 'close'; return button; } function createExportReadList() { const div = document.createElement('div'); const title = document.createElement('p'); const closeButton = document.createElement('button'); title.textContent = 'Export Read list'; title.style.marginBottom = '10px'; div.id = 'marked-as-read-export-list-container'; div.style.cssText = 'display: none; z-index: 1000; position: fixed; width: 800px; height: 600px; padding: 20px; background-color: #ddd; top: 0; left: 0;right: 0;bottom: 0;margin: auto'; const textarea = document.createElement('textarea'); textarea.id = 'marked-as-read-export-list'; textarea.style.cssText = 'font-size: 14px; resize: none; width: 100%; height: 500px;'; closeButton.id = 'marked-as-read-export-read-list-close-button'; closeButton.textContent = 'close'; div.appendChild(title); div.appendChild(textarea); div.appendChild(closeButton); return div; } function createWidget() { const buttonContainer = document.querySelector('.crayons-article-actions__inner'); const pageContent = document.querySelector('#page-content-inner'); const readButton = createReadButton(); const widget = document.createElement('div'); const panel = document.createElement('div'); const widgetHeader = document.createElement('p'); const exportButton = createExportButton(); const importButton = createImportButton(); const closeMenuButton = createCloseMenuButton(); const exportReadList = createExportReadList(); widget.id = 'marked-as-read-menu'; widget.style.cssText = [ 'z-index: 100;', 'position: fixed;', 'top: 300px;', 'transform-origin: 25px;', 'transform: rotate(270deg);', 'background-color: #ddd;', 'border: 1px solid #aaa;', 'padding: 10px;', 'text-align: center;', ].join(''); widgetHeader.textContent = 'Marked As Read Menu'; panel.id = 'marked-as-read-menu-panel'; panel.style.cssText = 'display: none;'; panel.appendChild(exportButton); panel.appendChild(importButton); panel.appendChild(closeMenuButton); widget.appendChild(panel); widget.prepend(widgetHeader); pageContent.appendChild(widget); pageContent.appendChild(exportReadList); buttonContainer.prepend(readButton); return widget; } function onWidgetClick() { const panel = document.querySelector('#marked-as-read-menu-panel'); const widget = document.querySelector('#marked-as-read-menu'); panel.style.display = 'block'; widget.style.transform = 'none'; } function onExportReadListCloseButtonClick(e) { e.stopPropagation(); const readlistWidget = document.querySelector('#marked-as-read-export-list-container'); readlistWidget.style.display = 'none'; } async function onExportButtonClick(e) { e.stopPropagation(); const readlistWidget = document.querySelector('#marked-as-read-export-list-container'); const readList = document.querySelector('#marked-as-read-export-list'); readlistWidget.style.display = 'block'; const list = await getReadListFromStorage(); readList.value = list.join('\n'); } function onCloseMenuButtonClick(e) { e.stopPropagation(); const panel = document.querySelector('#marked-as-read-menu-panel'); const widget = document.querySelector('#marked-as-read-menu'); const readlistWidget = document.querySelector('#marked-as-read-export-list-container'); panel.style.display = 'none'; widget.style.transform = 'rotate(270deg)'; readlistWidget.style.display = 'none'; } async function onReadButtonClick(e) { e.stopPropagation(); const isRead = await checkReadStatus(); if (!isRead) { const r = AddItemToReadListStorage(window.location.href); if (r) { setReadButtonState(); } } else { const r = removeItemFromReadListStorage(); if (r) { resetReadButtonState(); } } } async function init() { createWidget(); bindEvent(); await initStorage(); const isRead = await checkReadStatus(); if (isRead) { setReadButtonState(); } } function setReadButtonState() { const readButton = document.querySelector('#marked-as-read-button'); readButton.textContent = buttonTexts.read; readButton.classList.add('read'); } function resetReadButtonState() { const readButton = document.querySelector('#marked-as-read-button'); readButton.textContent = buttonTexts.unread; readButton.classList.remove('read'); } async function checkReadStatus() { const list = await getReadListFromStorage(); const idx = list.findIndex((url) => url === window.location.href); return idx > -1; } async function AddItemToReadListStorage(item) { const list = await getReadListFromStorage(); const exists = list.findIndex((url) => url === item) > -1; if (exists) return false; list.push(item); localforage.setItem('list', list); return true; } async function removeItemFromReadListStorage(item) { const list = await getReadListFromStorage(); const idx = list.findIndex((url) => url === item); list.splice(idx, 1); localforage.setItem('list', list); return true; } function getReadListFromStorage() { return localforage.getItem('list').catch(console.log); } init(); })();