12 Commits
5.0.2 ... main

Author SHA1 Message Date
69c9438a20 Merge pull request 'dev-main' (#1) from dev-main into main
Some checks failed
PHP Lint / lint (8.2) (push) Has been cancelled
PHP Lint / lint (8.3) (push) Has been cancelled
PHP Lint / lint (8.4) (push) Has been cancelled
PHP CodeSniffer / phpcs (push) Has been cancelled
Reviewed-on: #1
2026-08-08 07:07:22 +00:00
Milos Sen
1a5b07273d change plugin name
Some checks failed
PHP Lint / lint (8.2) (pull_request) Has been cancelled
PHP Lint / lint (8.3) (pull_request) Has been cancelled
PHP Lint / lint (8.4) (pull_request) Has been cancelled
PHP CodeSniffer / phpcs (pull_request) Has been cancelled
2026-08-08 09:28:20 +03:00
Milos Sen
64d2ff3e98 change plugi name 2026-08-08 09:20:09 +03:00
Milos Sen
85a0d4f9ec change plugi name 2026-08-08 09:03:01 +03:00
Milos Sen
84c7b31dfb update comment 2026-08-08 06:53:11 +03:00
1499347b7b added workbench to list of skins 2026-08-07 19:41:40 +00:00
1d39bc03d8 Update SQL/postgres.initial.sql
makes the script safe to re-run against an already-migrated database
2026-08-06 05:10:47 +00:00
Laurent Dinclaux
8f45736b1b fix(skins): keep menu links visible and align native select look
The wrapper had width:100% which pushed the right-side menu links
(mail/contacts/settings/logout) out of #taskbar in Classic, and the
global custom select styling defeated Roundcube's native select
rendering — even when reset — because any background-image on a
<select> disables the native gradient/rounded corners.

- Scope the custom appearance/SVG-chevron CSS to Elastic and Larry
- Add a Classic-specific rule (width:auto, max-width, -2px nudge) that
  preserves Roundcube's native select look in #taskbar
- Drop the inline padding:0 imposed by the PHP renderer; let CSS own it
- Cap the Larry wrapper width to keep .button-logout in flow, and move
  the Larry-only styling out of the JS into the scoped CSS rule

Fixes #1
2026-05-12 17:02:32 +11:00
Laurent Dinclaux
8385197f11 chore: refresh composer.lock 2026-05-06 20:26:54 +11:00
Laurent Dinclaux
ca16e3ca39 ci: bump actions/checkout to v5 (Node 24) 2026-05-06 20:21:04 +11:00
Laurent Dinclaux
c194661fa3 fix: prevent username flash on load and remove select max-width 2026-05-06 20:11:10 +11:00
Laurent Dinclaux
e539c60e9b feat: use primary identity display name in switcher dropdown
The account switcher dropdown previously showed the IMAP username
(typically the email address) for the primary account. Now it falls
back to the identity's display name when available, matching the
behavior of secondary accounts which use their custom label.

Priority order: global_alias session var > primary identity display
name > IMAP username.
2026-05-06 17:37:50 +11:00
9 changed files with 96 additions and 29 deletions

View File

@@ -13,7 +13,7 @@ jobs:
matrix: matrix:
php-version: ['8.2', '8.3', '8.4'] php-version: ['8.2', '8.3', '8.4']
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- name: Setup PHP ${{ matrix.php-version }} - name: Setup PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2

View File

@@ -10,7 +10,7 @@ jobs:
phpcs: phpcs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2

View File

@@ -1,4 +1,13 @@
CREATE TABLE ident_switch -- Patched version of SQL/postgres.initial.sql for gecka/ident-switch.
-- Commit this into your Gitea fork at the same path, replacing the
-- upstream file. Only two changes from upstream:
-- 1. CREATE TABLE / CREATE INDEX -> IF NOT EXISTS
-- 2. INSERT INTO system -> upsert via ON CONFLICT
-- This makes the script safe to re-run against an already-migrated
-- database, which happens on every container restart unless
-- /var/www/html is persisted (composer re-installs "fresh" each time).
CREATE TABLE IF NOT EXISTS ident_switch
( (
id id
serial serial
@@ -82,8 +91,9 @@ CREATE TABLE ident_switch
UNIQUE (user_id, label) UNIQUE (user_id, label)
); );
CREATE INDEX IX_ident_switch_user_id ON ident_switch(user_id); CREATE INDEX IF NOT EXISTS IX_ident_switch_user_id ON ident_switch(user_id);
CREATE INDEX IX_ident_switch_iid ON ident_switch(iid); CREATE INDEX IF NOT EXISTS IX_ident_switch_iid ON ident_switch(iid);
CREATE INDEX IX_ident_switch_parent_id ON ident_switch(parent_id); CREATE INDEX IF NOT EXISTS IX_ident_switch_parent_id ON ident_switch(parent_id);
INSERT INTO system (name, value) VALUES ('ident_switch-version', '2026021000'); INSERT INTO system (name, value) VALUES ('ident_switch-version', '2026021000')
ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value;

View File

@@ -1,5 +1,5 @@
{ {
"name": "gecka/ident-switch", "name": "senior/s-ident-switch",
"type": "roundcube-plugin", "type": "roundcube-plugin",
"description": "This plugin allows users to switch between different accounts (including remote) in single Roundcube session.", "description": "This plugin allows users to switch between different accounts (including remote) in single Roundcube session.",
"homepage": "https://github.com/Gecka-Apps/roundcube-ident_switch", "homepage": "https://github.com/Gecka-Apps/roundcube-ident_switch",

4
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "46b2d3558cb304fde0539d1b85971382", "content-hash": "c1389b5e002c6014eeddc8105bf77db1",
"packages": [ "packages": [
{ {
"name": "roundcube/plugin-installer", "name": "roundcube/plugin-installer",
@@ -154,5 +154,5 @@
"ext-ctype": "*" "ext-ctype": "*"
}, },
"platform-dev": {}, "platform-dev": {},
"plugin-api-version": "2.6.0" "plugin-api-version": "2.9.0"
} }

View File

@@ -2,7 +2,7 @@
* ident_switch - Account switcher UI and new mail notifications. * ident_switch - Account switcher UI and new mail notifications.
* *
* Places the hidden <select> from the footer into the appropriate * Places the hidden <select> from the footer into the appropriate
* skin location (Larry, Classic, Elastic), shows it, and registers * skin location (Larry, Classic, Elastic, Workbench), shows it, and registers
* notification listeners for background mail checking. * notification listeners for background mail checking.
* *
* Copyright (C) 2016-2018 Boris Gulay * Copyright (C) 2016-2018 Boris Gulay
@@ -22,7 +22,6 @@ $(function() {
var $sw = $wrapper.find('#plugin-ident_switch-account'); var $sw = $wrapper.find('#plugin-ident_switch-account');
var placed = false; var placed = false;
switch (rcmail.env.skin) { switch (rcmail.env.skin) {
case 'larry': case 'larry':
placed = plugin_switchIdent_addCbLarry($wrapper, $sw); placed = plugin_switchIdent_addCbLarry($wrapper, $sw);
@@ -33,6 +32,9 @@ $(function() {
case 'elastic': case 'elastic':
placed = plugin_switchIdent_addCbElastic($wrapper, $sw); placed = plugin_switchIdent_addCbElastic($wrapper, $sw);
break; break;
case 'workbench':
placed = plugin_switchIdent_addCbElastic($wrapper, $sw);
break;
} }
if (!placed) { if (!placed) {
@@ -57,7 +59,7 @@ $(function() {
}); });
/** /**
* Place switcher in Larry skin: replace username in top-right corner. * Place switcher in Larry skin: replace username in top-right corner of #topline.
*/ */
function plugin_switchIdent_addCbLarry($wrapper, $sw) { function plugin_switchIdent_addCbLarry($wrapper, $sw) {
var $topRight = $('#topline .topright'); var $topRight = $('#topline .topright');
@@ -65,12 +67,6 @@ function plugin_switchIdent_addCbLarry($wrapper, $sw) {
return false; return false;
} }
$sw.css({
'font-weight': 'bold',
'max-width': '200px',
'text-overflow': 'ellipsis'
});
$topRight.find('.username').hide(); $topRight.find('.username').hide();
$topRight.prepend($wrapper); $topRight.prepend($wrapper);
return true; return true;
@@ -104,7 +100,6 @@ function plugin_switchIdent_addCbElastic($wrapper, $sw) {
'font-weight': 'bold', 'font-weight': 'bold',
'color': 'inherit', 'color': 'inherit',
'box-shadow': 'none', 'box-shadow': 'none',
'max-width': '200px',
'text-overflow': 'ellipsis', 'text-overflow': 'ellipsis',
'padding': '0 1.2em 0 0.25em' 'padding': '0 1.2em 0 0.25em'
}); });

View File

@@ -15,8 +15,12 @@
width: 100%; width: 100%;
} }
/* Account switcher <select>: hide native arrow, show SVG chevron */ /* Account switcher <select>: hide native arrow, show SVG chevron.
.ident-switch-wrapper select { Scoped to Elastic (.header-title.username) and Larry (#topline .topright) where
the switcher replaces a styled header label; Classic uses Roundcube's native
select rendering instead (see #taskbar rule below). */
.header-title.username .ident-switch-wrapper select,
#topline .topright .ident-switch-wrapper select {
cursor: pointer; cursor: pointer;
-webkit-appearance: none; -webkit-appearance: none;
-moz-appearance: none; -moz-appearance: none;
@@ -27,6 +31,62 @@
background-size: 10px 6px; background-size: 10px 6px;
} }
/*
* Prevent flash of original username before JS prepends the switcher.
* The JS hides these elements at DOMReady, but the CSS rules below hide them
* earlier (during initial render) — only loaded when the switcher will actually
* be shown, so users without secondary accounts keep the default username display.
*/
/* Elastic skin: switcher is prepended inside .header-title.username.
Hide the original username via visibility on the parent (affects the text
node), then re-show the wrapper. Unlike color/font-size, this preserves the
parent's flex/line-height context, keeping the wrapper vertically centered. */
.header-title.username {
visibility: hidden;
}
.header-title.username > *:not(.ident-switch-wrapper) {
display: none;
}
.header-title.username > .ident-switch-wrapper {
visibility: visible;
}
/* Larry skin: switcher is prepended next to .username inside #topline .topright */
#topline .topright > .username {
display: none;
}
/* Larry skin: blend the <select> into the dark #topline header (Larry styles a
<span> here, not a <select>, so without these the native white control sticks
out on the dark background). */
#topline .topright .ident-switch-wrapper {
width: auto;
}
#topline .topright .ident-switch-wrapper select {
background-color: transparent;
border: none;
box-shadow: none;
color: inherit;
font-weight: bold;
padding: 0 1.2em 0 0.25em;
text-overflow: ellipsis;
}
/* Classic skin: switcher is prepended inside #taskbar (right-aligned, nowrap, 24px tall).
Drop width:100% so it sits inline before the menu links instead of pushing them
off-screen. The select keeps Roundcube's default native rendering. */
#taskbar > .ident-switch-wrapper {
width: auto;
vertical-align: middle;
margin-right: 8px;
}
#taskbar > .ident-switch-wrapper select {
max-width: 180px;
position: relative;
top: -2px;
}
/* Unread badge */ /* Unread badge */
.ident-switch-badge { .ident-switch-badge {
flex-shrink: 0; flex-shrink: 0;

View File

@@ -177,6 +177,9 @@ class IdentSwitchChecker
$host = 'tls://' . $host; $host = 'tls://' . $host;
} }
$primaryIdentity = $rc->user->get_identity();
$primaryName = !empty($primaryIdentity['name']) ? $primaryIdentity['name'] : null;
return [ return [
'iid' => 0, 'iid' => 0,
'imap_host' => $host, 'imap_host' => $host,
@@ -185,7 +188,7 @@ class IdentSwitchChecker
'username' => $rc->user->data['username'], 'username' => $rc->user->data['username'],
'password' => $_SESSION['password' . $postfix], 'password' => $_SESSION['password' . $postfix],
'email' => $rc->user->data['username'], 'email' => $rc->user->data['username'],
'label' => $_SESSION['global_alias'] ?? $rc->user->data['username'], 'label' => $_SESSION['global_alias'] ?? $primaryName ?? $rc->user->data['username'],
'notify_basic' => null, 'notify_basic' => null,
'notify_sound' => null, 'notify_sound' => null,
'notify_desktop' => null, 'notify_desktop' => null,

View File

@@ -158,10 +158,6 @@ class ident_switch extends rcube_plugin
default => null, default => null,
}; };
if ($rc->task === 'mail') {
$this->include_stylesheet('ident_switch.css');
}
return $args; return $args;
} }
@@ -188,7 +184,9 @@ class ident_switch extends rcube_plugin
$iid = intval($iid_s); $iid = intval($iid_s);
} }
$accNames = [$_SESSION['global_alias'] ?? $rc->user->data['username']]; $primaryIdentity = $rc->user->get_identity();
$primaryName = !empty($primaryIdentity['name']) ? $primaryIdentity['name'] : null;
$accNames = [$_SESSION['global_alias'] ?? $primaryName ?? $rc->user->data['username']];
$accValues = [-1]; $accValues = [-1];
$accSelected = -1; $accSelected = -1;
$iidMap = [0 => -1]; // primary account: iid 0 → select value -1 $iidMap = [0 => -1]; // primary account: iid 0 → select value -1
@@ -216,6 +214,7 @@ class ident_switch extends rcube_plugin
return; return;
} }
$this->include_stylesheet('ident_switch.css');
$this->include_script('ident_switch-switch.js'); $this->include_script('ident_switch-switch.js');
// Pass config to JS environment // Pass config to JS environment
@@ -223,7 +222,7 @@ class ident_switch extends rcube_plugin
$select = new html_select([ $select = new html_select([
'id' => 'plugin-ident_switch-account', 'id' => 'plugin-ident_switch-account',
'style' => 'display: none; padding: 0;', 'style' => 'display: none;',
'onchange' => 'plugin_switchIdent_switch(this.value);', 'onchange' => 'plugin_switchIdent_switch(this.value);',
]); ]);
$select->add($accNames, $accValues); $select->add($accNames, $accValues);