Skip to content

Commit 8562446

Browse files
authored
Merge pull request #217 from ospfranco/delete-clipboard-items
Delete clipboard items support
2 parents f7e1ca3 + e9901b2 commit 8562446

File tree

3 files changed

+95
-40
lines changed

3 files changed

+95
-40
lines changed

src/stores/clipboard.store.tsx

Lines changed: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import AsyncStorage from '@react-native-async-storage/async-storage'
2-
import {solNative} from 'lib/SolNative'
3-
import {autorun, makeAutoObservable, runInAction, toJS} from 'mobx'
4-
import {EmitterSubscription} from 'react-native'
5-
import {IRootStore} from 'store'
6-
import {Widget} from './ui.store'
2+
import { solNative } from 'lib/SolNative'
3+
import { autorun, makeAutoObservable, runInAction, toJS } from 'mobx'
4+
import { EmitterSubscription } from 'react-native'
5+
import { IRootStore } from 'store'
6+
import { Widget } from './ui.store'
77
import MiniSearch from 'minisearch'
8-
import {storage} from './storage'
9-
import {captureException} from '@sentry/react-native'
8+
import { storage } from './storage'
9+
import { captureException } from '@sentry/react-native'
1010

1111
const MAX_ITEMS = 1000
1212

@@ -20,27 +20,31 @@ export type PasteItem = {
2020
text: string
2121
url?: string | null
2222
bundle?: string | null
23+
datetime: number // Unix timestamp when copied
2324
}
2425

2526
let minisearch = new MiniSearch({
2627
fields: ['text', 'bundle'],
27-
storeFields: ['text', 'url', 'bundle'],
28+
storeFields: ['id', 'text', 'url', 'bundle', 'datetime'],
2829
tokenize: (text: string, fieldName?: string) => text.split(/[\s\.]+/),
29-
searchOptions: {
30-
boost: {text: 2},
31-
fuzzy: true,
32-
prefix: true,
33-
},
3430
})
3531

3632
export const createClipboardStore = (root: IRootStore) => {
3733
const store = makeAutoObservable({
34+
deleteItem: (index: number) => {
35+
if (index >= 0 && index < store.items.length) {
36+
minisearch.remove(store.items[index])
37+
store.items.splice(index, 1)
38+
}
39+
},
40+
deleteAllItems: () => {
41+
store.items = []
42+
minisearch.removeAll()
43+
},
3844
items: [] as PasteItem[],
3945
saveHistory: false,
40-
onFileCopied: (obj: {text: string; url: string; bundle: string | null}) => {
41-
let newItem = {id: +Date.now(), ...obj}
42-
43-
console.warn(`Received copied file! ${JSON.stringify(newItem)}`)
46+
onFileCopied: (obj: { text: string; url: string; bundle: string | null }) => {
47+
let newItem: PasteItem = { id: +Date.now(), datetime: Date.now(), ...obj }
4448

4549
// If save history move file to more permanent storage
4650
if (store.saveHistory) {
@@ -65,12 +69,12 @@ export const createClipboardStore = (root: IRootStore) => {
6569
// Remove last item from minisearch
6670
store.removeLastItemIfNeeded()
6771
},
68-
onTextCopied: (obj: {text: string; bundle: string | null}) => {
72+
onTextCopied: (obj: { text: string; bundle: string | null }) => {
6973
if (!obj.text) {
7074
return
7175
}
7276

73-
let newItem = {id: Date.now().valueOf(), ...obj}
77+
let newItem: PasteItem = { id: Date.now().valueOf(), datetime: Date.now(), ...obj }
7478

7579
const index = store.items.findIndex(t => t.text === newItem.text)
7680
// Item already exists, move to top
@@ -91,11 +95,27 @@ export const createClipboardStore = (root: IRootStore) => {
9195
store.removeLastItemIfNeeded()
9296
},
9397
get clipboardItems(): PasteItem[] {
98+
let items = store.items;
99+
94100
if (!root.ui.query || root.ui.focusedWidget !== Widget.CLIPBOARD) {
95-
return root.clipboard.items
101+
return items
96102
}
97103

98-
return minisearch.search(root.ui.query) as any
104+
// Boost recent items in search results
105+
const now = Date.now();
106+
return minisearch.search(root.ui.query, {
107+
boostDocument: (documentId, term, storedFields) => {
108+
const dt = typeof storedFields?.datetime === 'number' ? storedFields.datetime : Number(storedFields?.datetime);
109+
if (!dt || isNaN(dt)) return 1;
110+
// Boost items copied in the last 24h, scale down for older
111+
const hoursAgo = (now - dt) / (1000 * 60 * 60);
112+
if (hoursAgo < 1) return 2; // very recent
113+
if (hoursAgo < 24) return 1.5; // recent
114+
return 1;
115+
},
116+
boost: { text: 2 },
117+
fuzzy: true,
118+
}) as any
99119
},
100120
removeLastItemIfNeeded: () => {
101121
if (store.items.length > MAX_ITEMS) {
@@ -160,7 +180,13 @@ export const createClipboardStore = (root: IRootStore) => {
160180

161181
if (entry) {
162182
let items = JSON.parse(entry)
163-
183+
// Ensure all items have datetime
184+
items = items.map((item: any) => ({
185+
...item,
186+
datetime: typeof item.datetime === 'number' && !isNaN(item.datetime)
187+
? item.datetime
188+
: (item.id || Date.now()), // fallback: use id or now
189+
}))
164190
runInAction(() => {
165191
store.items = items
166192
minisearch.addAll(store.items)
@@ -171,18 +197,24 @@ export const createClipboardStore = (root: IRootStore) => {
171197

172198
const persist = async () => {
173199
if (store.saveHistory) {
174-
const history = toJS(store)
200+
// Ensure all items have datetime before persisting
201+
const itemsToPersist = store.items.map(item => ({
202+
...item,
203+
datetime: typeof item.datetime === 'number' && !isNaN(item.datetime)
204+
? item.datetime
205+
: (item.id || Date.now()),
206+
}))
175207
try {
176208
await solNative.securelyStore(
177209
'@sol.clipboard_history_v2',
178-
JSON.stringify(history.items),
210+
JSON.stringify(itemsToPersist),
179211
)
180212
} catch (e) {
181213
console.warn('Could not persist data', e)
182214
}
183215
}
184216

185-
let storeWithoutItems = {...store}
217+
let storeWithoutItems = { ...store }
186218
storeWithoutItems.items = []
187219

188220
try {

src/stores/keystroke.store.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@ export const createKeystrokeStore = (root: IRootStore) => {
3232
shift: boolean
3333
}) => {
3434
switch (keyCode) {
35+
case 51: {
36+
if (root.ui.focusedWidget === Widget.CLIPBOARD) {
37+
if (shift) {
38+
root.clipboard.deleteAllItems()
39+
} else {
40+
root.clipboard.deleteItem(root.ui.selectedIndex)
41+
}
42+
return
43+
}
44+
break
45+
}
3546
// "j" key
3647
case 38: {
3748
// simulate a down key press

src/widgets/clipboard.widget.tsx

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
1-
import {LegendList, LegendListRef} from '@legendapp/list'
1+
import { LegendList, LegendListRef } from '@legendapp/list'
22
import clsx from 'clsx'
3-
import {FileIcon} from 'components/FileIcon'
4-
import {LoadingBar} from 'components/LoadingBar'
5-
import {MainInput} from 'components/MainInput'
6-
import {observer} from 'mobx-react-lite'
7-
import {FC, useEffect, useRef} from 'react'
8-
import {StyleSheet, Text, TouchableOpacity, View, ViewStyle} from 'react-native'
9-
import {useStore} from 'store'
10-
import {PasteItem} from 'stores/clipboard.store'
3+
import { FileIcon } from 'components/FileIcon'
4+
import { Key } from 'components/Key'
5+
import { LoadingBar } from 'components/LoadingBar'
6+
import { MainInput } from 'components/MainInput'
7+
import { observer } from 'mobx-react-lite'
8+
import { FC, useEffect, useRef } from 'react'
9+
import { StyleSheet, Text, TouchableOpacity, View, ViewStyle } from 'react-native'
10+
import { useStore } from 'store'
11+
import { PasteItem } from 'stores/clipboard.store'
1112

1213
interface Props {
1314
style?: ViewStyle
1415
className?: string
1516
}
1617

1718
const RenderItem = observer(
18-
({item, index}: {item: PasteItem; index: number}) => {
19+
({ item, index }: { item: PasteItem; index: number }) => {
1920
const store = useStore()
2021
const selectedIndex = store.ui.selectedIndex
2122
const isActive = index === selectedIndex
@@ -32,8 +33,8 @@ const RenderItem = observer(
3233
<FileIcon
3334
url={decodeURIComponent(
3435
item.bundle?.replace('file://', '') ??
35-
item.url?.replace('file://', '') ??
36-
'',
36+
item.url?.replace('file://', '') ??
37+
'',
3738
)}
3839
className="h-6 w-6"
3940
/>
@@ -62,7 +63,7 @@ function isPngOrJpg(url: string | null | undefined) {
6263
)
6364
}
6465

65-
export const ClipboardWidget: FC<Props> = observer(({style}) => {
66+
export const ClipboardWidget: FC<Props> = observer(() => {
6667
const store = useStore()
6768
const data = store.clipboard.clipboardItems
6869
const selectedIndex = store.ui.selectedIndex
@@ -80,13 +81,15 @@ export const ClipboardWidget: FC<Props> = observer(({style}) => {
8081
return (
8182
<View className="flex-1">
8283
<View className="flex-row px-3">
83-
<MainInput placeholder="Search pasteboard history..." showBackButton />
84+
<MainInput placeholder="Search Pasteboard..." showBackButton />
8485
</View>
8586
<LoadingBar />
8687
<View className="flex-1 flex-row">
87-
<View className="w-64">
88+
<View className="w-64 h-full">
8889
<LegendList
90+
key={`${data.length}`}
8991
data={data}
92+
className='flex-1'
9093
contentContainerStyle={STYLES.contentContainer}
9194
ref={listRef}
9295
recycleItems
@@ -130,6 +133,15 @@ export const ClipboardWidget: FC<Props> = observer(({style}) => {
130133
)}
131134
</View>
132135
</View>
136+
{/* Shortcut bar at the bottom */}
137+
<View className="py-2 px-4 flex-row items-center justify-end gap-1 subBg">
138+
<Text className="text-xs darker-text mr-1">Delete All</Text>
139+
<Key symbol={'⇧'} />
140+
<Key symbol={'⏎'} />
141+
<View className="mx-2" />
142+
<Text className="text-xs darker-text mr-1">Delete</Text>
143+
<Key symbol={'⌫'} />
144+
</View>
133145
</View>
134146
)
135147
})

0 commit comments

Comments
 (0)