1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-07 22:15:38 -05:00
Files
DankMaterialShell/Modules/ClipboardHistory.qml
2025-07-22 23:51:18 -04:00

488 lines
17 KiB
QML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Widgets
DankModal {
id: clipboardHistory
property bool isVisible: false
property int totalCount: 0
property var activeTheme: Theme
property bool showClearConfirmation: false
property var clipboardEntries: []
property string searchText: ""
function updateFilteredModel() {
filteredClipboardModel.clear();
for (let i = 0; i < clipboardModel.count; i++) {
const entry = clipboardModel.get(i).entry;
if (searchText.trim().length === 0) {
filteredClipboardModel.append({
"entry": entry
});
} else {
const content = getEntryPreview(entry).toLowerCase();
if (content.includes(searchText.toLowerCase()))
filteredClipboardModel.append({
"entry": entry
});
}
}
clipboardHistory.totalCount = filteredClipboardModel.count;
}
function toggle() {
if (isVisible)
hide();
else
show();
}
function show() {
clipboardHistory.isVisible = true;
refreshClipboard();
console.log("ClipboardHistory: Opening and refreshing");
}
function hide() {
clipboardHistory.isVisible = false;
clipboardHistory.searchText = "";
cleanupTempFiles();
}
function cleanupTempFiles() {
cleanupProcess.command = ["sh", "-c", "rm -f /tmp/clipboard_preview_*.png"];
cleanupProcess.running = true;
}
function refreshClipboard() {
clipboardProcess.running = true;
}
function copyEntry(entry) {
const entryId = entry.split('\t')[0];
copyProcess.command = ["sh", "-c", `cliphist decode ${entryId} | wl-copy`];
copyProcess.running = true;
console.log("ClipboardHistory: Entry copied, hiding interface");
hide();
}
function deleteEntry(entry) {
console.log("Deleting entry:", entry);
deleteProcess.command = ["sh", "-c", `echo '${entry.replace(/'/g, "'\\''")}' | cliphist delete`];
deleteProcess.running = true;
}
function clearAll() {
clearProcess.running = true;
}
function getEntryPreview(entry) {
let content = entry.replace(/^\s*\d+\s+/, "");
if (content.includes("image/") || content.includes("binary data") || /\.(png|jpg|jpeg|gif|bmp|webp)/i.test(content)) {
const dimensionMatch = content.match(/(\d+)x(\d+)/);
if (dimensionMatch)
return `Image ${dimensionMatch[1]}×${dimensionMatch[2]}`;
const typeMatch = content.match(/\b(png|jpg|jpeg|gif|bmp|webp)\b/i);
if (typeMatch)
return `Image (${typeMatch[1].toUpperCase()})`;
return "Image";
}
if (content.length > 100)
return content.substring(0, 100) + "...";
return content;
}
function getEntryType(entry) {
if (entry.includes("image/") || entry.includes("binary data") || /\.(png|jpg|jpeg|gif|bmp|webp)/i.test(entry) || /\b(png|jpg|jpeg|gif|bmp|webp)\b/i.test(entry))
return "image";
if (entry.length > 200)
return "long_text";
return "text";
}
// DankModal configuration
visible: isVisible
width: 650
height: 550
keyboardFocus: "ondemand"
onBackgroundClicked: {
hide();
}
content: Component {
Column {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
// Header with search
Rectangle {
width: parent.width
height: 40
color: "transparent"
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankIcon {
name: "content_paste"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: `Clipboard History (${totalCount})`
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankActionButton {
iconName: "delete"
iconSize: Theme.iconSize - 4
iconColor: Theme.error
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
onClicked: {
showClearConfirmation = true;
}
}
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
onClicked: hide()
}
}
}
// Search field
DankTextField {
id: searchField
width: parent.width
placeholderText: "Search clipboard history..."
leftIconName: "search"
showClearButton: true
onTextChanged: {
clipboardHistory.searchText = text;
updateFilteredModel();
}
Connections {
target: clipboardHistory
function onOpened() {
searchField.forceActiveFocus();
}
function onDialogClosed() {
searchField.clearFocus();
}
}
}
// Clipboard entries list
Rectangle {
width: parent.width
height: parent.height - 110
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.1)
clip: true
ScrollView {
anchors.fill: parent
anchors.margins: Theme.spacingS
clip: true
ScrollBar.vertical.policy: ScrollBar.AsNeeded
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ListView {
id: clipboardListView
width: parent.availableWidth
model: filteredClipboardModel
spacing: Theme.spacingXS
delegate: Rectangle {
width: clipboardListView.width
height: Math.max(60, contentText.contentHeight + Theme.spacingL)
radius: Theme.cornerRadius
color: mouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5)
Rectangle {
anchors.fill: parent
anchors.margins: Theme.spacingM
color: "transparent"
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankIcon {
name: {
const type = getEntryType(model.entry);
if (type === "image") return "image";
if (type === "long_text") return "subject";
return "content_copy";
}
size: Theme.iconSize
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
Text {
id: contentText
text: getEntryPreview(model.entry)
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
width: clipboardListView.width - Theme.iconSize - 80 - Theme.spacingM * 4
wrapMode: Text.Wrap
elide: Text.ElideRight
maximumLineCount: 3
anchors.verticalCenter: parent.verticalCenter
}
}
DankActionButton {
iconName: "delete"
iconSize: 16
iconColor: Theme.error
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
onClicked: {
deleteEntry(model.entry);
refreshClipboard();
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: copyEntry(model.entry)
}
}
Text {
text: "No clipboard entries found"
anchors.centerIn: parent
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
visible: filteredClipboardModel.count === 0
}
}
}
}
}
}
// Clear confirmation dialog
DankModal {
id: clearConfirmDialog
visible: showClearConfirmation
width: 350
height: 150
keyboardFocus: "ondemand"
onBackgroundClicked: {
showClearConfirmation = false;
}
content: Component {
Item {
anchors.fill: parent
Column {
anchors.centerIn: parent
width: parent.width - Theme.spacingM * 2
spacing: Theme.spacingM
Text {
text: "Clear All History?"
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
text: "This will permanently delete all clipboard history."
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
wrapMode: Text.WordWrap
width: parent.width
horizontalAlignment: Text.AlignHCenter
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingM
Rectangle {
width: 100
height: 40
radius: Theme.cornerRadius
color: cancelClearButton.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
Text {
text: "Cancel"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
anchors.centerIn: parent
}
MouseArea {
id: cancelClearButton
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: showClearConfirmation = false
}
}
Rectangle {
width: 100
height: 40
radius: Theme.cornerRadius
color: confirmClearButton.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.9) : Theme.error
Text {
text: "Clear All"
font.pixelSize: Theme.fontSizeMedium
color: Theme.primaryText
font.weight: Font.Medium
anchors.centerIn: parent
}
MouseArea {
id: confirmClearButton
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
clearAll();
showClearConfirmation = false;
hide();
}
}
}
}
}
}
}
}
// Data models
ListModel {
id: clipboardModel
}
ListModel {
id: filteredClipboardModel
}
// Processes
Process {
id: clipboardProcess
command: ["cliphist", "list"]
running: false
stdout: StdioCollector {
onStreamFinished: {
clipboardModel.clear();
const lines = text.trim().split('\n');
for (const line of lines) {
if (line.trim().length > 0) {
clipboardModel.append({"entry": line});
}
}
updateFilteredModel();
}
}
}
Process {
id: copyProcess
running: false
onExited: (exitCode) => {
if (exitCode !== 0)
console.error("Copy failed with exit code:", exitCode);
}
}
Process {
id: deleteProcess
running: false
onExited: (exitCode) => {
if (exitCode === 0) {
refreshClipboard();
} else {
console.error("Delete failed with exit code:", exitCode);
}
}
}
Process {
id: clearProcess
command: ["cliphist", "wipe"]
running: false
onExited: (exitCode) => {
if (exitCode === 0) {
clipboardModel.clear();
filteredClipboardModel.clear();
totalCount = 0;
} else {
console.error("Clear failed with exit code:", exitCode);
}
}
}
Process {
id: cleanupProcess
running: false
}
IpcHandler {
function open() {
console.log("ClipboardHistory: IPC open() called");
clipboardHistory.show();
return "CLIPBOARD_OPEN_SUCCESS";
}
function close() {
console.log("ClipboardHistory: IPC close() called");
clipboardHistory.hide();
return "CLIPBOARD_CLOSE_SUCCESS";
}
function toggle() {
console.log("ClipboardHistory: IPC toggle() called");
clipboardHistory.toggle();
return "CLIPBOARD_TOGGLE_SUCCESS";
}
target: "clipboard"
}
}