recital / packages / twine-format / Recital.ts
Recital.ts
Raw
import stringify from '@iarna/toml/stringify'

class Recital {
	protected converted: boolean
	constructor() {
		this.converted = false
	}

	convert() {
		if (this.converted) return

		this.converted = true
		let output = window.document.getElementById('output')
		output.innerHTML = this.export()
	}

	export() {
		var buffer = []

		var userScript = window.document.getElementById('twine-user-script')
		if (userScript && userScript.innerText)
			buffer.push(this.buildPassage('UserScript', 'script', userScript.innerHTML))

		var userStylesheet = window.document.getElementById('twine-user-stylesheet')
		if (userStylesheet && userStylesheet.innerText)
			buffer.push(this.buildPassage('UserStylesheet', 'stylesheet', userStylesheet.innerHTML))

		var passages = window.document.getElementsByTagName('tw-passagedata')
		for (var i = 0; i < passages.length; ++i) buffer.push(this.buildPassageFromElement(passages[i]))

		return buffer.join('')
	}

	/**
	 *
	 * @param {HTMLElement} passage
	 * @returns
	 */
	buildPassageFromElement(passage) {
		var name = passage.getAttribute('name')
		if (!name) name = 'Untitled Passage'

		var tags = passage.getAttribute('tags')
		var pos = passage.getAttribute('position')
		var size = passage.getAttribute('size')
		var meta = pos || size ? { position: pos, size: size } : ''
		var content = passage.textContent

		return this.buildPassage(name, tags, content, meta)
	}

	/**
	 * Creates a passage in Recital format from a parsed passage.
	 * @param {string} title
	 * @param {string[]} tags
	 * @param {string} content
	 * @param {any} meta
	 * @returns {string} - the parsed passage.
	 */
	buildPassage(title, tags, content, meta?) {
		let finalString = `#: ${title}`
		let metaObj: any = meta ?? {}
		if (tags?.length) {
			metaObj.tags = tags
		}

		this.parseDimension(metaObj, 'position')
		this.parseDimension(metaObj, 'size')

		if (Object.keys(metaObj).length) {
			finalString += `
---
${stringify(metaObj).trim()}
---

`
		}

		finalString += `${this.scrub(content)}\n\n`

		return finalString
	}

	scrub(content) {
		if (content) {
			content = content.replace(/^::/gm, ' ::').replace(/\</gm, '&lt;').replace(/\>/gm, '&gt;')
		}
		return content
	}

	/**
	 * Turns a parameter from the type `metaObj[key] = "number, number"` into the type
	 * `metaObj[key] = { x: number, y: number }`
	 * @param metaObj
	 * @param key
	 */
	protected parseDimension(metaObj: any, key: string) {
		if (!metaObj[key]) {
			return metaObj
		}
		let oldValue = metaObj[key]
		if (typeof oldValue !== 'string') {
			return metaObj
		}
		let splits = oldValue.split(',')
		metaObj[key] = {
			x: parseFloat(splits[0]),
			y: parseFloat(splits[1]),
		}

		return metaObj
	}
}

let recital = new Recital()
;(window as any).Recital = recital

window.addEventListener('load', (evt) => {
	recital.convert()
})