Source: modules/paged-media/following.js

import Handler from "../handler.js";
import csstree from "css-tree";
import { UUID } from "../../utils/utils.js";

class Following extends Handler {
	/**
	 * Creates an instance of Following handler.
	 *
	 * @param {Object} chunker - The chunker instance.
	 * @param {Object} polisher - The polisher instance.
	 * @param {Object} caller - The caller instance.
	 */
	constructor(chunker, polisher, caller) {
		super(chunker, polisher, caller);

		/**
		 * Reference to the stylesheet where new CSS rules will be inserted.
		 */
		this.styleSheet = polisher.styleSheet;

		/**
		 * Stores selectors with their associated UUIDs and declarations.
		 * Structure: { selector: [uuid, declarations] }
		 */
		this.selectors = {};
	}

	/**
	 * Processes a CSS rule node. Extracts rules containing adjacent sibling selectors (+)
	 * and stores them with a unique ID for later processing. Removes processed rules from the list.
	 *
	 * @param {Object} ruleNode - The CSS rule node.
	 * @param {number} ruleItem - The index of the rule in the rule list.
	 * @param {Array} rulelist - The list of CSS rules.
	 */
	onRule(ruleNode, ruleItem, rulelist) {
		let selector = csstree.generate(ruleNode.prelude);
		if (selector.match(/\+/)) {
			let declarations = csstree.generate(ruleNode.block);
			declarations = declarations.replace(/[{}]/g, "");

			let uuid = "following-" + UUID();

			selector.split(",").forEach((s) => {
				if (!this.selectors[s]) {
					this.selectors[s] = [uuid, declarations];
				} else {
					this.selectors[s][1] = `${this.selectors[s][1]};${declarations}`;
				}
			});

			rulelist.remove(ruleItem);
		}
	}

	/**
	 * Called after the entire CSS has been parsed.
	 * Starts processing stored selectors to apply attributes and insert CSS rules.
	 *
	 * @param {Document} parsed - The parsed document to apply the selectors on.
	 */
	afterParsed(parsed) {
		this.processSelectors(parsed, this.selectors);
	}

	/**
	 * For each stored selector, finds matching elements in the parsed document,
	 * adds a data-following attribute with the selector's UUID,
	 * and inserts corresponding CSS rules into the stylesheet.
	 *
	 * @param {Document} parsed - The parsed document to query elements from.
	 * @param {Object} selectors - Map of selectors with UUID and declarations.
	 */
	processSelectors(parsed, selectors) {
		// add the new attributes to matching elements
		for (let s in selectors) {
			let elements = parsed.querySelectorAll(s);

			for (var i = 0; i < elements.length; i++) {
				let dataFollowing = elements[i].getAttribute("data-following");

				if (dataFollowing && dataFollowing != "") {
					dataFollowing = `${dataFollowing},${selectors[s][0]}`;
					elements[i].setAttribute("data-following", dataFollowing);
				} else {
					elements[i].setAttribute("data-following", selectors[s][0]);
				}
			}

			let rule = `*[data-following*='${selectors[s][0]}'] { ${selectors[s][1]}; }`;
			this.styleSheet.insertRule(rule, this.styleSheet.cssRules.length);
		}
	}
}

export default Following;