WebOrbiton
v3.0.0.2

FlatlyPage

147 lines · 6.3 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. const waits = script.src && !old.hasAttribute('async') && !old.hasAttribute('defer');
  29. if (waits) {
  30. script.async = false;
  31. script.onload = script.onerror = () => resolve();
  32. }
  33. placeholder.replaceWith(script);
  34. if (!waits) resolve();
  35. });
  36. }
  37. ​
  38. async function activate() {
  39. if (activated) return;
  40. activated = true;
  41. for (const template of document.querySelectorAll('template[data-flatly-consent]')) {
  42. const fragment = document.importNode(template.content, true);
  43. const pending = Array.from(fragment.querySelectorAll('script')).map(old => {
  44. const placeholder = document.createComment('');
  45. old.replaceWith(placeholder);
  46. return [old, placeholder];
  47. });
  48. template.replaceWith(fragment);
  49. for (const [old, placeholder] of pending) {
  50. await loadScript(old, placeholder);
  51. }
  52. }
  53. }
  54. ​
  55. function addStyles() {
  56. const style = document.createElement('style');
  57. style.textContent = `
  58. .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}
  59. .flatly-consent[hidden]{display:none}
  60. .flatly-consent p{margin:0 0 14px}
  61. .flatly-consent a{color:inherit;text-decoration:underline}
  62. .flatly-consent-actions{display:flex;gap:8px;justify-content:flex-end;flex-wrap:wrap}
  63. .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}
  64. .flatly-consent-actions button[data-choice="accepted"]{background:var(--foreground,#fafafa);color:var(--background,#111);border-color:transparent}
  65. .flatly-consent-actions button:hover{opacity:.9}
  66. .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}
  67. .flatly-consent-toggle:hover{border-color:var(--muted,#888)}
  68. .flatly-consent-toggle.is-floating{position:fixed;left:16px;bottom:16px;z-index:9998}
  69. @media (max-width:480px){.flatly-consent{left:8px;bottom:8px;width:calc(100% - 16px)}}`;
  70. document.head.appendChild(style);
  71. }
  72. ​
  73. function choose(value) {
  74. const previous = readChoice();
  75. writeChoice(value);
  76. if (banner) banner.hidden = true;
  77. if (value === 'accepted') {
  78. activate();
  79. } else if (previous === 'accepted' && activated) {
  80. location.reload();
  81. }
  82. }
  83. ​
  84. function showBanner() {
  85. if (!banner) {
  86. banner = document.createElement('div');
  87. banner.className = 'flatly-consent';
  88. banner.setAttribute('role', 'dialog');
  89. banner.setAttribute('aria-label', 'Cookie consent');
  90. ​
  91. const text = document.createElement('p');
  92. text.textContent = cfg.text || '';
  93. if (cfg.privacy) {
  94. const link = document.createElement('a');
  95. link.href = cfg.privacy;
  96. link.textContent = 'Privacy policy';
  97. text.append(' ', link);
  98. }
  99. ​
  100. const actions = document.createElement('div');
  101. actions.className = 'flatly-consent-actions';
  102. [['rejected', cfg.reject || 'Reject'], ['accepted', cfg.accept || 'Accept']].forEach(([value, label]) => {
  103. const button = document.createElement('button');
  104. button.type = 'button';
  105. button.dataset.choice = value;
  106. button.textContent = label;
  107. button.addEventListener('click', () => choose(value));
  108. actions.appendChild(button);
  109. });
  110. ​
  111. banner.append(text, actions);
  112. document.body.appendChild(banner);
  113. }
  114. banner.hidden = false;
  115. }
  116. ​
  117. function addIcon() {
  118. const button = document.createElement('button');
  119. button.type = 'button';
  120. button.className = 'flatly-consent-toggle';
  121. button.title = 'Cookie settings';
  122. button.setAttribute('aria-label', 'Cookie settings');
  123. button.innerHTML = ICON_COOKIE;
  124. button.addEventListener('click', showBanner);
  125. ​
  126. const bottom = document.querySelector('.footer-bottom');
  127. const themeToggle = bottom && bottom.querySelector('.theme-toggle');
  128. if (themeToggle) {
  129. themeToggle.before(button);
  130. } else if (bottom) {
  131. bottom.appendChild(button);
  132. } else {
  133. button.classList.add('is-floating');
  134. document.body.appendChild(button);
  135. }
  136. }
  137. ​
  138. addStyles();
  139. const choice = readChoice();
  140. if (choice === 'accepted') {
  141. activate();
  142. } else if (choice === null) {
  143. showBanner();
  144. }
  145. if (cfg.icon) addIcon();
  146. })();
  147. ​