|
17 | 17 | `.trim(); |
18 | 18 |
|
19 | 19 | const WATERMARK_SELECTOR = 'a.watermark'; |
20 | | - const APPLIED_ATTR = 'data-custom-watermark'; |
| 20 | + const STYLE_ID = 'custom-watermark-style'; |
| 21 | + const SIBLING_ATTR = 'data-custom-watermark-sibling'; |
21 | 22 |
|
22 | | - function replaceFooterContents(root = document) { |
| 23 | + function injectStyles() { |
| 24 | + if (document.getElementById(STYLE_ID)) return; |
| 25 | + |
| 26 | + const style = document.createElement('style'); |
| 27 | + style.id = STYLE_ID; |
| 28 | + style.textContent = ` |
| 29 | + a.watermark { |
| 30 | + display: none !important; |
| 31 | + } |
| 32 | + `; |
| 33 | + document.head.appendChild(style); |
| 34 | + } |
| 35 | + |
| 36 | + function ensureSiblingAfterWatermark(el) { |
| 37 | + if (!(el instanceof HTMLElement)) return; |
| 38 | + |
| 39 | + // If the immediate next sibling is our disclaimer, update it; otherwise, insert one. |
| 40 | + const nextEl = el.nextElementSibling; |
| 41 | + if (nextEl && nextEl.getAttribute(SIBLING_ATTR) === '1') { |
| 42 | + if (nextEl.innerHTML.trim() !== CUSTOM_FOOTER_HTML) { |
| 43 | + nextEl.innerHTML = CUSTOM_FOOTER_HTML; |
| 44 | + } |
| 45 | + return; |
| 46 | + } |
| 47 | + |
| 48 | + const container = document.createElement('div'); |
| 49 | + container.setAttribute(SIBLING_ATTR, '1'); |
| 50 | + container.style.margin = '0'; |
| 51 | + container.style.pointerEvents = 'auto'; |
| 52 | + container.setAttribute('aria-live', 'polite'); |
| 53 | + container.innerHTML = CUSTOM_FOOTER_HTML; |
| 54 | + |
| 55 | + // Insert directly after the watermark anchor |
| 56 | + el.insertAdjacentElement('afterend', container); |
| 57 | + } |
| 58 | + |
| 59 | + function applyAll(root = document) { |
23 | 60 | const nodes = root instanceof Element |
24 | 61 | ? root.querySelectorAll(WATERMARK_SELECTOR) |
25 | 62 | : document.querySelectorAll(WATERMARK_SELECTOR); |
26 | 63 |
|
27 | | - nodes.forEach((el) => { |
28 | | - if (!(el instanceof HTMLElement)) return; |
29 | | - if (el.getAttribute(APPLIED_ATTR) === '1') return; |
30 | | - |
31 | | - el.innerHTML = CUSTOM_FOOTER_HTML; |
32 | | - |
33 | | - // disable the link behaviour |
34 | | - el.removeAttribute('href'); |
35 | | - el.removeAttribute('target'); |
36 | | - el.style.pointerEvents = 'none'; |
| 64 | + nodes.forEach((el) => ensureSiblingAfterWatermark(el)); |
| 65 | + } |
37 | 66 |
|
38 | | - el.setAttribute(APPLIED_ATTR, '1'); |
39 | | - }); |
| 67 | + function init() { |
| 68 | + injectStyles(); |
| 69 | + applyAll(document); |
40 | 70 | } |
41 | 71 |
|
42 | | - // Initial run (in case the element is already present). |
43 | 72 | if (document.readyState === 'loading') { |
44 | | - document.addEventListener('DOMContentLoaded', () => replaceFooterContents(document)); |
| 73 | + document.addEventListener('DOMContentLoaded', init); |
45 | 74 | } else { |
46 | | - replaceFooterContents(document); |
| 75 | + init(); |
47 | 76 | } |
48 | 77 |
|
49 | | - // Re-apply on future UI updates (SPA re-renders). |
| 78 | + // Re-apply on future UI updates |
50 | 79 | const mo = new MutationObserver((mutations) => { |
51 | 80 | for (const m of mutations) { |
52 | | - for (const node of m.addedNodes) { |
53 | | - if (node instanceof Element) { |
54 | | - // If the watermark itself is added or its parent subtree changes, update. |
55 | | - if (node.matches?.(WATERMARK_SELECTOR) || node.querySelector?.(WATERMARK_SELECTOR)) { |
56 | | - replaceFooterContents(node); |
57 | | - } |
58 | | - } |
| 81 | + if (m.type === 'childList') { |
| 82 | + // Re-ensure CSS and siblings if the UI changes. |
| 83 | + if (!document.getElementById(STYLE_ID)) injectStyles(); |
| 84 | + applyAll(document); |
| 85 | + break; |
59 | 86 | } |
60 | 87 | } |
61 | 88 | }); |
|
0 commit comments