expo-paste-input 0.1.15 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ios/ExpoPasteInputView.swift +49 -91
- package/package.json +1 -1
|
@@ -511,14 +511,17 @@ class ExpoPasteInputView: ExpoView {
|
|
|
511
511
|
var attachmentRanges: [NSRange] = []
|
|
512
512
|
var mediaPayloads: [MediaPayload] = []
|
|
513
513
|
|
|
514
|
+
// Only track ranges for attachments we successfully extract a real payload
|
|
515
|
+
// from. Attachments without a payload (e.g. iOS dictation placeholders)
|
|
516
|
+
// are left alone — sanitizing them would delete characters the system
|
|
517
|
+
// manages itself, and emitting "unsupported" would raise a spurious error.
|
|
514
518
|
attributedText.enumerateAttribute(.attachment, in: NSRange(location: 0, length: attributedText.length), options: []) { value, range, _ in
|
|
515
519
|
guard let attachment = value as? NSTextAttachment else {
|
|
516
520
|
return
|
|
517
521
|
}
|
|
518
522
|
|
|
519
|
-
attachmentRanges.append(range)
|
|
520
|
-
|
|
521
523
|
if let payload = self.extractMediaPayload(from: attachment, textView: textView, range: range) {
|
|
524
|
+
attachmentRanges.append(range)
|
|
522
525
|
mediaPayloads.append(payload)
|
|
523
526
|
}
|
|
524
527
|
}
|
|
@@ -529,9 +532,8 @@ class ExpoPasteInputView: ExpoView {
|
|
|
529
532
|
return
|
|
530
533
|
}
|
|
531
534
|
|
|
532
|
-
attachmentRanges.append(range)
|
|
533
|
-
|
|
534
535
|
if let payload = self.extractMediaPayload(from: adaptiveGlyph) {
|
|
536
|
+
attachmentRanges.append(range)
|
|
535
537
|
mediaPayloads.append(payload)
|
|
536
538
|
}
|
|
537
539
|
}
|
|
@@ -539,17 +541,12 @@ class ExpoPasteInputView: ExpoView {
|
|
|
539
541
|
|
|
540
542
|
attachmentRanges = uniqueRanges(attachmentRanges)
|
|
541
543
|
|
|
542
|
-
guard !
|
|
544
|
+
guard !mediaPayloads.isEmpty else {
|
|
543
545
|
return
|
|
544
546
|
}
|
|
545
547
|
|
|
546
548
|
sanitizeAttachments(in: textView, ranges: attachmentRanges)
|
|
547
549
|
|
|
548
|
-
guard !mediaPayloads.isEmpty else {
|
|
549
|
-
handleUnsupportedPaste()
|
|
550
|
-
return
|
|
551
|
-
}
|
|
552
|
-
|
|
553
550
|
emitImagesAsync(for: mediaPayloads)
|
|
554
551
|
}
|
|
555
552
|
|
|
@@ -651,6 +648,11 @@ class ExpoPasteInputView: ExpoView {
|
|
|
651
648
|
}
|
|
652
649
|
|
|
653
650
|
private func extractMediaPayload(from attachment: NSTextAttachment, textView: UITextView, range: NSRange) -> MediaPayload? {
|
|
651
|
+
// Only accept attachments that carry real image payloads. We intentionally
|
|
652
|
+
// do not fall back to `image(forBounds:)` or rendering the text view's
|
|
653
|
+
// hierarchy, because system-inserted attachments (e.g. the iOS dictation
|
|
654
|
+
// placeholder) draw themselves via those paths and would cause us to
|
|
655
|
+
// emit a screenshot of the composer as a "pasted image".
|
|
654
656
|
if let fileWrapperData = attachment.fileWrapper?.regularFileContents,
|
|
655
657
|
let payload = extractMediaPayload(fromData: fileWrapperData) {
|
|
656
658
|
return payload
|
|
@@ -667,20 +669,6 @@ class ExpoPasteInputView: ExpoView {
|
|
|
667
669
|
return .image(image)
|
|
668
670
|
}
|
|
669
671
|
|
|
670
|
-
let attachmentBounds = attachment.bounds.size.width > 0 && attachment.bounds.size.height > 0
|
|
671
|
-
? attachment.bounds
|
|
672
|
-
: CGRect(origin: .zero, size: CGSize(width: 128, height: 128))
|
|
673
|
-
|
|
674
|
-
if let image = attachment.image(forBounds: attachmentBounds, textContainer: textView.textContainer, characterIndex: range.location),
|
|
675
|
-
image.size.width > 0,
|
|
676
|
-
image.size.height > 0 {
|
|
677
|
-
return .image(image)
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
if let renderedImage = renderTextAttachment(in: textView, range: range) {
|
|
681
|
-
return .image(renderedImage)
|
|
682
|
-
}
|
|
683
|
-
|
|
684
672
|
return nil
|
|
685
673
|
}
|
|
686
674
|
|
|
@@ -701,47 +689,6 @@ class ExpoPasteInputView: ExpoView {
|
|
|
701
689
|
return .imageData(data)
|
|
702
690
|
}
|
|
703
691
|
|
|
704
|
-
private func renderTextAttachment(in textView: UITextView, range: NSRange) -> UIImage? {
|
|
705
|
-
let glyphRange = textView.layoutManager.glyphRange(forCharacterRange: range, actualCharacterRange: nil)
|
|
706
|
-
var rect = textView.layoutManager.boundingRect(forGlyphRange: glyphRange, in: textView.textContainer)
|
|
707
|
-
|
|
708
|
-
rect.origin.x += textView.textContainerInset.left - textView.contentOffset.x
|
|
709
|
-
rect.origin.y += textView.textContainerInset.top - textView.contentOffset.y
|
|
710
|
-
rect = rect.integral
|
|
711
|
-
|
|
712
|
-
guard rect.width > 1, rect.height > 1 else {
|
|
713
|
-
return nil
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
let format = UIGraphicsImageRendererFormat.default()
|
|
717
|
-
format.scale = textView.window?.screen.scale ?? UIScreen.main.scale
|
|
718
|
-
format.opaque = false
|
|
719
|
-
|
|
720
|
-
let image = UIGraphicsImageRenderer(size: rect.size, format: format).image { _ in
|
|
721
|
-
let drawRect = CGRect(
|
|
722
|
-
origin: CGPoint(x: -rect.origin.x, y: -rect.origin.y),
|
|
723
|
-
size: textView.bounds.size
|
|
724
|
-
)
|
|
725
|
-
|
|
726
|
-
if textView.window != nil {
|
|
727
|
-
textView.drawHierarchy(in: drawRect, afterScreenUpdates: false)
|
|
728
|
-
} else {
|
|
729
|
-
guard let context = UIGraphicsGetCurrentContext() else {
|
|
730
|
-
return
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
context.translateBy(x: -rect.origin.x, y: -rect.origin.y)
|
|
734
|
-
textView.layer.render(in: context)
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
guard image.size.width > 0, image.size.height > 0 else {
|
|
739
|
-
return nil
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
return image
|
|
743
|
-
}
|
|
744
|
-
|
|
745
692
|
@available(iOS 18.0, *)
|
|
746
693
|
private func handleAdaptiveImageGlyphInsertion(_ adaptiveGlyph: NSAdaptiveImageGlyph) -> Bool {
|
|
747
694
|
guard let payload = extractMediaPayload(from: adaptiveGlyph) else {
|
|
@@ -910,32 +857,32 @@ class ExpoPasteInputView: ExpoView {
|
|
|
910
857
|
return headerBytes == gif87aSignature || headerBytes == gif89aSignature
|
|
911
858
|
}
|
|
912
859
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
860
|
+
private func inferredImageFileExtension(from imageSource: CGImageSource, fallbackData data: Data) -> String {
|
|
861
|
+
if let type = CGImageSourceGetType(imageSource) as String? {
|
|
862
|
+
switch type {
|
|
863
|
+
case "public.png":
|
|
864
|
+
return "png"
|
|
865
|
+
case "public.jpeg":
|
|
866
|
+
return "jpg"
|
|
867
|
+
case "public.gif":
|
|
868
|
+
return "gif"
|
|
869
|
+
case "public.webp", "org.webmproject.webp":
|
|
870
|
+
return "webp"
|
|
871
|
+
case "public.heic":
|
|
872
|
+
return "heic"
|
|
873
|
+
case "public.heif":
|
|
874
|
+
return "heif"
|
|
875
|
+
case "public.tiff":
|
|
876
|
+
return "tiff"
|
|
877
|
+
default:
|
|
878
|
+
break
|
|
879
|
+
}
|
|
931
880
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
return nil
|
|
881
|
+
|
|
882
|
+
if isGIFData(data) {
|
|
883
|
+
return "gif"
|
|
936
884
|
}
|
|
937
|
-
|
|
938
|
-
return image
|
|
885
|
+
return "png"
|
|
939
886
|
}
|
|
940
887
|
|
|
941
888
|
private func processTextPaste() {
|
|
@@ -1016,11 +963,22 @@ class ExpoPasteInputView: ExpoView {
|
|
|
1016
963
|
}
|
|
1017
964
|
|
|
1018
965
|
private func writeTemporaryImageData(_ data: Data) -> String? {
|
|
1019
|
-
guard let
|
|
966
|
+
guard let imageSource = CGImageSourceCreateWithData(data as CFData, nil),
|
|
967
|
+
CGImageSourceGetCount(imageSource) > 0 else {
|
|
1020
968
|
return nil
|
|
1021
969
|
}
|
|
1022
970
|
|
|
1023
|
-
|
|
971
|
+
let fileExtension = inferredImageFileExtension(from: imageSource, fallbackData: data)
|
|
972
|
+
let fileURL = FileManager.default.temporaryDirectory
|
|
973
|
+
.appendingPathComponent(UUID().uuidString)
|
|
974
|
+
.appendingPathExtension(fileExtension)
|
|
975
|
+
|
|
976
|
+
do {
|
|
977
|
+
try data.write(to: fileURL)
|
|
978
|
+
return fileURL.absoluteString
|
|
979
|
+
} catch {
|
|
980
|
+
return nil
|
|
981
|
+
}
|
|
1024
982
|
}
|
|
1025
983
|
|
|
1026
984
|
private func writeTemporaryImage(_ image: UIImage) -> String? {
|
package/package.json
CHANGED