GreasyFork: Check To Search

1/8/2024, 6:31:18 PM

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        GreasyFork: Check To Search
// @namespace   Violentmonkey Scripts
// @match       https://greasyfork.org/*
// @grant       none
// @version     0.1.0
// @license     MIT
// @description 1/8/2024, 6:31:18 PM
// @run-at document-start
// ==/UserScript==


function fixURL(url) {
    url = url.replace(/(\/\w+\/\d+)\-[%\d\w-]+([?#]|$)/, '$1$2').replace(`${location.origin}/`, '/');
    if (!url) return '';
    if (url.includes('|')) return '';
    return url
  }


  const _genericTextChars = {
    1: '\x20\xA0\u2000-\u200A\u202F\u205F\u3000',
    2: '\u200B-\u200D\u2060\uFEFF',
    4: '\u180E\u2800\u3164',
    8: '\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\u2800',
    16: '\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\u2800\t\r\n' // plus tab (\x09), newline (\x0A), \r
  }
  const _genericTextREs = {
    1: new RegExp(`[${_genericTextChars[1]}]`, 'g'),
    2: new RegExp(`[${_genericTextChars[2]}]`, 'g'),
    4: new RegExp(`[${_genericTextChars[4]}]`, 'g'),
    8: new RegExp(`[${_genericTextChars[8]}]`, 'g'),
    16: new RegExp(`[${_genericTextChars[16]}]`, 'g')
  }
  function genericText(text, flag) {

    // https://unicode-explorer.com/articles/space-characters
    // https://medium.com/@ray102467/js-regex-3fbfe4d3115

    if (!text || typeof text !== 'string') return text;

    // regular space to space
    if (flag & 1) text = text.replace(_genericTextREs[1], (flag & (1 << 8)) ? '' : ' '); // 1 | 256

    // zero-width space to empty
    if (flag & 2) text = text.replace(_genericTextREs[2], '');

    // space chars to space
    if (flag & 4) text = text.replace(_genericTextREs[4], (flag & (1 << 8)) ? '' : ' '); // 4 | 1024

    // improper chars to empty
    if (flag & 8) text = text.replace(_genericTextREs[8], '');

    // improper+ chars to empty
    if (flag & 16) text = text.replace(_genericTextREs[16], '');

    return text;

  }

  const cssTextFn = () => `

  .r41-custom-search-input {
  font-size: 16px;
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  transition: border-color 0.3s ease;
  }

  .r41-custom-search-input:focus {
  border-color: #007bff; /* Blue border on focus */
  outline: none;
  }

  .r41-loading-spinner {
  display: none;
  border: 4px solid #f3f3f3; /* Light grey */
  border-top: 4px solid #3498db; /* Blue */
  border-radius: 50%;
  width: 20px;
  height: 20px;
  animation: r41-spin 2s linear infinite;
  margin-left: 4px;
  }

  @keyframes r41-spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
  }

  .r41-custom-search-input-header{

  display: flex;
  flex-direction: row;
  align-items: center;

  }

  .r41-custom-search-input{
  opacity: 0;
  position:absolute;
  z-index:-1;
  }
  .r41-custom-search-input:focus{
  opacity: 1;
  position:relative;
  z-index:initial;
  }
  .r41-custom-search-input:focus + .r41-loading-spinner {

  margin-left: -34px;
  }



  `;

  let onloadPromise = Promise.resolve();

  function addElements() {


    if (!document.querySelector('#gf_390_lz')) {

      // Load LZ-String library
      const script = document.createElement('script');
      script.id = 'gf_390_lz';
      script.src = 'https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.5.0/lz-string.min.js';
      document.head.appendChild(script);

      onloadPromise = new Promise(resolve => {
        script.onload = resolve;
      });

    }


    if (!document.querySelector('#gf_391_style')) {

      const style = document.createElement('style');
      style.id = 'gf_391_style';
      style.textContent = cssTextFn();
      document.head.appendChild(style);
    }


    return onloadPromise;

  }


  const onReadyPromise = new Promise((resolve) => {

    document.addEventListener('DOMContentLoaded', resolve)


  });

  const delayedReady = onReadyPromise.then(() => {

    if (!location.href.includes('/users/')) return false;
    new Promise(resolve => setTimeout(resolve, 200))
  });




  const hookToFn = (w) => {


    let currentPageListHtml = '';
    const {sectionId, storagePrefix, listId,elementIdPrefix} = w;

    delayedReady.then(async (xt) => {

      if (xt === false) return;

      const h3 = document.querySelector(`#${sectionId}>header>h3`);
      if (h3) {

        const onloadPromise = addElements();

        let onload2 = onloadPromise.then((xt) => {
          if(xt === false) return;
          checkCache();
          cacheCurrentPageList();
        });

        h3.addEventListener('click', function () {
          clicked = true;
          onload2.then(() => {
            if ((getSelection() + "") === "" && h3.isConnected === true) {
              if (showInput(h3) === false) replaceWithInput(h3);
            }
          });
        });
      }


    });


    function checkCache() {

      let urls = sessionStorage.getItem(`${storagePrefix}urls`) || "";
      if (urls.includes(`|${fixURL(location.href)}|`)) return;
      for (const url of urls.split('|')) {
        if (!url) continue;
        sessionStorage.removeItem(`${storagePrefix}${url}`);
      }
      sessionStorage.removeItem(`${storagePrefix}urls`);

    }

    function cacheCurrentPageList() {

      const listElement = document.querySelector(`ol#${listId}`);
      if (listElement) {
        currentPageListHtml = listElement.outerHTML;
        saveCache(location.href, currentPageListHtml);
      }
    }

    let globalChangeCounter = 0;


    function showInput(h3Element) {

      const input = document.getElementById(`${elementIdPrefix}custom-search-input`)
      if (!input) return false;
      // input.value = h3Element.textContent;
      // input.id = `${elementIdPrefix}custom-search-input`;
      // input.classList.add('r41-custom-search-input')
      // h3Element.parentNode.classList.add(`${elementIdPrefix}custom-search-input-header`, 'r41-custom-search-input-header');
      // h3Element.parentNode.insertBefore(input, h3Element.nextSibling)
      // h3Element.parentNode.replaceChild(input, h3Element);
      input.select();
      // input.addEventListener('input', () => handleInputChange(input));

      // Add loading spinner (hidden by default)
      // const spinner = document.createElement('div');
      // spinner.id = 'loading-spinner';
      // input.parentNode.insertBefore(spinner, input.nextSibling);
    }

    function replaceWithInput(h3Element) {
      const input = document.createElement('input');
      // input.value = h3Element.textContent;
      input.id = `${elementIdPrefix}custom-search-input`;
      input.classList.add('r41-custom-search-input')
      h3Element.parentNode.classList.add(`${elementIdPrefix}custom-search-input-header`, 'r41-custom-search-input-header');
      h3Element.parentNode.insertBefore(input, h3Element.nextSibling)
      // h3Element.parentNode.replaceChild(input, h3Element);
      input.select();
      input.addEventListener('input', () => handleInputChange(input));

      // Add loading spinner (hidden by default)
      const spinner = document.createElement('div');
      spinner.classList.add('r41-loading-spinner')
      spinner.id = `${elementIdPrefix}loading-spinner`;
      input.parentNode.insertBefore(spinner, input.nextSibling);
    }
    let lastState = false;
    function setLoadingState(isLoading) {
      if (lastState === isLoading) return;
      const spinner = document.getElementById(`${elementIdPrefix}loading-spinner`);
      if (spinner) {
        spinner.style.display = isLoading ? 'inline-block' : 'none';
      }
      lastState = isLoading;
    }

  let busyCache = 0;

    async function handleInputChange(input) {
      const currentChangeCount = ++globalChangeCounter;
      if (busyCache === 0) {
        setLoadingState(true);
        await fetchAndCacheScripts();
        await new Promise(resolve => setTimeout(resolve, 140));
      } else if (busyCache === 1) {
        setLoadingState(true);
        await new Promise(resolve => setTimeout(resolve, 600));
      } else if (busyCache === 2) {
        setLoadingState(true);
        await new Promise(resolve => setTimeout(resolve, 140));
      }
      if (currentChangeCount === globalChangeCounter) {
        setLoadingState(true);
        const filteredResults = await fetchAndFilterScripts(input.value);
        updateCurrentList(filteredResults);
        setLoadingState(false);
      }
    }

    function saveCache(url, text) {
      url = fixURL(url);
      if (!url) return;
      text = LZString.compress(text || "");
      text = text || ""
      sessionStorage.setItem(`${storagePrefix}${url}`, text);
      sessionStorage.setItem(`${storagePrefix}urls`, (sessionStorage.getItem(`${storagePrefix}urls`) || "") + "|" + url + "|");

    }
    function restoreCache(url) {
      url = fixURL(url);
      if (!url) return;
      let text = LZString.decompress(sessionStorage.getItem(`${storagePrefix}${url}`) || "") || "";
      return text;

    }

    async function fetchAndCacheScripts() {
      if (busyCache > 0) return;
      busyCache = 1;
      const pages = Array.from(document.querySelectorAll(`#${sectionId} .pagination > a[href]`))
        .map(link => link.getAttribute('href'));


      let mMap = new Map();
      for (const url of pages) {
        mMap.set(fixURL(url), url)

      }


      for (const [fixedURL, pageURL] of mMap) {
        const page = pageURL;
        let url = fixedURL;
        if (url && !sessionStorage.getItem(`${storagePrefix}${url}`)) {
          const response = await fetch(page);
          const text = await response.text();
          console.log(123, Date.now(), page)
          saveCache(page, text);
        }
      }
      busyCache = 2;
    }



    async function fetchAndFilterScripts(inputValue) {
      inputValue = inputValue || '';
      inputValue = genericText(inputValue, 1 | 2 | 8);
      let pages = Array.from(document.querySelectorAll(`#${sectionId} .pagination > a[href]`))
        .map(link => link.getAttribute('href'));

      let mMap = new Map();
      for (const url of pages) {
        mMap.set(fixURL(url), url)

      }

      const allScripts = [];

      // Get current page's list from sessionStorage
      const currentPageHtml = restoreCache(location.href);
      const currentDoc = new DOMParser().parseFromString(currentPageHtml, 'text/html');
      filterScripts(currentDoc, inputValue || true, allScripts);
      for (const [fixedURL, pageURL] of mMap) {

        let page = pageURL;
        let pageHtml = restoreCache(page);
        if (!pageHtml) {
          const response = await fetch(page);
          const text = await response.text();
          console.log(456, Date.now(), page)
          saveCache(page, text);
          pageHtml = text;
        }

        const doc = new DOMParser().parseFromString(pageHtml, 'text/html');
        filterScripts(doc, inputValue || false, allScripts);

      }

      return allScripts;
    }

    function filterScripts(doc, inputValue, allScripts) {
      if (inputValue === false) return;
      const scripts = doc.querySelectorAll(`ol#${listId} li[data-script-id]`);
      scripts.forEach(li => {
        const html = getContentHTML(li, inputValue);
        if (html) allScripts.push(html);
      });
    }

    function getContentHTML(li, inputValue) {
      const name = genericText(li.querySelector('a.script-link[href]').textContent, 1 | 2 | 8);
      const description = genericText(li.querySelector('.script-description').textContent, 1 | 2 | 8);
      let text = name + `\n` + description;
      if (inputValue === true || text.toLowerCase().includes(inputValue.toLowerCase())) {
        return li.outerHTML;
      }
    }

    function updateCurrentList(filteredResults) {
      const list = document.querySelector(`ol#${listId}`);
      if (list) {
        list.innerHTML = filteredResults.join('');
      }
    }


  };

  hookToFn({
    listId: 'user-script-list',
    sectionId: 'user-script-list-section',
    storagePrefix: 'gF_7H8TV_',
    elementIdPrefix: 'gimoa-'
  });

  hookToFn({
    listId: 'user-unlisted-script-list',
    sectionId: 'user-unlisted-script-list-section',
    storagePrefix: 'gF_84IUu_',
    elementIdPrefix: 'jexsq-'
  });

  hookToFn({
    listId: 'user-library-script-list',
    sectionId: 'user-library-list-section',
    storagePrefix: 'gF_39rrO_',
    elementIdPrefix: 'm01xt-'
  });