Skip to content

Commit 35a6fce

Browse files
authored
Merge pull request #24 from SillyLittleTech/cursor/embed-media-renderer-5158
HotFix: Fix Linux Building Errors
2 parents df14682 + de6e705 commit 35a6fce

11 files changed

Lines changed: 698 additions & 37 deletions

File tree

Jot.xcodeproj/project.pbxproj

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@
414414
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
415415
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
416416
MACOSX_DEPLOYMENT_TARGET = 14.0;
417-
MARKETING_VERSION = 2.5.8;
417+
MARKETING_VERSION = 2.8.1;
418418
PRODUCT_BUNDLE_IDENTIFIER = slf.PinStick;
419419
PRODUCT_NAME = PinStick;
420420
REGISTER_APP_GROUPS = YES;
@@ -456,7 +456,7 @@
456456
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
457457
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
458458
MACOSX_DEPLOYMENT_TARGET = 14.0;
459-
MARKETING_VERSION = 2.5.8;
459+
MARKETING_VERSION = 2.8.1;
460460
PRODUCT_BUNDLE_IDENTIFIER = slf.PinStick;
461461
PRODUCT_NAME = PinStick;
462462
REGISTER_APP_GROUPS = YES;
@@ -479,7 +479,7 @@
479479
GENERATE_INFOPLIST_FILE = YES;
480480
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
481481
MACOSX_DEPLOYMENT_TARGET = 15.5;
482-
MARKETING_VERSION = 2.5.8;
482+
MARKETING_VERSION = 2.8.1;
483483
PRODUCT_BUNDLE_IDENTIFIER = slf.PinStickTests;
484484
PRODUCT_NAME = PinStickTests;
485485
SDKROOT = auto;
@@ -502,7 +502,7 @@
502502
GENERATE_INFOPLIST_FILE = YES;
503503
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
504504
MACOSX_DEPLOYMENT_TARGET = 15.5;
505-
MARKETING_VERSION = 2.5.8;
505+
MARKETING_VERSION = 2.8.1;
506506
PRODUCT_BUNDLE_IDENTIFIER = slf.PinStickTests;
507507
PRODUCT_NAME = PinStickTests;
508508
SDKROOT = auto;
@@ -524,7 +524,7 @@
524524
GENERATE_INFOPLIST_FILE = YES;
525525
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
526526
MACOSX_DEPLOYMENT_TARGET = 15.5;
527-
MARKETING_VERSION = 2.5.8;
527+
MARKETING_VERSION = 2.8.1;
528528
PRODUCT_BUNDLE_IDENTIFIER = slf.PinStickUITests;
529529
PRODUCT_NAME = PinStickUITests;
530530
SDKROOT = auto;
@@ -546,7 +546,7 @@
546546
GENERATE_INFOPLIST_FILE = YES;
547547
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
548548
MACOSX_DEPLOYMENT_TARGET = 15.5;
549-
MARKETING_VERSION = 2.5.8;
549+
MARKETING_VERSION = 2.8.1;
550550
PRODUCT_BUNDLE_IDENTIFIER = slf.PinStickUITests;
551551
PRODUCT_NAME = PinStickUITests;
552552
SDKROOT = auto;

MediaNoteSupport.swift

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import Foundation
2+
import UniformTypeIdentifiers
3+
4+
enum MediaKind: String, Codable {
5+
case image
6+
case video
7+
}
8+
9+
struct RemoteMediaItem: Identifiable, Equatable {
10+
let id: String
11+
let url: URL
12+
let kind: MediaKind
13+
}
14+
15+
struct LocalMediaItem: Equatable {
16+
let url: URL
17+
let kind: MediaKind
18+
}
19+
20+
enum MediaNoteSupport {
21+
static let noteStorageKey = "pinstick-note"
22+
static let localMediaBookmarkKey = "pinstick-local-media-bookmark"
23+
24+
private static let imageExtensions = ["avif", "bmp", "gif", "jpg", "jpeg", "png", "svg", "webp"]
25+
private static let videoExtensions = ["m4v", "mov", "mp4", "ogg", "ogv", "webm"]
26+
private static let trailingPunctuation = CharacterSet(charactersIn: "),.!?;:")
27+
28+
static func classifyMediaURL(_ url: URL) -> MediaKind? {
29+
let path = url.path.lowercased()
30+
let ext = (path as NSString).pathExtension
31+
if imageExtensions.contains(ext) {
32+
return .image
33+
}
34+
if videoExtensions.contains(ext) {
35+
return .video
36+
}
37+
return nil
38+
}
39+
40+
static func classifyLocalFile(url: URL) -> MediaKind? {
41+
if let type = try? url.resourceValues(forKeys: [.contentTypeKey]).contentType {
42+
if type.conforms(to: .image) {
43+
return .image
44+
}
45+
if type.conforms(to: .movie) || type.conforms(to: .video) {
46+
return .video
47+
}
48+
}
49+
return classifyMediaURL(url)
50+
}
51+
52+
static func parseRemoteMedia(from noteText: String) -> [RemoteMediaItem] {
53+
var items: [RemoteMediaItem] = []
54+
var seen = Set<String>()
55+
56+
let markdownPattern = #"!\[[^\]]*\]\((https?://[^)\s]+)\)"#
57+
if let regex = try? NSRegularExpression(pattern: markdownPattern) {
58+
let range = NSRange(noteText.startIndex..<noteText.endIndex, in: noteText)
59+
regex.enumerateMatches(in: noteText, range: range) { match, _, _ in
60+
guard let match,
61+
match.numberOfRanges > 1,
62+
let urlRange = Range(match.range(at: 1), in: noteText) else { return }
63+
appendRemoteURL(String(noteText[urlRange]), to: &items, seen: &seen)
64+
}
65+
}
66+
67+
let urlPattern = #"https?://[^\s<>"')\]]+"#
68+
if let regex = try? NSRegularExpression(pattern: urlPattern) {
69+
let range = NSRange(noteText.startIndex..<noteText.endIndex, in: noteText)
70+
regex.enumerateMatches(in: noteText, range: range) { match, _, _ in
71+
guard let match, let urlRange = Range(match.range, in: noteText) else { return }
72+
appendRemoteURL(String(noteText[urlRange]), to: &items, seen: &seen)
73+
}
74+
}
75+
76+
return items
77+
}
78+
79+
static func removeRemoteMedia(url: URL, from noteText: String) -> String {
80+
let urlString = url.absoluteString
81+
var updated = noteText
82+
83+
let markdownPattern = #"!\[[^\]]*\]\(\#(NSRegularExpression.escapedPattern(for: urlString))\)\s*"#
84+
if let regex = try? NSRegularExpression(pattern: markdownPattern) {
85+
updated = regex.stringByReplacingMatches(
86+
in: updated,
87+
range: NSRange(updated.startIndex..<updated.endIndex, in: updated),
88+
withTemplate: ""
89+
)
90+
}
91+
92+
updated = updated.replacingOccurrences(of: urlString, with: "")
93+
while updated.contains("\n\n\n") {
94+
updated = updated.replacingOccurrences(of: "\n\n\n", with: "\n\n")
95+
}
96+
return updated.trimmingCharacters(in: .whitespacesAndNewlines)
97+
}
98+
99+
static func saveLocalMediaBookmark(for url: URL) {
100+
guard url.startAccessingSecurityScopedResource() else { return }
101+
defer { url.stopAccessingSecurityScopedResource() }
102+
103+
do {
104+
let data = try url.bookmarkData(
105+
options: .withSecurityScope,
106+
includingResourceValuesForKeys: nil,
107+
relativeTo: nil
108+
)
109+
UserDefaults.standard.set(data, forKey: localMediaBookmarkKey)
110+
} catch {
111+
NSLog("Failed to save media bookmark: \(error.localizedDescription)")
112+
}
113+
}
114+
115+
static func loadLocalMedia() -> LocalMediaItem? {
116+
guard let data = UserDefaults.standard.data(forKey: localMediaBookmarkKey) else {
117+
return nil
118+
}
119+
120+
var isStale = false
121+
do {
122+
let url = try URL(
123+
resolvingBookmarkData: data,
124+
options: .withSecurityScope,
125+
relativeTo: nil,
126+
bookmarkDataIsStale: &isStale
127+
)
128+
if isStale {
129+
saveLocalMediaBookmark(for: url)
130+
}
131+
guard url.startAccessingSecurityScopedResource() else { return nil }
132+
guard let kind = classifyLocalFile(url: url) else {
133+
url.stopAccessingSecurityScopedResource()
134+
return nil
135+
}
136+
return LocalMediaItem(url: url, kind: kind)
137+
} catch {
138+
NSLog("Failed to resolve media bookmark: \(error.localizedDescription)")
139+
return nil
140+
}
141+
}
142+
143+
static func clearLocalMedia() {
144+
if let existing = loadLocalMedia() {
145+
existing.url.stopAccessingSecurityScopedResource()
146+
}
147+
UserDefaults.standard.removeObject(forKey: localMediaBookmarkKey)
148+
}
149+
150+
private static func appendRemoteURL(
151+
_ rawURL: String,
152+
to items: inout [RemoteMediaItem],
153+
seen: inout Set<String>
154+
) {
155+
let trimmed = rawURL.trimmingCharacters(in: trailingPunctuation)
156+
guard let url = URL(string: trimmed),
157+
let kind = classifyMediaURL(url),
158+
seen.insert(trimmed).inserted else { return }
159+
items.append(RemoteMediaItem(id: trimmed, url: url, kind: kind))
160+
}
161+
}

OverlayWindowSupport.swift

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import AppKit
2+
import SwiftUI
3+
4+
enum OverlayWindowSupport {
5+
static let opacityStorageKey = "pinstick-overlay-opacity"
6+
7+
static func defaultOpacity() -> Double {
8+
let stored = UserDefaults.standard.double(forKey: opacityStorageKey)
9+
if stored > 0 {
10+
return min(1, max(0.4, stored))
11+
}
12+
return 0.7
13+
}
14+
15+
static func saveOpacity(_ value: Double) {
16+
let clamped = min(1, max(0.4, value))
17+
UserDefaults.standard.set(clamped, forKey: opacityStorageKey)
18+
}
19+
20+
static func applyOverlay(on window: NSWindow, opacity: Double) {
21+
window.level = .floating
22+
window.isOpaque = false
23+
window.backgroundColor = .clear
24+
window.alphaValue = opacity
25+
window.ignoresMouseEvents = true
26+
}
27+
28+
static func restoreNormalWindow(_ window: NSWindow, isPinned: Bool) {
29+
window.ignoresMouseEvents = false
30+
window.isOpaque = true
31+
window.backgroundColor = nil
32+
window.alphaValue = 1
33+
window.level = isPinned ? .floating : .normal
34+
}
35+
36+
static func frameInScreenCoordinates(for view: NSView) -> CGRect {
37+
let bounds = view.bounds
38+
let origin = view.convert(NSPoint(x: bounds.minX, y: bounds.minY), to: nil)
39+
let size = view.convert(NSPoint(x: bounds.maxX, y: bounds.maxY), to: nil)
40+
let windowRect = NSRect(
41+
x: min(origin.x, size.x),
42+
y: min(origin.y, size.y),
43+
width: abs(size.x - origin.x),
44+
height: abs(size.y - origin.y)
45+
)
46+
return view.window?.convertToScreen(windowRect) ?? .zero
47+
}
48+
49+
static func isMouseOver(rect: CGRect) -> Bool {
50+
guard !rect.isEmpty else { return false }
51+
let mouse = NSEvent.mouseLocation
52+
return rect.contains(mouse)
53+
}
54+
}
55+
56+
/// Polls mouse position without Accessibility permissions (unlike global event monitors).
57+
final class OverlayMouseMonitor {
58+
private var timer: Timer?
59+
private weak var window: NSWindow?
60+
private let hitTest: () -> Bool
61+
62+
init(window: NSWindow, hitTest: @escaping () -> Bool) {
63+
self.window = window
64+
self.hitTest = hitTest
65+
}
66+
67+
func start() {
68+
stop()
69+
timer = Timer.scheduledTimer(withTimeInterval: 0.06, repeats: true) { [weak self] _ in
70+
guard let self, let window = self.window else { return }
71+
window.ignoresMouseEvents = !self.hitTest()
72+
}
73+
}
74+
75+
func stop() {
76+
timer?.invalidate()
77+
timer = nil
78+
}
79+
80+
deinit {
81+
stop()
82+
}
83+
}

0 commit comments

Comments
 (0)