Declutter LinkedIn

Remove the feed, LinkedIn Premium upsells, news, and other clutter from LinkedIn

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

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

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

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.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name         Declutter LinkedIn
// @namespace    August4067
// @version      0.2.0-alpha
// @description  Remove the feed, LinkedIn Premium upsells, news, and other clutter from LinkedIn
// @license      MIT
// @match        https://www.linkedin.com/*
// @icon         https://www.linkedin.com/favicon.ico
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @require      https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js
// ==/UserScript==

/*--- waitForKeyElements():  A utility function, for Greasemonkey scripts,
    that detects and handles AJAXed content.

    Usage example:

        waitForKeyElements (
            "div.comments"
            , commentCallbackFunction
        );

        //--- Page-specific function to do what we want when the node is found.
        function commentCallbackFunction (jNode) {
            jNode.text ("This comment changed by waitForKeyElements().");
        }

    IMPORTANT: This function requires your script to have loaded jQuery.
*/

// Pulled from: https://gist.github.com/raw/2625891/waitForKeyElements.js
function waitForKeyElements(
  selectorTxt /* Required: The jQuery selector string that
                        specifies the desired element(s).
                    */,
  actionFunction /* Required: The code to run when elements are
                        found. It is passed a jNode to the matched
                        element.
                    */,
  bWaitOnce /* Optional: If false, will continue to scan for
                        new elements even after the first match is
                        found.
                    */,
  iframeSelector /* Optional: If set, identifies the iframe to
                        search.
                    */,
) {
  var targetNodes, btargetsFound;

  if (typeof iframeSelector == "undefined") targetNodes = $(selectorTxt);
  else targetNodes = $(iframeSelector).contents().find(selectorTxt);

  if (targetNodes && targetNodes.length > 0) {
    btargetsFound = true;
    /*--- Found target node(s).  Go through each and act if they
            are new.
        */
    targetNodes.each(function () {
      var jThis = $(this);
      var alreadyFound = jThis.data("alreadyFound") || false;

      if (!alreadyFound) {
        //--- Call the payload function.
        var cancelFound = actionFunction(jThis);
        if (cancelFound) btargetsFound = false;
        else jThis.data("alreadyFound", true);
      }
    });
  } else {
    btargetsFound = false;
  }

  //--- Get the timer-control variable for this selector.
  var controlObj = waitForKeyElements.controlObj || {};
  var controlKey = selectorTxt.replace(/[^\w]/g, "_");
  var timeControl = controlObj[controlKey];

  //--- Now set or clear the timer as appropriate.
  if (btargetsFound && bWaitOnce && timeControl) {
    //--- The only condition where we need to clear the timer.
    clearInterval(timeControl);
    delete controlObj[controlKey];
  } else {
    //--- Set a timer, if needed.
    if (!timeControl) {
      timeControl = setInterval(function () {
        waitForKeyElements(
          selectorTxt,
          actionFunction,
          bWaitOnce,
          iframeSelector,
        );
      }, 100);
      controlObj[controlKey] = timeControl;
    }
  }
  waitForKeyElements.controlObj = controlObj;
}

// SETTINGS
const SETTING_REMOVE_FEED = "removeFeed";
const SETTING_REMOVE_NEWS = "removeNews";

class Setting {
  constructor(settingName, settingDisplayName, settingDefault) {
    this.settingName = settingName;
    this.settingDisplayName = settingDisplayName;
    this.settingDefault = settingDefault;
  }

  currentValue() {
    return GM_getValue(this.settingName, this.settingDefault);
  }

  toggleSetting() {
    var current = this.currentValue();
    GM_setValue(this.settingName, !current);
  }
}

class SettingRemoveFeed extends Setting {
  constructor() {
    super(SETTING_REMOVE_FEED, "Remove feed", true);
  }
}

class SettingRemoveNews extends Setting {
  constructor() {
    super(SETTING_REMOVE_NEWS, "Remove news", true);
  }
}

const SETTINGS = {
  [SETTING_REMOVE_FEED]: new SettingRemoveFeed(),
  [SETTING_REMOVE_NEWS]: new SettingRemoveNews(),
};

// MENU SETTINGS
function toggleMenuSetting(settingName) {
  var setting = SETTINGS[settingName];
  setting.toggleSetting();
  updateSettingsMenu();
  console.debug(`Setting ${settingName} set to: ${setting.currentValue()}}`);
  location.reload();
}

function updateSettingsMenu() {
  for (const [setting_name, setting] of Object.entries(SETTINGS)) {
    GM_registerMenuCommand(
      `${setting.settingDisplayName}: ${setting.currentValue() ? "Enabled" : "Disabled"}`,
      () => {
        toggleMenuSetting(setting_name);
      },
    );
  }
}

function waitAndRemove(selector, func) {
  if (func === undefined) {
    func = (node) => {
      node.style.display = "none";
    };
  }
  waitForKeyElements(selector, function (nodes) {
    if (nodes && nodes.length > 0 && nodes[0]) {
      func(nodes[0]);
    }
  });
}

function removeNotificationBadgeFromHomeIcon() {
  waitAndRemove(
    'li[class^="global-nav__primary-item"] span[class^="notification-badge"]',
  );
  waitAndRemove(
    'a[data-view-name="navigation-homepage"] span[class$="_1ptbkx61f4"]',
  );
}

function removeProfilePrompts() {
  waitAndRemove("#promo", (node) => node.parentElement.remove());
  waitAndRemove("#guidance", (node) => node.parentElement.remove());
  waitAndRemove("#resources", (node) => node.parentElement.remove());
}

function removeNewsFromRightSidebar() {
  if (!SETTINGS[SETTING_REMOVE_NEWS].currentValue()) {
    return;
  }

  waitAndRemove('aside[aria-label="LinkedIn News"]');
}

function removeUpsellLinks() {
  waitAndRemove(".premium-upsell-link--extra-long");
  waitAndRemove(".premium-upsell-link");
  waitAndRemove(
    'span[class="global-nav__secondary-premium-cta-text"]',
    (node) => node.parentElement.parentElement.parentElement.remove(),
  );
  waitAndRemove(
    'span[class="feed-identity-module__premium-cta-text"]',
    (node) => node.closest('div[class^="artdeco-card"]')?.remove(),
  );

  // Remove upsell section on mynetwork page
  waitAndRemove(
    'div[data-view-name="premium-upsell-card"]',
    (node) => (node.closest("section").style.display = "none"),
  );

  // Remove "try premium for $0" navbar menu item
  waitAndRemove(
    'a[data-view-name="premium-nav-upsell-text"]',
    (node) => (node.closest("li").style.display = "none"),
  );

  // Remove "try 1 month of premium for $0" in profile dropdown
  waitAndRemove('div[data-view-name="nav-me-my-premium"]', (node) =>
    node.parentElement.remove(),
  );

  // Remove premium upsell on contact info edit card
  waitAndRemove('h3[class$="card-upsell-v2__headline"]', (node) =>
    node.parentElement?.parentElement?.parentElement?.remove(),
  );
}

function removeRightSidebarOnProfile() {
  waitAndRemove(".scaffold-layout__aside");
}

function removeForBusinessDropDown() {
  document.querySelector('span[title="For Business"]')?.closest("li")?.remove();
}

function removeProfileEnhanceCTAs() {
  // Remove enhance button from top of profile
  waitForKeyElements(
    'button[class^="pvs-profile-actions__custom-action"]',
    function (nodes) {
      var customActionButtons = document.querySelectorAll(
        'button[class^="pvs-profile-actions__custom-action"]',
      );
      customActionButtons.forEach((button) => {
        if (
          button.innerText &&
          button.innerText.trim().toLowerCase() === "enhance profile"
        ) {
          button.remove();
        }
      });
    },
  );

  // Premium upsell with "enhance with AI" buttons on edit experience page
  waitForKeyElements(
    'svg[data-test-icon="premium-signal-ai-small"]',
    function (nodes) {
      var aiIcons = document.querySelectorAll(
        'svg[data-test-icon="premium-signal-ai-small"]',
      );
      aiIcons.forEach((icon) => {
        icon.closest("button").remove();
      });
    },
  );

  // Premium upsell with "enhance with AI" suggestion on edit about modal
  waitAndRemove('div[class^="ai-suggestions-bar"]');
}

function removeFeed() {
  if (!SETTINGS[SETTING_REMOVE_FEED].currentValue()) {
    return;
  }

  waitAndRemove('main[aria-label="Main Feed"]', (node) => {
    node.querySelector('div[class="relative"]')?.remove();
    var buttons = node.querySelectorAll("button");
    if (buttons.length > 0) {
      buttons[buttons.length - 1].remove();
    }
  });
}

function removeUpsellsFromEditIntroModal() {
  waitAndRemove('div[class$="pe-profile-top-card-form__upsell-wrapper"]');
}

function removeUpsellsFromOtherProfiles() {
  waitAndRemove('button[class="pv-top-card__premium-feature-banner"]');
}

function removeUpsellsFromSearchResults() {
  waitAndRemove('button[id="searchFilter_upsellFilter"]', (node) =>
    node.closest('li[class="search-reusables__primary-filter"]')?.remove(),
  );
}

function removeUpsellsFromSearchAppearances() {
  waitAndRemove(
    'div[class="member-analytics-addon-insight-v2  member-analytics-addon-insight-v2--bg-default"]',
  );
}

function removeSuggestedSearches() {
  waitAndRemove('ul[aria-label="Search suggestions"]');
  waitAndRemove(
    'h2[class="search-typeahead-v2__section-header pt3 pb2 t-black ph4 t-14 search-typeahead-v2__section-header--top-divide t-bold"]',
  );
}

// Shows up when you are on the /notifications page
function removeTurnNotificationsBackOnRequestBanner() {
  waitAndRemove('div[class="nt-setting-banner__container"]', (node) =>
    node.closest("article").remove(),
  );
}

function removeUpsellLinksFromJobsSearch() {
  // Remove the upsell from the job posting
  waitAndRemove(
    'a[class="ember-view job-details-how-you-fit__upsell-link"]',
    (node) => node.closest("div").remove(),
  );

  // Remove the upsell from the all filters sidebar on the right
  waitAndRemove('li[class="search-reusables__secondary-filters-upsell"]');
}

(function () {
  "use strict";

  updateSettingsMenu();

  removeNotificationBadgeFromHomeIcon();
  removeNewsFromRightSidebar();
  removeRightSidebarOnProfile();
  removeProfilePrompts();
  removeForBusinessDropDown();
  removeFeed();
  removeProfileEnhanceCTAs();
  removeUpsellLinks();
  removeUpsellLinksFromJobsSearch();
  removeUpsellsFromEditIntroModal();
  removeUpsellsFromOtherProfiles();
  removeUpsellsFromSearchResults();
  removeUpsellsFromSearchAppearances();
  removeSuggestedSearches();
  removeTurnNotificationsBackOnRequestBanner();
})();