Skip to content

Instantly share code, notes, and snippets.

@0xdevalias
Last active December 5, 2023 14:33
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Embed
What would you like to do?
Custom themes/CSS styling hacks/overrides for Beeper (universal chat app aggregator, built on top of matrix)

Beeper Custom Theme Styles

Table of Contents

devalias' Beeper CSS Hacks

See devalias-beeper-css-hacks.css for the customisations I am using myself.

You'll also find a number of hacks/techniques on my theme issue:

See Also

My Other Related Deepdive Gist's and Projects

Bonus Section

Accessing the Matrix client from within Beeper / Electron's DevTools

mxMatrixClientPeg.matrixClient

eg. to send a message:

const roomId = "!KlacjKWnARbprTLuRM:nova.chat";

mxMatrixClientPeg.matrixClient.sendMessage(roomId, {
  msgtype: "m.text",
  body: "This is a test message sent using mxMatrixClientPeg.matrixClient.sendMessage"
})

Enabling 'internal' mode to see extra labs features/etc

(The following tip comes via @cameronaaron from the Beeper Community)

Open DevTools console, and enter the following:

bpWhoamiMonitor.whoami.userInfo.channel = "INTERNAL"

Then go to your Beeper Settings -> Labs -> and look at all the new features you can flip (note that you can also probably flip all of these directly via /devtools -> Settings Explorer as well)

Accessing the React internals for the Beeper 'Rooms' component using Beeper / Electron's DevTools

const el = $('#matrixchat > .mx_MatrixChat_wrapper > .mx_MatrixChat > .bp_LeftPanel > .bp_LeftPanel_contentWrapper > .bp_LeftPanel_content > .rooms')
 
const elProps = Object.getOwnPropertyNames(el);
 
const elReactFiberKey = elProps.filter(k => k.includes('__reactFiber'))
const elReactPropsKey = elProps.filter(k => k.includes('__reactProps'))

const elReactInternals = {
  reactFiber: el[elReactFiberKey],
  reactProps: el[elReactPropsKey],
}
 
//console.log(elReactInternals)

const UnreadList = elReactInternals.reactProps.children[3].props.children[0]
const ReadList = elReactInternals.reactProps.children[3].props.children[1]

console.log('Inbox Chats', UnreadList.props.unreads)
// (303)Β [Room, Room, ...]

console.log('Archived Chats', ReadList.props.rooms)
// (1633)Β [Room, Room, ...]

Some notes on how to achieve custom Beeper / Electron JS hacks/customisations (eg. more customizability than CSS hacks alone)

Warning: This section is way more advanced than CSS hacks, and comes with much higher risks if you run arbitrary untrusted code, as using malicious JS code might steal your beeper auth tokens, any of your private messages, etc, etc. You've been warned.

These notes originally come from my Debugging Electron Apps (and related memory issues) (0xdevalias gist) gist (Ref):

On macOS you can get to the Beeper *.asar files by:

β‡’ cd /Applications/Beeper.app/Contents/Resources

β‡’ ls
af.lproj/           cs.lproj/      fa.lproj/   icon.icns  ml.lproj/     ru.lproj/  todesktop-runtime-config.json
am.lproj/           da.lproj/      fi.lproj/   icons/     mr.lproj/     sk.lproj/  tr.lproj/
app-update.yml      de.lproj/      fil.lproj/  id.lproj/  ms.lproj/     sl.lproj/  uk.lproj/
app.asar            el.lproj/      fr.lproj/   it.lproj/  nb.lproj/     sr.lproj/  ur.lproj/
app.asar.unpacked/  en.lproj/      gu.lproj/   ja.lproj/  nl.lproj/     sv.lproj/  vi.lproj/
ar.lproj/           en_GB.lproj/   he.lproj/   kn.lproj/  pl.lproj/     sw.lproj/  webapp.asar
bg.lproj/           es.lproj/      hi.lproj/   ko.lproj/  pt_BR.lproj/  ta.lproj/  zh_CN.lproj/
bn.lproj/           es_419.lproj/  hr.lproj/   lt.lproj/  pt_PT.lproj/  te.lproj/  zh_TW.lproj/
ca.lproj/           et.lproj/      hu.lproj/   lv.lproj/  ro.lproj/     th.lproj/

Where the most relevant files/folders there are:

  • app.asar
  • app.asar.unpacked/
  • webapp.asar

From memory, I believe app.asar is more related to the core electron/element type features, and webapp.asar was more related to the more custom Beeper features; but I didn't look super deeply into that side of things.

We can then use the node asar package via npx to inspect the contents of the *.asar files:

β‡’ npx asar list --is-pack app.asar | grep -v node_modules

β‡’ npx asar list --is-pack webapp.asar | grep -v node_modules

We can then run Beeper passing the node remote debugging --inspect-brk command to set a breakpoint at the entrypoint of the code:

β‡’ open /Applications/Beeper.app --args --inspect-brk=1337

Which we can then connect to by opening a Chrome browser, navigating to chrome://inspect/#devices, and under 'Remote Target' looking for something like the following:

image

electron/js2c/browser_init file:///

Then clicking on 'inspect', which will open the Chrome DevTools 'Sources' tab and show the entrypoint line where the debugger has stopped execution, in this case, in the file:///Applications/Beeper.app/Contents/Resources/app.asar/lib/electron-main.js file:

image

We can then skim around the code in this file to understand what it does, and what other options are available.

For example, here are some command line arguments documentation; of which --devtools sounds interesting:

if (argv["help"]) {
    console.log("Options:");
    console.log("  --profile-dir {path}: Path to where to store the profile.");
    console.log("  --profile {name}:     Name of alternate profile to use, allows for running multiple accounts.");
    console.log("  --devtools:           Install and use react-devtools and react-perf.");
    console.log("  --no-update:          Disable automatic updating.");
    console.log("  --default-frame:      Use OS-default window decorations.");
    console.log("  --hidden:             Start the application hidden in the system tray.");
    console.log("  --help:               Displays this help message.");
    console.log("And more such as --proxy, see:" +
        "https://electronjs.org/docs/api/command-line-switches");
    electron_1.app.exit();
}

We can also see some path loading aspects of where the app looks for webapp.asar:

// Find the webapp resources and set up things that require them
async function setupGlobals() {
    // find the webapp asar.
    asarPath = await tryPaths("webapp", __dirname, [
        // If run from the source checkout, this will be in the directory above
        '../webapp.asar',
        // but if run from a packaged application, electron-main.js will be in
        // a different asar file so it will be two levels above
        '../../webapp.asar',
        // also try without the 'asar' suffix to allow symlinking in a directory
        '../webapp',
        // from a packaged application
        '../../webapp',
        // Workaround for developing beeper on windows, where symlinks are poorly supported.
        "../../nova-web/webapp",
    ]);
    console.log("Web App Path is", asarPath);
    iconsPath = await tryPaths("icons", __dirname, [
        '../res/icons',
        '../../icons'
    ]);
    console.log("iconsPath path is", iconsPath);
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    vectorConfig = require(asarPath + 'config.json');
    console.log("Loading vector config for brand", vectorConfig.brand);
    try {
        // Load local config and use it to override values from the one baked with the build
        // eslint-disable-next-line @typescript-eslint/no-var-requires
        const localConfig = require(path_1.default.join(electron_1.app.getPath('userData'), 'config.json'));

There are also some hidden/undocumented CLI arguments, localdev / localapi:

const localdev = Array.isArray(argv._) && argv._.includes("localdev");
const localapi = Array.isArray(argv._) && argv._.includes("localapi");

We find the code that processes the --devtools arg here:

if (argv['devtools']) {
        try {
            const { default: installExt, REACT_DEVELOPER_TOOLS, REACT_PERF, } = require("electron-devtools-installer");
            await installExt([REACT_DEVELOPER_TOOLS, REACT_PERF], { loadExtensionOptions: { allowFileAccess: true } });
        }
        catch (e) {
            console.log(e);
        }
    }

A little further down we see how localdev / localapi are handled:

if (localdev) {
        // Open dev tools at startup if in dev mode
        mainWindow.webContents.openDevTools();
        electron_1.app.on("certificate-error", (event, webContents, url, error, certificate, callback) => {
            // On certificate error we disable default behaviour (stop loading the page)
            // and we then say "it is all fine - true" to the callback
            event.preventDefault();
            callback(true);
        });
    }
    if (localapi) {
        vectorConfig.novaApiUrl = `https://localhost:4001`;
    }
    mainWindow.loadURL(localdev ? "http://localhost:8080" : 'nova://nova-web/webapp/');

Then beyond that, you're sort of getting deeper into the internals of Electron apps and how Beeper / Element is built on top of Electron; so really depends what you're wanting to achieve at that point.


While at the initial 'entrypoint' debugger breakpoint (from --inspect-brk), we could also choose to manually load/inject some custom code of our own. For example:

With a code file like:

// /Users/devalias/Desktop/beeperInjectionHax.js
console.log("Hello World, is this custom JS in Beeper?");

We could run the following in the Chrome Devtools console while at the initial app loading debug breakpoint:

require('/Users/devalias/Desktop/beeperInjectionHax.js')

Which would show an output message such as:

beeperInjectionHax.js:1 Hello World, is this custom JS in Beeper?

image

Various Beeper Inbox Selectors (Favourite, Pinned, Not Pinned, Unread, Etc)

// Inbox - Favourites List
$$('.rooms > .favourites [data-type="bp_RoomTile"]')

// Inbox - Favourites List - Unread
$$('.rooms > .favourites .isUnread[data-type="bp_RoomTile"]')

// Inbox - Favourites List - Muted
$$('.rooms > .favourites .isMuted[data-type="bp_RoomTile"]')

// Inbox Chats
$$('.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]')

// Inbox Chats - Favourite
$$('.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]:has([data-src="img/beeper/heart-filled16.b7ad82d.svg"])')

// Inbox Chats - Pinned
$$('.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]:has([data-src="img/beeper/pin-filled16.b7cb2af.svg"])')

// Inbox Chats - Not Pinned
$$('.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]:not(:has([data-src="img/beeper/pin-filled16.b7cb2af.svg"]))')

// Inbox - Unread - Favourite-Avatar
$$('.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .favourite-avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon')

// Inbox - Unread - Avatar
$$('.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon')

// Inbox - Unread - Combined
$$('.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .favourite-avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon, .rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon')

Counting chats that match the above

This is a bit of a hacky WIP / PoC, but it seems to do the trick:

/*************************/
/* Counting Chat Types */
/************************/

/* Initialize counters */
.rooms {
  counter-reset: unread favourite pinned not-pinned;
}

/* Increment favorites */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] [data-src="img/beeper/heart-filled16.b7ad82d.svg"] {
  counter-increment: favourite;
}

/* Increment pinned */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] [data-src="img/beeper/pin-filled16.b7cb2af.svg"] {
  counter-increment: pinned;
}

/* Increment not pinned */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]:not(:has([data-src="img/beeper/pin-filled16.b7cb2af.svg"])) {
  counter-increment: not-pinned;
}

/* Increment unread */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .favourite-avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon,
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon {
  counter-increment: unread;
}

/* Show Counters */
/*.rooms::after {
  content: "Counters: Unread (" counter(unread) "), Favourite (" counter(favourite) "), Pinned (" counter(pinned) "), Not-Pinned (" counter(not-pinned) ")"
}*/
.rooms::after {
  content: "Unread (" counter(unread) "), Not-Pinned (" counter(not-pinned) ")";

  font-size: 10px;

  position: absolute;
  top: calc(31vh - 2px);
  left: calc(8vw - 4px);

  background-color: dimgrey;
}

/* Set position to relative for the hovered element */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] {
  position: relative;
}

/* Show the not-pinned counter on hover */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]:hover::before {
  content: "Not-Pinned above: " counter(not-pinned);

  font-size: 10px;
  white-space: nowrap;

  position: absolute;
  bottom: 5px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1000;

  border-radius: 3px;
  padding: 2px;

  background-color: dimgrey;
}

Beeper - Password Reset (JWT)

See the following repo for my notes (formatted as a ChatGPT prompt) on implementing a JWT-based password reset flow using Beeper's new 'email login' flow; as well as Proof of Concept (PoC) code implementing a CLI tool for using this flow.

/* Will this work with https://developer.mozilla.org/en-US/docs/Web/CSS/@import ? I don't think so.. but maybe I was using it wrong..? */
/***************************************************************/
/* De-emphasise distracting chats - *Beeper* Community/Hackers */
/***************************************************************/
.bp_LeftPanel:not(:has(svg[data-src="img/beeper/back16.024b7d1.svg"])) div[data-type="bp_RoomTile"]:has(.nv_BridgedIcon_beeper, svg[data-src="img/beeper/color-beeper16.1c8391b.svg"]):has(div[title*="Beeper"i]):has(div[title*="Community"i], div[title*="Hackers"i]) {
opacity: 0.5;
}
/*******************************************************/
/* De-emphasise distracting chats - UC Outdoors Club */
/*******************************************************/
.bp_LeftPanel:not(:has(svg[data-src="img/beeper/back16.024b7d1.svg"])) div[data-type="bp_RoomTile"]:has(.nv_BridgedIcon_facebook, svg[data-src="img/beeper/color-facebook16.a8cac84.svg"]):has(div[title*="UC Outdoors Club"]) {
opacity: 0.5;
}
/**********************************************/
/* Hide Left Panel/Sidebar While Chat Is Open */
/**********************************************/
/*.mx_MatrixChat:has(> .bp_MainPanel > .mx_RoomView) > .bp_LeftPanel {
display: none;
}
.mx_MatrixChat:has(> .bp_MainPanel > .mx_RoomView) > div:has(.spaceBar) {
display: none;
}*/
/*************************/
/* Counting Chat Types */
/************************/
/* Initialize counters */
.rooms {
counter-reset: unread favourite pinned not-pinned;
}
/* Increment favorites */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] [data-src="img/beeper/heart-filled16.b7ad82d.svg"] {
counter-increment: favourite;
}
/* Increment pinned */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] [data-src="img/beeper/pin-filled16.b7cb2af.svg"] {
counter-increment: pinned;
}
/* Increment not pinned */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]:not(:has([data-src="img/beeper/pin-filled16.b7cb2af.svg"])) {
counter-increment: not-pinned;
}
/* Increment unread */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .favourite-avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon,
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon {
counter-increment: unread;
}
/* Show Counters */
/*.rooms::after {
content: "Counters: Unread (" counter(unread) "), Favourite (" counter(favourite) "), Pinned (" counter(pinned) "), Not-Pinned (" counter(not-pinned) ")"
}*/
.rooms::after {
content: "Unread (" counter(unread) "), Not-Pinned (" counter(not-pinned) ")";
font-size: 10px;
position: absolute;
top: calc(31vh - 2px);
left: calc(8vw - 4px);
background-color: dimgrey;
}
/* Set position to relative for the hovered element */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] {
position: relative;
}
/* Show the not-pinned counter on hover */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]:hover::before {
content: "Not-Pinned above: " counter(not-pinned);
font-size: 10px;
white-space: nowrap;
position: absolute;
bottom: 5px;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
border-radius: 3px;
padding: 2px;
background-color: dimgrey;
}
/******************************************************************/
/* Ensure 'report bug' dialog doesn't take over the entire screen */
/******************************************************************/
#mx_Dialog_Container div[aria-describedby="report_bug"] {
/* Don't take up the entire screen */
width: fit-content;
height: fit-content;
padding: 20px;
border-radius: 20px;
/* Center the dialog */
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/**********************/
/* Main Settings Menu */
/**********************/
#mx_ContextualMenu_Container .mx_AccessibleButton[aria-label="Chat Networks"] {
display: none;
}
#mx_ContextualMenu_Container .mx_AccessibleButton[aria-label="Mark All As Read"] {
display: none;
}
#mx_ContextualMenu_Container .mx_AccessibleButton[aria-label="Check for Update"] {
display: none;
}
#mx_ContextualMenu_Container .mx_AccessibleButton[aria-label="Download Mobile App"] {
display: none;
}
/*******************/
/* Settings Dialog */
/*******************/
/* Settings Dialog - Tab - Chat Networks */
.mx_SettingsDialog_content > .mx_TabbedView > .mx_TabbedView_tabLabels > .mx_TabbedView_tabLabel:has(> span.mx_UserSettingsDialog_chatSettingsIcon) > .mx_TabbedView_tabLabel_text::after {
content: "πŸ”—";
margin-left: 16px;
}
/* Settings Dialog - Tab - Manage Subscription */
.mx_SettingsDialog_content > .mx_TabbedView > .mx_TabbedView_tabLabels > .mx_TabbedView_tabLabel:has(> span.mx_UserSettingsDialog_manageSubscriptionIcon) > .mx_TabbedView_tabLabel_text::after {
content: "πŸ”—";
margin-left: 16px;
}
/* Settings Dialog - Appearance - Custom CSS TextArea */
.mx_AppearanceUserSettingsTab .mx_Field.mx_Field_textarea {
width: 100% !important;
height: 500px !important;
}
.mx_AppearanceUserSettingsTab .mx_Field.mx_Field_textarea textarea {
font-family: Menlo, monospace;
font-size: 13px !important;
}
/**********************/
/* Inbox - Favourites */
/**********************/
/* Inbox - Favourites */
.bp_LeftPanel .rooms > .favourites {
/* Custom Variables */
--devalias-fav-section-max-height: 30vh;
--devalias-fav-grid-row-gap: 6px;
--devalias-fav-grid-col-gap: 6px;
--devalias-fav-width: 100%;
--devalias-fav-height: fit-content;
--devalias-fav-max-width: 64px;
--devalias-fav-avatar-width: 30px;
--devalias-fav-avatar-height: 30px;
--devalias-fav-avatar-font-size: 17px;
}
.bp_LeftPanel .rooms > .favourites > .favourites__icons > .favourites__tiles {
max-height: var(--devalias-fav-section-max-height, none) !important;
overflow: auto;
padding-left: unset;
padding: 0;
/* Favourites Grid Spacing */
grid-row-gap: unset;
grid-gap: unset;
gap: unset;
row-gap: var(--devalias-fav-grid-row-gap, 12px);
}
.bp_LeftPanel .rooms > .favourites > .favourites__icons > .favourites__tiles > .favourites__row {
height: fit-content;
/* Favourites Grid Spacing */
grid-gap: unset;
gap: unset;
column-gap: var(--devalias-fav-grid-col-gap, 12px);
}
.bp_LeftPanel .rooms > .favourites > .favourites__icons > .favourites__tiles > .favourites__row > div {
height: var(--devalias-fav-height, 60px) !important;
}
.bp_LeftPanel .rooms .favourites .bp_RoomTile.small {
width: var(--devalias-fav-width, 100%) !important;
height: var(--devalias-fav-height, 48px) !important;
max-width: var(--devalias-fav-max-width, 100%) !important;
}
.bp_LeftPanel .rooms .favourites .bp_RoomTile.small .mx_BaseAvatar_image {
width: var(--devalias-fav-avatar-width, 44px) !important;
height: var(--devalias-fav-avatar-height, 44px) !important;
}
.bp_LeftPanel .rooms .favourites .bp_RoomTile.small .mx_BaseAvatar_initial {
font-size: var(--devalias-fav-avatar-font-size, 19.8px) !important;
width: var(--devalias-fav-avatar-width, 44px) !important;
line-height: var(--devalias-fav-avatar-width, 44px) !important;
}
.bp_LeftPanel .rooms .favourites .bp_RoomTile.small .outline {
max-width: var(--devalias-fav-max-width, 110px) !important;
}
.bp_LeftPanel .rooms .favourites .bp_RoomTile.small .outline > span {
max-width: var(--devalias-fav-max-width, 110px) !important;
}
/************************/
/* Beeper Space Sidebar */
/************************/
/* Beeper Space Sidebar - Collapsed - Hide Floating 'Open Archive' Button */
.mx_MatrixChat > div > div > .mx_AccessibleButton:has(> .bp_icon > div > svg[data-src="img/beeper/archive16.2003809.svg"]) {
display: none;
}
/* Beeper Space Sidebar - Show Floating 'Archive All Read Messages' Button */
.mx_MatrixChat > div > div:has(.mx_AccessibleButton > .bp_icon > div > svg[data-src="img/beeper/new-sweep16.978771b.svg"]) {
width: max-content !important;
/*position: absolute;
top: calc(38vh);
left: 15vw;*/
}
.mx_MatrixChat > div > div > div:has(.mx_AccessibleButton > .bp_icon > div > svg[data-src="img/beeper/new-sweep16.978771b.svg"]) {
display: block !important;
}
.mx_MatrixChat > div > div > div > .mx_AccessibleButton:has(> .bp_icon > div > svg[data-src="img/beeper/new-sweep16.978771b.svg"]) {
opacity: unset;
}
/* Beeper Space Sidebar - Hide Original Icons */
#beeperSpaceBar svg:not(svg[data-src="img/beeper/square-inbox16.ea471fd.svg"]):not(svg[data-src="img/beeper/square-lowpriority16.6779879.svg"]):not(svg[data-src="img/beeper/square-archive16.b1ef8a0.svg"]):not(svg[data-src="img/beeper/square-bookmarks16.6853926.svg"]):not(svg[data-src="img/beeper/add-network16.1eb5cb1.svg"]) {
display: none
}
/* Beeper Space Sidebar - Use Lineart Icons */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg) > div > div > div {
width: 100%;
height: 100%;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
}
/* Beeper Space Sidebar - Use Lineart Icons - Facebook */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-facebook16.9d9e23d.svg"]) > div > div > div {
background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/messenger.1544eb2.svg");
}
/* Beeper Space Sidebar - Use Lineart Icons - Instagram */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-instagram16.1e8ed4e.svg"]) > div > div > div {
background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/instagram.e9184e9.svg");
}
/* Beeper Space Sidebar - Use Lineart Icons - iMessage */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-imessage16.11b6604.svg"]) > div > div > div {
background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/imessage.aedae37.svg");
}
/* Beeper Space Sidebar - Use Lineart Icons - Twitter */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-twitter16.ddd9bad.svg"]) > div > div > div {
background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/twitter.972096c.svg");
}
/* Beeper Space Sidebar - Use Lineart Icons - Telegram */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-telegram16.d011ded.svg"]) > div > div > div {
background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/telegram.77fa320.svg");
}
/* Beeper Space Sidebar - Use Lineart Icons - Signal */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-signal16.85ba0c4.svg"]) > div > div > div {
background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/signal.315d199.svg");
}
/* Beeper Space Sidebar - Use Lineart Icons - LinkedIn */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-linkedin16.f764edc.svg"]) > div > div > div {
background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/linkedin.2297fef.svg");
}
/* Beeper Space Sidebar - Use Lineart Icons - Discord */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-discord16.e91cca3.svg"]) > div > div > div {
background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/discord.6daf490.svg");
}
/* Beeper Space Sidebar - Use Lineart Icons - Beeper */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-beeper16.749ed9b.svg"]) > div > div > div {
background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/beeper.c685f80.svg");
}
/* Beeper Space Sidebar - Use Lineart Icons - WhatsApp */
#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-whatsapp16.411f722.svg"]) > div > div > div {
background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/whatsapp.2210499.svg");
}
/*****************/
/* Uncategorized */
/*****************/
/* Ensure full image preview is visible, rather than a zoomed part of it */
.mx_EventTile_image .mx_MImageBody img.mx_MImageBody_thumbnail {
object-fit: contain !important;
}
@kubo6472
Copy link

May I "request" an update for all the new useless buttons that bloat the screen since the latest update, please? πŸ˜…

@0xdevalias
Copy link
Author

@kubo6472 Haha, you can always ask; and if it's not too hard I can probably knock something together for you :) Though which buttons in particular are you referring to?

@kubo6472
Copy link

Heya, I didn't know what was the better way to ask, so I did both.
file_0_20230712082522

mainly hiding this new top button that "conveniently" doesn't have any class. that'd be lovely.

@0xdevalias
Copy link
Author

0xdevalias commented Jul 12, 2023

Something like this should do it:

.mx_MatrixChat > .bp_LeftPanel > .bp_Header > .controls > div:has(svg[data-src="img/beeper/question-mark16.7154382.svg"]) {
  display: none;
}

Also added to the other issue thread here:


Use 'Toggle Developer Tools' -> select the element you want to look at, then look at what things you could target in the hierarchy above it, etc; you can then test it in the devtools console with `$$('the css selector here') and see if it matches the thing you expect/want.

MDN docs are your friend:

@kubo6472
Copy link

kubo6472 commented Jul 12, 2023 via email

@kubo6472
Copy link

This is what works for me currently. I did not need the counters.

/* Will this work with https://developer.mozilla.org/en-US/docs/Web/CSS/@import ? */

/**********************************************/
/* Hide Left Panel/Sidebar While Chat Is Open */
/**********************************************/

/*.mx_MatrixChat:has(> .bp_MainPanel > .mx_RoomView) > .bp_LeftPanel {
  display: none;
}

.mx_MatrixChat:has(> .bp_MainPanel > .mx_RoomView) > div:has(.spaceBar) {
  display: none;
}*/

/**********************/
/* Main Settings Menu */
/**********************/

#mx_ContextualMenu_Container .mx_AccessibleButton[aria-label="Chat Networks"] {
  display: none;
}

#mx_ContextualMenu_Container .mx_AccessibleButton[aria-label="Mark All As Read"] {
  display: none;
}

#mx_ContextualMenu_Container .mx_AccessibleButton[aria-label="Check for Update"] {
  display: none;
}

#mx_ContextualMenu_Container .mx_AccessibleButton[aria-label="Download Mobile App"] {
  display: none;
}

#mx_ContextualMenu_Container .mx_AccessibleButton[aria-label="Help"] {
  display: none;
}

/*******************/
/* Settings Dialog */
/*******************/

/* Settings Dialog - Tab - Chat Networks */

.mx_SettingsDialog_content > .mx_TabbedView > .mx_TabbedView_tabLabels > .mx_TabbedView_tabLabel:has(> span.mx_UserSettingsDialog_chatSettingsIcon) > .mx_TabbedView_tabLabel_text::after {
  content: "πŸ”—";
  margin-left: 16px;
}

/* Settings Dialog - Tab - Manage Subscription */

.mx_SettingsDialog_content > .mx_TabbedView > .mx_TabbedView_tabLabels > .mx_TabbedView_tabLabel:has(> span.mx_UserSettingsDialog_manageSubscriptionIcon) > .mx_TabbedView_tabLabel_text::after {
  content: "πŸ”—";
  margin-left: 16px;
}

/* Settings Dialog - Appearance - Custom CSS TextArea */

.mx_AppearanceUserSettingsTab .mx_Field.mx_Field_textarea {
  width: 100% !important;
  height: 500px !important;
}

/**********************/
/* Inbox - Favourites */
/**********************/

/* Inbox - Favourites */

.bp_LeftPanel .rooms > .favourites {
  /* Custom Variables */
  --devalias-fav-section-max-height: 30vh;
  --devalias-fav-grid-row-gap: 6px;
  --devalias-fav-grid-col-gap: 6px;
  --devalias-fav-width: 100%;
  --devalias-fav-height: fit-content;
  --devalias-fav-max-width: 64px;
  --devalias-fav-avatar-width: 30px;
  --devalias-fav-avatar-height: 30px;
  --devalias-fav-avatar-font-size: 17px;
}

.bp_LeftPanel .rooms > .favourites > .favourites__icons > .favourites__tiles {
  max-height: var(--devalias-fav-section-max-height, none) !important;
  overflow: auto;

  padding-left: unset;
  padding: 0;

  /* Favourites Grid Spacing */
  grid-row-gap: unset;
  grid-gap: unset;
  gap: unset;
  row-gap: var(--devalias-fav-grid-row-gap, 12px);
}

.bp_LeftPanel .rooms > .favourites > .favourites__icons > .favourites__tiles > .favourites__row {
  height: fit-content;

  /* Favourites Grid Spacing */
  grid-gap: unset;
  gap: unset;
  column-gap: var(--devalias-fav-grid-col-gap, 12px);
}

.bp_LeftPanel .rooms > .favourites > .favourites__icons > .favourites__tiles > .favourites__row > div {
  height: var(--devalias-fav-height, 60px) !important;
}

.bp_LeftPanel .rooms .favourites .bp_RoomTile.small {
  width: var(--devalias-fav-width, 100%) !important;
  height: var(--devalias-fav-height, 48px) !important;

  max-width: var(--devalias-fav-max-width, 100%) !important;
}

.bp_LeftPanel .rooms .favourites .bp_RoomTile.small .mx_BaseAvatar_image {
  width: var(--devalias-fav-avatar-width, 44px) !important;
  height: var(--devalias-fav-avatar-height, 44px) !important;
}

.bp_LeftPanel .rooms .favourites .bp_RoomTile.small .mx_BaseAvatar_initial {
  font-size: var(--devalias-fav-avatar-font-size, 19.8px) !important;
  width: var(--devalias-fav-avatar-width, 44px) !important;
  line-height: var(--devalias-fav-avatar-width, 44px) !important;
}

.bp_LeftPanel .rooms .favourites .bp_RoomTile.small .outline {
  max-width: var(--devalias-fav-max-width, 110px) !important;
}

.bp_LeftPanel .rooms .favourites .bp_RoomTile.small .outline > span {
  max-width: var(--devalias-fav-max-width, 110px) !important;
}

/************************/
/* Beeper Space Sidebar */
/************************/

/* Beeper Space Sidebar - Collapsed - Hide Floating 'Open Archive' Button */

.mx_MatrixChat > div > div > .mx_AccessibleButton:has(> .bp_icon > div > svg[data-src="img/beeper/archive16.2003809.svg"]) {
  display: none;
}

/* Beeper Space Sidebar - Show Floating 'Archive All Read Messages' Button */

.mx_MatrixChat > div > div:has(.mx_AccessibleButton > .bp_icon > div > svg[data-src="img/beeper/new-sweep16.978771b.svg"]) {
  width: max-content !important;

  /*position: absolute;
  top: calc(38vh);
  left: 15vw;*/
}

.mx_MatrixChat > div > div > div:has(.mx_AccessibleButton > .bp_icon > div > svg[data-src="img/beeper/new-sweep16.978771b.svg"]) {
  display: block !important;
}

.mx_MatrixChat > div > div > div > .mx_AccessibleButton:has(> .bp_icon > div > svg[data-src="img/beeper/new-sweep16.978771b.svg"]) {
  opacity: unset;
}

/* Beeper Space Sidebar - Hide Original Icons */

#beeperSpaceBar svg:not(svg[data-src="img/beeper/square-inbox16.ea471fd.svg"]):not(svg[data-src="img/beeper/square-lowpriority16.6779879.svg"]):not(svg[data-src="img/beeper/square-archive16.b1ef8a0.svg"]):not(svg[data-src="img/beeper/square-bookmarks16.6853926.svg"]):not(svg[data-src="img/beeper/add-network16.1eb5cb1.svg"]) {
  display: none
}

/* Beeper Space Sidebar - Use Lineart Icons */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg) > div > div > div {
    width: 100%;
    height: 100%;
    background-repeat: no-repeat;
    background-position: center;
    background-size: contain;
}

/* Beeper Space Sidebar - Use Lineart Icons - Facebook */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-facebook16.9d9e23d.svg"]) > div > div > div {
    background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/messenger.1544eb2.svg");
}

/* Beeper Space Sidebar - Use Lineart Icons - Instagram */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-instagram16.1e8ed4e.svg"]) > div > div > div {
    background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/instagram.e9184e9.svg");
}

/* Beeper Space Sidebar - Use Lineart Icons - iMessage */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-imessage16.11b6604.svg"]) > div > div > div {
    background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/imessage.aedae37.svg");
}

/* Beeper Space Sidebar - Use Lineart Icons - Twitter */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-twitter16.ddd9bad.svg"]) > div > div > div {
    background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/twitter.972096c.svg");
}

/* Beeper Space Sidebar - Use Lineart Icons - Telegram */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-telegram16.d011ded.svg"]) > div > div > div {
    background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/telegram.77fa320.svg");
}

/* Beeper Space Sidebar - Use Lineart Icons - Signal */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-signal16.85ba0c4.svg"]) > div > div > div {
    background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/signal.315d199.svg");
}

/* Beeper Space Sidebar - Use Lineart Icons - LinkedIn */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-linkedin16.f764edc.svg"]) > div > div > div {
    background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/linkedin.2297fef.svg");
}

/* Beeper Space Sidebar - Use Lineart Icons - Discord */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-discord16.e91cca3.svg"]) > div > div > div {
    background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/discord.6daf490.svg");
}

/* Beeper Space Sidebar - Use Lineart Icons - Beeper */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-beeper16.749ed9b.svg"]) > div > div > div {
    background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/beeper.c685f80.svg");
}

/* Beeper Space Sidebar - Use Lineart Icons - WhatsApp */

#beeperSpaceBar > .mx_AccessibleButton:has(.bp_icon svg[data-src="img/beeper/square-whatsapp16.411f722.svg"]) > div > div > div {
    background-image: url("nova://nova-web/webapp/img/social/lineart-color/dark/whatsapp.2210499.svg");
}

/*****************/
/* Uncategorized */
/*****************/

/* Ensure full image preview is visible, rather than a zoomed part of it */
.mx_EventTile_image .mx_MImageBody img.mx_MImageBody_thumbnail {
  object-fit: contain !important;
}

.mx_MatrixChat > .bp_LeftPanel > .bp_Header > .controls > div:has(svg[data-src="img/beeper/question-mark16.7154382.svg"]) {
  display: none;
}

@kubo6472
Copy link

image
this is funny πŸ˜…

@0xdevalias
Copy link
Author

That worked, lovely, thank you!

@kubo6472 Glad it did :)

I did not need the counters.

@kubo6472 Mm, that's fair. They're not perfect for sure, but for me they are a CSS only hack towards getting better functionality that I would like to see on the inbox filters.

this is funny πŸ˜…

@kubo6472 Lol, yeah. Definitely an edge case not currently accounted for in the CSS hacks as is. I suspect the solution would be to also hide the 'error state' icons similar to how we hide the default icons; and then also detect when the error state should be shown and use an 'error state' lineart icon instead of the normal one in that instance.

@jorgeparamos
Copy link

jorgeparamos commented Aug 1, 2023

Amazing work @0xdevalias, thank you!

Two suggestions/requests (that may have been covered elsewher, but I couldn't find them) to achieve the effect in the mock-up:

  1. Turn the main window fully transparent.
  2. Make chat bubbles by different users have more contrasting colors.

Any help is appreciated: from sharing a full solution to just pointing me to which CSS elements are relevant. Thanks!
image

@0xdevalias
Copy link
Author

Amazing work @0xdevalias, thank you!

@jorgeparamos Glad it's helpful :)


Make chat bubbles by different users have more contrasting colors

@jorgeparamos This took a while to figure out, as it wasn't super clear where the background colours for the messages were even coming from at first..

The DOM structure of a message in a chat looks like this, with the particularly interesting bit for the colours of the message body seeming to be the <div class="mx_EventTile_line mx_EventTile_line--with-ts">:

image

Looking at the CSS styles applied here, I can see some rules in theme-dark.css that seem to impact these messages + set a background on them; and then there are some other rules that also seem to match for the replies part of things:

.mx_EventTile_messageIn .mx_EventTile_line
.mx_EventTile_messageIn.mx_EventTile_lastInSection .mx_EventTile_line:before
.mx_EventTile_messageIn .mx_ReplyChain
.mx_ReplyChain

From a quick 'playing around', it seems like toggling off the backgrounds in all of those rules removes them from the chat:

image

If we then write some rules like this, we can set all of the bits of the backgrounds to the colours we choose (there seems to be the 'main bubble', the 'bubble tail', and then the 'reply section':

.mx_RoomView_MessageList .mx_EventTile_messageIn .mx_EventTile_line {
    background-color: red;
}

.mx_RoomView_MessageList .mx_EventTile_messageIn.mx_EventTile_lastInSection .mx_EventTile_line:before {
    background-color: green;
}

.mx_RoomView_MessageList .mx_EventTile_messageIn .mx_ReplyChain {
    background-color: blue;
}

image

Looking a little closer at the DOM structure and the CSS classes applied to it, the following look interesting/potentially useful:

.mx_EventTile_message.mx_EventTile_firstInGroup
.mx_EventTile_message.mx_EventTile_continuation
.mx_EventTile_message.mx_EventTile_lastInGroup
.mx_EventTile_message.mx_EventTile_lastInSection

If we were wanting to target/set a colour for a specific username, it seems we could write a CSS selector that matches against the username in the title attr of the profile picture with something like this (not that that is necessarily helpful to the usecase here, but figured i'd note it as I explored things):

$$('.mx_EventTile_message .mx_MessageBody .mx_EventTile_avatar .mx_BaseAvatar_image[title="HReflex"]')

My first thought was that maybe we could do something tricky using CSS variables, but the easiest way for that to work would have been if all of the 'grouped messages' of a user had the same parent element we could target, but it seems that's not the case:

My next thought is that perhaps we could do something tricky using CSS counters somehow, though not exactly sure what that would be/how it would work exactly (if it's even possible); and even if we do manage to make it work, I'm not sure that it would easily allow us to always keep the same colour mapped to the same user:

The high level of my thought with CSS counters is that maybe we could have it set a colour value at the start of a group of messages, use that throughout those messages, and then have it reset at the end of that group (or at the start of the next one); but I'm not even sure if that is actually possible.

This is the type of thing that would be almost trivial to achieve with JavaScript/similar; but through CSS alone might be super challenging if not impossible unfortunately.


Turn the main window fully transparent

@jorgeparamos This isn't something I'm deeply familiar with, but I found the following resources for how to achieve this with an electron app (which is what Beeper is built on):

Based on that, the short answer is that it relies on being able to change JavaScript in some of the core internals of Beeper/Electron, so I believe that it's basically not possible to do with just CSS changes alone.

If we start Beeper with remote debugging enabled (eg. /Applications/Beeper.app/Contents/MacOS/Beeper --inspect-brk=1337), then connect to it from Chrome DevTools (chrome://inspect), then we can search the source for BrowserWindow, which we find in electron-main.js. There are 2 main instances of this:

  • The first is seemingly used for some bridge auth related things
    • image
  • Whereas the second looks to be the main window we interact with
    • image

Looking at the options passed to that 2nd BrowserWindow, we can see that it sets frame as frame: argv['default-frame'] ?? false, implying that it might be possible to influence this via command line arguments; but there doesn't seem to be a transparent option defined at all.

Even if I hack the source to enable transparent in the debugger, and then manually crawl through the various DOM nodes with background colours and set them to transparent as well, it still seems to give me a blurred/'frosted' looking transparent, rather than purely transparent (which weirdly seems to become opaque again when I don't have the Beeper window selected):

image

I'll also note that there are these settings in the standard Beeper settings, that give some transparency, but also not the full transparency you seem to be after:

image

Though looking at the code, they might only be shown to Mac users for some reason (even though apparently electron for windows can handle transparency as well):

image

Looking in theme-dark.css via Beeper's DevTools, there seem to be a lot of CSS variables that could be overridden to tweak things. I haven't looked too deeply at them, but the 'Transparency - Chat View' settings toggle seems to interact with the --main-panel-transparent-bg CSS variable; so that might potentially be an area worth looking into more:

image

There may be some other CSS properties interacting with this 'frosted'/'blurred' transparency, such as background-blend-mode or blur or otherwise.. or it might just be a limitation in how electron does things in general. Hard to say for sure without diving deeper into things:

@0xdevalias
Copy link
Author

@jorgeparamos Just tried out a basic test of my thoughts/theory with using the CSS counters to change background colours, and it seems like it wouldn't work:

@jorgeparamos
Copy link

@jorgeparamos Just tried out a basic test of my thoughts/theory with using the CSS counters to change background colours, and it seems like it wouldn't work:

Whoa! Thanks a lot for all the hints and effort put into it: I understood half and managed to implement another half of that, and attained that translucid (i.e. sort of transparent, but blurred) look β€” which I find still benefits the app a lot.

Thanks again @0xdevalias!

@0xdevalias
Copy link
Author

I understood half and managed to implement another half of that, and attained that translucid (i.e. sort of transparent, but blurred) look β€” which I find still benefits the app a lot.

Thanks again @0xdevalias!

@jorgeparamos Awesome, glad you could get some of it figured :) If you're open to sharing the CSS hacks you ended up with here they might be useful for someone else in future too πŸ–€

@evkaw
Copy link

evkaw commented Oct 10, 2023

Someone please help me get rid of the "invite to beeper" button in chats, I don't know how to use CSS nor how to get into the Inspect tool.. Thanks

@0xdevalias
Copy link
Author

Someone please help me get rid of the "invite to beeper" button in chats, I don't know how to use CSS nor how to get into the Inspect tool.. Thanks

@evkaw On Beeper Desktop, go to the menu bar -> View -> Toggle Developer Tools; then from the devtools you can select the 'Elements' tab, and then in the top left you should find the 'inspect element' tool (Shortcut seems to be apple+shift+c, or whatever the windows equivalent of that is)

Something like this would probably do it:

.bp_MainPanel > .mx_RoomView > .mx_RoomHeader .mx_RoomHeader_rightActions .mx_RoomHeader_rightActions_static > :nth-child(1 of .mx_HeaderButtons) {
  display: none;
}

@FaviFake
Copy link

FaviFake commented Nov 6, 2023

Hey,

I don't know why, but Beeper seems to be the only messaging app that uses a much smaller font for the chat list compared to all other UI elements. Also, the message previews occupy two rows instead of just one. I'd love if you could make the text used in the chat list as big as the one used in the message bubbles, and, if possible, also remove the second row in the preview. I have no idea how CSS works, so idk if what I'm asking is very hard or incredibly simple. I've made a badly-photoshopped concept to show how it could look:

Before

image

After

1699185663659

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment