@apollohg/react-native-rich-text-editor 2.0.1 → 2.0.3
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/README.md +29 -123
- package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +20 -6
- package/android/src/main/java/com/apollohg/editor/EditorEditTextClipboard.kt +181 -36
- package/android/src/main/java/com/apollohg/editor/EditorEditTextImages.kt +25 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextSelection.kt +7 -0
- package/android/src/main/java/com/apollohg/editor/EditorTextSurfaceInteraction.kt +31 -11
- package/android/src/main/java/com/apollohg/editor/EditorV2Adapter.kt +36 -0
- package/android/src/main/java/com/apollohg/editor/EditorV2Backend.kt +1 -0
- package/android/src/main/java/com/apollohg/editor/EditorV2Driver.kt +10 -0
- package/android/src/main/java/com/apollohg/editor/EditorV2RenderValidation.kt +1 -0
- package/android/src/main/java/com/apollohg/editor/ImageResizeOverlayView.kt +101 -33
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +2 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewProperties.kt +4 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +3 -0
- package/android/src/main/java/com/apollohg/editor/RenderMediaSpans.kt +15 -3
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +14 -0
- package/android/src/main/java/com/apollohg/editor/UniffiEditorV2Backend.kt +4 -0
- package/android/src/main/java/com/apollohg/editor/viewer/AndroidProseLayoutEngine.kt +1 -1
- package/dist/EditorStyleSheetNormalization.js +1 -1
- package/dist/EditorStyleSheetTypes.d.ts +1 -1
- package/dist/NativeRichTextEditor.d.ts +1 -1
- package/dist/RichTextEditorNativeTypes.d.ts +2 -1
- package/dist/RichTextEditorTypes.d.ts +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/useRichTextEditorPresentation.d.ts +1 -1
- package/dist/useRichTextEditorPresentation.js +2 -2
- package/dist/useRichTextEditorState.d.ts +5 -3
- package/dist/useRichTextEditorState.js +4 -3
- package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
- package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
- package/ios/EditorInteractionOverlays.swift +114 -122
- package/ios/EditorLayoutManager.swift +13 -3
- package/ios/EditorStyleSheet.swift +12 -1
- package/ios/EditorTextView+Images.swift +32 -10
- package/ios/EditorTextView+KeyboardInput.swift +299 -0
- package/ios/EditorTextView.swift +64 -44
- package/ios/EditorV2Adapter+Commands.swift +22 -0
- package/ios/EditorV2Adapter+RenderValidation.swift +2 -2
- package/ios/EditorV2Shadow.swift +12 -0
- package/ios/Generated_editor_core.swift +10 -0
- package/ios/NativeEditorExpoView+Properties.swift +4 -0
- package/ios/NativeEditorModule.swift +3 -0
- package/ios/NativeToolbar+ButtonStyle.swift +17 -5
- package/ios/NativeToolbar+Diagnostics.swift +15 -0
- package/ios/RenderBridge+Atoms.swift +0 -1
- package/ios/RenderBridge+Attachments.swift +0 -29
- package/ios/RichTextEditorView.swift +4 -12
- package/ios/Viewer/CoreTextProseLayoutEngine+AttributedText.swift +3 -2
- package/ios/editor_coreFFI/editor_coreFFI.h +11 -0
- package/package.json +1 -1
- package/rust/android/arm64-v8a/libeditor_core.so +0 -0
- package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
- package/rust/android/x86/libeditor_core.so +0 -0
- package/rust/android/x86_64/libeditor_core.so +0 -0
- package/rust/bindings/kotlin/uniffi/editor_core/editor_core.kt +18 -0
- package/src/EditorStyleSheetNormalization.ts +1 -1
- package/src/EditorStyleSheetTypes.ts +1 -1
- package/src/NativeRichTextEditor.tsx +1 -0
- package/src/RichTextEditorNativeTypes.tsx +2 -0
- package/src/RichTextEditorTypes.tsx +5 -0
- package/src/index.ts +1 -0
- package/src/useRichTextEditorPresentation.tsx +3 -0
- package/src/useRichTextEditorState.tsx +5 -2
|
@@ -408,38 +408,29 @@ final class ImageResizeOverlayView: UIView {
|
|
|
408
408
|
|
|
409
409
|
private struct DragState {
|
|
410
410
|
let corner: Corner
|
|
411
|
-
let
|
|
411
|
+
let originalSize: CGSize
|
|
412
412
|
let docPos: UInt32
|
|
413
413
|
let maximumWidth: CGFloat
|
|
414
|
+
let editorId: UInt64
|
|
415
|
+
let attachment: BlockImageAttachment
|
|
416
|
+
let originalWidth: CGFloat?
|
|
417
|
+
let originalHeight: CGFloat?
|
|
418
|
+
var previewSize: CGSize
|
|
414
419
|
}
|
|
415
420
|
|
|
416
421
|
private weak var editorView: RichTextEditorView?
|
|
417
422
|
private let selectionLayer = CAShapeLayer()
|
|
418
|
-
private let previewBackdropView = UIView()
|
|
419
|
-
private let previewImageView = UIImageView()
|
|
420
423
|
private var handleViews: [Corner: ImageResizeHandleView] = [:]
|
|
421
424
|
private var currentRect: CGRect?
|
|
422
425
|
private var currentDocPos: UInt32?
|
|
423
426
|
private var dragState: DragState?
|
|
424
427
|
private let handleSize: CGFloat = 20
|
|
425
|
-
private let minimumImageSize: CGFloat = 48
|
|
426
428
|
|
|
427
429
|
override init(frame: CGRect) {
|
|
428
430
|
super.init(frame: frame)
|
|
429
431
|
backgroundColor = .clear
|
|
430
432
|
clipsToBounds = true
|
|
431
433
|
|
|
432
|
-
previewBackdropView.isUserInteractionEnabled = false
|
|
433
|
-
previewBackdropView.isHidden = true
|
|
434
|
-
previewBackdropView.layer.zPosition = 1
|
|
435
|
-
addSubview(previewBackdropView)
|
|
436
|
-
|
|
437
|
-
previewImageView.isUserInteractionEnabled = false
|
|
438
|
-
previewImageView.isHidden = true
|
|
439
|
-
previewImageView.contentMode = .scaleToFill
|
|
440
|
-
previewImageView.layer.zPosition = 2
|
|
441
|
-
addSubview(previewImageView)
|
|
442
|
-
|
|
443
434
|
selectionLayer.strokeColor = UIColor.systemBlue.cgColor
|
|
444
435
|
selectionLayer.fillColor = UIColor.clear.cgColor
|
|
445
436
|
selectionLayer.lineWidth = 2
|
|
@@ -467,8 +458,8 @@ final class ImageResizeOverlayView: UIView {
|
|
|
467
458
|
}
|
|
468
459
|
|
|
469
460
|
func refresh() {
|
|
470
|
-
if dragState
|
|
471
|
-
|
|
461
|
+
if let dragState, !isCurrentDrag(dragState) {
|
|
462
|
+
finishPreviewResize(commit: false)
|
|
472
463
|
}
|
|
473
464
|
|
|
474
465
|
guard let editorView,
|
|
@@ -478,7 +469,6 @@ final class ImageResizeOverlayView: UIView {
|
|
|
478
469
|
return
|
|
479
470
|
}
|
|
480
471
|
|
|
481
|
-
hidePreviewLayers()
|
|
482
472
|
applyGeometry(rect: geometry.rect, docPos: geometry.docPos)
|
|
483
473
|
}
|
|
484
474
|
|
|
@@ -488,15 +478,16 @@ final class ImageResizeOverlayView: UIView {
|
|
|
488
478
|
}
|
|
489
479
|
|
|
490
480
|
func simulatePreviewResizeForTesting(width: CGFloat, height: CGFloat) {
|
|
491
|
-
|
|
492
|
-
let
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
481
|
+
if dragState == nil, !beginPreviewResize(from: .bottomRight) { return }
|
|
482
|
+
let size = editorView?.clampedImageSize(
|
|
483
|
+
CGSize(width: width, height: height), maximumWidth: dragState?.maximumWidth
|
|
484
|
+
) ?? CGSize(width: width, height: height)
|
|
485
|
+
updatePreviewSize(size)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
func simulatePreviewDragForTesting(corner: Corner, translation: CGPoint) {
|
|
489
|
+
if dragState == nil, !beginPreviewResize(from: corner) { return }
|
|
490
|
+
updatePreview(translation: translation)
|
|
500
491
|
}
|
|
501
492
|
|
|
502
493
|
func commitPreviewResizeForTesting() {
|
|
@@ -511,10 +502,6 @@ final class ImageResizeOverlayView: UIView {
|
|
|
511
502
|
!isHidden
|
|
512
503
|
}
|
|
513
504
|
|
|
514
|
-
var previewHasImageForTesting: Bool {
|
|
515
|
-
!previewImageView.isHidden && previewImageView.image != nil
|
|
516
|
-
}
|
|
517
|
-
|
|
518
505
|
func interceptsPointForTesting(_ location: CGPoint) -> Bool {
|
|
519
506
|
self.point(inside: location, with: nil)
|
|
520
507
|
}
|
|
@@ -530,8 +517,7 @@ final class ImageResizeOverlayView: UIView {
|
|
|
530
517
|
}
|
|
531
518
|
|
|
532
519
|
private func hideOverlay() {
|
|
533
|
-
|
|
534
|
-
dragState = nil
|
|
520
|
+
if dragState != nil { finishPreviewResize(commit: false) }
|
|
535
521
|
currentRect = nil
|
|
536
522
|
currentDocPos = nil
|
|
537
523
|
selectionLayer.path = nil
|
|
@@ -539,64 +525,94 @@ final class ImageResizeOverlayView: UIView {
|
|
|
539
525
|
}
|
|
540
526
|
|
|
541
527
|
private func applyGeometry(rect: CGRect, docPos: UInt32) {
|
|
542
|
-
|
|
543
|
-
currentRect =
|
|
528
|
+
guard currentRect != rect || currentDocPos != docPos || isHidden else { return }
|
|
529
|
+
currentRect = rect
|
|
544
530
|
currentDocPos = docPos
|
|
545
|
-
|
|
531
|
+
CATransaction.begin()
|
|
532
|
+
CATransaction.setDisableActions(true)
|
|
533
|
+
selectionLayer.path = UIBezierPath(roundedRect: rect, cornerRadius: 8).cgPath
|
|
546
534
|
isHidden = false
|
|
547
|
-
layoutHandleViews(for:
|
|
535
|
+
layoutHandleViews(for: rect)
|
|
536
|
+
CATransaction.commit()
|
|
548
537
|
}
|
|
549
538
|
|
|
550
|
-
private func
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
private func showPreview(docPos: UInt32, originalRect: CGRect) {
|
|
557
|
-
previewBackdropView.backgroundColor = editorView?.imageResizePreviewBackgroundColor() ?? .systemBackground
|
|
558
|
-
previewBackdropView.frame = originalRect
|
|
559
|
-
previewBackdropView.isHidden = false
|
|
560
|
-
|
|
561
|
-
previewImageView.image = editorView?.imagePreviewForResize(docPos: docPos)
|
|
562
|
-
previewImageView.frame = originalRect
|
|
563
|
-
previewImageView.isHidden = previewImageView.image == nil
|
|
539
|
+
private func isCurrentDrag(_ state: DragState) -> Bool {
|
|
540
|
+
guard let editorView, editorView.editorId == state.editorId,
|
|
541
|
+
editorView.textView.selectedImageSelectionState()?.docPos == state.docPos,
|
|
542
|
+
editorView.textView.blockImageAttachment(docPos: state.docPos)?.attachment === state.attachment
|
|
543
|
+
else { return false }
|
|
544
|
+
return true
|
|
564
545
|
}
|
|
565
546
|
|
|
566
547
|
@discardableResult
|
|
567
548
|
private func beginPreviewResize(from corner: Corner) -> Bool {
|
|
568
|
-
guard let currentRect, let currentDocPos
|
|
569
|
-
|
|
570
|
-
|
|
549
|
+
guard let editorView, let currentRect, let currentDocPos,
|
|
550
|
+
let image = editorView.textView.blockImageAttachment(docPos: currentDocPos)
|
|
551
|
+
else { return false }
|
|
552
|
+
editorView.setImageResizePreviewActive(true)
|
|
553
|
+
let inset = image.attachment.styleBox?.inset ?? .zero
|
|
554
|
+
let contentSize = CGSize(
|
|
555
|
+
width: max(1, currentRect.width - inset.left - inset.right),
|
|
556
|
+
height: max(1, currentRect.height - inset.top - inset.bottom)
|
|
557
|
+
)
|
|
571
558
|
dragState = DragState(
|
|
572
559
|
corner: corner,
|
|
573
|
-
|
|
560
|
+
originalSize: contentSize,
|
|
574
561
|
docPos: currentDocPos,
|
|
575
|
-
maximumWidth:
|
|
562
|
+
maximumWidth: max(48, editorView.maximumImageWidthForResizeGesture() - inset.left - inset.right),
|
|
563
|
+
editorId: editorView.editorId,
|
|
564
|
+
attachment: image.attachment,
|
|
565
|
+
originalWidth: image.attachment.preferredWidth,
|
|
566
|
+
originalHeight: image.attachment.preferredHeight,
|
|
567
|
+
previewSize: contentSize
|
|
576
568
|
)
|
|
577
|
-
showPreview(docPos: currentDocPos, originalRect: currentRect)
|
|
578
569
|
return true
|
|
579
570
|
}
|
|
580
571
|
|
|
581
|
-
private func
|
|
582
|
-
guard let
|
|
583
|
-
|
|
584
|
-
|
|
572
|
+
private func updatePreviewSize(_ proposedSize: CGSize) {
|
|
573
|
+
guard var state = dragState, let editorView else { return }
|
|
574
|
+
guard isCurrentDrag(state) else {
|
|
575
|
+
finishPreviewResize(commit: false)
|
|
576
|
+
return
|
|
577
|
+
}
|
|
578
|
+
let scale = window?.screen.scale ?? 1
|
|
579
|
+
let size = proposedSize == state.originalSize ? proposedSize : CGSize(
|
|
580
|
+
width: (proposedSize.width * scale).rounded() / scale,
|
|
581
|
+
height: (proposedSize.height * scale).rounded() / scale
|
|
582
|
+
)
|
|
583
|
+
guard size != state.previewSize else { return }
|
|
584
|
+
state.previewSize = size
|
|
585
|
+
dragState = state
|
|
586
|
+
UIView.performWithoutAnimation {
|
|
587
|
+
editorView.textView.previewResizeImageAtDocPos(
|
|
588
|
+
state.docPos,
|
|
589
|
+
width: size.width,
|
|
590
|
+
height: size.height
|
|
591
|
+
)
|
|
592
|
+
if let geometry = editorView.selectedImageGeometry() {
|
|
593
|
+
applyGeometry(rect: geometry.rect, docPos: geometry.docPos)
|
|
594
|
+
}
|
|
595
|
+
}
|
|
585
596
|
}
|
|
586
597
|
|
|
587
598
|
private func finishPreviewResize(commit: Bool) {
|
|
588
|
-
guard let dragState else { return }
|
|
589
|
-
let
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
editorView?.resizeImage(docPos: dragState.docPos, size: finalSize)
|
|
599
|
+
guard let state = dragState, let editorView else { return }
|
|
600
|
+
let shouldCommit = commit && isCurrentDrag(state)
|
|
601
|
+
dragState = nil
|
|
602
|
+
if shouldCommit, state.previewSize != state.originalSize {
|
|
603
|
+
editorView.resizeImage(docPos: state.docPos, size: state.previewSize)
|
|
594
604
|
} else {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
self?.refresh()
|
|
605
|
+
editorView.textView.restoreImageResizePreview(
|
|
606
|
+
state.attachment, width: state.originalWidth, height: state.originalHeight
|
|
607
|
+
)
|
|
599
608
|
}
|
|
609
|
+
editorView.setImageResizePreviewActive(false)
|
|
610
|
+
refresh()
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
override func didMoveToWindow() {
|
|
614
|
+
super.didMoveToWindow()
|
|
615
|
+
if window == nil { finishPreviewResize(commit: false) }
|
|
600
616
|
}
|
|
601
617
|
|
|
602
618
|
private func layoutHandleViews(for rect: CGRect) {
|
|
@@ -624,50 +640,30 @@ final class ImageResizeOverlayView: UIView {
|
|
|
624
640
|
}
|
|
625
641
|
}
|
|
626
642
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
return CGPoint(x: rect.maxX, y: rect.minY)
|
|
635
|
-
case .bottomRight:
|
|
636
|
-
return CGPoint(x: rect.minX, y: rect.minY)
|
|
637
|
-
}
|
|
643
|
+
static func resizedSize(from size: CGSize, corner: Corner, translation: CGPoint) -> CGSize {
|
|
644
|
+
let dx = (corner == .topRight || corner == .bottomRight) ? translation.x : -translation.x
|
|
645
|
+
let dy = (corner == .bottomLeft || corner == .bottomRight) ? translation.y : -translation.y
|
|
646
|
+
let lengthSquared = max(1, size.width * size.width + size.height * size.height)
|
|
647
|
+
let minimumScale = 48 / max(1, min(size.width, size.height))
|
|
648
|
+
let scale = max(minimumScale, 1 + (dx * size.width + dy * size.height) / lengthSquared)
|
|
649
|
+
return CGSize(width: size.width * scale, height: size.height * scale)
|
|
638
650
|
}
|
|
639
651
|
|
|
640
|
-
private func
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
) -> CGRect {
|
|
646
|
-
let aspectRatio = max(originalRect.width / max(originalRect.height, 1), 0.1)
|
|
647
|
-
let signedDx = (corner == .topRight || corner == .bottomRight) ? translation.x : -translation.x
|
|
648
|
-
let signedDy = (corner == .bottomLeft || corner == .bottomRight) ? translation.y : -translation.y
|
|
649
|
-
let widthScale = (originalRect.width + signedDx) / max(originalRect.width, 1)
|
|
650
|
-
let heightScale = (originalRect.height + signedDy) / max(originalRect.height, 1)
|
|
651
|
-
let scale = max(minimumImageSize / max(originalRect.width, 1), widthScale, heightScale)
|
|
652
|
-
let unclampedSize = CGSize(
|
|
653
|
-
width: max(minimumImageSize, originalRect.width * scale),
|
|
654
|
-
height: max(minimumImageSize / aspectRatio, (max(minimumImageSize, originalRect.width * scale) / aspectRatio))
|
|
655
|
-
)
|
|
656
|
-
let clampedSize = editorView?.clampedImageSize(unclampedSize, maximumWidth: maximumWidth) ?? unclampedSize
|
|
657
|
-
let width = clampedSize.width
|
|
658
|
-
let height = clampedSize.height
|
|
659
|
-
let anchor = anchorPoint(for: corner, in: originalRect)
|
|
660
|
-
|
|
661
|
-
switch corner {
|
|
662
|
-
case .topLeft:
|
|
663
|
-
return CGRect(x: anchor.x - width, y: anchor.y - height, width: width, height: height)
|
|
664
|
-
case .topRight:
|
|
665
|
-
return CGRect(x: anchor.x, y: anchor.y - height, width: width, height: height)
|
|
666
|
-
case .bottomLeft:
|
|
667
|
-
return CGRect(x: anchor.x - width, y: anchor.y, width: width, height: height)
|
|
668
|
-
case .bottomRight:
|
|
669
|
-
return CGRect(x: anchor.x, y: anchor.y, width: width, height: height)
|
|
652
|
+
private func updatePreview(translation: CGPoint) {
|
|
653
|
+
guard let dragState else { return }
|
|
654
|
+
if translation == .zero {
|
|
655
|
+
updatePreviewSize(dragState.originalSize)
|
|
656
|
+
return
|
|
670
657
|
}
|
|
658
|
+
let size = Self.resizedSize(
|
|
659
|
+
from: dragState.originalSize,
|
|
660
|
+
corner: dragState.corner,
|
|
661
|
+
translation: translation
|
|
662
|
+
)
|
|
663
|
+
let clampedSize = editorView?.clampedImageSize(
|
|
664
|
+
size, maximumWidth: dragState.maximumWidth
|
|
665
|
+
) ?? size
|
|
666
|
+
updatePreviewSize(clampedSize)
|
|
671
667
|
}
|
|
672
668
|
|
|
673
669
|
@objc
|
|
@@ -676,22 +672,18 @@ final class ImageResizeOverlayView: UIView {
|
|
|
676
672
|
|
|
677
673
|
switch gesture.state {
|
|
678
674
|
case .began:
|
|
679
|
-
|
|
675
|
+
if beginPreviewResize(from: handleView.corner) {
|
|
676
|
+
updatePreview(translation: gesture.translation(in: window))
|
|
677
|
+
}
|
|
680
678
|
case .changed:
|
|
681
|
-
|
|
682
|
-
let nextRect = resizedRect(
|
|
683
|
-
from: dragState.originalRect,
|
|
684
|
-
corner: dragState.corner,
|
|
685
|
-
translation: gesture.translation(in: self),
|
|
686
|
-
maximumWidth: dragState.maximumWidth
|
|
687
|
-
)
|
|
688
|
-
updatePreviewRect(nextRect)
|
|
679
|
+
updatePreview(translation: gesture.translation(in: window))
|
|
689
680
|
case .ended:
|
|
681
|
+
updatePreview(translation: gesture.translation(in: window))
|
|
690
682
|
finishPreviewResize(commit: true)
|
|
691
683
|
case .cancelled, .failed:
|
|
692
684
|
finishPreviewResize(commit: false)
|
|
693
685
|
default:
|
|
694
|
-
|
|
686
|
+
break
|
|
695
687
|
}
|
|
696
688
|
}
|
|
697
689
|
}
|
|
@@ -54,7 +54,16 @@ final class EditorLayoutManager: NSLayoutManager, NSLayoutManagerDelegate {
|
|
|
54
54
|
storage.enumerateAttribute(editorInlineLineHeightAttribute, in: characters) { value, _, _ in
|
|
55
55
|
height = max(height, EditorTheme.cgFloat(value) ?? 0)
|
|
56
56
|
}
|
|
57
|
+
var ascent = baselineOffset.pointee + (height - lineFragmentUsedRect.pointee.height) / 2
|
|
58
|
+
var descent = height - ascent
|
|
59
|
+
storage.enumerateAttribute(editorMentionBoxAttribute, in: characters) { value, _, _ in
|
|
60
|
+
guard let chip = value as? EditorMentionRenderedBox else { return }
|
|
61
|
+
ascent = max(ascent, chip.baselineOffset)
|
|
62
|
+
descent = max(descent, chip.size.height - chip.baselineOffset)
|
|
63
|
+
}
|
|
64
|
+
height = ascent + descent
|
|
57
65
|
let extra = height - lineFragmentUsedRect.pointee.height
|
|
66
|
+
let baselineAdjustment = ascent - baselineOffset.pointee
|
|
58
67
|
var leading: CGFloat = 0
|
|
59
68
|
var overlap: CGFloat = 0
|
|
60
69
|
if characters.location < storage.length,
|
|
@@ -76,7 +85,7 @@ final class EditorLayoutManager: NSLayoutManager, NSLayoutManagerDelegate {
|
|
|
76
85
|
lineFragmentRect.pointee.size.height += extra + leading
|
|
77
86
|
lineFragmentUsedRect.pointee.size.height = height
|
|
78
87
|
lineFragmentUsedRect.pointee.origin.y += leading + overlap
|
|
79
|
-
baselineOffset.pointee +=
|
|
88
|
+
baselineOffset.pointee += baselineAdjustment + leading
|
|
80
89
|
return true
|
|
81
90
|
}
|
|
82
91
|
|
|
@@ -226,10 +235,11 @@ final class EditorLayoutManager: NSLayoutManager, NSLayoutManagerDelegate {
|
|
|
226
235
|
storage.enumerateAttribute(editorMentionBoxAttribute, in: visible) { value, range, _ in
|
|
227
236
|
guard let box = value as? EditorMentionRenderedBox else { return }
|
|
228
237
|
let glyph = self.glyphIndexForCharacter(at: range.location)
|
|
229
|
-
let line = self.lineFragmentUsedRect(forGlyphAt: glyph, effectiveRange: nil)
|
|
230
238
|
let location = self.location(forGlyphAt: glyph)
|
|
231
239
|
let fragment = self.lineFragmentRect(forGlyphAt: glyph, effectiveRange: nil)
|
|
232
|
-
let rect = CGRect(x: fragment.minX + location.x + origin.x,
|
|
240
|
+
let rect = CGRect(x: fragment.minX + location.x + origin.x,
|
|
241
|
+
y: fragment.minY + location.y + origin.y - box.baselineOffset,
|
|
242
|
+
width: box.size.width, height: box.size.height)
|
|
233
243
|
box.box.draw(in: rect, context: context)
|
|
234
244
|
let labelHeight = box.label?.size().height ?? 0
|
|
235
245
|
box.label?.draw(at: CGPoint(x: rect.minX + box.padding.left,
|
|
@@ -150,6 +150,14 @@ struct EditorStyleBox {
|
|
|
150
150
|
}
|
|
151
151
|
var inset: UIEdgeInsets { padding.adding(borders) }
|
|
152
152
|
var outerInsets: UIEdgeInsets { inset.adding(margin) }
|
|
153
|
+
var mentionInsets: UIEdgeInsets {
|
|
154
|
+
UIEdgeInsets(
|
|
155
|
+
top: number("paddingTop", fallback: number("padding", fallback: 4)),
|
|
156
|
+
left: number("paddingLeft", fallback: number("padding", fallback: 6)),
|
|
157
|
+
bottom: number("paddingBottom", fallback: number("padding", fallback: 4)),
|
|
158
|
+
right: number("paddingRight", fallback: number("padding", fallback: 6))
|
|
159
|
+
).adding(borders)
|
|
160
|
+
}
|
|
153
161
|
private func insets(_ key: String) -> UIEdgeInsets {
|
|
154
162
|
UIEdgeInsets(top: number(key + "Top", fallback: number(key)), left: number(key + "Left", fallback: number(key)), bottom: number(key + "Bottom", fallback: number(key)), right: number(key + "Right", fallback: number(key)))
|
|
155
163
|
}
|
|
@@ -410,15 +418,18 @@ final class EditorMentionRenderedBox: NSObject {
|
|
|
410
418
|
let label: NSAttributedString?
|
|
411
419
|
let size: CGSize
|
|
412
420
|
let padding: UIEdgeInsets
|
|
421
|
+
let baselineOffset: CGFloat
|
|
413
422
|
|
|
414
423
|
init(box: EditorStyleBox, label: NSAttributedString? = nil) {
|
|
415
424
|
self.box = box
|
|
416
425
|
self.label = label
|
|
417
|
-
padding =
|
|
426
|
+
padding = box.mentionInsets
|
|
418
427
|
let measured = label?.size() ?? .zero
|
|
419
428
|
let lineHeight = box.number("lineHeight", fallback: measured.height)
|
|
420
429
|
size = CGSize(width: ceil(measured.width) + padding.left + padding.right,
|
|
421
430
|
height: ceil(max(measured.height, lineHeight)) + padding.top + padding.bottom)
|
|
431
|
+
let font = label.flatMap { $0.length > 0 ? $0.attribute(.font, at: 0, effectiveRange: nil) as? UIFont : nil }
|
|
432
|
+
baselineOffset = padding.top + (size.height - padding.top - padding.bottom - measured.height) / 2 + (font?.ascender ?? 0)
|
|
422
433
|
}
|
|
423
434
|
}
|
|
424
435
|
|
|
@@ -289,7 +289,7 @@ extension EditorTextView {
|
|
|
289
289
|
return (selectionState.docPos, rect)
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
-
|
|
292
|
+
func blockImageAttachment(docPos: UInt32) -> (range: NSRange, attachment: BlockImageAttachment)? {
|
|
293
293
|
let fullRange = NSRange(location: 0, length: textStorage.length)
|
|
294
294
|
var resolved: (range: NSRange, attachment: BlockImageAttachment)?
|
|
295
295
|
textStorage.enumerateAttribute(
|
|
@@ -309,10 +309,6 @@ extension EditorTextView {
|
|
|
309
309
|
return resolved
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
-
func imagePreviewForDocPos(_ docPos: UInt32) -> UIImage? {
|
|
313
|
-
blockImageAttachment(docPos: docPos)?.attachment.previewImage()
|
|
314
|
-
}
|
|
315
|
-
|
|
316
312
|
func maximumRenderableImageWidth() -> CGFloat {
|
|
317
313
|
let containerWidth: CGFloat
|
|
318
314
|
if bounds.width > 0 {
|
|
@@ -337,14 +333,40 @@ extension EditorTextView {
|
|
|
337
333
|
}
|
|
338
334
|
}
|
|
339
335
|
|
|
340
|
-
func previewResizeImageAtDocPos(_ docPos: UInt32, width: CGFloat
|
|
336
|
+
func previewResizeImageAtDocPos(_ docPos: UInt32, width: CGFloat?, height: CGFloat?) {
|
|
341
337
|
guard let attachmentState = blockImageAttachment(docPos: docPos) else { return }
|
|
342
|
-
attachmentState.attachment.
|
|
343
|
-
|
|
344
|
-
|
|
338
|
+
updateImageResizePreview(attachmentState.attachment, range: attachmentState.range, width: width, height: height)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
func restoreImageResizePreview(_ attachment: BlockImageAttachment, width: CGFloat?, height: CGFloat?) {
|
|
342
|
+
var attachmentRange: NSRange?
|
|
343
|
+
textStorage.enumerateAttribute(.attachment, in: NSRange(location: 0, length: textStorage.length)) { value, range, stop in
|
|
344
|
+
if value as? BlockImageAttachment === attachment {
|
|
345
|
+
attachmentRange = range
|
|
346
|
+
stop.pointee = true
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
guard let attachmentRange else { return }
|
|
350
|
+
updateImageResizePreview(attachment, range: attachmentRange, width: width, height: height)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private func updateImageResizePreview(_ attachment: BlockImageAttachment, range: NSRange, width: CGFloat?, height: CGFloat?) {
|
|
354
|
+
guard attachment.preferredWidth != width || attachment.preferredHeight != height else { return }
|
|
355
|
+
let previousOffset = contentOffset
|
|
356
|
+
attachment.preferredWidth = width
|
|
357
|
+
attachment.preferredHeight = height
|
|
358
|
+
layoutManager.invalidateLayout(forCharacterRange: range, actualCharacterRange: nil)
|
|
359
|
+
layoutManager.invalidateDisplay(forCharacterRange: range)
|
|
345
360
|
textStorage.beginEditing()
|
|
346
|
-
textStorage.edited(.editedAttributes, range:
|
|
361
|
+
textStorage.edited(.editedAttributes, range: range, changeInLength: 0)
|
|
347
362
|
textStorage.endEditing()
|
|
363
|
+
invalidateAutoGrowHeightMeasurement()
|
|
364
|
+
layoutManager.ensureLayout(forCharacterRange: range)
|
|
365
|
+
setNeedsLayout()
|
|
366
|
+
layoutIfNeeded()
|
|
367
|
+
setContentOffset(previousOffset, animated: false)
|
|
368
|
+
notifyHeightChangeIfNeeded()
|
|
369
|
+
onViewportMayChange?()
|
|
348
370
|
}
|
|
349
371
|
|
|
350
372
|
func setImageResizePreviewActive(_ active: Bool) {
|