diff --git a/config/plugins/webhooks/news/config.yml b/config/plugins/webhooks/news/config.yml
new file mode 100644
index 00000000000..9c35b671c8c
--- /dev/null
+++ b/config/plugins/webhooks/news/config.yml
@@ -0,0 +1,7 @@
+id: news
+type:
+ - masthead
+activate: true
+
+icon: fa-bell
+tooltip: See the Galaxy Release Notes
diff --git a/config/plugins/webhooks/news/script.js b/config/plugins/webhooks/news/script.js
new file mode 100644
index 00000000000..0931a9f5c49
--- /dev/null
+++ b/config/plugins/webhooks/news/script.js
@@ -0,0 +1,103 @@
+function removeNewsOverlay() {
+ document.getElementById("news-container").style.visibility = "hidden";
+}
+
+function showNewsOverlay() {
+ document.getElementById("news-container").style.visibility = "visible";
+ newsSeen();
+}
+
+function newsSeen() {
+ var el = document.getElementById("news-unseen-pip");
+ el.parentNode.removeChild(el);
+ window.localStorage.setItem(
+ "galaxy-news-seen-release",
+ Galaxy.config.version_major
+ );
+}
+
+function newsUnseen() {
+ let econtainer = document.getElementById("news").children[0];
+ econtainer.insertAdjacentHTML(
+ "beforeend",
+ `
+
+ `
+ );
+}
+
+function addNewsIframe() {
+ let currentGalaxyVersion = Galaxy.config.version_major;
+ let releaseNotes = `https://docs.galaxyproject.org/en/master/releases/${currentGalaxyVersion}_announce_user.html`;
+ let lastSeenVersion = window.localStorage.getItem("galaxy-news-seen-release");
+ // Check that they've seen the current version's release notes.
+ if (lastSeenVersion != currentGalaxyVersion) {
+ newsUnseen();
+ }
+
+ document.querySelector("body.full-content").insertAdjacentHTML(
+ "afterbegin",
+ `
+
`
+ );
+
+ // Clicking outside of GTN closes it
+ document.getElementById("news-screen").addEventListener("click", () => {
+ removeNewsOverlay();
+ });
+}
+
+/* The masthead icon may not exist yet when this webhook executes; we need this to wait for that to happen.
+ * elementReady function from gist:
+ * https://gist.github.com/jwilson8767/db379026efcbd932f64382db4b02853e
+ */
+function elementReadyNews(selector) {
+ return new Promise((resolve, reject) => {
+ let el = document.querySelector(selector);
+ if (el) {
+ resolve(el);
+ }
+ new MutationObserver((mutationRecords, observer) => {
+ // Query for elements matching the specified selector
+ Array.from(document.querySelectorAll(selector)).forEach(element => {
+ resolve(element);
+ //Once we have resolved we don't need the observer anymore.
+ observer.disconnect();
+ });
+ }).observe(document.documentElement, {
+ childList: true,
+ subtree: true
+ });
+ });
+}
+
+elementReadyNews("#news a").then(el => {
+ // External stuff may also have attached a click handler here (vue-based masthead)
+ // replace with a clean copy of the node to remove all that cruft.
+ clean = el.cloneNode(true);
+ el.parentNode.replaceChild(clean, el);
+
+ // This gets added by default.
+ addNewsIframe();
+
+ clean.addEventListener("click", e => {
+ e.preventDefault();
+ e.stopPropagation();
+ showNewsOverlay();
+ });
+});
+
+// Remove the overlay on escape button click
+document.addEventListener("keydown", e => {
+ // Check for escape button - "27"
+ if (e.which === 27 || e.keyCode === 27) {
+ removeNewsOverlay();
+ }
+});
diff --git a/config/plugins/webhooks/news/styles.css b/config/plugins/webhooks/news/styles.css
new file mode 100644
index 00000000000..7ac3231209d
--- /dev/null
+++ b/config/plugins/webhooks/news/styles.css
@@ -0,0 +1,28 @@
+#news-screen {
+ position: fixed;
+ z-index: 202;
+ width:100%;
+ height:100%;
+}
+
+#news-screen-overlay {
+ position: fixed;
+ top:0;
+ left:0;
+ background: rgba(224, 224, 224, 0.75);
+ z-index: 201;
+ width:100%;
+ height:100%;
+ opacity: 2;
+}
+
+#news-header {
+ position: fixed;
+ height: 100%;
+ width: 100%;
+ z-index: 203;
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ align-items: center;
+}