Skip to content

Instantly share code, notes, and snippets.

@oovz
Last active May 20, 2025 19:31
Show Gist options
  • Save oovz/e1f36ff2bf18c0ab64180e785f837a12 to your computer and use it in GitHub Desktop.
Save oovz/e1f36ff2bf18c0ab64180e785f837a12 to your computer and use it in GitHub Desktop.
A tampermonkey/violentmonkey script to highlight updated chapter in Jinjiang (jjwxc.net)
// ==UserScript==
// @name 晋江章节修改高亮
// @namespace http://tampermonkey.net/
// @version 0.3
// @description 高亮修改过的章节,区别首发时间和更新时间
// @author oovz
// @match *://www.jjwxc.net/onebook.php?novelid=*
// @exclude *://www.jjwxc.net/onebook.php?novelid=*&chapterid=*
// @source https://greasyfork.org/en/scripts/536627-%E6%99%8B%E6%B1%9F%E7%AB%A0%E8%8A%82%E4%BF%AE%E6%94%B9%E9%AB%98%E4%BA%AE
// @source https://gist.github.com/oovz/e1f36ff2bf18c0ab64180e785f837a12
// @license MIT
// @grant GM_addStyle
// ==/UserScript==
(function() {
'use strict';
const HIGHLIGHT_COLOR = '#FFFACD'; // Light yellow
const TOGGLE_BUTTON_ID = 'toggle-chapter-highlight';
const STORAGE_KEY = 'jjwxc-chapter-highlight-enabled';
GM_addStyle(`
.chapter-modified {
background-color: ${HIGHLIGHT_COLOR} !important;
}
#${TOGGLE_BUTTON_ID} {
margin-left: 10px;
cursor: pointer;
}
#${TOGGLE_BUTTON_ID}:hover {
text-decoration: underline;
}
.highlight-tooltip {
position: relative;
display: inline-block;
}
.highlight-tooltip .tooltip-text {
visibility: hidden;
width: 200px;
background-color: #FFF8DC;
color: #000;
text-align: center;
border-radius: 3px;
border: 1px solid #DEB887;
padding: 3px 5px;
position: absolute;
z-index: 1000;
bottom: 125%;
left: 50%;
margin-left: -100px;
opacity: 0;
transition: opacity 0.3s;
font-size: 12px;
}
.highlight-tooltip:hover .tooltip-text {
visibility: visible;
opacity: 1;
}
`);
// State
// highlighting is enabled by default
const storedValue = localStorage.getItem(STORAGE_KEY);
let isHighlightEnabled = storedValue === null || storedValue === 'true'; // Default to enabled if not set
/**
* Find all chapter rows in the document
* @returns {NodeList} List of chapter rows
*/
function findChapterRows() {
// latest updated chapter will have itemprop="chapter newestChapter"
return document.querySelectorAll('tr[itemprop*="chapter"]');
}
/**
* Extract the publish time from a chapter row
* @param {Element} chapterRow - The TR element representing a chapter
* @returns {string|null} The extracted publish time or null if not found
*/
function getPublishTimeFromChapter(chapterRow) {
try {
const titleCell = chapterRow.querySelector("td[title]");
if (!titleCell || !titleCell.title) {
return null;
}
// Title will contain both store and publish times
// '章节存稿时间:2022-09-09 16:23:42\n章节首发时间:2022-09-09 16:25:00'
const titleText = titleCell.title;
// Extract the publish time after "章节首发时间:"
const publishTimeMatch = titleText.match(/章节首发时间:([^\n]+)/);
if (publishTimeMatch && publishTimeMatch[1]) {
return publishTimeMatch[1].trim();
}
} catch (e) {
console.warn('Error extracting publish time:', e);
}
return null; // No publish time found
}
/**
* Extract the update time from a chapter row
* @param {Element} chapterRow - The TR element representing a chapter
* @returns {string|null} The extracted update time or null if not found
*/
function getUpdateTimeFromChapter(chapterRow) {
try {
return chapterRow.querySelector("td[title] > span:not([id='latestUpdates'])").textContent.trim();
} catch (e) {
console.warn('Error extracting update time:', e);
}
return null; // No update time found
}
/**
* Apply highlighting to chapters with different publish vs update times
*/
function highlightModifiedChapters() {
const chapterRows = findChapterRows();
let modifiedCount = 0;
chapterRows.forEach(row => {
try {
const publishTime = getPublishTimeFromChapter(row);
const updateTime = getUpdateTimeFromChapter(row);
if (publishTime && updateTime && publishTime !== updateTime) {
// Different times - highlight the row
row.classList.add('chapter-modified');
// Store original classes if needed for toggling
if (!row.dataset.originalBg) {
row.dataset.originalBg = 'saved';
}
modifiedCount++;
}
} catch (e) {
console.warn('Error processing chapter row:', e);
}
});
console.log(`Found ${modifiedCount} modified chapters out of ${chapterRows.length} total`);
}
/**
* Remove all highlighting from chapter rows
*/
function removeHighlights() {
const chapterRows = findChapterRows();
chapterRows.forEach(row => {
row.classList.remove('chapter-modified');
});
}
/**
* Create and insert the toggle button
*/
function createToggleButton() {
// Find the second tr element in the document
const secondRow = document.querySelector('tbody > tr:nth-of-type(2) > td');
if (!secondRow) {
console.error('Cannot insert toggle button');
} else {
// Create toggle button wrapper with tooltip
const wrapper = document.createElement('span');
wrapper.className = 'highlight-tooltip';
wrapper.style.marginLeft = '10px';
const button = createButton();
wrapper.appendChild(button);
const tooltip = document.createElement('span');
tooltip.className = 'tooltip-text';
tooltip.textContent = '高亮显示更新时间与首发时间不同的章节';
wrapper.appendChild(tooltip);
// Insert the button at the end of the second row
secondRow.appendChild(wrapper);
}
}
/**
* Create the toggle button element
* @returns {HTMLElement} The created button
*/
function createButton() {
// Create a link that looks like a button
const button = document.createElement('a');
button.id = TOGGLE_BUTTON_ID;
button.href = 'javascript:void(0);'; // Make it clickable but not navigate
button.textContent = isHighlightEnabled ? '[插件:关闭章节高亮]' : '[插件:开启章节高亮]';
button.style.color = '#FF0000'; // Red text color
button.style.textDecoration = 'none'; // No underline
// Add click handler
button.addEventListener('click', handleToggleClick);
return button;
}
/**
* Handle toggle button click - toggle highlighting on/off
*/
function handleToggleClick() {
isHighlightEnabled = !isHighlightEnabled;
const button = document.getElementById(TOGGLE_BUTTON_ID);
if (button) {
button.textContent = isHighlightEnabled ? '[插件:关闭章节高亮]' : '[插件:开启章节高亮]';
}
if (isHighlightEnabled) {
highlightModifiedChapters();
} else {
removeHighlights();
}
try {
localStorage.setItem(STORAGE_KEY, isHighlightEnabled ? 'true' : 'false');
} catch (e) {
console.warn('Could not save highlight preference to localStorage', e);
}
}
/**
* Initialize the script
*/
function initialize() {
console.log('Initializing chapter highlight script...');
createToggleButton();
if (isHighlightEnabled) {
highlightModifiedChapters();
}
}
// Wait for the DOM to be ready before initializing
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize);
} else {
initialize();
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment