User:Lectrician1/embeds.js

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
/* 
	This script shows embeds on external ID statements
	
	The supported properties and their embeds can be seen in the embeds JS object below.
	
	To use it, add the following line to your common.js:
	mw.loader.load("//www.wikidata.org/w/index.php?title=User:Lectrician1/embeds.js&action=raw&ctype=text/javascript");
	
	License: CC0
	
	---
	
	Feel free to contribute embeds not supported yet. 
	Doing so does not require coding knowledge and is easy.
	
	Just copy the template below, replace the <values>, and request it on the talk page.
	
	<service_name>: {
		<embed_name>: {
			property: "<Wikidata property whose value the embed uses>",
			html: `<html of the embed. use $1 to indicate where the external ID should be inserted>`
		},
	},
*/

(function () {
	"use strict";
	"esversion: 9";

	// EMBEDS
	const embeds = {
		spotify: {
			album: {
				property: "P2205",
				html: `<iframe src="https://open.spotify.com/embed/album/$1" width="300" height="380" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe>`
			},
			track: {
				property: "P2207",
				html: `<iframe src="https://open.spotify.com/embed/track/$1" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe>`
			},
			artist: {
				property: "P1902",
				html: `<iframe src="https://open.spotify.com/embed/artist/$1" width="300" height="380" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe>`
			},
			playlist: {
				property: "P8704",
				html: `<iframe src="https://open.spotify.com/embed/playlist/$1" width="300" height="380" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe>`
			},
			show: {
				id: "P5916",
				html: `<iframe src="https://open.spotify.com/embed-podcast/show/$1" width="100%" height="232" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe>`
			},
			episode: {
				property: "P9882",
				html: `<iframe src="https://open.spotify.com/embed-podcast/episode/$1" width="100%" height="232" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe>`
			},
		},
		apple: {
			// Apple Music artist, label, music video, have no embeds
			
			album: {
				property: "P2281",
				html: `<iframe allow="autoplay *; encrypted-media *; fullscreen *" frameborder="0" height="450" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/us/album/$1"></iframe>`
			},
			track: {
				property: "P10110",
				html: `<iframe allow="autoplay *; encrypted-media *; fullscreen *" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/us/album/$1"></iframe>`
			},
			
			// Apple TV show, episode, movie, have no embeds
			
			// Apple Books has no embeds
			
			// iTunes has no embeds
			
			podcast: {
				property: "P5842",
				html: `<iframe allow="autoplay *; encrypted-media *; fullscreen *" frameborder="0" height="450" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.podcasts.apple.com/us/podcast/$1"></iframe>`
			}
			
			// podcast episode to be added after property proposed
			
		},
		youtube: {
			video: {
				property: "P1651",
				html: `<div style="padding-bottom:56.25%;position:relative;"><iframe style="position:absolute;top:0;left:0;width:100%;height:100%;" src="https://www.youtube.com/embed/$1" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>`
			},
			playlist: {
				property: "P4300",
				html: `<div style="padding-bottom:56.25%;position:relative;"><iframe style="position:absolute;top:0;left:0;width:100%;height:100%;" src="https://www.youtube.com/embed/videoseries?list=$1" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>`
			}
		},
		deezer: {
			track: {
				property: "P2724",
				html: `<iframe title="deezer-widget" src="https://widget.deezer.com/widget/auto/track/$1" width="100%" height="300" frameborder="0" allowtransparency="true" allow="encrypted-media; clipboard-write"></iframe>`
			},
			artist: {
				property: "P2722",
				html: `<iframe title="deezer-widget" src="https://widget.deezer.com/widget/auto/artist/$1/top_tracks" width="100%" height="300" frameborder="0" allowtransparency="true" allow="encrypted-media; clipboard-write"></iframe>`
			},
			album: {
				property: "P2723",
				html: `<iframe title="deezer-widget" src="https://widget.deezer.com/widget/auto/album/$1" width="100%" height="300" frameborder="0" allowtransparency="true" allow="encrypted-media; clipboard-write"></iframe>`
			},
			show: {
				property: "P5988",
				html: `<iframe title="deezer-widget" src="https://widget.deezer.com/widget/auto/show/$1" width="100%" height="300" frameborder="0" allowtransparency="true" allow="encrypted-media; clipboard-write"></iframe>`
			},
		},
		genius: {
			song: {
				property: "P6361",
				html: `<script>
                        $("#embed-" + $.escapeSelector("$2"))
                            .append($("<iframe />")
                                .attr({ 
                                    "src": URL.createObjectURL(new Blob(["<!DOCTYPE html><html><head><style>body{font-family:sans-serif;margin:0;}.rg_embed,body{margin:0 !important;}</style></head><body><div id='rg_embed_link_$1' class='rg_embed_link' data-song-id='$1'></div><script src='https://genius.com/songs/$1/embed.js'><\\/script></body></html>"], {type: "text/html"})),
                                    "frameborder": "0",
                                    "width": "100%",
                                    "height": "400"
                                })
                            );
                    </script>`
			}
		},
		soundcloud: {
			id: {
				property: "P3040",
				html: `<iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url=https%3A//soundcloud.com/$1&show_teaser=true"></iframe>`
			}
		},
		tidal: {
			album: {
				property: "P4577",
				html: `<iframe src="https://embed.tidal.com/albums/$1" width="100%" height="300px" frameborder="no" allowfullscreen allow="encrypted-media;"></iframe>`
			}
		},
		twitter: {
			username: {
				property: "P2002",
				html: `<a class="twitter-timeline" data-height="500" data-dnt="true" href="https://twitter.com/$1">Tweets by $1</a> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>`
			},
			tweet: {
				property: "P5933",
				html: `<blockquote class="twitter-tweet"><a href="https://twitter.com/_/status/$1"></a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>`
			}
		},
		facebook: {
			page: {
				property: "P2013",
				html: `<iframe src="https://www.facebook.com/plugins/page.php?href=https%3A%2F%2Fwww.facebook.com%2F$1&tabs=timeline%2Cevents&width=340&height=500&small_header=true&adapt_container_width=true&hide_cover=true&show_facepile=false&appId" width="340" height="500" style="border:none;overflow:hidden" scrolling="no" frameborder="0" allowfullscreen="true" allow="autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share" loading="lazy"></iframe>`
			}
		},
		vimeo: {
			video: {
				property: "P4015",
				html: `<div style="padding:56.25% 0 0 0;position:relative;"><iframe src="https://player.vimeo.com/video/$1" style="position:absolute;top:0;left:0;width:100%;height:100%;" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe></div><script src="https://player.vimeo.com/api/player.js"></script>

					<script type="text/javascript">
						if (isNaN("$1")) {
					  		$("#embed-" + $.escapeSelector("$2")).hide();
						}
					</script>`
			}
		},
		steam: {
		    application: {
		        property: "P1733",
		        html: `<iframe src="https://store.steampowered.com/widget/$1" frameborder="0" width="100%" height="190"></iframe>`
		    },
		},
		kickstarter: {
			project: {
				property: "P8019",
				html: `<iframe src="https://www.kickstarter.com/projects/$1/widget/card.html?v=2" width="220" height="420" frameborder="0"></iframe>`
			}
			
			// No embed for kickstarter usernames (P9271)
		},
		pinterest: {
			profile: {
				property: "P3836",
				html: `<a data-pin-do="embedUser" data-pin-board-width="400" data-pin-scale-height="240" data-pin-scale-width="80" href="https://www.pinterest.com/$1"></a><script async defer src="//assets.pinterest.com/js/pinit.js"></script>`
			}
		},
		internet_archive: {
			embed: {
				property: "P724",
				html: `<iframe src="https://archive.org/embed/$1&playlist=1&list_height=400" width="100%" height="400" frameborder="0" webkitallowfullscreen="true" mozallowfullscreen="true" allowfullscreen></iframe>`
			}
		},
		mixcloud: {
			id: {
				property: "P9509",
				html: `<iframe width="200" height="250" src="https://www.mixcloud.com/widget/follow/?u=%2F$1%2F" frameborder="0" ></iframe>`
			}
		},
		amazon_music: {
			artist: {
				property: "P6276",
				html: `<iframe id='AmazonMusicEmbed$1' src='https://music.amazon.com/embed/$1' width='100%' height='300px' style='border:1px solid rgba(0, 0, 0, 0.12);max-width:500px'></iframe>`
			},
			/*
			album: {
				property: "",
				html: `<iframe id='AmazonMusicEmbed$1' src='https://music.amazon.com/embed/$1' width='100%' height='380px' style='border:1px solid rgba(0, 0, 0, 0.12);max-width:300px'></iframe>`
			},
			*/
		},
		iHeart: {
			artist: {
				property: "P7317",
				html: `<iframe allow="autoplay" width="100%" height="300" src="https://www.iheart.com/artist/$1/?embed=true" frameborder="0"></iframe>`
			},
		},
		tiktok: {
			user: {
				property: "P7085",
				html: `<blockquote class="tiktok-embed" cite="https://www.tiktok.com/@$1" data-unique-id="$1"  data-embed-type="creator" style="max-width: 720px; min-width: 288px;" > <section> <a target="_blank" href="https://www.tiktok.com/@$1">@$1</a> </section> </blockquote> <script async src="https://www.tiktok.com/embed.js"></script>`
			}
			
			// Posts have no property yet
		},
		national_film_board_of_canada: {
			film: {
				property: "P4606",
				html: `<iframe src="https://www.nfb.ca/film/$1/embed/player/" width="560" height="315" frameborder="0" allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true"></iframe>`
			},
		},
		
		/* 
			Services that can't be supported yet:
			
			Instagram: Requires API request
			Twitch: it's being dumb (X-Frame-Options are set to block embeds)
			Indiegogo: also X-Frame-Options issues
			Tumblr: We don't have a property for posts yet. I can make insert a preview of the site, but it's horizontal width will be larger than that of a typical statement so you'll have to horizontally scroll. I don't think it's worth it.
			LinkedIn: Doesn't have embeds
			Streamable: No property yet
			
		*/
	};
	
	function addEmbed(claimID, embedID, html) {
		// Escape to prevent XSS vulnerability
		claimID = mw.html.escape(claimID);
		embedID = mw.html.escape(embedID);

		// Append the embed's html to the end of the MainSnak. Qualifiers are after the MainSnak so this is added before them. 
		$("#" + $.escapeSelector(claimID) + " .wikibase-edittoolbar-container")
			.after( $("<div />")
				.attr({
					"id": "embed-" + claimID,
					"style": "padding-left: 26px; padding-top: 15px; max-width: 25em"
				})
					.html(html.replaceAll("$1", embedID).replaceAll("$2", claimID))
			);
	}

	// When the page has rendered
	mw.hook("wikibase.entityPage.entityView.rendered").add(function () {
		
		// When the entity has rendered
		mw.hook("wikibase.entityPage.entityLoaded").add(function (entity) {
			
			// Loop through the embeds object 
			for (const service in embeds) {
				for (const embed in embeds[service]) {
					
					// Check if any of the properties that should have embeds are present on the items
					if (entity.claims.hasOwnProperty(embeds[service][embed].property)) {
						
						// For every value of the property
						$.each(entity.claims[embeds[service][embed].property], function (index, claim) {
							
							// Add the embed
							addEmbed(claim.id, claim.mainsnak.datavalue.value, embeds[service][embed].html);
							
						});
						
					}
					
				}
			}
			
		});
		
		// When a statement is saved (after adding or editing a statement)
		mw.hook("wikibase.statement.saved").add(function (entityID, claimID, oldClaim, newClaim) {
			
			// Loop through the embeds object
			for (const service in embeds) {
				for (const embed in embeds[service]) {
					
					// Check if the property of the changed statement equals an embed property
					if (newClaim.getClaim().getMainSnak().getPropertyId() == embeds[service][embed].property) {
						
						var embedNode = $(`#embed-${$.escapeSelector(claimID)}`);
						var claimValue = newClaim.getClaim().getMainSnak().getValue().getValue();
						var embedHTML = embeds[service][embed].html;
						
						// If there already was an embed on the claim
						if (embedNode.length) {
							embedNode.remove();
							addEmbed(claimID, claimValue, embedHTML);
						}
						else {
							addEmbed(claimID, claimValue, embedHTML);
						}
						
					}
					
				}
			}
		});
		
	});
})();