WebOrbiton
v3.0.0.0

FlatlyPage

149 lines · 6.5 KB
  1. (function () {
  2. 'use strict';
  3. ​
  4. const cfg = window.FLATLY_CONSENT;
  5. if (!cfg) return;
  6. ​
  7. const COOKIE = 'flatly_consent';
  8. const MAX_AGE = 180 * 24 * 60 * 60;
  9. const ICON_COOKIE = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 2a10 10 0 1 0 10 10 4 4 0 0 1-5-5 4 4 0 0 1-5-5"/><path d="M8.5 8.5v.01"/><path d="M16 15.5v.01"/><path d="M12 12v.01"/><path d="M11 17v.01"/><path d="M7 14v.01"/></svg>';
  10. ​
  11. let activated = false;
  12. let banner = null;
  13. ​
  14. function readChoice() {
  15. const match = document.cookie.match(/(?:^|;\s*)flatly_consent=(accepted|rejected)/);
  16. return match ? match[1] : null;
  17. }
  18. ​
  19. function writeChoice(value) {
  20. document.cookie = COOKIE + '=' + value + '; max-age=' + MAX_AGE + '; path=/; SameSite=Lax' + (location.protocol === 'https:' ? '; Secure' : '');
  21. }
  22. ​
  23. function loadScript(old, placeholder) {
  24. return new Promise(resolve => {
  25. const script = document.createElement('script');
  26. Array.from(old.attributes).forEach(attr => script.setAttribute(attr.name, attr.value));
  27. script.text = old.textContent;
  28. // External scripts without async run in order: the next one waits for this one to load.
  29. const waits = script.src && !old.hasAttribute('async') && !old.hasAttribute('defer');
  30. if (waits) {
  31. script.async = false;
  32. script.onload = script.onerror = () => resolve();
  33. }
  34. placeholder.replaceWith(script);
  35. if (!waits) resolve();
  36. });
  37. }
  38. ​
  39. async function activate() {
  40. if (activated) return;
  41. activated = true;
  42. for (const template of document.querySelectorAll('template[data-flatly-consent]')) {
  43. const fragment = document.importNode(template.content, true);
  44. const pending = Array.from(fragment.querySelectorAll('script')).map(old => {
  45. const placeholder = document.createComment('');
  46. old.replaceWith(placeholder);
  47. return [old, placeholder];
  48. });
  49. template.replaceWith(fragment);
  50. for (const [old, placeholder] of pending) {
  51. await loadScript(old, placeholder);
  52. }
  53. }
  54. }
  55. ​
  56. function addStyles() {
  57. const style = document.createElement('style');
  58. style.textContent = `
  59. .flatly-consent{position:fixed;left:16px;bottom:16px;z-index:9999;max-width:420px;width:calc(100% - 32px);box-sizing:border-box;padding:18px;border-radius:12px;background:var(--card,#1a1a1a);color:var(--foreground,#fafafa);border:1px solid var(--border,#2a2a2a);box-shadow:0 12px 32px rgba(0,0,0,.35);font-size:.875rem;line-height:1.5}
  60. .flatly-consent[hidden]{display:none}
  61. .flatly-consent p{margin:0 0 14px}
  62. .flatly-consent a{color:inherit;text-decoration:underline}
  63. .flatly-consent-actions{display:flex;gap:8px;justify-content:flex-end;flex-wrap:wrap}
  64. .flatly-consent-actions button{font:inherit;font-weight:500;padding:8px 16px;border-radius:8px;cursor:pointer;border:1px solid var(--border,#2a2a2a);background:transparent;color:inherit}
  65. .flatly-consent-actions button[data-choice="accepted"]{background:var(--foreground,#fafafa);color:var(--background,#111);border-color:transparent}
  66. .flatly-consent-actions button:hover{opacity:.9}
  67. .flatly-consent-toggle{background:var(--card,#1a1a1a);border:1px solid var(--border,#2a2a2a);color:var(--foreground,#fafafa);padding:8px 12px;border-radius:8px;cursor:pointer;display:flex;align-items:center;justify-content:center}
  68. .flatly-consent-toggle:hover{border-color:var(--muted,#888)}
  69. .flatly-consent-toggle.is-floating{position:fixed;left:16px;bottom:16px;z-index:9998}
  70. @media (max-width:480px){.flatly-consent{left:8px;bottom:8px;width:calc(100% - 16px)}}`;
  71. document.head.appendChild(style);
  72. }
  73. ​
  74. function choose(value) {
  75. const previous = readChoice();
  76. writeChoice(value);
  77. if (banner) banner.hidden = true;
  78. if (value === 'accepted') {
  79. activate();
  80. } else if (previous === 'accepted' && activated) {
  81. // Scripts that already ran cannot be unloaded, so reload without them.
  82. location.reload();
  83. }
  84. }
  85. ​
  86. function showBanner() {
  87. if (!banner) {
  88. banner = document.createElement('div');
  89. banner.className = 'flatly-consent';
  90. banner.setAttribute('role', 'dialog');
  91. banner.setAttribute('aria-label', 'Cookie consent');
  92. ​
  93. const text = document.createElement('p');
  94. text.textContent = cfg.text || '';
  95. if (cfg.privacy) {
  96. const link = document.createElement('a');
  97. link.href = cfg.privacy;
  98. link.textContent = 'Privacy policy';
  99. text.append(' ', link);
  100. }
  101. ​
  102. const actions = document.createElement('div');
  103. actions.className = 'flatly-consent-actions';
  104. [['rejected', cfg.reject || 'Reject'], ['accepted', cfg.accept || 'Accept']].forEach(([value, label]) => {
  105. const button = document.createElement('button');
  106. button.type = 'button';
  107. button.dataset.choice = value;
  108. button.textContent = label;
  109. button.addEventListener('click', () => choose(value));
  110. actions.appendChild(button);
  111. });
  112. ​
  113. banner.append(text, actions);
  114. document.body.appendChild(banner);
  115. }
  116. banner.hidden = false;
  117. }
  118. ​
  119. function addIcon() {
  120. const button = document.createElement('button');
  121. button.type = 'button';
  122. button.className = 'flatly-consent-toggle';
  123. button.title = 'Cookie settings';
  124. button.setAttribute('aria-label', 'Cookie settings');
  125. button.innerHTML = ICON_COOKIE;
  126. button.addEventListener('click', showBanner);
  127. ​
  128. const bottom = document.querySelector('.footer-bottom');
  129. const themeToggle = bottom && bottom.querySelector('.theme-toggle');
  130. if (themeToggle) {
  131. themeToggle.before(button);
  132. } else if (bottom) {
  133. bottom.appendChild(button);
  134. } else {
  135. button.classList.add('is-floating');
  136. document.body.appendChild(button);
  137. }
  138. }
  139. ​
  140. addStyles();
  141. const choice = readChoice();
  142. if (choice === 'accepted') {
  143. activate();
  144. } else if (choice === null) {
  145. showBanner();
  146. }
  147. if (cfg.icon) addIcon();
  148. })();
  149. ​