js/vercengen/components/ComponentBIUF.js

if (!ve) ve = {};
/**
 * Refer to <span color = "yellow">{@link ve.Component}</span> for methods or fields inherited from this Component's parent such as `.options.attributes` or `.element`.
 *
 * Single-line rich text editor with bold, italic, underline, and clear text formatting options.
 *
 * Stands for Bold, Italic, Underline, Formatting.
 * - Functional binding: <span color=00ffff>veBIUF</span>().
 *
 * ##### Constructor:
 * - `arg0_value`: {@link string} - The HTML content to initialise the component with.
 * - `arg1_options`: {@link Object}
 *   - `.disabled=false`: {@link boolean} - Controls the .readonly attribute.
 *
 * ##### Instance:
 * - `.v`: {@link string} - Returns an HTML string.
 *
 * ##### Methods:
 * - <span color=00ffff>{@link ve.BIUF.handleBIUF|handleBIUF}</span>(arg0_biuf_el:{@link HTMLElement})
 * - <span color=00ffff>{@link ve.BIUF.initBIUFToolbar|initBIUFToolbar}</span>()
 * - <span color=00ffff>{@link ve.BIUF.sendOnchangeEvent|sendOnchangeEvent}</span>()
 *
 * @augments ve.Component
 * @augments {@link ve.Component}
 * @type {ve.BIUF}
 */
ve.BIUF = class extends ve.Component {
	static demo_value = `<b>Bold</b> <i>Italic</i>, <u>Underline</u>, and regular text formatting are supported by BIUF fields.`;
	static demo_options = {
		onchange: (e) => {
			console.log(`ve.ComponentBIUF:`, e);
		}
	};
	
	constructor (arg0_value, arg1_options) {
		//Convert from parameters
		let value = (arg0_value) ? arg0_value : "";
		let options = (arg1_options) ? arg1_options : {};
			super(options);
		
		//Initialise options
		options.attributes = (options.attributes) ? options.attributes : {};
		
		//Declare local instance variables
		let attributes = {
			readonly: options.disabled,
			...options.attributes
		};
		this.element = document.createElement("div");
			this.element.setAttribute("component", "ve-biuf");
			this.element.instance = this;
		this.options = options;
		HTML.applyTelestyle(this.element, options.style);
		
		this.value = value;
		
		//Format html_string
		let html_string = [];
		
		html_string.push(`<span id = "name"></span>`);
		html_string.push(`<div id = "biuf-toolbar" class = "biuf-toolbar">`);
			html_string.push(`<button id = "bold-button" class = "bold-icon"><icon>format_bold</icon></button>`);
			html_string.push(`<button id = "italic-button" class = "italic-icon"><icon>format_italic</icon></button>`);
			html_string.push(`<button id = "underline-button" class = "underline-icon"><icon>format_underline</icon></button>`);
			html_string.push(`<button id = "clear-button" class = "clear-icon"><icon>format_clear</icon></button>`);
		html_string.push(`</div>`);
		html_string.push(`<div id = "biuf-input" class = "biuf-input" contenteditable = "true" ${HTML.objectToAttributes(attributes)}></div>`);
		
		//Populate element and initialise handlers
		this.element.innerHTML = html_string.join("");
		this.element.querySelector(`#biuf-input`).addEventListener("input", (e) => {
			this.sendOnchangeEvent();
			this.handleBIUF(e.target);
		});
		this.initBIUFToolbar();
		super.name = options.name;
		this.v = this.value;
	}
	
	/**
	 * Returns the current HTML content in the present Component.
	 * - Accessor of {@link ve.ComponentBIUF}
	 * 
	 * @returns {string}
	 */
	get v () {
		//Return statement
		return this.element.querySelector(`#biuf-input`).innerHTML;
	}
	
	/**
	 * Sets the HTML content value for {@link ve.ComponentBIUF}
	 * - Accessor of {@link ve.ComponentBIUF}
	 * 
	 * @param {string} arg0_value
	 */
	set v (arg0_value) {
		//Convert from parameters
		let value = (arg0_value) ? arg0_value : "";
		if (value === undefined) return; //Internal guard clause if value is undefined
		
		//Set #biuf-input value
		this.element.querySelector(`#biuf-input`).innerHTML = value;
	}
	
	//Internal helper functions
	
	/**
	 * Initialises all event handlers for BIUF buttons and keybinds.
	 * - Method of: {@link ve.ComponentBIUF}
	 * 
	 * @param {HTMLElement} arg0_biuf_el - The mounted BIUF element.
	 */
	handleBIUF (arg0_biuf_el) {
		//Convert from parameters
		let biuf_el = arg0_biuf_el;
		
		//Declare local instance variables
		let child_el = biuf_el.firstChild;
		
		//Declare while loop, break when next sibling element can no longer be found.
		while (child_el) {
			let remove_node = null;
			
			//Check if child_el is not of <b><i><u> tags.
			if (child_el.tagName && (!["b", "i", "u"].includes(child_el.tagName.toLowerCase())))
				remove_node = child_el;
			child_el = child_el.nextSibling;
			
			//Remove node if flag is true
			if (remove_node)
				remove_node.parentNode.removeChild(remove_node);
		}
	}
	
	/**
	 * Initialises the present BIUF toolbar.
	 * - Method of: {@link ve.ComponentBIUF}
	 */
	initBIUFToolbar () {
		//Declare local instance variables
		let toolbar_el = this.element.querySelector(`#biuf-toolbar`);
		
		//Declare element references
		let bold_button = toolbar_el.querySelector("#bold-button");
		let clear_button = toolbar_el.querySelector("#clear-button");
		let italic_button = toolbar_el.querySelector("#italic-button");
		let underline_button = toolbar_el.querySelector("#underline-button");
		
		//Show toolbar when text is selected
		toolbar_el.style.display = "none";
		document.addEventListener("mouseup", (e) => {
			let selection = window.getSelection();
			
			if (selection.toString() !== "" && this.element.querySelector(`#biuf-input:focus`)) {
				//var range = selection.getRangeAt(0);
				let rect = toolbar_el.getBoundingClientRect();
				
				toolbar_el.style.display = "table";
				toolbar_el.style.top = `-${toolbar_el.offsetHeight/2.5}px`;
			} else {
				toolbar_el.style.display = "none";
			}
		});
		
		//Apply formatting when various toolbar buttons are clicked
		bold_button.addEventListener("click", () => {
			document.execCommand("bold");
		});
		clear_button.addEventListener("click", () => {
			document.execCommand("removeFormat");
		});
		italic_button.addEventListener("click", () => {
			document.execCommand("italic");
		});
		underline_button.addEventListener("click", () => {
			document.execCommand("underline");
		});
	}
	
	/**
	 * Fires an onuserchange event whilst synchronising the present value.
	 * - Method of: {@link ve.ComponentBIUF}
	 */
	sendOnchangeEvent () {
		this.value = this.v;
		this.fireToBinding();
	}
};

//Functional binding

/**
 * @returns {ve.ComponentBIUF}
 */
veBIUF = function () {
	//Return statement
	return new ve.BIUF(...arguments);
};