bbcode.js

/** @module bbcode */

import * as Parser from "./parser.js"
import * as Util from "./util.js"

/**
 * Converts an array of keys into a tag object
 * @param   {Object[]} keys
 * @param   {string}   keys.name
 * @param   {string}   keys.value
 * @returns {object}
 */
function keysToTags(keys) {
	const tags = {opening: "", closing: ""}
	tags.opening += "["
	keys.forEach((key, index) => {
		if (index >= 1) {
			tags.opening += " "
		}
		tags.opening += key.name
		if (key.value !== null) {
			if (key.value.includes(" ") === true) {
				tags.opening += "=" + "\"" + key.value + "\""
			} else {
				tags.opening += "=" + key.value
			}
		}
	})
	tags.opening += "]"
	tags.closing = "[/" + keys[0].name + "]"
	return tags
}

/**
 * Converts HTML to BBCode
 * @param   {string} input         HTML string to be converted into BBCode
 * @param   {string} templateName  The name of the BBCode template
 * @returns {string}               The BBCode output
 */
function bbcode(input, template) {
	const element = document.createElement("div")
	element.innerHTML = input

	const treeWalker = document.createTreeWalker(element)
	const nodeList = []
	while (treeWalker.nextNode()) {
		nodeList.push(treeWalker.currentNode)
	}

	const ignoreList = []
	const items = []
	let output = ""

	nodeList.forEach(node => {
		if (ignoreList.indexOf(node) !== -1) {
			return
		}
		let tags
		let nodeOutput = ""
		if (node.nodeType === Node.ELEMENT_NODE) {
			let bbcode = template.keys.find(key => key.testNode(node) === true)
			if (typeof bbcode === "object") {
				if (typeof bbcode.updateIgnoreList === "function") {
					bbcode.updateIgnoreList(ignoreList, node)
				}
				if (typeof bbcode.tags === "object") {
					tags = bbcode.tags
				}
				else {
					tags = keysToTags(bbcode.keys(node))
				}
				if (typeof bbcode.getStr === "function") {
					nodeOutput = bbcode.getStr(bbcode.keys(node), node)
				}
				else {
					nodeOutput = tags.opening + tags.closing
				}
			} else {
				nodeOutput = node.outerHTML
				let treeWalker2 = document.createTreeWalker(node)
				while (treeWalker2.nextNode())
					ignoreList.push(treeWalker2.currentNode)
			}
		} else if (node.nodeType === Node.TEXT_NODE) {
				nodeOutput = node.textContent
		}

		const parent = items.find(item => item.node === node.parentNode)
		const item = { node }

		if (typeof parent === "undefined") {
			output += nodeOutput
			if (typeof tags === "object") {
				item.childIndex = output.length - tags.closing.length
			}
		} else {
			output = Util.insertText(output, nodeOutput, parent.childIndex)
			const parents = []
			let currentNode = node.parentNode
			while (currentNode != null) {
				parents.push(currentNode)
				currentNode = currentNode.parentNode
			}
			parents.forEach(parentItem => {
				const item2 = items.find(listItem => listItem.node === parentItem)
				if (typeof item2 !== "undefined") {
					item2.childIndex += nodeOutput.length
				}
			})
			if (typeof tags === "object") {
				item.childIndex = parent.childIndex - tags.closing.length
			}
		}
		items.push(item)
	})
	return output
}

/**
 * Converts BBCode to HTML
 * Parse BBCode input into abstract tree made of tokens and turn them into nodes
 * @param   {string} input         BBCode string to be converted into HTML
 * @param   {Object} templateName  The name of the BBCode template
 * @returns {string}               The HTML output
 */
function html(input, template) {
	const elements = Parser.parse(input)
	const element = document.createElement("div")
	const items = []
	elements.forEach(token => {
		let node
		if (token.name === "bbcode") {
			const bbcode = template.keys.find(key => key.name === token.openingTag.keys[0].name)
			if (typeof bbcode !== "undefined") {
				node = bbcode.node(token.openingTag.keys, token)
			}
			else {
				node = document.createTextNode(token.openingTag.buffer + token.closingTag.buffer)
			}
		} else {
			node = document.createTextNode(token.buffer)
		}
		const parents = items.filter(parent => token.parents.includes(parent.bbcode))
		if (parents.length >= 1) {
			template.handleChildNode(node, token, items, parents)
		}
		else {
			element.appendChild(node)
		}
		items.push({bbcode: token, node})
	})
	return element.innerHTML
}

export {
	keysToTags,
	bbcode,
	html
}