- Sneedchat: detect a reconnect that silently lands as a guest (server's own can_send permission flag) and force a session refresh + relogin; add an idle-read timeout so a dead websocket is actually noticed - Sneedchat: multipart file upload support in http.rs, wired into sendMessage's attachment path (qu.ax-hosted images/video) - Discord-style optimistic message sends: dim pending messages, resolve to the real server echo, turn red and offer Retry on failure/timeout - MessageList: fix scroll-to-end/New-divider landing, anchored-scroll reliability during scrollbar drags, and BBCode [img][/img] tags merging into one URL when posted back-to-back - MediaEmbed: real audio output device, working scrub-bar seek/pan, restart-from-start on a finished video - DaemonStatusSettings: chatd connection indicator + build (git branch) badge for DMS's plugin manager pane - BufferList: scope the "+" join-channel affordance to IRC accounts only - DisplaySettingsPanel: new toggle for content-sniffing unrecognized media links Co-Authored-By: Claude Sonnet 5 <[email protected]>
202 lines
7.1 KiB
QML
202 lines
7.1 KiB
QML
import QtQuick
|
|
import QtQuick.Layouts
|
|
import Quickshell.Io
|
|
import qs.Common
|
|
import qs.Widgets
|
|
|
|
// Registered as plugin.json's "settings" key - the only thing shown in
|
|
// DMS's global Settings -> Plugins -> Dank Chat area, by explicit user
|
|
// direction (this pane started empty and gets exactly what's asked for,
|
|
// nothing assumed beyond it). Per-protocol chat settings (message
|
|
// filters, connection defaults, SASL, etc.) live in ChatSettings.qml
|
|
// instead, reachable from inside the chat panel's own three-dot menu -
|
|
// "is the background daemon actually running" is a different, always-
|
|
// relevant concern from any of that.
|
|
//
|
|
// DMS's own PluginsTab (see PluginListItem.qml's settingsLoader) injects
|
|
// `pluginService` into any loaded settings component that declares the
|
|
// property, the same way it does for every other plugin's "settings" -
|
|
// no manual embedding needed here, unlike ChatSettings.qml's in-panel
|
|
// case (which isn't reached through this normal path at all).
|
|
Item {
|
|
id: root
|
|
|
|
property var pluginService: null
|
|
property bool chatdConnected: false
|
|
|
|
function refreshStatus() {
|
|
root.chatdConnected = root.pluginService ? !!root.pluginService.getGlobalVar("dankChat", "chatdConnected", false) : false;
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
root.refreshStatus();
|
|
root.checkGitBranch();
|
|
}
|
|
onPluginServiceChanged: {
|
|
root.refreshStatus();
|
|
root.checkGitBranch();
|
|
}
|
|
|
|
// Which checkout this running plugin is actually loaded from - see
|
|
// /home/ultra/.claude memory on the development-branch workflow:
|
|
// "master" is the stable build users should be running day to day,
|
|
// "development" is where bug fixes/new features land first. Read live
|
|
// via git rather than baked in at some build step, since this plugin
|
|
// has no such step at all (the QML is interpreted directly from
|
|
// whatever's currently checked out) - so this always reflects the
|
|
// real current state, including mid-session branch switches once the
|
|
// plugin's settings page is reopened.
|
|
readonly property string pluginDir: {
|
|
if (root.pluginService) {
|
|
const p = root.pluginService.getPluginPath("dankChat");
|
|
if (p)
|
|
return p.endsWith("/") ? p : p + "/";
|
|
}
|
|
// Fallback matches ChatDaemon.qml's own pluginDir - this settings
|
|
// pane's QML file lives in the plugin's own directory regardless of
|
|
// whether pluginService.getPluginPath() comes through, since the
|
|
// component is loaded directly off disk either way.
|
|
const dirUrl = Qt.resolvedUrl(".").toString();
|
|
return dirUrl.replace(/^file:\/\//, "");
|
|
}
|
|
property string gitBranch: ""
|
|
readonly property string buildLabel: {
|
|
if (root.gitBranch === "master")
|
|
return "Production";
|
|
if (root.gitBranch === "development")
|
|
return "Development";
|
|
if (root.gitBranch === "")
|
|
return "Unknown";
|
|
return root.gitBranch; // some other branch - show it verbatim rather than guessing
|
|
}
|
|
readonly property color buildLabelColor: {
|
|
if (root.gitBranch === "master")
|
|
return Theme.primary;
|
|
if (root.gitBranch === "development")
|
|
return Theme.warning;
|
|
return Theme.surfaceVariantText;
|
|
}
|
|
|
|
function checkGitBranch() {
|
|
if (root.pluginDir && !gitBranchProcess.running)
|
|
gitBranchProcess.running = true;
|
|
}
|
|
|
|
onPluginDirChanged: root.checkGitBranch()
|
|
|
|
Process {
|
|
id: gitBranchProcess
|
|
running: false
|
|
command: ["git", "-C", root.pluginDir, "rev-parse", "--abbrev-ref", "HEAD"]
|
|
stdout: StdioCollector {
|
|
onStreamFinished: root.gitBranch = (text || "").trim()
|
|
}
|
|
}
|
|
|
|
// getGlobalVar() is a plain function call, not a property read - QML's
|
|
// binding dependency tracking can't auto-refresh from that, so this
|
|
// has to explicitly re-poll on the one signal that means the value
|
|
// might have changed instead of binding chatdConnected directly.
|
|
Connections {
|
|
target: root.pluginService
|
|
enabled: root.pluginService !== null
|
|
function onGlobalVarChanged(changedPluginId, varName) {
|
|
if (changedPluginId === "dankChat" && varName === "chatdConnected")
|
|
root.refreshStatus();
|
|
}
|
|
}
|
|
|
|
implicitHeight: content.implicitHeight
|
|
height: implicitHeight
|
|
|
|
ColumnLayout {
|
|
id: content
|
|
width: parent.width
|
|
spacing: Theme.spacingM
|
|
|
|
StyledText {
|
|
Layout.fillWidth: true
|
|
text: "Chat Daemon"
|
|
font.pixelSize: Theme.fontSizeLarge
|
|
font.weight: Font.Medium
|
|
color: Theme.surfaceText
|
|
}
|
|
|
|
StyledText {
|
|
Layout.fillWidth: true
|
|
text: "The background process (chatd) that actually talks to IRC/XMPP servers on Dank Chat's behalf. If it's not running, no account can connect regardless of what's configured for it."
|
|
wrapMode: Text.WordWrap
|
|
color: Theme.surfaceVariantText
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: Theme.spacingS
|
|
|
|
Rectangle {
|
|
width: 10
|
|
height: 10
|
|
radius: 5
|
|
color: root.chatdConnected ? Theme.primary : Theme.error
|
|
}
|
|
|
|
StyledText {
|
|
text: root.chatdConnected ? "Running" : "Not running"
|
|
color: Theme.surfaceText
|
|
font.weight: Font.Medium
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
height: 1
|
|
color: Theme.outlineMedium
|
|
}
|
|
|
|
StyledText {
|
|
Layout.fillWidth: true
|
|
text: "Build"
|
|
font.pixelSize: Theme.fontSizeLarge
|
|
font.weight: Font.Medium
|
|
color: Theme.surfaceText
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: Theme.spacingS
|
|
|
|
Rectangle {
|
|
radius: Theme.cornerRadius
|
|
color: Theme.withAlpha(root.buildLabelColor, 0.18)
|
|
implicitWidth: buildLabelText.implicitWidth + Theme.spacingM * 2
|
|
implicitHeight: buildLabelText.implicitHeight + Theme.spacingXS * 2
|
|
|
|
StyledText {
|
|
id: buildLabelText
|
|
anchors.centerIn: parent
|
|
text: root.buildLabel
|
|
color: root.buildLabelColor
|
|
font.weight: Font.Bold
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
}
|
|
}
|
|
|
|
StyledText {
|
|
text: root.gitBranch ? ("(" + root.gitBranch + " branch)") : ""
|
|
visible: text !== ""
|
|
color: Theme.surfaceVariantText
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
}
|
|
}
|
|
|
|
DankButton {
|
|
Layout.alignment: Qt.AlignLeft
|
|
text: "Restart daemon"
|
|
onClicked: {
|
|
if (root.pluginService)
|
|
root.pluginService.setGlobalVar("dankChat", "restartNonce", Date.now());
|
|
}
|
|
}
|
|
}
|
|
}
|