recital / packages / ext-common-commands / src / lib / convert-wiki-link-to-md.ts
convert-wiki-link-to-md.ts
Raw
/**
 * Copyright (c) 2022 Amorphous
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

import { FlatObject, FragmentObject, SceneObject } from '@a-morphous/recital/dist/types/types'
import { enforceIds, findObjectViaLink } from '../tools/stage-tools'
import { parseWikiLink } from '../tools/text-tools'

/**
 * Converts all the wiki links in a .stage file into valid Markdown links.
 * Wiki links have the format [[link]], and can also have an arrow to have text separate from the destination, e.g.
 * [[This is the displayed text->destination]]
 * @param stageObjects
 * @returns an array of FlatObjects that has all the text converted over
 */
export const convertWikiLinkToMd = (stageObjects: FlatObject[]): FlatObject[] => {
	const newFlatObjects: FlatObject[] = []
	const stageObjectsWithId = enforceIds(stageObjects)

	for (let obj of stageObjectsWithId) {
		if (obj.type !== 'paragraph') {
			newFlatObjects.push(obj)
			continue
		}

		// now, we check for wikilinks.
		const newText = renderWikilinks(stageObjectsWithId, obj.text)
		newFlatObjects.push({
			...obj,
			text: newText,
		})
	}

	return newFlatObjects
}

// inspired by
// https://github.com/klembot/chapbook/blob/92e6d5c2b0e6c1cec10d4cad226bf207013746ca/src/template/render-links.js#L30
const renderWikilinks = (stageObjects: FlatObject[], source: string): string => {
	return source.replace(/\[\[(.*?)\]\]/g, (_, target) => {
		const parsedLink = parseWikiLink(target)

		return renderLink(stageObjects, parsedLink.target, parsedLink.label || parsedLink.target)
	})
}

/**
 * Renders a markdown-style link from the target (which can resolve to an object) and a label
 * If the target does indeed resolve to an object, the link will point to the object's id
 * @param stageObjects reference list of objects that we can find links to
 * @param target string that can be the url (for an external link), or the title or primary of
 * the target object. Targets prefixed with '#' will only point to primaries.
 * @param label Text that is displayed on the link.
 * @returns
 */
const renderLink = (stageObjects: FlatObject[], target: string, label: string) => {
	if (/^\w+:\/\/\/?\w/i.test(target)) {
		// this is an external link. We convert it as-is to a Markdown link.
		return `[${label}](${target})`
	}

	const linkTarget = findObjectViaLink(stageObjects, target)
	if (!linkTarget) {
		console.warn(
			'link to ' +
				target +
				' did not produce a valid link, and may not resolve correctly in editor.'
		)
		return `[${label}](${target})`
	}

	return `[${label}](${getHrefForObject(linkTarget)})`
}

/**
 * Gets what should go into the link for the object.
 * This keys off of id.
 * @param object
 */
const getHrefForObject = (object: SceneObject | FragmentObject) => {
	if (!object.id) {
		throw new Error('Cannot create a link to an object without an id')
	}

	return `#${object.id}`
}