KG_Full_Emoticons

Display a popup panel with every available emoticon on the site.

目前为 2025-02-14 提交的版本。查看 最新版本

// ==UserScript==
// @name         KG_Full_Emoticons
// @namespace    http://klavogonki.ru/
// @version      0.8
// @description  Display a popup panel with every available emoticon on the site.
// @match        *://klavogonki.ru/g*
// @match        *://klavogonki.ru/forum/*
// @match        *://klavogonki.ru/u/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=klavogonki.ru
// @grant        none
// ==/UserScript==

(function () {
  // --------------------------
  // Data & Global Variables
  // --------------------------
  const categories = {
    Boys: [
      "smile", "biggrin", "angry", "blink", "blush", "cool", "dry", "excl", "happy",
      "huh", "laugh", "mellow", "ohmy", "ph34r", "rolleyes", "sad", "sleep", "tongue",
      "unsure", "wacko", "wink", "wub", "first", "second", "third", "power", "badcomp",
      "complaugh", "crazy", "boredom", "cry", "bye", "dance", "gamer", "rofl", "beer",
      "kidtruck", "angry2", "spiteful", "sorry", "boykiss", "kissed", "yes", "no", "heart",
      "hi", "ok", "facepalm", "friends", "shok", "megashok", "dash", "music", "acute", "victory",
      "scare", "clapping", "whistle", "popcorn", "hello", "rose", "good", "silence", "bad", "tea",
      "sick", "confuse", "rofl2", "nervous", "chaingun", "diablo", "cult", "russian", "birthday",
      "champ2", "champ", "confetti", "formula1"
    ],
    Girls: [
      "girlnotebook", "girlkiss", "curtsey", "girlblum", "girlcrazy", "girlcry",
      "girlwink", "girlwacko", "umbrage", "girlinlove", "girldevil", "girlimpossible",
      "girlwitch", "hysteric", "tender", "spruceup", "girlsad", "girlscare", "girltea",
      "girlsick", "grose", "cheerful", "cheerleader", "girlconfuse", "spruceup1",
      "angrygirl", "clapgirl", "goody", "hiya", "girlsilence", "girlstop", "girlnervous",
      "girlwonder", "girlwonder", "kgrace", "kgagainstaz", "girlkissboy", "girlmusic"
    ],
    Christmas: [
      "cheers", "christmasevil", "heyfrombag", "merrychristmas", "moose", "santa",
      "santa2", "santa3", "santasnegurka", "snegurka", "snegurochka", "snowball",
      "snowgirlwave", "snowhand", "snowhit", "snowman", "spruce"
    ],
    Inlove: [
      "adultery", "airkiss", "cave", "flowers", "flowers2", "frog", "girlfrog",
      "girlheart2", "girllove", "grose", "heart2", "heartcake", "hug", "inlove",
      "nolove", "smell", "wecheers", "wedance", "wedding", "wine", "val", "girlval", "bemine"
    ],
    Army: [
      "ak47", "armyfriends", "armyscare", "armystar", "armytongue", "barret",
      "bayanist", "budenov", "captain", "comandos", "fly", "foolrifle", "girlpogran",
      "girlranker", "girlrogatka", "girlvdv", "kirpich", "partizan", "pogran",
      "pogranflowers", "pogranmail", "pogranmama", "pogranminigun", "pogranrose",
      "pograntort", "prival", "radistka", "ranker", "rogatka", "soldier", "tank",
      "uzi", "vdv", "vpered", "vtik"
    ],
    WomenDay: [
      "boystroking", "cheerleader", "confetti", "enjoygift", "firework", "girlicecream",
      "girlmad", "girlobserve", "girlrevolve", "girlshighfive", "girlstroking", "girlsuper",
      "grats", "hairdryer", "leisure", "primp", "respect", "serenade", "spruceup"
    ],
    Halloween: [
      "alien", "batman", "bebebe", "bite", "carpet", "clown", "corsair", "cowboy",
      "cyborg", "dandy", "death", "dwarf", "gangster", "ghost", "girlpirate", "holmes",
      "indigenous", "jester", "mafia", "musketeer", "paladin", "pioneer", "pirate",
      "pirates", "robot", "rocker", "spider", "supergirl", "terminator", "turtle",
      "vampire", "witch", "wizard"
    ],
    Favourites: [] // Loaded from localStorage
  };

  const categoryEmojis = {
    Boys: "😃",
    Girls: "👧",
    Christmas: "🎄",
    Inlove: "❤️",
    Army: "🔫",
    WomenDay: "🌼",
    Halloween: "🎃",
    Favourites: "🌟"
  };

  let activeCategory = localStorage.getItem("activeCategory") || "Boys";
  let isPopupCreated = false;
  let currentEmoticonIndex = 0;
  const categoryHistory = [];
  let currentSortedEmoticons = [];
  let lastFocusedInput = null;

  // --------------------------
  // Style Helpers: Calculate Background Colors
  // --------------------------
  const bodyLightness = getLightness(window.getComputedStyle(document.body).backgroundColor);
  const popupBackground = getAdjustedBackground("popupBackground");
  const defaultButtonBackground = getAdjustedBackground("defaultButton");
  const hoverButtonBackground = getAdjustedBackground("hoverButton");
  const activeButtonBackground = getAdjustedBackground("activeButton");
  const selectedButtonBackground = getAdjustedBackground("selectedButton");

  // Returns lightness (0-100) from an RGB color string.
  function getLightness(color) {
    const match = color.match(/\d+/g);
    if (match && match.length === 3) {
      const [r, g, b] = match.map(Number);
      const max = Math.max(r, g, b) / 255;
      const min = Math.min(r, g, b) / 255;
      return Math.round(((max + min) / 2) * 100);
    }
    return 0;
  }
  // Returns an HSL background color based on the type.
  function getAdjustedBackground(type) {
    const adjustments = {
      popupBackground: 10,
      defaultButton: 15,
      hoverButton: 25,
      activeButton: 35,
      selectedButton: 45,
    };
    const adjustment = adjustments[type] || 0;
    const adjustedLightness =
      bodyLightness < 50 ? bodyLightness + adjustment : bodyLightness - adjustment;
    return `hsl(0, 0%, ${adjustedLightness}%)`;
  }

  // --------------------------
  // Favorite Emoticons Handling
  // --------------------------
  function loadFavoriteEmoticons() {
    categories.Favourites = JSON.parse(localStorage.getItem("favoriteEmoticons")) || [];
  }

  // --------------------------
  // Event Delegation on Text Inputs/Textareas
  // --------------------------
  document.addEventListener("focusin", (e) => {
    if (e.target.matches("textarea, input.text")) {
      lastFocusedInput = e.target;
    }
  });
  document.addEventListener("dblclick", (e) => {
    if (e.shiftKey && e.target.matches("textarea, input.text")) {
      e.preventDefault();
      toggleEmoticonsPopup();
    }
  });
  document.addEventListener("keydown", (e) => {
    if (e.ctrlKey && e.code === "Semicolon") {
      e.preventDefault();
      toggleEmoticonsPopup();
    }
  });
  document.addEventListener("keydown", handleKeydownForEmoticons);
  // Handle keyboard close events
  document.addEventListener("keydown", (e) => {
    const popup = document.querySelector(".emoticons-popup");
    const closeKeys = new Set(['Escape', ' ']);

    if (popup && closeKeys.has(e.key)) {
      e.preventDefault();
      removeEmoticonsPopup();
    }
  });

  // Handle click-outside events
  document.addEventListener("click", (e) => {
    const popup = document.querySelector(".emoticons-popup");
    if (popup && !popup.contains(e.target)) {
      removeEmoticonsPopup();
    }
  });

  // --------------------------
  // Emoticon Code Determination & Insertion
  // --------------------------
  function getEmoticonCode(emoticon) {
    return window.location.pathname.includes("/forum")
      ? `[img]https://klavogonki.ru/img/smilies/${emoticon}.gif[/img] `
      : `:${emoticon}: `;
  }
  function insertEmoticonCode(emoticon) {
    if (!lastFocusedInput) {
      alert("No input field in focus.");
      return;
    }
    const code = getEmoticonCode(emoticon);
    const pos = lastFocusedInput.selectionStart || 0;
    const currentVal = lastFocusedInput.value || "";
    lastFocusedInput.value = currentVal.slice(0, pos) + code + currentVal.slice(pos);
    lastFocusedInput.setSelectionRange(pos + code.length, pos + code.length);
    lastFocusedInput.focus();
  }

  // --------------------------
  // Popup Creation & Removal
  // --------------------------
  function removeEmoticonsPopup() {
    const popup = document.querySelector(".emoticons-popup");
    if (popup) {
      popup.remove();
      isPopupCreated = false;
    }
  }
  function toggleEmoticonsPopup() {
    if (isPopupCreated) {
      removeEmoticonsPopup();
    } else {
      currentEmoticonIndex = 0;
      createEmoticonsPopup(activeCategory);
    }
  }
  function createEmoticonsPopup(category) {
    if (isPopupCreated) return;
    loadFavoriteEmoticons();
    const popup = document.createElement("div");
    popup.className = "emoticons-popup";
    popup.style.setProperty('border-radius', '0.4em', 'important');
    Object.assign(popup.style, {
      position: "fixed",
      display: "grid",
      gridTemplateRows: "50px auto",
      gap: "10px",
      backgroundColor: popupBackground,
      padding: "10px",
      zIndex: "9999",
      top: "50vh",
      left: "0",
      transform: "translate(50%, -50%)",
      maxWidth: "50vw",
      minWidth: "550px",
      width: "50vw",
      maxHeight: "50vh",
      overflow: "auto",
      boxShadow: `
        0 8px 30px rgba(0, 0, 0, 0.12),
        0 4px 6px rgba(0, 0, 0, 0.04),
        0 2px 2px rgba(0, 0, 0, 0.08)
    `,
    });
    const closeButton = document.createElement("button");
    closeButton.innerHTML = "&#x2716;";
    Object.assign(closeButton.style, {
      background: "rgb(57, 19, 19)",
      color: "rgb(217, 140, 140)",
      border: "1px solid rgb(153, 51, 51)",
      cursor: "pointer",
      width: "50px",
      height: "50px",
      margin: "8px",
      position: "absolute",
      right: "0"
    });
    closeButton.addEventListener("click", (e) => {
      if (e.ctrlKey) {
        if (confirm("Clear emoticon usage data?")) {
          localStorage.removeItem("emoticonUsageData");
        }
      }
      removeEmoticonsPopup();
    });
    popup.appendChild(closeButton);
    popup.appendChild(createCategoryContainer(activeCategory));
    createEmoticonsContainer(category).then((container) => {
      popup.appendChild(container);
      // Ensure highlight update happens after layout.
      requestAnimationFrame(updateEmoticonHighlight);
    });
    popup.addEventListener("dblclick", removeEmoticonsPopup);
    document.addEventListener("keydown", changeCategoryOnTabPress);
    document.body.appendChild(popup);
    isPopupCreated = true;
  }

  // --------------------------
  // Category Buttons & State
  // --------------------------
  function createCategoryContainer(category) {
    const container = document.createElement("div");
    container.className = "category-buttons";
    container.style.cssText = "display: flex; justify-content: center;";
    for (let cat in categories) {
      if (categories.hasOwnProperty(cat)) {
        const btn = document.createElement("button");
        btn.innerHTML = categoryEmojis[cat];
        btn.dataset.category = cat;
        btn.style.background = (cat === activeCategory ? activeButtonBackground : defaultButtonBackground);
        btn.style.border = "none";
        btn.style.cursor = "pointer";
        btn.style.minWidth = "50px";
        btn.style.minHeight = "50px";
        btn.style.fontSize = "1.4em";
        btn.style.marginRight = "5px";
        if (cat === "Favourites") {
          if (categories.Favourites.length) {
            btn.style.opacity = "";
          } else {
            btn.style.opacity = "0.5";
          }
          btn.addEventListener("click", ((btn) => {
            return (e) => {
              if (e.shiftKey) {
                localStorage.removeItem("favoriteEmoticons");
                categories.Favourites = [];
                if (categoryHistory.length) {
                  activeCategory = categoryHistory.pop();
                  localStorage.setItem("activeCategory", activeCategory);
                  updateCategoryButtonsState(activeCategory);
                  updateEmoticonsContainer();
                }
              }
            };
          })(btn));
        }
        btn.addEventListener("click", ((cat) => {
          return (e) => {
            if (!e.shiftKey && !e.ctrlKey) {
              changeActiveCategoryOnClick(cat);
            }
          };
        })(cat));
        btn.addEventListener("mouseover", () => {
          btn.style.background = hoverButtonBackground;
        });
        btn.addEventListener("mouseout", ((btn, cat) => {
          return () => {
            btn.style.background = (cat === activeCategory ? activeButtonBackground : defaultButtonBackground);
            if (cat === "Favourites") {
              if (categories.Favourites.length) {
                btn.style.opacity = "";
              } else {
                btn.style.opacity = "0.5";
              }
            }
          };
        })(btn, cat));
        container.appendChild(btn);
      }
    }
    return container;
  }
  function updateCategoryButtonsState(newCategory) {
    document.querySelectorAll(".category-buttons button").forEach((btn) => {
      if (btn.dataset.category === newCategory) {
        btn.style.background = activeButtonBackground;
      } else {
        btn.style.background = defaultButtonBackground;
      }
      if (btn.dataset.category === "Favourites") {
        if (categories.Favourites.length) {
          btn.style.opacity = "";
        } else {
          btn.style.opacity = "0.5";
        }
      }
    });
  }

  // --------------------------
  // Emoticon Usage Data
  // --------------------------
  function loadEmoticonUsageData() {
    return JSON.parse(localStorage.getItem("emoticonUsageData")) || {};
  }
  function saveEmoticonUsageData(data) {
    localStorage.setItem("emoticonUsageData", JSON.stringify(data));
  }
  function incrementEmoticonUsage(emoticon) {
    const data = loadEmoticonUsageData();
    data[activeCategory] = data[activeCategory] || {};
    data[activeCategory][emoticon] = (data[activeCategory][emoticon] || 0) + 1;
    saveEmoticonUsageData(data);
  }
  function getSortedEmoticons(category) {
    const usage = loadEmoticonUsageData()[category] || {};
    return categories[category].slice().sort((a, b) => (usage[b] || 0) - (usage[a] || 0));
  }

  // --------------------------
  // Emoticon Buttons Container
  // --------------------------
  async function createEmoticonsContainer(category) {
    const container = document.createElement("div");
    container.className = "emoticon-buttons";
    container.style.display = "none";
    container.style.gap = "10px";
    currentSortedEmoticons = getSortedEmoticons(category);
    const promises = [];
    currentSortedEmoticons.forEach((emoticon, idx) => {
      const btn = document.createElement("button");
      const imgSrc = `/img/smilies/${emoticon}.gif`;
      btn.innerHTML = `<img src="${imgSrc}" alt="${emoticon}">`;
      btn.title = emoticon;
      btn.style.border = "none";
      btn.style.cursor = "pointer";
      btn.style.background = (idx === currentEmoticonIndex ? selectedButtonBackground : defaultButtonBackground);
      promises.push(
        new Promise((resolve) => {
          const img = new Image();
          img.onload = resolve;
          img.src = imgSrc;
        })
      );
      btn.addEventListener("click", ((emoticon) => {
        return (e) => {
          if (e.ctrlKey) {
            insertEmoticonCode(emoticon);
          } else if (e.shiftKey && activeCategory === "Favourites") {
            const fav = JSON.parse(localStorage.getItem("favoriteEmoticons")) || [];
            const pos = fav.indexOf(emoticon);
            if (pos !== -1) {
              fav.splice(pos, 1);
              localStorage.setItem("favoriteEmoticons", JSON.stringify(fav));
              const favIndex = categories.Favourites.indexOf(emoticon);
              if (favIndex !== -1) {
                categories.Favourites.splice(favIndex, 1);
              }
              updateCategoryButtonsState(activeCategory);
              updateEmoticonsContainer();
            }
          } else if (e.shiftKey && activeCategory !== "Favourites") {
            const fav = JSON.parse(localStorage.getItem("favoriteEmoticons")) || [];
            if (!fav.includes(emoticon)) {
              fav.push(emoticon);
              localStorage.setItem("favoriteEmoticons", JSON.stringify(fav));
              categories.Favourites.push(emoticon);
              updateCategoryButtonsState(activeCategory);
              requestAnimationFrame(updateEmoticonHighlight);
            }
          } else {
            insertEmoticonCode(emoticon);
            incrementEmoticonUsage(emoticon);
            removeEmoticonsPopup();
          }
        };
      })(emoticon));
      btn.addEventListener("mouseover", () => {
        btn.style.background = hoverButtonBackground;
      });
      btn.addEventListener("mouseout", ((idx, title) => {
        return () => {
          if (idx === currentEmoticonIndex) {
            btn.style.background = selectedButtonBackground;
          } else {
            if (activeCategory === "Favourites") {
              btn.style.background = defaultButtonBackground;
            } else {
              if (isEmoticonFavorite(title)) {
                btn.style.background = activeButtonBackground;
              } else {
                btn.style.background = defaultButtonBackground;
              }
            }
          }
        };
      })(idx, btn.title));
      container.appendChild(btn);
    });
    await Promise.all(promises);
    const { maxImageWidth, maxImageHeight } = calculateMaxImageDimensions(categories[category]);
    container.style.gridTemplateColumns = `repeat(auto-fit, minmax(${maxImageWidth}px, 1fr))`;
    container.style.gridAutoRows = `minmax(${maxImageHeight}px, auto)`;
    container.style.display = "grid";
    requestAnimationFrame(updateEmoticonHighlight);
    return container;
  }
  // Update calculateMaxImageDimensions to use currentSortedEmoticons
  function calculateMaxImageDimensions() {
    let maxWidth = 34,
      maxHeight = 34;
    currentSortedEmoticons.forEach((emoticon) => {
      const img = new Image();
      img.src = `/img/smilies/${emoticon}.gif`;
      maxWidth = Math.max(maxWidth, img.width);
      maxHeight = Math.max(maxHeight, img.height);
    });
    return { maxImageWidth: maxWidth, maxImageHeight: maxHeight };
  }
  function updateEmoticonsContainer() {
    const old = document.querySelector(".emoticon-buttons");
    if (old) old.remove();
    createEmoticonsContainer(activeCategory).then((container) => {
      const popup = document.querySelector(".emoticons-popup");
      if (popup) popup.appendChild(container);
    });
  }
  function isEmoticonFavorite(emoticon) {
    const fav = JSON.parse(localStorage.getItem("favoriteEmoticons")) || [];
    return fav.includes(emoticon);
  }

  // --------------------------
  // Category Switching & Navigation
  // --------------------------
  function changeActiveCategoryOnClick(newCategory) {
    if (newCategory === "Favourites" && (JSON.parse(localStorage.getItem("favoriteEmoticons")) || []).length === 0) {
      return;
    }
    if (activeCategory !== "Favourites") {
      categoryHistory.push(activeCategory);
    }
    activeCategory = newCategory;
    localStorage.setItem("activeCategory", activeCategory);
    currentEmoticonIndex = 0; // Reset the current emoticon index
    currentSortedEmoticons = getSortedEmoticons(activeCategory); // Add this line
    updateCategoryButtonsState(activeCategory);
    updateEmoticonsContainer();
    requestAnimationFrame(updateEmoticonHighlight);
  }
  function changeCategoryOnTabPress(e) {
    if (e.key === "Tab" && document.querySelector(".emoticons-popup")) {
      e.preventDefault();
      const keys = Object.keys(categories);
      let idx = keys.indexOf(localStorage.getItem("activeCategory"));
      idx = (idx + 1) % keys.length;
      if (
        keys[idx] === "Favourites" &&
        (JSON.parse(localStorage.getItem("favoriteEmoticons")) || []).length === 0
      ) {
        idx = 0;
      }
      const next = keys[idx];
      currentEmoticonIndex = 0; // Reset the current emoticon index on Tab press
      currentSortedEmoticons = getSortedEmoticons(next); // Add this line
      localStorage.setItem("activeCategory", next);
      changeActiveCategoryOnClick(next);
      requestAnimationFrame(updateEmoticonHighlight);
    }
  }
  function updateEmoticonHighlight() {
    requestAnimationFrame(() => {
      const buttons = document.querySelectorAll(".emoticon-buttons button");
      buttons.forEach((btn, idx) => {
        if (idx === currentEmoticonIndex) {
          btn.style.background = selectedButtonBackground;
        } else {
          const emoticon = btn.title;
          if (activeCategory !== "Favourites" && isEmoticonFavorite(emoticon)) {
            btn.style.background = activeButtonBackground;
          } else {
            btn.style.background = defaultButtonBackground;
          }
        }
      });
    });
  }
  function handleKeydownForEmoticons(e) {
    // Get the emoticon popup element
    const popup = document.querySelector(".emoticons-popup");
    if (!popup) return; // Exit if the popup is not found

    // Ensure there are available emoticons to navigate
    if (!currentSortedEmoticons || currentSortedEmoticons.length === 0) return;

    // Handle "Enter" key: insert the selected emoticon
    if (e.key === "Enter") {
      e.preventDefault(); // Prevent default action (e.g., form submission)

      const emoticon = currentSortedEmoticons[currentEmoticonIndex];
      insertEmoticonCode(emoticon); // Insert the selected emoticon
      incrementEmoticonUsage(emoticon); // Track usage for sorting

      // If "Ctrl + Enter" is pressed, close the emoticon popup
      if (e.ctrlKey) removeEmoticonsPopup();
    }
    // Handle left navigation: Move selection left (previous emoticon)
    else if (e.code === "ArrowLeft" || e.code === "KeyH") {
      e.preventDefault(); // Prevent unwanted scrolling or default behavior

      // Move index to the previous emoticon, looping if necessary
      currentEmoticonIndex =
        (currentEmoticonIndex - 1 + currentSortedEmoticons.length) % currentSortedEmoticons.length;

      updateEmoticonHighlight(); // Update the UI highlight
    }
    // Handle right navigation: Move selection right (next emoticon)
    else if (e.code === "ArrowRight" || e.code === "KeyL") {
      e.preventDefault(); // Prevent unwanted scrolling or default behavior

      // Move index to the next emoticon, looping if necessary
      currentEmoticonIndex =
        (currentEmoticonIndex + 1) % currentSortedEmoticons.length;

      updateEmoticonHighlight(); // Update the UI highlight
    }
  }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址