diff --git a/docker-compose.yml b/docker-compose.yml index d889bda..cc6ac54 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,7 @@ services: - outline volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro + - ./theme:/usr/share/nginx/html/theme:ro networks: - default - dokploy-network diff --git a/nginx.conf b/nginx.conf index 094f3af..ddb3843 100644 --- a/nginx.conf +++ b/nginx.conf @@ -52,6 +52,14 @@ http { access_log off; } + # Same-origin static assets we inject (license footer script, etc.). + # Same-origin URLs satisfy Outline's CSP without needing a nonce. + location /theme/ { + root /usr/share/nginx/html; + expires 1h; + access_log off; + } + # Homepage: inject OG meta tags + license footer script location = / { proxy_pass http://outline; @@ -70,7 +78,7 @@ http { sub_filter '
' ''; # CC BY-SA 4.0 license footer (script self-gates to /doc/ pages). - sub_filter '' ''; + sub_filter '' ''; sub_filter_once on; sub_filter_types text/html; @@ -95,7 +103,7 @@ http { proxy_set_header Connection "upgrade"; # CC BY-SA 4.0 license footer (script self-gates to /doc/ pages). - sub_filter '' ''; + sub_filter '' ''; sub_filter_once on; sub_filter_types text/html; diff --git a/theme/license-footer.js b/theme/license-footer.js new file mode 100644 index 0000000..b54c66d --- /dev/null +++ b/theme/license-footer.js @@ -0,0 +1,76 @@ +(function () { + var URL_HREF = "https://creativecommons.org/licenses/by-sa/4.0/"; + var PREFIX = + "Content on this site by Baby Ghosts Studio Development Fund is licensed under CC BY-SA 4.0. " + + "To view a copy of this license, visit "; + + function isDoc() { + return location.pathname.indexOf("/doc/") === 0; + } + + function findTarget() { + var pm = document.querySelector(".ProseMirror"); + if (pm && pm.parentElement) return pm.parentElement; + return ( + document.querySelector("article") || + document.querySelector("[role=main]") || + document.querySelector("main") + ); + } + + function render() { + var existing = document.querySelector(".cc-license-footer"); + if (!isDoc()) { + if (existing) existing.remove(); + return; + } + if (existing) return; + var target = findTarget(); + if (!target) return; + var footer = document.createElement("footer"); + footer.className = "cc-license-footer"; + var p = document.createElement("p"); + p.appendChild(document.createTextNode(PREFIX)); + var a = document.createElement("a"); + a.href = URL_HREF; + a.target = "_blank"; + a.rel = "noopener noreferrer"; + a.textContent = URL_HREF; + p.appendChild(a); + footer.appendChild(p); + target.appendChild(footer); + } + + var pending = false; + function schedule() { + if (pending) return; + pending = true; + requestAnimationFrame(function () { + pending = false; + render(); + }); + } + + ["pushState", "replaceState"].forEach(function (m) { + var orig = history[m]; + history[m] = function () { + orig.apply(this, arguments); + schedule(); + }; + }); + window.addEventListener("popstate", schedule); + + function start() { + new MutationObserver(schedule).observe(document.body, { + childList: true, + subtree: true, + }); + schedule(); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", start); + } else { + start(); + } +})();