Commit f5c93364 authored by marius david's avatar marius david
Browse files

Made links configurable

parent 3a212f12
Loading
Loading
Loading
Loading
+76 −0
Original line number Diff line number Diff line
@@ -363,6 +363,82 @@ window.defaultPeriod = defaultPeriod
const useNumericalId = true
window.useNumericalId = useNumericalId

const externalLinksConfig = [
	{
		name: "Website",
		id: "website",
		generateLink: (link) => link,
		listingClass: "bi-globe",
		generateListingName: (link) => {
			try {
				const urlObject = new URL(link)
				return urlObject.hostname.replace(/^www./, "")
			} catch (e) {
				return "Website"
			}
		},
		displayHTML: "{urlid}",
		placeholder: "https://example.org",
		configureInputField: (inputField) => {
			inputField.type = "url"
			inputField.placeholder = "https://example.com"
			inputField.pattern = "https?://.*"
			inputField.title = "Website URL using the http:// or https:// protocol"
		}
	},
	{
		name: "Discord",
		id: "discord",
		generateLink: (link) => "https://discord.gg/" + link,
		generateListingName: (link) => link,
		listingClass: "bi-discord",
		editorPrefix: "discord.gg/",
		placeholder: "r/example",
		configureInputField: (inputField) => {
			inputField.placeholder = "pJkm23b2nA"
		},
		extractId: (content) => {
			const discordPattern = /^(?:(?:https?:\/\/)?(?:www\.)?(?:(?:discord)?\.?gg|discord(?:app)?\.com\/invite)\/)?([^\s/]+?)(?=\b)$/
			id = content.trim().match(discordPattern)?.[1]
			if (id) {
				return id;
			}
			return content;
		}
	},
	{
		name: "Subreddit",
		id: "subreddit",
		generateLink: (link) => "https://reddit.com/" + link,
		listingClass: "bi-reddit",
		generateListingName: (link) => "r/" + link,
		editorPrefix: "reddit.com/",
		placeholder: "pJkm23b2nA",
		configureInputField: (inputField) => {
			inputField.placeholder = "r/example"
			inputField.pattern = "^r\/[A-Za-z0-9][A-Za-z0-9_]{1,50}$"
			inputField.title = "Subreddit in format of r/example"
			inputField.minLength = "4"
			inputField.maxLength = "50"
		},
		extractId: (content) => {
			const subredditPattern = /^(?:(?:(?:(?:(?:https?:\/\/)?(?:(?:www|old|new|np)\.)?)?reddit\.com)?\/)?[rR]\/)?([A-Za-z0-9][A-Za-z0-9_]{1,20})(?:\/[^" ]*)*$/
			id = content.trim().match(subredditPattern)?.[1]
			if (id) {
				return id;
			}
			return content;
		},
		formatIdInEditor: (content) => {
			if (content != "") {
				return "r/" + content;
			}
			return "";
		}

	}
];

console.info(`%cThe 2023 r/place Atlas
%cCopyright (c) 2017 Roland Rytz <roland@draemm.li>
Copyright (c) 2023 Place Atlas Initiative and contributors
+137 −231
Original line number Diff line number Diff line
@@ -35,24 +35,12 @@ const exportModal = new bootstrap.Modal(exportModalElement)

const nameField = document.getElementById("nameField")
const descriptionField = document.getElementById("descriptionField")
const websiteGroup = document.getElementById("websiteGroup")
const subredditGroup = document.getElementById("subredditGroup")
const discordGroup = document.getElementById("discordGroup")
const wikiGroup = document.getElementById("wikiGroup")
const exportArea = document.getElementById("exportString")

const subredditPattern = /^(?:(?:(?:(?:(?:https?:\/\/)?(?:(?:www|old|new|np)\.)?)?reddit\.com)?\/)?[rR]\/)?([A-Za-z0-9][A-Za-z0-9_]{1,20})(?:\/[^" ]*)*$/
const discordPattern = /^(?:(?:https?:\/\/)?(?:www\.)?(?:(?:discord)?\.?gg|discord(?:app)?\.com\/invite)\/)?([^\s/]+?)(?=\b)$/

let entry
let path = []
let center = [canvasCenter.x, canvasCenter.y]

let websiteGroupElements = []
let subredditGroupElements = []
let discordGroupElements = []
let wikiGroupElements = []

let pathWithPeriods = []
let periodGroupElements = []

@@ -89,6 +77,15 @@ baseInputField.type = "text"
	})
})

let linkEditButtonListener = new WeakMap(); // store the "click" listener of button for later unmapping

let externalLinksConfigIndexById = {};
let linkGroupElements = {};
for (let i in externalLinksConfig) {
	externalLinksConfigIndexById[externalLinksConfig[i].id] = i;
	linkGroupElements[externalLinksConfig[i].id] = []
}

// https://gist.github.com/codeguy/6684588?permalink_comment_id=3243980#gistcomment-3243980
function slugify(text) {
	return text
@@ -310,15 +307,19 @@ function initDraw() {
			exportObject.center[key] = calculateCenter(value).map(int => int - 0.5)
		})

		const inputWebsite = websiteGroupElements.map(element => element.value.trim()).filter(element => element)
		const inputSubreddit = subredditGroupElements.map(element => element.value.trim().match(subredditPattern)?.[1]).filter(element => element)
		const inputDiscord = discordGroupElements.map(element => element.value.trim().match(discordPattern)?.[1]).filter(element => element)
		const inputWiki = wikiGroupElements.map(element => element.value.trim().replace(/ /g, '_')).filter(element => element)
		for (const linkConfig of externalLinksConfig) {
			const groupsElement = linkGroupElements[linkConfig.id];

		if (inputWebsite.length) exportObject.links.website = inputWebsite
		if (inputSubreddit.length) exportObject.links.subreddit = inputSubreddit
		if (inputDiscord.length) exportObject.links.discord = inputDiscord
		if (inputWiki.length) exportObject.links.wiki = inputWiki
			let linksToExport = [];
			const exportArray = groupsElement
				.map(element => element.value.trim())
				.map(element => linkConfig.extractId ? linkConfig.extractId(element) : element)
				.filter(element => element);
			
			if (exportArray.length) {
				exportObject.links[linkConfig.id] = exportArray
			}
		}

		return exportObject
	}
@@ -449,26 +450,14 @@ function initDraw() {
			nameField.value = ""
			descriptionField.value = ""

			// Clears input array
			websiteGroupElements = []
			subredditGroupElements = []
			discordGroupElements = []
			wikiGroupElements = []

			// Rebuilds multi-input list
			websiteGroup.replaceChildren()
			subredditGroup.replaceChildren()
			discordGroup.replaceChildren()
			/**
			 * @instanceonly
			 * Temporarily remove wikifield
			 * Lack of use, used incorrectly more than it is used correctly.
			 */
			// wikiGroup.replaceChildren()
			addWebsiteFields("", 0, [0])
			addSubredditFields("", 0, [0])
			addDiscordFields("", 0, [0])
			addWikiFields("", 0, [0])
			for (const linkConfig of externalLinksConfig) {
				document.getElementById(linkConfig.id + "Group").replaceChildren();
			}

			for (const linkConfig of externalLinksConfig) {
				clearLinkGroup(linkConfig.id);
			}

			// Resets periods
			pathWithPeriods = []
@@ -574,225 +563,143 @@ function initDraw() {
		return atlasAll[id]
	}

	function addFieldButton(inputButton, inputGroup, array, index, name) {
		if (inputButton.title === "Remove " + name) {
			removeFieldButton(inputGroup, array, index)
			return
		}
		inputButton.className = "btn btn-outline-secondary"
		inputButton.title = "Remove " + name
		inputButton.innerHTML = '<i class="bi bi-trash-fill" aria-hidden="true"></i>'
		if (name === "website") {
			addWebsiteFields(null, array.length, array)
		} else if (name === "subreddit") {
			addSubredditFields(null, array.length, array)
		} else if (name === "Discord invite") {
			addDiscordFields(null, array.length, array)
		} else if (name === "wiki page") {
			addWikiFields(null, array.length, array)
		}
	function getLinkGroup(linkTypeId) {
		return document.getElementById(linkTypeId + "Group");
	}

	function removeFieldButton(inputGroup, array, index) {
		delete array[index]
		inputGroup.remove()
	function getLinkConfig(linkTypeId) {
		return externalLinksConfig[externalLinksConfigIndexById[linkTypeId]]
	}

	function addWebsiteFields(link, index, array) {
		const inputGroup = baseInputGroup.cloneNode()
		websiteGroup.appendChild(inputGroup)

		const inputField = baseInputField.cloneNode()
		inputField.type = "url"
		inputField.id = "websiteField" + index
		inputField.placeholder = "https://example.com"
		inputField.pattern = "https?://.*"
		inputField.title = "Website URL using the http:// or https:// protocol"
		inputField.setAttribute("aria-labelledby", "websiteLabel")
		inputField.value = link
		inputGroup.appendChild(inputField)
		websiteGroupElements.push(inputField)

		const inputButton = document.createElement("button")
		inputButton.type = "button"
		// If button is the last in the array give it the add button
		if (array.length === index + 1) {
			inputButton.className = "btn btn-secondary"
			inputButton.title = "Add website"
			inputButton.innerHTML = '<i class="bi bi-plus-lg" aria-hidden="true"></i>'
			inputButton.addEventListener('click', () => addFieldButton(inputButton, inputGroup, websiteGroupElements, index, "website"))
		} else {
			inputButton.className = "btn btn-outline-secondary"
			inputButton.title = "Remove website"
			inputButton.innerHTML = '<i class="bi bi-trash-fill" aria-hidden="true"></i>'
			inputButton.addEventListener('click', () => removeFieldButton(inputGroup, websiteGroupElements, index))
		}
		inputGroup.appendChild(inputButton)
	/**
	 * Clear the link group of the provided tag in the editor, ensuring there only remain a single empty field
	 * @param {String} linkTypeId 
	 */
	function clearLinkGroup(linkTypeId) {
		getLinkGroup(linkTypeId).replaceChildren()
		linkGroupElements[linkTypeId] = []
		addFieldGeneric("", linkTypeId);
	}

	function addSubredditFields(link, index, array) {
		const inputGroup = baseInputGroup.cloneNode()
		subredditGroup.appendChild(inputGroup)
	function addFieldGeneric(link, linkTypeId) {
		const inputGroup = baseInputGroup.cloneNode();
		const linkConfig = getLinkConfig(linkTypeId)
		let linkGroup = getLinkGroup(linkTypeId)
		linkGroup.appendChild(inputGroup);

		let addonId = null;
		if (linkConfig.editorPrefix) {
			const inputAddon = baseInputAddon.cloneNode()
		inputAddon.id = "subredditField" + index + "-addon"
		inputAddon.textContent = "reddit.com/"
			inputAddon.textContent = linkConfig.editorPrefix
			addonId = linkTypeId + "Field" + linkGroupElements.length + "-addon"
			inputAddon.id = addonId
			inputGroup.appendChild(inputAddon)
		}

		const inputField = baseInputField.cloneNode()
		inputField.id = "subredditField" + index
		inputField.placeholder = "r/example"
		inputField.pattern = "^r\/[A-Za-z0-9][A-Za-z0-9_]{1,50}$"
		inputField.title = "Subreddit in format of r/example"
		inputField.minLength = "4"
		inputField.maxLength = "50"
		inputField.setAttribute("aria-labelledby", "subredditLabel")
		inputField.setAttribute("aria-describedby", "subredditField" + index + "-addon")
		if (link) {
			inputField.value = "r/" + link
		const inputField = baseInputField.cloneNode();
		inputField.id = linkTypeId + "Field" + linkGroupElements.length
		if (linkConfig.formatIdInEditor) {
			inputField.value = linkConfig.formatIdInEditor(link)
		} else {
			inputField.value = ""
			inputField.value = link
		}
		inputGroup.appendChild(inputField)
		subredditGroupElements.push(inputField)

		const inputButton = document.createElement("button")
		inputButton.type = "button"
		// If button is the last in the array give it the add button
		if (array.length === index + 1) {
			inputButton.className = "btn btn-secondary"
			inputButton.title = "Add subreddit"
			inputButton.innerHTML = '<i class="bi bi-plus-lg" aria-hidden="true"></i>'
			inputButton.addEventListener('click', () => addFieldButton(inputButton, inputGroup, subredditGroupElements, index, "subreddit"))
		} else {
			inputButton.className = "btn btn-outline-secondary"
			inputButton.title = "Remove subreddit"
			inputButton.innerHTML = '<i class="bi bi-trash-fill" aria-hidden="true"></i>'
			inputButton.addEventListener('click', () => removeFieldButton(inputGroup, subredditGroupElements, index))
		inputField.setAttribute("aria-labelledby", linkTypeId + "Label")
		if (addonId) {
			inputField.setAttribute("aria-describedby", addonId)
		}

		if (linkConfig.extractId) {
			inputField.addEventListener('paste', event => {
				let paste = (event.clipboardData || window.clipboardData).getData('text')
			paste = paste.trim().match(subredditPattern)?.[1]
			if (paste) {
				event.target.value = "r/" + paste
				let valueId = linkConfig.extractId(paste);
				if (valueId) {
					if (linkConfig.formatIdInEditor) {
						event.target.value = linkConfig.formatIdInEditor(valueId)
					} else {
						event.target.value = valueId
					}
					event.preventDefault()
				}
			})

		inputGroup.appendChild(inputButton)
		}

	function addDiscordFields(link, index, array) {
		const inputGroup = baseInputGroup.cloneNode()
		discordGroup.appendChild(inputGroup)

		const inputAddon = baseInputAddon.cloneNode()
		inputAddon.id = "discordField" + index + "-addon"
		inputAddon.textContent = "discord.gg/"
		inputGroup.appendChild(inputAddon)

		const inputField = baseInputField.cloneNode()
		inputField.id = "discordField" + index
		inputField.placeholder = "pJkm23b2nA"
		inputField.setAttribute("aria-labelledby", "discordLabel")
		inputField.setAttribute("aria-describedby", "discordField" + index + "-addon")
		inputField.value = link
		inputGroup.appendChild(inputField)
		discordGroupElements.push(inputField)
		linkConfig.configureInputField(inputField)
		inputGroup.appendChild(inputField);
		linkGroupElements[linkTypeId].push(inputField);

		const inputButton = document.createElement("button")
		inputButton.type = "button"
		// If button is the last in the array give it the add button
		if (array.length === index + 1) {
			inputButton.className = "btn btn-secondary"
			inputButton.title = "Add Discord invite"
			inputButton.innerHTML = '<i class="bi bi-plus-lg" aria-hidden="true"></i>'
			inputButton.addEventListener('click', () => addFieldButton(inputButton, inputGroup, discordGroupElements, index, "Discord invite"))
		} else {
			inputButton.className = "btn btn-outline-secondary"
			inputButton.title = "Remove Discord invite"
			inputButton.innerHTML = '<i class="bi bi-trash-fill" aria-hidden="true"></i>'
			inputButton.addEventListener('click', () => removeFieldButton(inputGroup, discordGroupElements, index))
		}
		inputGroup.appendChild(inputButton)

		inputField.addEventListener('paste', event => {
			let paste = (event.clipboardData || window.clipboardData).getData('text')
			paste = paste.trim().match(discordPattern)?.[1]
			if (paste) {
				event.target.value = paste
				event.preventDefault()
		refreshLinkGroupButtons(linkTypeId)
	}
		})

		inputGroup.appendChild(inputButton)
	function refreshLinkGroupButtons(linkTypeId) {
		const linkGroup = getLinkGroup(linkTypeId);
		const linkConfig = getLinkConfig(linkTypeId)
		const children = linkGroup.children;
		for (let i = 0; i < children.length; i++) {
			const button = children[i].getElementsByTagName("button")[0];
			if (linkEditButtonListener.get(button) != undefined) {
				button.removeEventListener('click', linkEditButtonListener.get(button))
			}
			let listener;
			if (children.length == i + 1) {
				button.className = "btn btn-secondary";
				button.title = "Add " + linkConfig.name
				button.innerHTML = '<i class="bi bi-plus-lg" aria-hidden="true"></i>'

	function addWikiFields(link, index, array) {
		const inputGroup = baseInputGroup.cloneNode()
		// wikiGroup.appendChild(inputGroup)

		const inputField = baseInputField.cloneNode()
		inputField.id = "wikiField" + index
		inputField.placeholder = "Page title"
		inputField.setAttribute("aria-labelledby", "wikiLabel")
		inputField.value = link
		inputGroup.appendChild(inputField)
		wikiGroupElements.push(inputField)

		const inputButton = document.createElement("button")
		inputButton.type = "button"
		// If button is the last in the array give it the add button
		if (array.length === index + 1) {
			inputButton.className = "btn btn-secondary"
			inputButton.title = "Add wiki page"
			inputButton.innerHTML = '<i class="bi bi-plus-lg" aria-hidden="true"></i>'
			inputButton.addEventListener('click', () => addFieldButton(inputButton, inputGroup, wikiGroupElements, index, "wiki page"))
				listener = () => addFieldGeneric("", linkTypeId);
			} else {
			inputButton.className = "btn btn-outline-secondary"
			inputButton.title = "Remove wiki page"
			inputButton.innerHTML = '<i class="bi bi-trash-fill" aria-hidden="true"></i>'
			inputButton.addEventListener('click', () => removeFieldButton(inputGroup, wikiGroupElements, index))
				button.className = "btn btn-outline-secondary"
				button.title = "Remove " + linkConfig.name
				button.innerHTML = '<i class="bi bi-trash-fill" aria-hidden="true"></i>'
				
				const childToRemove = children[i];
				listener = () => {
					linkGroupElements[linkTypeId].splice(i, 1);
					linkGroup.removeChild(childToRemove);
					refreshLinkGroupButtons(linkTypeId);
				}
			}
			linkEditButtonListener.set(button, listener)
			button.addEventListener('click', listener)
		}
		inputGroup.appendChild(inputButton)
	}

	const params = new URLSearchParams(document.location.search)
	const entryId = params.get('id')
	entry = getEntry(entryId)

	const linkContainer = document.getElementById("linkContainer");
	linkContainer.replaceChildren();
	for (const linkConfig of externalLinksConfig) {
		linkLabel = document.createElement("label");
		linkLabel.id = linkConfig.id + "Label";
		linkLabel.className = "form-label";
		linkLabel.textContent = linkConfig.name;
		linkContainer.appendChild(linkLabel);

		linkGroup = document.createElement("div");
		linkGroup.id = linkConfig.id + "Group";
		linkGroup.className = "mb-3 d-flex flex-column gap-2";
		linkContainer.appendChild(linkGroup);
	}

	if (entry) {

		nameField.value = entry.name
		descriptionField.value = entry.description

		if (entry.links.website.length) {
			entry.links.website.forEach((link, index, array) => {
				addWebsiteFields(link, index, array)
			})
		} else {
			addWebsiteFields("", -1, entry.links.website)
		}
		if (entry.links.subreddit.length) {
			entry.links.subreddit.forEach((link, index, array) => {
				addSubredditFields(link, index, array)
			})
		} else {
			addSubredditFields("", -1, entry.links.subreddit)
		for (const linkConfig of externalLinksConfig) {
			if (entry.links[linkConfig.id].length) {
				for (const link of entry.links[linkConfig.id]) {
					addFieldGeneric(link, linkConfig.id)
				}
		if (entry.links.discord.length) {
			entry.links.discord.forEach((link, index, array) => {
				addDiscordFields(link, index, array)
			})
			} else {
			addDiscordFields("", -1, entry.links.discord)
				clearLinkGroup(linkConfig.id)
			}
		if (entry.links.wiki.length) {
			entry.links.wiki.forEach((link, index, array) => {
				addWikiFields(link, index, array)
			})
		} else {
			addWikiFields("", -1, entry.links.wiki)
		}
		redoButton.disabled = true
		undoButton.disabled = false
@@ -808,10 +715,9 @@ function initDraw() {
		pathWithPeriods.push([formatPeriod(currentPeriod, null, currentVariation), []])

		// Builds multi-input list
		addWebsiteFields("", 0, [0])
		addSubredditFields("", 0, [0])
		addDiscordFields("", 0, [0])
		addWikiFields("", 0, [0])
		for (const linkConfig of externalLinksConfig) {
			clearLinkGroup(linkConfig.id);
		}
	}

	initPeriodGroups()
+21 −55
Original line number Diff line number Diff line
@@ -115,62 +115,28 @@ function createInfoBlock(entry, mode = 0) {
		}
	}

	if (entry.links?.subreddit?.length) {
		const subredditGroupElement = baseGroupElement.cloneNode()
		linkListElement.appendChild(subredditGroupElement)

		entry.links.subreddit.forEach(subreddit => {
			if (!subreddit) return
			subreddit = "r/" + subreddit
			const subredditLinkElement = baseLinkElement.cloneNode()
			subredditLinkElement.href = "https://reddit.com/" + subreddit
			subredditLinkElement.innerHTML = `<i class="bi bi-reddit" aria-hidden="true"></i> ${subreddit}`
			subredditGroupElement.appendChild(subredditLinkElement)
		})
	}

	if (entry.links?.website?.length) {
		const websiteGroupElement = baseGroupElement.cloneNode()
		linkListElement.appendChild(websiteGroupElement)
	for (const linkConfig of externalLinksConfig) {
		if (entry.links) {
			if (entry.links[linkConfig.id]?.length) {
				const groupElement = baseGroupElement.cloneNode()
				linkListElement.appendChild(groupElement)

		entry.links.website.forEach(link => {
				entry.links[linkConfig.id].forEach(link => {
					if (!link) return
			const websiteLinkElement = baseLinkElement.cloneNode()
			websiteLinkElement.href = link
			try {
				const urlObject = new URL(link)
				websiteLinkElement.innerHTML = `<i class="bi bi-globe" aria-hidden="true"></i> ${urlObject.hostname.replace(/^www./, "")}`
			} catch (e) {
				websiteLinkElement.innerHTML = `<i class="bi bi-globe" aria-hidden="true"></i> Website`
			}
			websiteGroupElement.appendChild(websiteLinkElement)
		})
	}
					const linkElement = baseLinkElement.cloneNode()
					linkElement.href = linkConfig.generateLink(link)

	if (entry.links?.discord?.length) {
		const discordGroupElement = baseGroupElement.cloneNode()
		linkListElement.appendChild(discordGroupElement)
					let logoChild = document.createElement("i");
					logoChild.classList.add("bi");
					logoChild.classList.add(linkConfig.listingClass);
					logoChild.setAttribute("aria-hidden", "true");
					linkElement.appendChild(logoChild);

		entry.links.discord.forEach(link => {
			if (!link) return
			const discordLinkElement = baseLinkElement.cloneNode()
			discordLinkElement.href = "https://discord.gg/" + link
			discordLinkElement.innerHTML = `<i class="bi bi-discord" aria-hidden="true"></i> ${link}`
			discordGroupElement.appendChild(discordLinkElement)
					linkElement.append(" " + linkConfig.generateListingName(link))
					groupElement.appendChild(linkElement)
				})
			}

	if (entry.links?.wiki?.length) {
		const wikiGroupElement = baseGroupElement.cloneNode()
		linkListElement.appendChild(wikiGroupElement)

		entry.links.wiki.forEach(link => {
			if (!link) return
			const wikiLinkElement = baseLinkElement.cloneNode()
			wikiLinkElement.href = "https://place-wiki.stefanocoding.me/wiki/" + link.replace(/ /g, '_')
			wikiLinkElement.innerHTML = `<i class="bi bi-book" aria-hidden="true"></i> r/place Wiki Article`
			wikiGroupElement.appendChild(wikiLinkElement)
		})
		}
	}

	// Adds id footer
+0 −4
Original line number Diff line number Diff line
@@ -509,10 +509,6 @@ function generateAtlasAll(atlas = atlasAll) {
		entry._index = index
		const currentLinks = entry.links
		entry.links = {
			website: [],
			subreddit: [],
			discord: [],
			wiki: [],
			...currentLinks
		}
		const currentPath = entry.path
+22 −16
Original line number Diff line number Diff line
[    {
	"id": -1,
	"name": "i",
	"description": "test",
	"links": {},
	"name": "a",
	"description": "b",
	"links": {
		"website": [
			"https://example.org"
		],
		"discord": [
			"123214"
		],
		"subreddit": [
			"example"
		]
	},
	"path": {
		"0": [
			[
				406,
				263
			],
			[
				501,
				288
				331,
				298
			],
			[
				383,
				326
				382,
				413
			],
			[
				356,
				243
				518,
				247
			]
		]
	},
	"center": {
		"0": [
			401,
			291
			394,
			328
		]
	}
}]
 No newline at end of file