Greasy Fork

Greasy Fork is available in English.

KaTeXFlowy-with-AsciiMath

Supports formula rendering in WorkFlowy with KaTeX. Also supports AsciiMath.

目前为 2022-02-13 提交的版本。查看 最新版本

// ==UserScript==
// @name         KaTeXFlowy-with-AsciiMath
// @namespace    https://github.com/BettyJJ
// @version      0.2.3+am
// @description  Supports formula rendering in WorkFlowy with KaTeX. Also supports AsciiMath.
// @author       Betty
// @match        https://workflowy.com/*
// @match        https://*.workflowy.com/*
// @run-at       document-idle
// @grant        GM.addStyle
// @grant        GM_getResourceText
// @resource     KATEX_CSS https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/contrib/auto-render.min.js
// @require      https://unpkg.com/[email protected]/dist/asciimath2tex.umd.js
// ==/UserScript==

(function () {
	'use strict';


	init();


	/**
	 * initialize
	 */
	function init() {
		watch_page();

		load_css();

		hide_raw();

	}


	/**
	 * watch the page
	 */
	function watch_page() {

		// wathe the page, so that the rendering is updated when new contents come in as the user edits or navigates
		const observer = new MutationObserver(function (mutationlist) {
			for (const { addedNodes } of mutationlist) {
				for (const node of addedNodes) {
					if (!node.tagName) continue; // not an element

					if (node.classList.contains('innerContentContainer')) {
						handle_node(node);
					}

				}
			}
		});
		observer.observe(document.body, { childList: true, subtree: true });

	}


	/**
	 * insert a container after the node with formula to contain the rendered result
	 * @param {Node} node Dom Node
	 */
	function handle_node(node) {
		// sometimes there is a dummy node without parent. don't know why, but we need to check and exclude it first
		const parent = node.parentElement;
		if (!parent) {
			return;
		}

		// if a container already exists, remove it first to avoid duplication
		if (parent.nextSibling && parent.nextSibling.classList.contains('rendered-latex')) {
			parent.nextSibling.remove();

			// also remove the class name we added previously
			parent.classList.remove('has-latex');
		}

		// check if the node contains anything that should be rendered
		if (!has_latex(node) && !has_asciimath(node)) {
			return;
		}

		// give the parent a class name so we can handle it later
		parent.classList.add('has-latex');

		// add an element to contain the rendered latex
		const container = document.createElement('div');
		// if it's AsciiMath, it needs to be converted to LaTeX first
		container.innerHTML = convert_to_latex(node.innerHTML);
		container.className = 'rendered-latex';
		parent.insertAdjacentElement('afterend', container);

		// replicate this class name of the parent so that the rendered block can preserve WF's original style
		container.classList.add(parent.classList[1]);

		// render it
		const options = {
			delimiters: [
				{ left: '$$', right: '$$', display: true },
				{ left: '$', right: '$', display: false }
			]
		};
		renderMathInElement(container, options);

		// when the element is clicked, make the focus in the corresponding node so that the user can begin typing
		container.addEventListener('click', () => {
			parent.focus();
		});

	}


	/**
	 * check if the node contains LaTeX that should be rendered
	 * @param {Node} node Dom Node
	 * @returns {boolean}
	 */
	function has_latex(node) {
		// use $ or $$ as delimiters
		const text = node.textContent;
		const regex = /\$(\$)?(.+?)\$(\$)?/s;
		const match = text.match(regex);
		if (match !== null) {
			return true;
		}

		return false;
	}


	/**
	 * check if the node contains AsciiMath that should be rendered
	 * @param {Node} node Dom Node
	 * @returns {boolean}
	 */
	function has_asciimath(node) {
		// use ` as delimiters
		const text = node.textContent;
		const regex = /`(.+?)`/s;
		const match = text.match(regex);
		if (match !== null) {
			return true;
		}

		return false;
	}


	/**
	 * convert a string, changing the AsciiMath parts into LaTeX, keeping the rest unchanged
	 * @param {string} str a string containing AsciiMath
	 * @returns {string} a string containing converted LaTeX
	 */
	function convert_to_latex(str) {
		// AsciiMath uses ` as delimiters
		const regex = /`(.+?)`/g;
		const parser = new AsciiMathParser();
		const result = str.replaceAll(regex, function (match, p1) {
			// convert to LaTeX with $ as delimiters
			return '$' + parser.parse(p1) + '$';
		});
		return result;
	}


	/**
	 * hide the raw content with LaTeX. only shows it when it has focus
	 */
	function hide_raw() {
		GM.addStyle('.name .has-latex .innerContentContainer { display:none } ');
		GM.addStyle('.name .has-latex.content { height: 0; min-height: 0 } ');

		GM.addStyle('.name--focused .has-latex .innerContentContainer { display:inline} ');
		GM.addStyle('.name--focused .has-latex.content { height: auto} ');

		// add a background to make the raw part look clearer
		GM.addStyle('.name--focused .has-latex { background: #eee } ');

		// preserve line breaks in notes
		GM.addStyle('.notes .rendered-latex { white-space: pre-wrap } ');
	}


	/**
	 * load KaTex css
	 */
	function load_css() {
		let css = GM_getResourceText("KATEX_CSS");

		// the font path in the css file is relative, we need to change it to absolute
		css = css.replace(
			/fonts\//g,
			'https://cdn.jsdelivr.net/npm/[email protected]/dist/fonts/'
		);

		GM.addStyle(css);
	}


})();