-
Notifications
You must be signed in to change notification settings - Fork 7
Add full clip context menu with editor, preview, and sharing #78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -279,6 +279,92 @@ final class ClipboardStore { | |
| scheduleSave() | ||
| } | ||
|
|
||
| func item(withID id: UUID) -> ClipItem? { | ||
| if let item = history.first(where: { $0.id == id }) { return item } | ||
| return pinboards.lazy.flatMap(\.items).first(where: { $0.id == id }) | ||
| } | ||
|
|
||
| @discardableResult | ||
| func updateTextContent(_ text: String, richTextData: Data? = nil, for item: ClipItem) -> Bool { | ||
| guard [.text, .richText, .link].contains(item.type), | ||
| !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return false } | ||
| let type: ClipType = richTextData != nil ? .richText : (isWebLink(text) ? .link : .text) | ||
| return updateContent(of: item) { existing in | ||
| var updated = existing | ||
| updated.type = type | ||
| updated.text = text | ||
| updated.rtfData = richTextData | ||
| updated.colorHex = nil | ||
| return updated | ||
| } | ||
| } | ||
|
|
||
| @discardableResult | ||
| func updateColorContent(_ hex: String, for item: ClipItem) -> Bool { | ||
| guard item.type == .color, let color = NSColor(hex: hex) else { return false } | ||
| let normalized = color.hexString | ||
| return updateContent(of: item) { existing in | ||
| var updated = existing | ||
| updated.type = .color | ||
| updated.text = nil | ||
| updated.rtfData = nil | ||
| updated.colorHex = normalized | ||
| return updated | ||
| } | ||
| } | ||
|
|
||
| private func updateContent(of item: ClipItem, transform: (ClipItem) -> ClipItem) -> Bool { | ||
| var changed = false | ||
| let now = Date() | ||
|
|
||
| if let i = history.firstIndex(where: { $0.id == item.id }) { | ||
| var updated = transform(history[i]) | ||
| if updated != history[i] { | ||
| updated.createdAt = now | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If the same-content duplicate has a Useful? React with 👍 / 👎. |
||
| history.remove(at: i) | ||
| removeContentDuplicates(of: updated, in: &history) | ||
| history.insert(updated, at: 0) | ||
| changed = true | ||
| } | ||
| } | ||
|
|
||
| for b in pinboards.indices { | ||
| guard let i = pinboards[b].items.firstIndex(where: { $0.id == item.id }) else { continue } | ||
| var updated = transform(pinboards[b].items[i]) | ||
| if updated != pinboards[b].items[i] { | ||
| updated.createdAt = now | ||
| pinboards[b].items[i] = updated | ||
| removeContentDuplicates(of: updated, in: &pinboards[b].items) | ||
| changed = true | ||
| } | ||
| } | ||
|
|
||
| guard changed else { return false } | ||
| retentionPrunedRecordNames.remove(item.id.uuidString) | ||
| if selectedItem == nil { selectFirst() } | ||
| reconcileMultiSelection() | ||
| scheduleSave() | ||
| return true | ||
| } | ||
|
|
||
| private func removeContentDuplicates(of item: ClipItem, in items: inout [ClipItem]) { | ||
| let key = contentKey(item) | ||
| let duplicates = items.filter { $0.id != item.id && contentKey($0) == key } | ||
| guard !duplicates.isEmpty else { return } | ||
| items.removeAll { $0.id != item.id && contentKey($0) == key } | ||
| for duplicate in duplicates { deleteImageFile(duplicate) } | ||
| } | ||
|
|
||
| private func isWebLink(_ text: String) -> Bool { | ||
| let value = text.trimmingCharacters(in: .whitespacesAndNewlines) | ||
| guard !value.contains(" "), !value.contains("\n"), | ||
| let url = URL(string: value), | ||
| let scheme = url.scheme?.lowercased(), | ||
| ["http", "https"].contains(scheme), | ||
| url.host != nil else { return false } | ||
| return true | ||
| } | ||
|
|
||
| func setTitle(_ title: String, for item: ClipItem) { | ||
| if let i = history.firstIndex(where: { $0.id == item.id }) { history[i].customTitle = title } | ||
| for b in pinboards.indices { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For every link clip, this branch passes an
NSStringtoNSSharingServicePickerinstead of anNSURL. Sharing-service availability and behavior depend on the supplied object types, so URL-specific destinations are omitted and services that remain receive plain text rather than a link; the existing drag provider already preserves the intended semantics by registering anNSURLfor.linkitems.Useful? React with 👍 / 👎.