Custom CSS Thread

Posted under General

Different colors for Builders, Approvers and "Restricted" users

Show
body {
   --user-builder-color: #8870ff;
   --user-contributor-color: #de9cff;
   --user-approver-color: #ff31c7;
   --user-restricted-color: #cfe8ff;
}

body a.user-approver {
    color: var(--user-approver-color);
}

body a.user-contributor {
    color: var(--user-contributor-color);
}

body a.user-builder {
    color: var(--user-builder-color);
}

.user-tooltip-badge-contributor {
    background-color: var(--user-contributor-color) !important;
}

.user-tooltip-badge-approver {
    background-color: var(--user-approver-color) !important;
}

Red strikethrough for banned users

Show
a.user.user-member.user-banned {
    text-decoration: line-through;
    text-decoration-color: red;
}

EDIT 1: Fixed colors for badges that appear in user hover tooltips.

Updated by user 1211591

Highlight private favgroups

(Because I was getting lost, and wanted to add table rows for this but got lazy and settled for CSS instead.)

Show
/* Highlight private favgroups */
[data-is-public='false'] tr, tr[data-is-public='false'] {
  .name-column::before { content: "[Private]"; color: red; display: inline-block; };
}

Scale pixel art more appropriately:

[data-tags~="pixel_art"] { image-rendering: pixelated; }

Useful when the browser upscales thanks to device pixel ratio or downscales thanks to inadequate space or a pre-upscaled source image.
Works on thumbnails too but with mediocre effects since the thumbnail sample was already non-pixel scaled from the original.

Sometimes you'll find a post like post #7650090 or post #8749942 which has pixel art along with non-pixel art. If you prefer to prioritize the non-pixel rendering there, could try:

[data-tags~="pixel_art"]:not([data-tags~="multiple_views"]) { image-rendering: pixelated; }

Updated by Super Affection

Is it possible to replace the thumbnail border for pending posts with something that stands out more in the catalog, like a solid color background? Ideally it would apply only to pending posts and not child/parent etc.

alphashitlord said:

Would it be possible to put a slash through banned artists' tags like with forum #310250? Just in case I try to upload their work without realizing

You're allowed to upload banned artists. This was also the case before the recent changes.

Strikethrough for banned artists

Because it's not possible to select elements that contain banned artists directly, we need to retrieve data for all banned artists instead. This means you'll need to periodically update the CSS to account for new bans.

Paste the code below into your browser’s console and run it. This will generate a CSS style sheet [example] that adds a strikethrough to all banned artists. The script includes optional configuration settings at the top.

Show
const config = {
  sidebarQuestionMark: true,
  dtextTag: true, // [[artist_name]]
  dtextId: true // artist #1234
};

async function fetchBannedArtistData() {
  let page = 1;
  let allData = [];
  while (true) {
    const resp = await fetch("/artists.json?search[is_banned]=true&search[order]=created_at&only=id,name&limit=200&page=" + page);
    if (!resp.ok) throw new Error(`HTTP error: ${resp.status}`);
    const data = await resp.json();
    if (!Array.isArray(data)) throw new Error("Expected an array in response");
    allData.push(...data);
    if (data.length < 200) break;
    else page++;
  }
  return allData;
}

const sidebarSelector = name => {
  return config.sidebarQuestionMark ? `.tag-type-1 [href*='=${name}&']` : `.tag-type-1 [href*='tags=${name}&']`;
};
const dtextTagSelector = name => `.tag-type-1[href$='=${name}']`;
const dtextIdSelector = id => `[href='/artists/${id}']`;

async function generateCss() {
  const data = await fetchBannedArtistData();
  data.forEach(i => {
    i.name = escape(i.name);
  });
  let sel = ["", "", ""];
  data.forEach(i => {
    sel[0] += sidebarSelector(i.name) + ",";
    if (config.dtextTag) sel[1] += dtextTagSelector(i.name) + ",";
    if (config.dtextId) sel[2] += dtextIdSelector(i.id) + ",";
  });
  return sel.join("").slice(0, -1) + "{text-decoration:line-through}\n";
}

const css = await generateCss();
copy(css);
console.warn("If the code doesn’t automatically copy the CSS to your clipboard, please manually enter copy(css) to copy it.");
console.log("CSS generated & copied.");

TODO: Provide an automated script to keep the custom CSS up to date. See forum #363012

Updated by Sibyl

Sibyl said:

TODO: Provide an automated script to keep the custom CSS up to date.

You could distribute it via Gist for example and provide that as the downloadURL. It would just be you updating the actual script on your end and the userscript extension should pull the newest content and ask the player if they want to update their script. Though I guess that's only relevant if the code itself needs to be changed and not something generated then cached forever.

[Userscript] Strikethrough for banned artists

Because it's not possible to select elements that contain banned artists directly, we need to retrieve data for all banned artists instead. This means you'll need to periodically update the CSS to account for new bans.

See forum #370967.

Although this userscript is published under Custom CSS Thread, its main purpose is to provide data updates for your custom CSS in user settings, while ensuring that any other custom styles remain untouched.
You can now use this userscript instead of the browser console script above.
The script offers both manual and automatic updates through the menu options. There are also some customizable options available at the top of the script code.

Show
// ==UserScript==
// @name        Strikethrough for banned artists
// @namespace   https://danbooru.donmai.us/forum_topics/9662
// @match       *://*.donmai.us/*
// @version     1.0
// @author      Sibyl
// @description Add strikethrough for banned artists via custom CSS.
// @grant       GM_registerMenuCommand
// @grant       GM_unregisterMenuCommand
// ==/UserScript==

// Set your preferences here
const preference = {
  updateInterval: 7 * 24 * 60 * 60 * 1000, // 7 days, in milliseconds
  sidebarQuestionMark: true,
  dtextTag: true, // [[artist_name]]
  dtextId: true // artist #1234
};
// Set your preferences ↑↑↑

const config = JSON.parse(localStorage.getItem("s4ba_config")) || {
  lastUpdate: 0,
  autoUpdate: true
};

const saveConfig = () => localStorage.setItem("s4ba_config", JSON.stringify(config));

GM_registerMenuCommand("🗑️ Remove from custom CSS", () => {
  config.autoUpdate = false;
  updateCustomCss(false);
});
GM_registerMenuCommand("✍️ Manually fetch artists info", updateCustomCss);

const nextUpdateStr = () => {
  if (!config.autoUpdate) return "disabled";
  else {
    if (config.lastUpdate === 0) return `(Next: ${new Date(Date.now()).toLocaleString()})`;
    else return `(Next: ${new Date(config.lastUpdate + preference.updateInterval).toLocaleString()})`;
  }
};
let menuId = GM_registerMenuCommand((config.autoUpdate ? "✔️" : "❌") + " Auto update " + nextUpdateStr(), function toggleAutoUpdate() {
  GM_unregisterMenuCommand(menuId);
  config.autoUpdate = !config.autoUpdate;
  Danbooru.Utility.notice("Auto update banned artists info " + (config.autoUpdate ? "enabled. " + nextUpdateStr() : "disabled"));
  const prefix = config.autoUpdate ? "✔️" : "❌";
  menuId = GM_registerMenuCommand(prefix + " Auto Update " + nextUpdateStr(), toggleAutoUpdate);
  saveConfig();
});

if (config.autoUpdate && Date.now() - config.lastUpdate > preference.updateInterval) updateCustomCss();

async function updateCustomCss(update = true) {
  Danbooru.notice("Fetching current custom CSS...");
  const oldCss = (await (await fetch("/settings.json?only=custom_style")).json()).custom_style.trim();
  const pattern = /\s*\/\* S4BA START (\d+) \*\/[\s\S]+?\/\* S4BA END \*\/\s*/;
  if (update) {
    Danbooru.notice("Fetching banned artists info...");
    const data = await fetchBannedArtistData();
    let newCss = generateCss(data),
      ts = Date.now();
    newCss = `\n\n/* S4BA START ${ts} */\n${newCss}\n/* S4BA END */\n\n`;
    if (pattern.test(oldCss)) newCss = oldCss.replace(pattern, newCss);
    else newCss = oldCss + newCss;
    if (await applySettings(newCss.trim())) {
      Danbooru.Utility.notice(`Custom CSS updated successfully. ${data.length} banned artists found.`);
      config.lastUpdate = ts;
      saveConfig();
    }
  } else {
    Danbooru.notice("Removing strikethrough code from custom CSS settings...");
    const newCss = oldCss.replace(pattern, "\n\n").trim();
    if (await applySettings(newCss)) {
      Danbooru.Utility.notice("Strikethrough code removed successfully.");
      saveConfig();
    }
  }
  location.pathname === "/settings" && setTimeout(() => location.reload(), 1000);
}

async function applySettings(css) {
  const resp = await fetch("/users/" + Danbooru.CurrentUser.data("id"), {
    body: new URLSearchParams({
      commit: "Submit",
      authenticity_token: Danbooru.Utility.meta("csrf-token"),
      _method: "patch",
      "user[custom_style]": css
    }),
    method: "POST"
  });
  if (resp.ok) return true;
  else {
    const tip = "Failed to apply settings: " + resp.status;
    Danbooru.Utility.error(tip);
    throw new Error(tip);
  }
}

async function fetchBannedArtistData() {
  let page = 1;
  let allData = [];
  while (true) {
    const resp = await fetch("/artists.json?search[is_banned]=true&search[order]=created_at&only=id,name&limit=200&page=" + page);
    if (!resp.ok) {
      const msg = `Failed to get artist info: ${resp.status}`;
      Danbooru.Utility.error(msg);
      throw new Error(msg);
    }
    const data = await resp.json();
    if (!Array.isArray(data)) {
      const msg = "Failed to get artist info: Expected an array in response";
      Danbooru.Utility.error(msg);
      throw new Error(msg);
    }
    allData.push(...data);
    if (data.length < 200) break;
    else page++;
  }
  return allData;
}

const sidebarSelector = name => {
  return preference.sidebarQuestionMark ? `.tag-type-1 [href*='=${name}&']` : `.tag-type-1 [href*='tags=${name}&']`;
};
const dtextTagSelector = name => `.tag-type-1[href$='=${name}']`;
const dtextIdSelector = id => `[href='/artists/${id}']`;

function generateCss(data) {
  data.forEach(i => {
    i.name = escape(i.name);
  });
  let sel = ["", "", ""];
  data.forEach(i => {
    sel[0] += sidebarSelector(i.name) + ",";
    if (preference.dtextTag) sel[1] += dtextTagSelector(i.name) + ",";
    if (preference.dtextId) sel[2] += dtextIdSelector(i.id) + ",";
  });
  return sel.join("").slice(0, -1) + "{text-decoration:line-through}";
}

Updated by Sibyl

Sibyl said:

Strikethrough for banned artists

Because it's not possible to select elements that contain banned artists directly, we need to retrieve data for all banned artists instead. This means you'll need to periodically update the CSS to account for new bans.

Paste the code below into your browser’s console and run it. This will generate a CSS style sheet [example] that adds a strikethrough to all banned artists. The script includes optional configuration settings at the top.

Show
const config = {
  sidebarQuestionMark: true,
  dtextTag: true, // [[artist_name]]
  dtextId: true // artist #1234
};

async function fetchBannedArtistData() {
  let page = 1;
  let allData = [];
  while (true) {
    const resp = await fetch("/artists.json?search[is_banned]=true&search[order]=created_at&only=id,name&limit=200&page=" + page);
    if (!resp.ok) throw new Error(`HTTP error: ${resp.status}`);
    const data = await resp.json();
    if (!Array.isArray(data)) throw new Error("Expected an array in response");
    allData.push(...data);
    if (data.length < 200) break;
    else page++;
  }
  return allData;
}

const sidebarSelector = name => {
  return config.sidebarQuestionMark ? `.tag-type-1>[href*='=${name}&']` : `.tag-type-1>[href*='tags=${name}&']`;
};
const dtextTagSelector = name => `.tag-type-1[href$='=${name}']`;
const dtextIdSelector = id => `[href='/artists/${id}']`;

async function generateCss() {
  const data = await fetchBannedArtistData();
  data.forEach(i => {
    i.name = escape(i.name);
  });
  let sel = ["", "", ""];
  data.forEach(i => {
    sel[0] += sidebarSelector(i.name) + ",";
    if (config.dtextTag) sel[1] += dtextTagSelector(i.name) + ",";
    if (config.dtextId) sel[2] += dtextIdSelector(i.id) + ",";
  });
  return sel.join("").slice(0, -1) + "{text-decoration:line-through}\n";
}

const css = await generateCss();
copy(css);
console.warn("If the code doesn’t automatically copy the CSS to your clipboard, please manually enter copy(css) to copy it.");
console.log("CSS generated & copied.");

TODO: Provide an automated script to keep the custom CSS up to date.

Tried putting this in ViolentMonkey and got a couple of errors:

SyntaxError: await is only valid in async functions, async generators and modules
Error: Promised response from onMessage listener went out of scope

Copy-pasting the CSS works though (albeit not on the sidebar)

WRS said:

The code won’t need frequent updates as long as the website itself doesn’t change.

alphashitlord said:
Tried putting this in ViolentMonkey and got a couple of errors:

Because the code in the forum #362999 is only intended to run in the browser console, not as a userscript.

albeit not on the sidebar

Fixed.

1 16 17 18 19 20