- Matrix: interactive device verification (SAS emoji flow), recovery-key
backup/restore, membership announcement moderation, member/room avatars,
power-level-aware moderation menu, and an encryption lock indicator on
the composer - verified live with two independent devices.
- Replace the single combined join panel with a dedicated page per
protocol (IrcJoinPanel/MatrixJoinPanel/DiscordJoinPanel), routed by
whichever account the "+" button was clicked for rather than listing
every account in one expandable page.
- Discord: friends list (Online/All, click-to-DM, add-friend, matches
READY's relationships + live PRESENCE_UPDATE), create-a-server, and
join-by-invite alongside the existing DM-by-ID flow.
- Discord: an expired attachment-link fallback ("Open in Discord") for
scrollback media whose signed cdn.discordapp.com URL has lapsed -
Discord's own /attachments/refresh-urls re-sign endpoint hard-rejects
this backend's user-token requests, confirmed live, so this opens the
real message instead of showing a broken embed.
- Discord: detect gateway close code 4004 (revoked token) as a distinct
ConnState::AuthFailed instead of retrying a dead credential forever;
the Accounts pane shows a clear "needs login" state that routes
straight to a fresh QR login, which upserts the same account id in
place (no lost buffers/scrollback).
- Fix the unread-DM pinned-buffer flash never reliably triggering: an
implicit property binding computed via nested closures over two
separate properties wasn't consistently re-evaluated by QML's
dependency tracker; replaced with an explicit recompute function called
at every actual mutation point.
- Fix ChatBarWidget.qml's pill double-padding against BasePill's own
padding.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
109 lines
4.1 KiB
QML
109 lines
4.1 KiB
QML
import QtQuick
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.Widgets
|
|
import qs.Modules.Plugins
|
|
|
|
// Composite-plugin "widget" surface: the DankBar pill. Reads connection/
|
|
// unread state the daemon surface (ChatDaemon.qml) publishes via
|
|
// PluginService global vars - see PLUGINS/README.md's "Plugin Global
|
|
// Variables" section for why this indirection exists (daemon and widget
|
|
// are separate object instances with no direct reference to each other).
|
|
//
|
|
// Clicking the pill bumps a "toggleNonce" global var the daemon watches,
|
|
// rather than PluginService.togglePlugin(pluginId) - that generic lookup
|
|
// didn't reliably find this composite plugin's daemon instance when
|
|
// tested against this DMS version. See ChatDaemon.qml for the other end.
|
|
PluginComponent {
|
|
id: root
|
|
|
|
pillClickAction: () => {
|
|
const n = PluginService.getGlobalVar(root.pluginId, "toggleNonce", 0);
|
|
PluginService.setGlobalVar(root.pluginId, "toggleNonce", n + 1);
|
|
}
|
|
|
|
PluginGlobalVar {
|
|
id: chatdConnected
|
|
varName: "chatdConnected"
|
|
defaultValue: false
|
|
}
|
|
|
|
PluginGlobalVar {
|
|
id: totalUnread
|
|
varName: "totalUnread"
|
|
defaultValue: 0
|
|
}
|
|
|
|
// Set by ChatDaemon.qml whenever a pinned buffer (see BufferList.qml's
|
|
// Pin menu item) has an unread DM or highlighted mention pending -
|
|
// chatd only ever emits the underlying "notification" event for one
|
|
// of those two cases, never plain traffic, so this is exactly "unread
|
|
// DM or mention from a pinned channel", not just "any unread".
|
|
PluginGlobalVar {
|
|
id: hasPinnedAlert
|
|
varName: "hasPinnedAlert"
|
|
defaultValue: false
|
|
}
|
|
|
|
// Deliberately just the icon+count Row, no background/sizing of our
|
|
// own - the host BasePill (see PluginComponent.qml, which wraps
|
|
// whatever horizontalBarPill provides as this component's `content`)
|
|
// already supplies the pill's real background, padding, and radius,
|
|
// sized from this Row's own natural implicitWidth/implicitHeight.
|
|
// Matches every other composite-plugin bar widget's own convention
|
|
// (see PLUGINS/ExampleCompositePlugin/CompositeBarWidget.qml) -
|
|
// wrapping this in a second StyledRect of our own, as it used to be,
|
|
// just draws a redundant background with its own different padding
|
|
// math, which is what made this pill a different size than its
|
|
// siblings in the first place.
|
|
horizontalBarPill: Component {
|
|
Row {
|
|
id: content
|
|
spacing: Theme.spacingXS
|
|
|
|
DankIcon {
|
|
id: chatIcon
|
|
name: "chat"
|
|
size: Theme.iconSize - 4
|
|
color: hasPinnedAlert.value ? Theme.error : (chatdConnected.value ? Theme.primary : Theme.surfaceVariantText)
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
// Pulses opacity while a pinned alert is pending - stops
|
|
// (and resets to fully opaque, rather than possibly
|
|
// freezing mid-fade) the instant hasPinnedAlert clears,
|
|
// e.g. once the buffer's actually been read.
|
|
SequentialAnimation {
|
|
running: hasPinnedAlert.value
|
|
loops: Animation.Infinite
|
|
onRunningChanged: if (!running) chatIcon.opacity = 1.0
|
|
|
|
NumberAnimation {
|
|
target: chatIcon
|
|
property: "opacity"
|
|
to: 0.35
|
|
duration: 500
|
|
easing.type: Easing.InOutQuad
|
|
}
|
|
|
|
NumberAnimation {
|
|
target: chatIcon
|
|
property: "opacity"
|
|
to: 1.0
|
|
duration: 500
|
|
easing.type: Easing.InOutQuad
|
|
}
|
|
}
|
|
}
|
|
|
|
StyledText {
|
|
visible: totalUnread.value > 0
|
|
text: totalUnread.value
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceText
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|