Royal Road: Add Word Count to Statistics Section

Adds the word count of a fiction (taken from the information tooltip in the Pages statistic) as it's own statistic

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name           Royal Road: Add Word Count to Statistics Section
// @namespace      https://github.com/w4tchdoge
// @version        1.1.0-20250611_121836
// @description    Adds the word count of a fiction (taken from the information tooltip in the Pages statistic) as it's own statistic
// @author         w4tchdoge
// @homepage       https://github.com/w4tchdoge/MISC-UserScripts
// @match          *://*.royalroad.com/fiction/*
// @exclude        *://*.royalroad.com/fiction/*/*/chapter/*
// @license        AGPL-3.0-or-later
// @run-at         document-start
// @history        1.1.0 — Add word count next to chapter count in the ToC header bar thing
// @history        1.0.0 — Initial release
// ==/UserScript==

(async function () {
	`use strict`;

	// modified from https://stackoverflow.com/a/61511955/11750206
	function waitForElm(selector, search_root = document) {
		return new Promise(resolve => {
			if (search_root.querySelector(selector)) {
				return resolve(search_root.querySelector(selector));
			}

			const observer = new MutationObserver(mutations => {
				if (search_root.querySelector(selector)) {
					observer.disconnect();
					resolve(search_root.querySelector(selector));
				}
			});

			// If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336
			observer.observe(document.documentElement, {
				childList: true,
				subtree: true
			});
		});
	}

	// wait for fic info section to load in
	const ficinfo = await waitForElm(`div.fiction-info`);

	// Get the element which contains the word "Pages" and which also contains the word count in an informational tooltip
	const statsPage_pages_elem = ficinfo.querySelector(`.fiction-stats .stats-content .list-unstyled li:has(> i)`);

	// Get Word Count
	const word_count_str = statsPage_pages_elem.querySelector(`i`).getAttribute(`data-content`).replace(/.*calculated from (.*) words./gmi, `$1`);
	console.log(`
RR WORD COUNT SCRIPT
WORD COUNT: ${word_count_str}`);

	// Make the "Heading" element that describes the data below it. i.e. it says "Words"
	const elm_wordcount_title = (() => {
		let outelm = Object.assign(document.createElement(`li`), {
			id: `userscript-words-title`,
			innerHTML: `Words :`
		});

		// Add the bold and uppercase classes to make it look like the other stats page entries
		outelm.classList.add(`bold`, `uppercase`);

		return outelm;
	})();

	// Make the "Data" element that has the actual data. i.e. it says the word count
	const elm_wordcount_data = (() => {
		let outelm = Object.assign(document.createElement(`li`), {
			id: `userscript-words-data`,
			innerHTML: `${word_count_str}`
		});

		// Add the bold and uppercase classes to make it look like the other stats page entries
		outelm.classList.add(`bold`, `uppercase`, `font-red-sunglo`);

		return outelm;
	})();

	// Add the word count elements before the page count elements
	statsPage_pages_elem.before(elm_wordcount_title, elm_wordcount_data);

	const tocbar_ch_cnt = (() => {
		const tocbar = ficinfo.querySelector(`div.portlet > .portlet-title:has(> .caption > a#toc)`);
		const xpath = `.//*[contains(concat(" ",normalize-space(@class)," ")," actions ")]/span[contains(normalize-space(),"Chapter")]`;
		const chcntpar = document.evaluate(xpath, tocbar, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
		return chcntpar;
	})();

	const tocbar_wc = (() => {
		let base_elm = tocbar_ch_cnt.cloneNode(true);
		base_elm.textContent = `${word_count_str} Words`;
		base_elm.style.marginRight = `0.25em`;
		return base_elm;
	})();

	tocbar_ch_cnt.after(tocbar_wc);
})();