mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-13 09:12:08 -04:00
clipboard: Fix pinned entry logic
- Add keyboard nav to pinned entries - Fix wrong copied selection upon Enter
This commit is contained in:
@@ -146,9 +146,16 @@ func handleCopyEntry(conn net.Conn, req models.Request, m *Manager) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.TouchEntry(uint64(id)); err != nil {
|
if entry.Pinned {
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
if err := m.CreateHistoryEntryFromPinned(entry); err != nil {
|
||||||
return
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := m.TouchEntry(uint64(id)); err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "copied to clipboard"})
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "copied to clipboard"})
|
||||||
|
|||||||
@@ -388,6 +388,10 @@ func (m *Manager) deduplicateInTx(b *bolt.Bucket, hash uint64) error {
|
|||||||
if extractHash(v) != hash {
|
if extractHash(v) != hash {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
entry, err := decodeEntry(v)
|
||||||
|
if err == nil && entry.Pinned {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if err := b.Delete(k); err != nil {
|
if err := b.Delete(k); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -842,6 +846,62 @@ func (m *Manager) TouchEntry(id uint64) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) CreateHistoryEntryFromPinned(pinnedEntry *Entry) error {
|
||||||
|
if m.db == nil {
|
||||||
|
return fmt.Errorf("database not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new unpinned entry with the same data
|
||||||
|
newEntry := Entry{
|
||||||
|
Data: pinnedEntry.Data,
|
||||||
|
MimeType: pinnedEntry.MimeType,
|
||||||
|
Size: pinnedEntry.Size,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
IsImage: pinnedEntry.IsImage,
|
||||||
|
Preview: pinnedEntry.Preview,
|
||||||
|
Pinned: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.storeEntryWithoutDedup(newEntry); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.updateState()
|
||||||
|
m.notifySubscribers()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) storeEntryWithoutDedup(entry Entry) error {
|
||||||
|
if m.db == nil {
|
||||||
|
return fmt.Errorf("database not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.Hash = computeHash(entry.Data)
|
||||||
|
|
||||||
|
return m.db.Update(func(tx *bolt.Tx) error {
|
||||||
|
b := tx.Bucket([]byte("clipboard"))
|
||||||
|
|
||||||
|
id, err := b.NextSequence()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.ID = id
|
||||||
|
|
||||||
|
encoded, err := encodeEntry(entry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.Put(itob(id), encoded); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.trimLengthInTx(b)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) ClearHistory() {
|
func (m *Manager) ClearHistory() {
|
||||||
if m.db == nil {
|
if m.db == nil {
|
||||||
return
|
return
|
||||||
@@ -1419,6 +1479,37 @@ func (m *Manager) PinEntry(id uint64) error {
|
|||||||
return fmt.Errorf("database not available")
|
return fmt.Errorf("database not available")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entryToPin, err := m.GetEntry(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var hashExists bool
|
||||||
|
if err := m.db.View(func(tx *bolt.Tx) error {
|
||||||
|
b := tx.Bucket([]byte("clipboard"))
|
||||||
|
if b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c := b.Cursor()
|
||||||
|
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||||
|
entry, err := decodeEntry(v)
|
||||||
|
if err != nil || !entry.Pinned {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if entry.Hash == entryToPin.Hash {
|
||||||
|
hashExists = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if hashExists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Check pinned count
|
// Check pinned count
|
||||||
cfg := m.getConfig()
|
cfg := m.getConfig()
|
||||||
pinnedCount := 0
|
pinnedCount := 0
|
||||||
@@ -1443,7 +1534,7 @@ func (m *Manager) PinEntry(id uint64) error {
|
|||||||
return fmt.Errorf("maximum pinned entries reached (%d)", cfg.MaxPinned)
|
return fmt.Errorf("maximum pinned entries reached (%d)", cfg.MaxPinned)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := m.db.Update(func(tx *bolt.Tx) error {
|
err = m.db.Update(func(tx *bolt.Tx) error {
|
||||||
b := tx.Bucket([]byte("clipboard"))
|
b := tx.Bucket([]byte("clipboard"))
|
||||||
v := b.Get(itob(id))
|
v := b.Get(itob(id))
|
||||||
if v == nil {
|
if v == nil {
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ Item {
|
|||||||
}
|
}
|
||||||
visible: modal.activeTab === "saved"
|
visible: modal.activeTab === "saved"
|
||||||
|
|
||||||
|
currentIndex: clipboardContent.modal ? clipboardContent.modal.selectedIndex : 0
|
||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
interactive: true
|
interactive: true
|
||||||
flickDeceleration: 1500
|
flickDeceleration: 1500
|
||||||
@@ -173,6 +174,26 @@ Item {
|
|||||||
pressDelay: 0
|
pressDelay: 0
|
||||||
flickableDirection: Flickable.VerticalFlick
|
flickableDirection: Flickable.VerticalFlick
|
||||||
|
|
||||||
|
function ensureVisible(index) {
|
||||||
|
if (index < 0 || index >= count) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const itemHeight = ClipboardConstants.itemHeight + spacing;
|
||||||
|
const itemY = index * itemHeight;
|
||||||
|
const itemBottom = itemY + itemHeight;
|
||||||
|
if (itemY < contentY) {
|
||||||
|
contentY = itemY;
|
||||||
|
} else if (itemBottom > contentY + height) {
|
||||||
|
contentY = itemBottom - height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCurrentIndexChanged: {
|
||||||
|
if (clipboardContent.modal?.keyboardNavigationActive && currentIndex >= 0) {
|
||||||
|
ensureVisible(currentIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: I18n.tr("No saved clipboard entries")
|
text: I18n.tr("No saved clipboard entries")
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
@@ -190,7 +211,7 @@ Item {
|
|||||||
entry: modelData
|
entry: modelData
|
||||||
entryIndex: index + 1
|
entryIndex: index + 1
|
||||||
itemIndex: index
|
itemIndex: index
|
||||||
isSelected: false
|
isSelected: clipboardContent.modal?.keyboardNavigationActive && index === clipboardContent.modal.selectedIndex
|
||||||
modal: clipboardContent.modal
|
modal: clipboardContent.modal
|
||||||
listView: savedListView
|
listView: savedListView
|
||||||
onCopyRequested: clipboardContent.modal.copyEntry(modelData)
|
onCopyRequested: clipboardContent.modal.copyEntry(modelData)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ Rectangle {
|
|||||||
|
|
||||||
readonly property string entryType: modal ? modal.getEntryType(entry) : "text"
|
readonly property string entryType: modal ? modal.getEntryType(entry) : "text"
|
||||||
readonly property string entryPreview: modal ? modal.getEntryPreview(entry) : ""
|
readonly property string entryPreview: modal ? modal.getEntryPreview(entry) : ""
|
||||||
|
readonly property bool hasPinnedDuplicate: !entry.pinned && modal ? modal.hashedPinnedEntry(entry.hash) : false
|
||||||
|
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: {
|
color: {
|
||||||
@@ -111,8 +112,8 @@ Rectangle {
|
|||||||
DankActionButton {
|
DankActionButton {
|
||||||
iconName: "push_pin"
|
iconName: "push_pin"
|
||||||
iconSize: Theme.iconSize - 6
|
iconSize: Theme.iconSize - 6
|
||||||
iconColor: entry.pinned ? Theme.primary : Theme.surfaceText
|
iconColor: (entry.pinned || hasPinnedDuplicate) ? Theme.primary : Theme.surfaceText
|
||||||
backgroundColor: entry.pinned ? Theme.primarySelected : "transparent"
|
backgroundColor: (entry.pinned || hasPinnedDuplicate) ? Theme.primarySelected : "transparent"
|
||||||
onClicked: entry.pinned ? unpinRequested() : pinRequested()
|
onClicked: entry.pinned ? unpinRequested() : pinRequested()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ DankModal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
property string activeTab: "recents"
|
property string activeTab: "recents"
|
||||||
|
onActiveTabChanged: {
|
||||||
|
ClipboardService.selectedIndex = 0;
|
||||||
|
ClipboardService.keyboardNavigationActive = false;
|
||||||
|
}
|
||||||
property bool showKeyboardHints: false
|
property bool showKeyboardHints: false
|
||||||
property Component clipboardContent
|
property Component clipboardContent
|
||||||
property int activeImageLoads: 0
|
property int activeImageLoads: 0
|
||||||
@@ -121,6 +125,10 @@ DankModal {
|
|||||||
return ClipboardService.getEntryType(entry);
|
return ClipboardService.getEntryType(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hashedPinnedEntry(entryHash) {
|
||||||
|
return ClipboardService.hashedPinnedEntry(entryHash);
|
||||||
|
}
|
||||||
|
|
||||||
visible: false
|
visible: false
|
||||||
modalWidth: ClipboardConstants.modalWidth
|
modalWidth: ClipboardConstants.modalWidth
|
||||||
modalHeight: ClipboardConstants.modalHeight
|
modalHeight: ClipboardConstants.modalHeight
|
||||||
|
|||||||
@@ -13,15 +13,17 @@ QtObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function selectNext() {
|
function selectNext() {
|
||||||
if (!ClipboardService.clipboardEntries || ClipboardService.clipboardEntries.length === 0) {
|
const entries = modal.activeTab === "saved" ? ClipboardService.pinnedEntries : ClipboardService.unpinnedEntries;
|
||||||
|
if (!entries || entries.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ClipboardService.keyboardNavigationActive = true;
|
ClipboardService.keyboardNavigationActive = true;
|
||||||
ClipboardService.selectedIndex = Math.min(ClipboardService.selectedIndex + 1, ClipboardService.clipboardEntries.length - 1);
|
ClipboardService.selectedIndex = Math.min(ClipboardService.selectedIndex + 1, entries.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectPrevious() {
|
function selectPrevious() {
|
||||||
if (!ClipboardService.clipboardEntries || ClipboardService.clipboardEntries.length === 0) {
|
const entries = modal.activeTab === "saved" ? ClipboardService.pinnedEntries : ClipboardService.unpinnedEntries;
|
||||||
|
if (!entries || entries.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ClipboardService.keyboardNavigationActive = true;
|
ClipboardService.keyboardNavigationActive = true;
|
||||||
@@ -29,19 +31,25 @@ QtObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function copySelected() {
|
function copySelected() {
|
||||||
if (!ClipboardService.clipboardEntries || ClipboardService.clipboardEntries.length === 0 || ClipboardService.selectedIndex < 0 || ClipboardService.selectedIndex >= ClipboardService.clipboardEntries.length) {
|
const entries = modal.activeTab === "saved" ? ClipboardService.pinnedEntries : ClipboardService.unpinnedEntries;
|
||||||
|
if (!entries || entries.length === 0 || ClipboardService.selectedIndex < 0 || ClipboardService.selectedIndex >= entries.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const selectedEntry = ClipboardService.clipboardEntries[ClipboardService.selectedIndex];
|
const selectedEntry = entries[ClipboardService.selectedIndex];
|
||||||
modal.copyEntry(selectedEntry);
|
modal.copyEntry(selectedEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteSelected() {
|
function deleteSelected() {
|
||||||
if (!ClipboardService.clipboardEntries || ClipboardService.clipboardEntries.length === 0 || ClipboardService.selectedIndex < 0 || ClipboardService.selectedIndex >= ClipboardService.clipboardEntries.length) {
|
const entries = modal.activeTab === "saved" ? ClipboardService.pinnedEntries : ClipboardService.unpinnedEntries;
|
||||||
|
if (!entries || entries.length === 0 || ClipboardService.selectedIndex < 0 || ClipboardService.selectedIndex >= entries.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const selectedEntry = ClipboardService.clipboardEntries[ClipboardService.selectedIndex];
|
const selectedEntry = entries[ClipboardService.selectedIndex];
|
||||||
modal.deleteEntry(selectedEntry);
|
if (modal.activeTab === "saved") {
|
||||||
|
modal.deletePinnedEntry(selectedEntry);
|
||||||
|
} else {
|
||||||
|
modal.deleteEntry(selectedEntry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKey(event) {
|
function handleKey(event) {
|
||||||
|
|||||||
@@ -248,6 +248,13 @@ Singleton {
|
|||||||
return "text";
|
return "text";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hashedPinnedEntry(entryHash) {
|
||||||
|
if (!entryHash) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return pinnedEntries.some(pinnedEntry => pinnedEntry.hash === entryHash);
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: DMSService
|
target: DMSService
|
||||||
enabled: root.refCount > 0
|
enabled: root.refCount > 0
|
||||||
|
|||||||
Reference in New Issue
Block a user