FFZ Panel Resize 1.2.29

Расширение и перемещение панели эмодзи FFZ

  1. // ==UserScript==
  2. // @name FFZ Panel Resize 1.2.29
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2.29
  5. // @description Расширение и перемещение панели эмодзи FFZ
  6. // @author gullampis810
  7. // @match https://www.twitch.tv/*
  8. // @license MIT
  9. // @icon https://png.pngtree.com/png-vector/20220703/ourmid/pngtree-send-dark-mode-glyph-icon-png-image_5561369.png
  10. // @grant GM_addStyle
  11. // @run-at document-idle
  12. // @downloadURL
  13. // @updateURL
  14. // ==/UserScript==
  15.  
  16.  
  17.  
  18. (function() {
  19. 'use strict';
  20. // кнопка Chat Paused Due to Scroll //
  21. const observer = new MutationObserver(() => {
  22. const buttonContainer = document.querySelector('.tw-absolute.tw-border-radius-medium.tw-bottom-0.tw-c-background-overlay.tw-c-text-overlay.tw-mg-b-1');
  23. if (buttonContainer) {
  24. buttonContainer.style.height = '34px';
  25. buttonContainer.style.minHeight = '34px';
  26. buttonContainer.style.maxHeight = '34px';
  27. console.log('Высота контейнера кнопки установлена на 34px');
  28. }
  29. });
  30.  
  31. observer.observe(document.body, { childList: true, subtree: true });
  32.  
  33.  
  34.  
  35. // Добавляем стили для изменения размеров контейнера эмодзи
  36. GM_addStyle(`
  37.  
  38. .tw-absolute.tw-border-radius-medium.tw-bottom-0.tw-c-background-overlay.tw-c-text-overlay.tw-mg-b-1 {
  39. background: rgb(22 67 63 / 34%) !important;
  40. position: relative !important;
  41. top: 13px !important;
  42. z-index: 100002 !important;
  43. }
  44.  
  45. button.tw-align-items-center.tw-align-middle.tw-border-bottom-left-radius-medium.tw-border-bottom-right-radius-medium.tw-border-top-left-radius-medium.tw-border-top-right-radius-medium.ffz-core-button.ffz-core-button--overlay.ffz-core-button--text.tw-inline-flex.tw-interactive.tw-justify-content-center.tw-overflow-hidden.tw-relative {
  46. color: #ffffff5e !important;
  47. }
  48.  
  49. .emote-picker__controls-container.tw-relative {
  50. bottom: 3px !important;
  51. }
  52.  
  53. .emote-picker {
  54. width: 107rem !important; /* Увеличенная ширина */
  55. height: 100rem !important; /* Увеличенная высота */
  56. left: 24px !important;; /* Сдвиг влево */
  57. position: relative !important;
  58.  
  59. }
  60.  
  61. .ffz--emote-picker {
  62. position: relative !important;
  63. height: 785px !important;
  64. width: 1097px !important;
  65. left: -243px !important;
  66. }
  67.  
  68.  
  69. .ffz--emote-picker.ffz--emote-picker__tall .emote-picker__nav-content-overflow, .ffz--emote-picker.ffz--emote-picker__tall .emote-picker__tab-content {
  70. height: unset !important;
  71. max-height: 73rem !important;
  72. }
  73.  
  74. .tw-absolute.ffz-attached.ffz-attached--right.ffz-attached--up {
  75. width: 857px !important;
  76. right: 368px !important;
  77. bottom: 533px !important;
  78. }
  79.  
  80.  
  81. /* fix ballon when clicked ffz emote picke in chat input */
  82. .ffz-balloon.ffz-balloon--auto.tw-inline-block.tw-border-radius-large.tw-c-background-base.tw-c-text-inherit.tw-elevation-2.ffz--emote-picker.ffz--emote-picker__tall {
  83. top: 290px !important;
  84. }
  85.  
  86. .ffz-attached--up {
  87. bottom: 510% !important;
  88. }
  89.  
  90.  
  91. .tw-border-b.tw-border-l.tw-border-r.tw-border-t.tw-border-radius-medium.tw-c-background-base.tw-elevation-1 {
  92. width: 63px; !important;
  93. height: 216px; !important;
  94. }
  95.  
  96. .tw-absolute {
  97. position: absolute !important;
  98. height: 570px !important;
  99. }
  100.  
  101. .tw-border-b.tw-border-l.tw-border-r.tw-border-t.tw-border-radius-medium.tw-c-background-base.tw-elevation-1 {
  102. width: 60px !important;
  103. height: 200px !important;
  104. bottom: 6px !important;
  105. position: absolute !important;
  106. right: 5px !important;
  107. } /* emoji standard color choice mini panel */
  108.  
  109.  
  110. `);
  111.  
  112. console.log("[FFZ Emote Panel] Контейнер .emote-picker изменен: шире, выше, сдвинут влево.");
  113.  
  114. })();
  115.  
  116.  
  117.  
  118.  
  119.  
  120. // ============== FFZ Dialog Unconstrained Dragging свободное перемещение Панели ffz Settings main-menu vue js css ============ //
  121.  
  122.  
  123. (function() {
  124. 'use strict';
  125.  
  126. console.log('[FFZ Dialog Unconstrained Dragging and Resizing v1.3] Script started');
  127.  
  128. // Функция для проверки, является ли элемент полем ввода
  129. function isInputElement(target) {
  130. return target.tagName === 'INPUT' ||
  131. target.tagName === 'TEXTAREA' ||
  132. target.tagName === 'SELECT' ||
  133. target.closest('input, textarea, select');
  134. }
  135.  
  136. // Функция для инициализации перетаскивания и изменения размера
  137. function initDraggingAndResizing(dialog) {
  138. if (!dialog) {
  139. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Dialog not found');
  140. return;
  141. }
  142.  
  143. const header = dialog.querySelector('header');
  144. if (!header) {
  145. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Header not found');
  146. return;
  147. }
  148.  
  149. if (dialog.dataset.draggingInitialized) {
  150. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Dragging already initialized, skipping');
  151. return;
  152. }
  153. dialog.dataset.draggingInitialized = 'true';
  154.  
  155. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Initializing for dialog');
  156.  
  157. // Устанавливаем начальные стили для диалога
  158. dialog.style.position = 'absolute';
  159. dialog.style.minWidth = '200px';
  160. dialog.style.minHeight = '200px';
  161.  
  162. // Переменные для перетаскивания
  163. let isDragging = false;
  164. let startX, startY;
  165.  
  166. // Переменные для изменения размера
  167. let isResizing = false;
  168. let resizeStartX, resizeStartY;
  169.  
  170. // Обработчик начала перетаскивания
  171. header.addEventListener('mousedown', (e) => {
  172. if (e.target.closest('button') || isInputElement(e.target)) {
  173. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Ignoring button or input click');
  174. return;
  175. }
  176.  
  177. isDragging = true;
  178. startX = e.clientX - (parseFloat(dialog.style.left) || dialog.offsetLeft);
  179. startY = e.clientY - (parseFloat(dialog.style.top) || dialog.offsetTop);
  180.  
  181. dialog.style.zIndex = Math.max(parseInt(dialog.style.zIndex) || 9000, 9000) + 1;
  182.  
  183. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Drag started at', e.clientX, e.clientY);
  184. e.preventDefault();
  185. e.stopPropagation();
  186. }, { capture: true, passive: false });
  187.  
  188. // Обработчик движения мыши для перетаскивания
  189. document.addEventListener('mousemove', (e) => {
  190. if (isDragging) {
  191. requestAnimationFrame(() => {
  192. const newLeft = e.clientX - startX;
  193. const newTop = e.clientY - startY;
  194.  
  195. dialog.style.left = `${newLeft}px`;
  196. dialog.style.top = `${newTop}px`;
  197.  
  198. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Moved to', newLeft, newTop);
  199. });
  200. }
  201. }, { capture: true, passive: true });
  202.  
  203. // Обработчик окончания перетаскивания
  204. document.addEventListener('mouseup', () => {
  205. if (isDragging) {
  206. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Drag ended');
  207. isDragging = false;
  208. }
  209. }, { capture: true, passive: true });
  210.  
  211. // Поддержка сенсорных устройств для перетаскивания
  212. header.addEventListener('touchstart', (e) => {
  213. if (e.target.closest('button') || isInputElement(e.target)) {
  214. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Ignoring button or input touch');
  215. return;
  216. }
  217.  
  218. isDragging = true;
  219. const touch = e.touches[0];
  220. startX = touch.clientX - (parseFloat(dialog.style.left) || dialog.offsetLeft);
  221. startY = touch.clientY - (parseFloat(dialog.style.top) || dialog.offsetTop);
  222.  
  223. dialog.style.zIndex = Math.max(parseInt(dialog.style.zIndex) || 9000, 9000) + 1;
  224.  
  225. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Touch drag started at', touch.clientX, touch.clientY);
  226. e.preventDefault();
  227. e.stopPropagation();
  228. }, { capture: true, passive: false });
  229.  
  230. document.addEventListener('touchmove', (e) => {
  231. if (isDragging) {
  232. const touch = e.touches[0];
  233. requestAnimationFrame(() => {
  234. const newLeft = touch.clientX - startX;
  235. const newTop = touch.clientY - startY;
  236.  
  237. dialog.style.left = `${newLeft}px`;
  238. dialog.style.top = `${newTop}px`;
  239.  
  240. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Touch moved to', newLeft, newTop);
  241. });
  242. }
  243. }, { capture: true, passive: true });
  244.  
  245. document.addEventListener('touchend', () => {
  246. if (isDragging) {
  247. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Touch drag ended');
  248. isDragging = false;
  249. }
  250. }, { capture: true, passive: true });
  251.  
  252. // Создаем элемент для изменения размера
  253. const resizeHandle = document.createElement('div');
  254. resizeHandle.className = 'resize-handle';
  255. resizeHandle.style.position = 'absolute';
  256. resizeHandle.style.bottom = '0';
  257. resizeHandle.style.right = '0';
  258. resizeHandle.style.width = '15px';
  259. resizeHandle.style.height = '15px';
  260. resizeHandle.style.background = '#34767c';
  261. resizeHandle.style.cursor = 'se-resize';
  262. resizeHandle.style.zIndex = '10001';
  263. resizeHandle.style.border = '1px solid #ffffff';
  264. dialog.appendChild(resizeHandle);
  265.  
  266. // Обработчик начала изменения размера
  267. resizeHandle.addEventListener('mousedown', (e) => {
  268. if (isInputElement(e.target)) {
  269. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Ignoring input click on resize handle');
  270. return;
  271. }
  272.  
  273. isResizing = true;
  274. resizeStartX = e.clientX;
  275. resizeStartY = e.clientY;
  276.  
  277. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Resize started at', e.clientX, e.clientY);
  278. e.preventDefault();
  279. e.stopPropagation();
  280. }, { capture: true, passive: false });
  281.  
  282. // Обработчик движения мыши для изменения размера
  283. document.addEventListener('mousemove', (e) => {
  284. if (isResizing) {
  285. requestAnimationFrame(() => {
  286. const newWidth = (parseFloat(dialog.style.width) || dialog.offsetWidth) + (e.clientX - resizeStartX);
  287. const newHeight = (parseFloat(dialog.style.height) || dialog.offsetHeight) + (e.clientY - resizeStartY);
  288.  
  289. // Ограничиваем минимальные размеры
  290. if (newWidth >= 200) {
  291. dialog.style.width = `${newWidth}px`;
  292. resizeStartX = e.clientX;
  293. }
  294. if (newHeight >= 200) {
  295. dialog.style.height = `${newHeight}px`;
  296. resizeStartY = e.clientY;
  297. }
  298.  
  299. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Resized to', newWidth, newHeight);
  300. });
  301. }
  302. }, { capture: true, passive: true });
  303.  
  304. // Обработчик окончания изменения размера
  305. document.addEventListener('mouseup', () => {
  306. if (isResizing) {
  307. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Resize ended');
  308. isResizing = false;
  309. }
  310. }, { capture: true, passive: true });
  311.  
  312. // Поддержка сенсорных устройств для изменения размера
  313. resizeHandle.addEventListener('touchstart', (e) => {
  314. if (isInputElement(e.target)) {
  315. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Ignoring input touch on resize handle');
  316. return;
  317. }
  318.  
  319. isResizing = true;
  320. const touch = e.touches[0];
  321. resizeStartX = touch.clientX;
  322. resizeStartY = touch.clientY;
  323.  
  324. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Touch resize started at', touch.clientX, touch.clientY);
  325. e.preventDefault();
  326. e.stopPropagation();
  327. }, { capture: true, passive: false });
  328.  
  329. document.addEventListener('touchmove', (e) => {
  330. if (isResizing) {
  331. const touch = e.touches[0];
  332. requestAnimationFrame(() => {
  333. const newWidth = (parseFloat(dialog.style.width) || dialog.offsetWidth) + (touch.clientX - resizeStartX);
  334. const newHeight = (parseFloat(dialog.style.height) || dialog.offsetHeight) + (touch.clientY - resizeStartY);
  335.  
  336. if (newWidth >= 200) {
  337. dialog.style.width = `${newWidth}px`;
  338. resizeStartX = touch.clientX;
  339. }
  340. if (newHeight >= 200) {
  341. dialog.style.height = `${newHeight}px`;
  342. resizeStartY = touch.clientY;
  343. }
  344.  
  345. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Touch resized to', newWidth, newHeight);
  346. });
  347. }
  348. }, { capture: true, passive: true });
  349.  
  350. document.addEventListener('touchend', () => {
  351. if (isResizing) {
  352. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Touch resize ended');
  353. isResizing = false;
  354. }
  355. }, { capture: true, passive: true });
  356.  
  357. // Добавляем кнопку для сброса позиции
  358. const resetButton = document.createElement('button');
  359. resetButton.textContent = 'Reset Position';
  360. resetButton.style.position = 'absolute';
  361. resetButton.style.top = '625px';
  362. resetButton.style.right = '5px';
  363. resetButton.style.zIndex = '10000';
  364. resetButton.style.padding = '5px';
  365. resetButton.style.background = '#34767c';
  366. resetButton.style.borderRadius = '12px';
  367. resetButton.style.border = '1px solid #ffffff';
  368. resetButton.addEventListener('click', () => {
  369. dialog.style.left = '25%';
  370. dialog.style.top = '25%';
  371. dialog.style.width = ''; // Сбрасываем размеры
  372. dialog.style.height = '';
  373. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Position and size reset to 25%, 25%');
  374. });
  375. dialog.appendChild(resetButton);
  376.  
  377. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Dragging and resizing initialized successfully');
  378. }
  379.  
  380. // Наблюдатель за появлением диалога
  381. const observer = new MutationObserver((mutations) => {
  382. for (const mutation of mutations) {
  383. if (mutation.addedNodes.length) {
  384. const dialog = document.querySelector('.ffz-dialog.ffz-main-menu:not([data-dragging-initialized])');
  385. if (dialog) {
  386. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Dialog detected via observer');
  387. initDraggingAndResizing(dialog);
  388. }
  389. }
  390. }
  391. });
  392.  
  393. observer.observe(document.body, {
  394. childList: true,
  395. subtree: true
  396. });
  397.  
  398. const initialDialog = document.querySelector('.ffz-dialog.ffz-main-menu:not([data-dragging-initialized])');
  399. if (initialDialog) {
  400. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Initial dialog found');
  401. initDraggingAndResizing(initialDialog);
  402. }
  403.  
  404. console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Setup complete');
  405. })();
  406.  
  407.  
  408.  
  409. // ====================== ffz-viewer-card move freedom =========================== //
  410.  
  411.  
  412. (function() {
  413. 'use strict';
  414.  
  415. // Inject CSS for scrollable modified emotes list
  416. GM_addStyle(`
  417. .ffz-emote-card__modifiers {
  418. max-height: 200px !important;
  419. overflow-y: auto !important;
  420. overflow-x: hidden !important;
  421. padding-right: 8px !important;
  422. }
  423. .ffz-emote-card__modifiers::-webkit-scrollbar {
  424. width: 8px;
  425. }
  426. .ffz-emote-card__modifiers::-webkit-scrollbar-track {
  427. background: rgba(0, 0, 0, 0.1);
  428. }
  429. .ffz-emote-card__modifiers::-webkit-scrollbar-thumb {
  430. background: rgba(255, 255, 255, 0.3);
  431. border-radius: 4px;
  432. }
  433. .ffz-emote-card__modifiers > div {
  434. width: 100%;
  435. box-sizing: border-box;
  436. }
  437. `);
  438.  
  439. // Function to enable unconstrained dragging for ffz-viewer-card
  440. function enableUnconstrainedDragging() {
  441. // Find the ffz-viewer-card element
  442. const observer = new MutationObserver((mutations, obs) => {
  443. const viewerCard = document.querySelector('.ffz-viewer-card.tw-border');
  444. if (viewerCard) {
  445. // Initialize custom drag functionality
  446. setupCustomDrag(viewerCard);
  447. // Stop observing once the card is found
  448. obs.disconnect();
  449. }
  450. });
  451.  
  452. // Observe the document for the viewer card
  453. observer.observe(document.body, {
  454. childList: true,
  455. subtree: true
  456. });
  457. }
  458.  
  459. // Custom drag implementation
  460. function setupCustomDrag(card) {
  461. const header = card.querySelector('.ffz-viewer-card__header');
  462. if (!header) {
  463. console.log('[FFZ Enhancements] Header not found for dragging');
  464. return;
  465. }
  466.  
  467. let isDragging = false;
  468. let currentX;
  469. let currentY;
  470. let initialX;
  471. let initialY;
  472.  
  473. header.addEventListener('mousedown', (e) => {
  474. // Ignore if clicking on elements with viewer-card-drag-cancel
  475. if (e.target.closest('.viewer-card-drag-cancel')) return;
  476.  
  477. isDragging = true;
  478. initialX = e.clientX - currentX;
  479. initialY = e.clientY - currentY;
  480. card.style.transition = 'none'; // Disable transitions during drag
  481. e.preventDefault();
  482. });
  483.  
  484. document.addEventListener('mousemove', (e) => {
  485. if (!isDragging) return;
  486.  
  487. currentX = e.clientX - initialX;
  488. currentY = e.clientY - initialY;
  489. card.style.left = `${currentX}px`;
  490. card.style.top = `${currentY}px`;
  491. });
  492.  
  493. document.addEventListener('mouseup', () => {
  494. isDragging = false;
  495. card.style.transition = ''; // Restore transitions
  496. });
  497.  
  498. // Initialize position if not set
  499. if (!card.style.left || !card.style.top) {
  500. const rect = card.getBoundingClientRect();
  501. currentX = rect.left;
  502. currentY = rect.top;
  503. card.style.left = `${currentX}px`;
  504. card.style.top = `${currentY}px`;
  505. }
  506. }
  507.  
  508. // Re-integrate showEmoteSelectionPopup to follow the draggable card
  509. function showEmoteSelectionPopup(emotes, callback) {
  510. console.log("[FFZ Enhancements] Attempting to show emote selection popup with emotes:", emotes);
  511.  
  512. // Remove existing popup
  513. const existingPopup = document.getElementById('emote-selection-popup');
  514. if (existingPopup) {
  515. console.log("[FFZ Enhancements] Removing existing popup");
  516. existingPopup.remove();
  517. }
  518.  
  519. // Create popup
  520. const popup = document.createElement('div');
  521. popup.id = 'emote-selection-popup';
  522. popup.innerHTML = `
  523. <div class="close-button" style="cursor:pointer;position:absolute;top:6px;right:10px;font-size:20px;">✕</div>
  524. <div class="emote-options"></div>
  525. `;
  526. document.body.appendChild(popup);
  527. console.log("[FFZ Enhancements] Popup element created and appended to body");
  528.  
  529. // Inline styles for popup
  530. popup.style.position = 'fixed';
  531. popup.style.background = 'rgb(56, 90, 80)';
  532. popup.style.color = 'rgb(235, 235, 235)';
  533. popup.style.fontWeight = 'bold';
  534. popup.style.fontSize = '16px';
  535. popup.style.border = '1px solid #12b6a7';
  536. popup.style.borderRadius = '8px';
  537. popup.style.padding = '10px 10px 10px 10px';
  538. popup.style.zIndex = '10001';
  539. popup.style.maxWidth = '320px';
  540. popup.style.maxHeight = '500px';
  541. popup.style.overflowY = 'auto';
  542. popup.style.transition = 'opacity 0.2s ease, transform 0.2s ease';
  543. popup.style.opacity = '0';
  544. popup.style.transform = 'scale(0.9)';
  545. popup.style.display = 'block';
  546. popup.style.visibility = 'visible';
  547. popup.style.paddingTop = '32px';
  548.  
  549. // Position popup relative to ffz-viewer-card
  550. const viewerCard = document.querySelector('.ffz-viewer-card.tw-border');
  551. if (viewerCard) {
  552. const rect = viewerCard.getBoundingClientRect();
  553. const viewportWidth = window.innerWidth;
  554. const viewportHeight = window.innerHeight;
  555. const popupWidth = 320;
  556. const offset = 20;
  557. const extraOffset = 30;
  558.  
  559. let left = rect.right + offset + extraOffset;
  560. let top = rect.top;
  561.  
  562. if (left + popupWidth > viewportWidth) {
  563. left = rect.left - popupWidth - offset;
  564. }
  565.  
  566. if (top + popup.offsetHeight > viewportHeight) {
  567. top = viewportHeight - popup.offsetHeight - offset;
  568. }
  569.  
  570. if (top < 0) {
  571. top = offset;
  572. }
  573.  
  574. if (left < 0) {
  575. left = offset;
  576. if (rect.bottom + popup.offsetHeight + offset <= viewportHeight) {
  577. left = rect.left;
  578. top = rect.bottom + offset;
  579. }
  580. }
  581.  
  582. popup.style.left = `${left}px`;
  583. popup.style.top = `${top}px`;
  584. console.log("[FFZ Enhancements] Popup positioned at left:", left, "top:", top);
  585. } else {
  586. popup.style.right = '310px';
  587. popup.style.top = '385px';
  588. console.warn("[FFZ Enhancements] ffz-viewer-card not found, using fallback position");
  589. }
  590.  
  591. const optionsContainer = popup.querySelector('.emote-options');
  592.  
  593. // Populate popup
  594. emotes.forEach((emote, index) => {
  595. const option = document.createElement('div');
  596. option.className = 'emote-option';
  597. option.style.display = 'flex';
  598. option.style.alignItems = 'center';
  599. option.style.justifyContent = 'space-between';
  600. option.style.padding = '8px 0';
  601. option.style.borderBottom = '1px solid rgba(115, 209, 204, 0.16)';
  602. option.style.gap = '10px';
  603.  
  604. const left = document.createElement('div');
  605. left.style.display = 'flex';
  606. left.style.alignItems = 'center';
  607. left.style.minWidth = '0';
  608.  
  609. const img = document.createElement('img');
  610. img.src = emote.src || '';
  611. img.alt = emote.alt || 'Emote';
  612. img.style.width = '24px';
  613. img.style.height = '24px';
  614. img.style.marginRight = '10px';
  615. img.style.flexShrink = '0';
  616. img.style.userSelect = 'none';
  617.  
  618. const info = document.createElement('div');
  619. info.className = 'emote-info';
  620. info.style.fontSize = '14px';
  621. info.style.whiteSpace = 'nowrap';
  622. info.style.overflow = 'hidden';
  623. info.style.textOverflow = 'ellipsis';
  624. info.innerHTML = `<span>${emote.alt || 'Unnamed'} <span style="user-select:none;">(${emote.platform})</span></span>`;
  625.  
  626. left.appendChild(img);
  627. left.appendChild(info);
  628.  
  629. const blockButton = document.createElement('button');
  630. blockButton.className = 'block-button';
  631. blockButton.type = 'button';
  632. blockButton.textContent = 'Block';
  633. blockButton.style.background = '#ff5555';
  634. blockButton.style.color = '#ffffff';
  635. blockButton.style.border = 'none';
  636. blockButton.style.padding = '4px 8px';
  637. blockButton.style.borderRadius = '4px';
  638. blockButton.style.cursor = 'pointer';
  639. blockButton.style.fontSize = '12px';
  640. blockButton.style.marginLeft = '10px';
  641. blockButton.style.flexShrink = '0';
  642. blockButton.style.userSelect = 'none';
  643.  
  644. blockButton.addEventListener('click', (e) => {
  645. e.preventDefault();
  646. e.stopPropagation();
  647. console.log("[FFZ Enhancements] Block button clicked for emote:", emote);
  648. callback(emote);
  649. if (emote.element) {
  650. emote.element.style.display = 'none';
  651. console.log("[FFZ Enhancements] Emote element hidden:", emote.alt);
  652. const parentContainer = emote.element.closest('.ffz--inline, .chat-line__message, .chat-image');
  653. if (parentContainer) {
  654. const allEmotes = parentContainer.querySelectorAll(
  655. 'img.chat-line__message--emote, .ffz-emote, .seventv-emote, .bttv-emote, .twitch-emote, .chat-image'
  656. );
  657. const allBlocked = Array.from(allEmotes).every(e => e.style.display === 'none');
  658. if (allBlocked) {
  659. parentContainer.style.display = 'none';
  660. console.log("[FFZ Enhancements] Parent container hidden as all emotes are blocked");
  661. }
  662. }
  663. }
  664. popup.remove();
  665. });
  666.  
  667. option.appendChild(left);
  668. option.appendChild(blockButton);
  669. optionsContainer.appendChild(option);
  670. });
  671. console.log("[FFZ Enhancements] Popup populated with", emotes.length, "emotes");
  672.  
  673. // Close button
  674. const closeButton = popup.querySelector('.close-button');
  675. closeButton.onclick = () => {
  676. console.log("[FFZ Enhancements] Emote selection popup closed via close button");
  677. popup.remove();
  678. };
  679.  
  680. // Ensure visibility
  681. const computedStyle = window.getComputedStyle(popup);
  682. if (computedStyle.display === 'none' || computedStyle.visibility === 'hidden') {
  683. console.warn("[FFZ Enhancements] Popup is not visible, forcing visibility");
  684. popup.style.display = 'block';
  685. popup.style.visibility = 'visible';
  686. }
  687.  
  688. // Animation
  689. setTimeout(() => {
  690. popup.classList.add('visible');
  691. popup.style.opacity = '1';
  692. popup.style.transform = 'scale(1)';
  693. console.log("[FFZ Enhancements] Popup visibility class and styles applied");
  694. }, 10);
  695.  
  696. // Close on outside click
  697. document.addEventListener('click', function closePopup(e) {
  698. if (!popup.contains(e.target) && e.target !== popup && !viewerCard.contains(e.target)) {
  699. console.log("[FFZ Enhancements] Closing popup due to outside click");
  700. popup.remove();
  701. document.removeEventListener('click', closePopup);
  702. }
  703. }, { capture: true, once: true });
  704.  
  705. // Observe viewerCard for position changes
  706. const observer = new MutationObserver(() => {
  707. if (viewerCard) {
  708. const rect = viewerCard.getBoundingClientRect();
  709. const viewportWidth = window.innerWidth;
  710. const viewportHeight = window.innerHeight;
  711. const popupWidth = 320;
  712. const offset = 20;
  713. const extraOffset = 30;
  714.  
  715. let left = rect.right + offset + extraOffset;
  716. let top = rect.top;
  717.  
  718. if (left + popupWidth > viewportWidth) {
  719. left = rect.left - popupWidth - offset;
  720. }
  721.  
  722. if (top + popup.offsetHeight > viewportHeight) {
  723. top = viewportHeight - popup.offsetHeight - offset;
  724. }
  725.  
  726. if (top < 0) {
  727. top = offset;
  728. }
  729.  
  730. if (left < 0) {
  731. left = offset;
  732. if (rect.bottom + popup.offsetHeight + offset <= viewportHeight) {
  733. left = rect.left;
  734. top = rect.bottom + offset;
  735. }
  736. }
  737.  
  738. popup.style.left = `${left}px`;
  739. popup.style.top = `${top}px`;
  740. console.log("[FFZ Enhancements] Popup repositioned to left:", left, "top:", top);
  741. }
  742. });
  743.  
  744. if (viewerCard) {
  745. observer.observe(viewerCard, {
  746. attributes: true,
  747. attributeFilter: ['style', 'class']
  748. });
  749. }
  750.  
  751. popup.addEventListener('remove', () => {
  752. observer.disconnect();
  753. console.log("[FFZ Enhancements] MutationObserver disconnected");
  754. });
  755. }
  756.  
  757. // Initialize enhancements
  758. enableUnconstrainedDragging();
  759.  
  760. // Expose showEmoteSelectionPopup globally for external use
  761. window.showEmoteSelectionPopup = showEmoteSelectionPopup;
  762. })();