Jellyfin Forum
Add additional keyboard shortsuts for image selection - Printable Version

+- Jellyfin Forum (https://forum.jellyfin.org)
+-- Forum: Development (https://forum.jellyfin.org/f-development)
+--- Forum: Client Development (https://forum.jellyfin.org/f-client-development)
+--- Thread: Add additional keyboard shortsuts for image selection (/t-add-additional-keyboard-shortsuts-for-image-selection)



Add additional keyboard shortsuts for image selection - dilyo - 2025-03-15

I added some more keyboard shortcuts to edit images, because click path takes so long for modals to animate. Just throw the code into client.

Code:
// History of this ugly code - used with https://github.com/crittermike/shortkeys (split in per shortcut code, no shared functions). Moved it to a script, so i can use it in JMP.

function clickElement(selector) {
  // Do not use it while using an input like search
  const activeElement = document.activeElement;
  if (activeElement.tagName === "INPUT" || activeElement.tagName === "TEXTAREA") {
    return;
  }
  return new Promise((resolve) => {
    const element = document.querySelector(selector);
    if (element) {
      element.click();
      resolve();
    } else {
      reject();
    }
  });
}

// Loading and animation takes a moment for a element to match selector
function clickElementRetry(selector) {
  return new Promise((resolve) => {
    const interval = setInterval(() => {
      const element = document.querySelector(selector);
      if (element) {
        element.click();
        clearInterval(interval);
        resolve();
      }
    }, 100);
  });
}

// Loading and animation takes a moment for a element to match selector - edit hovered image type
function clickElementRetryImageType(selector, imageType) {
  return new Promise((resolve) => {
    const interval = setInterval(() => {
      const element = document.querySelector(selector);
      if (element) {
        element.setAttribute("data-imagetype", imageType);
        element.click();
        clearInterval(interval);
        resolve();
      }
    }, 100);
  });
}

function triggerRightClick(selector) {
  return new Promise((resolve) => {
    const element = document.querySelector(selector);
    if (element) {
      const event = new MouseEvent("contextmenu", {
        bubbles: true,
        cancelable: true,
        view: window,
      });
      element.dispatchEvent(event);
      resolve();
    } else {
      reject();
    }
  });
}

function extractTagValue(url) {
  const urlParams = new URLSearchParams(url.split("?")[1]);
  return urlParams.get("tag");
}

function getType() {
  const possibleTags = ["Logo", "Thumb", "Primary", "Art"];
  let imageWithTag;

  for (let tag of possibleTags) {
    imageWithTag = document.querySelector(
      `.card-withuserdata .cardImageContainer.coveredImage img[src*="/Images/${tag}?"]`
    );
    if (imageWithTag) {
      return tag;
    }
  }
  return "Primary";
}

document.addEventListener("keydown", function (event) {
  if (event.ctrlKey && event.key === "e") {
    event.preventDefault();
    if (
      document.querySelector(
        ".card-hoverable:hover, .detailPageContent div.listItem:hover"
      )
    ) {
      triggerRightClick(
        ".card-hoverable:hover, .detailPageContent div.listItem:hover"
      ).then(() => {
        let imageType = getType();
        clickElementRetry('[data-id="editimages"]').then(() => {
          clickElementRetryImageType(".btnBrowseAllImages", imageType);
        });
      });
    } else {
      clickElement(
        '#itemDetailPage:not(.hide) .detailRibbon [title="More"]'
      ).then(() => {
        clickElementRetry('[data-id="editimages"]').then(() => {
          clickElementRetry(".btnBrowseAllImages");
        });
      });
    }
  }

  if (event.ctrlKey && event.key === "3") {
    if (document.querySelector(".card-hoverable:hover")) {
      triggerRightClick(".card-hoverable:hover").then(() => {
        clickElementRetry('[data-id="editimages"]');
      });
    } else {
      clickElement(
        '#itemDetailPage:not(.hide) .detailRibbon [title="More"]'
      ).then(() => {
        clickElementRetry('[data-id="editimages"]');
      });
    }
  }

  if (event.key === "s") {
    clickElement('[href="#/search.html"]');
  }

  if (event.key === "h") {
    clickElement('[href="#/"]');
  }
});

Usage
Quote:
CTRL+e (e for edit and unused)
1. Dashboard (hovered element) - Next up/recently added episode will open selection for the primary episode image. Movie will open the image selection for primary image
2. List (hovered element) - will open the image selection for currently used image type (primary/thumb/logo or (clear)Art) - I know I patched art and also removed banner in my client
3. Search (hovered element) - will open the image selection for primary image
4. Detail view - opens primary image OR the hovered resource. e.g. current movie or episode, but if next up is hovered, it will open that instead

CTRL+3 (near e-key and unused)
Opens only overview for the images and not the selection

s
Go to search

h
Go to hone/dashboard



FAQ

Code looks complicated! Why?!
Over time, I wanted more and more features with same combo and I used a browser add-on, that had a code box per shortcut.
⇉ You Should use a switch-case! ⇒ "Fork" it and maintain it. Please post the plugin or PR below - thx  Ok-hand

Why not integrate it into jellyfin-web?
I know there is a shortcut functionality (used it before), but the jellyfin code base separates between devices types and shortcuts. I used a keyboard on Tablet, TV or Desktop. Easiest way was to just trigger/emulate the click path for my desired functionality. GitHub issues around shortcuts are stale, and I guess everybody wants something else (mapping and functions...). This can easily be adjusted for own needs e.g. add a metadata edit shortcut.

Known issues
- Clicks to fast and loading animation will be removed form finishing modal before - I DO NOT CARE (not a big fan of layering modals in a UI)
- s for search will also search for s (new - suppression of input was handled by browser add-on, before I moved to a script)

--- 

I shared this, because I found some useful stuff in forum and metadata manager has no image maintenance.