-
Notifications
You must be signed in to change notification settings - Fork 446
Scroll cursor back into view when it goes out of bounds #34
Changes from 8 commits
154e5e1
55396c1
5dbbf81
d3bf5b1
ae7c2fe
50597b9
c6c5be5
13ebbbf
7c59a46
d39d4c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -50,6 +50,7 @@ RE.rangeOrCaretSelectionExists = function() { | |
RE.editor.addEventListener("input", function() { | ||
RE.updatePlaceholder(); | ||
RE.backuprange(); | ||
RE.wrapTextNodes(); | ||
RE.callback("input"); | ||
}); | ||
|
||
|
@@ -372,3 +373,67 @@ RE.getSelectedHref = function() { | |
|
||
return href ? href : null; | ||
}; | ||
|
||
/* Make sure all text nodes are wrapped in divs! */ | ||
|
||
RE.wrapTextNodes = function() { | ||
var contents = RE.editor.childNodes; | ||
for (var i = 0; i < contents.length; i++) { | ||
if (contents[i].nodeType === 3) { | ||
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. It might be nice to have an object or enum or something so we don't have magic numbers flying around. Maybe Maybe 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. yea that works, totally reasonable |
||
var newNode = document.createElement('div'); | ||
RE.createWrapper(contents[i], newNode); | ||
RE.focus(); | ||
} | ||
} | ||
} | ||
|
||
|
||
RE.createWrapper = function(elms, node) { | ||
var child = node.cloneNode(true); | ||
var el = elms; | ||
|
||
var parent = el.parentNode; | ||
var sibling = el.nextSibling; | ||
|
||
child.appendChild(el); | ||
|
||
if (sibling) { | ||
parent.insertBefore(child, sibling); | ||
} else { | ||
parent.appendChild(child); | ||
} | ||
|
||
}; | ||
|
||
/* retrieve caret vertical position */ | ||
|
||
RE.getCaretPosition = function() { | ||
var x=0, y=0; | ||
var newLine = false; | ||
var result = []; | ||
var sel=window.getSelection(); | ||
if (sel.rangeCount) { | ||
var range = sel.getRangeAt(0); | ||
var needsWorkAround = (range.startOffset == 0) | ||
/* Removing fixes bug when node name other than 'div' */ | ||
// && range.startContainer.nodeName.toLowerCase() == 'div'); | ||
if (needsWorkAround) { | ||
console.log(range); | ||
x=range.startContainer.offsetLeft; | ||
y=range.startContainer.offsetTop; // add range.startContainer.clientHeight if want bottom of caret; | ||
newLine = true; // position is on new line with no content | ||
} else { | ||
if (range.getClientRects) { | ||
var rects=range.getClientRects(); | ||
if (rects.length > 0) { | ||
x = rects[0].left; | ||
y = rects[0].top; | ||
newLine = false; | ||
} | ||
} | ||
} | ||
} | ||
var json = JSON.stringify({height: y, newLine: newLine}); | ||
console.log(json); | ||
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. We don't need to keep logs sticking around. |
||
return json; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,13 +22,19 @@ html { | |
|
||
body { | ||
min-height: 100%; | ||
overflow: hidden; | ||
overflow: auto; | ||
} | ||
|
||
body, h1, p { | ||
margin: 0; | ||
} | ||
|
||
div { | ||
display: block; | ||
position: static; | ||
min-height: 28px; | ||
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. What is this 28px about? Is it necessary? I actually don't know the implications of much of this CSS that's being added. I hope it doesn't change the way it looks for people who are already using it. 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. I made the |
||
} | ||
|
||
#editor { | ||
-webkit-user-select: auto !important; | ||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0); | ||
|
@@ -47,6 +53,9 @@ body, h1, p { | |
|
||
#editor { | ||
min-height: 100%; | ||
height: 100%; | ||
overflow: auto; | ||
display: block; | ||
font-family: 'HelveticaNeue'; | ||
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. it looks like it wasn't your fault, but could you get this indention to be the same? |
||
color:#666666; | ||
line-height:28px; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -346,6 +346,12 @@ extension RichEditorView { | |
return runJS("RE.rangeOrCaretSelectionExists();") == "true" ? true : false | ||
} | ||
|
||
/** Returns caret vertical position */ | ||
|
||
public func getCaretPosition() -> String { | ||
return runJS("RE.getCaretPosition();") | ||
} | ||
|
||
/** | ||
* If the current selection's parent is an anchor tag, get the href. | ||
* @returns nil if href is empty, otherwise a non-empty String | ||
|
@@ -439,6 +445,75 @@ extension RichEditorView: UIGestureRecognizerDelegate { | |
// MARK: - Utilities | ||
extension RichEditorView { | ||
|
||
/** Sets content offset for scrollView based on caret location **/ | ||
|
||
private func fixScrollView(editor: RichEditorView) { | ||
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. This is an instance method, you don't need to pass in the Also |
||
|
||
var scrollView = editor.webView.scrollView | ||
|
||
var contentHeight: CGFloat? | ||
let htmlHeight = editor.runJS("document.getElementById('editor').clientHeight;") | ||
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. Generally a good practice to wrap the JS implementation details with a public accessor method on the RE namespace IMHO 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. I agree. Even in this case, since 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. did it that way to match: https://github.com/jesiegel1/RichEditorView/blob/master/RichEditorView/Classes/RichEditorView.swift#L176 |
||
if let n = NSNumberFormatter().numberFromString(htmlHeight) { | ||
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. In Swift you can just do 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. |
||
let floatValue = CGFloat(n) | ||
contentHeight = floatValue | ||
} else { | ||
contentHeight = scrollView.frame.height | ||
} | ||
scrollView.contentSize = CGSizeMake(scrollView.frame.width, contentHeight!) | ||
|
||
/* Caret above scrollView bounds */ | ||
|
||
// Usually getCaretPosition() returns the amount the caret is offset from the current contentOffset, so "-40" would | ||
// indicate that the contentOffset would need to be decreased by 40. | ||
// However, when the cursor is on a new line with no html content, getCaretPosition() returns the absolute position | ||
// of the caret. The resulting code differentiates between caret position values greater than 0 that could either mean | ||
// the caret position is beyond the scrollView lower bounds, or above the scrollView upper bounds and on a new line. | ||
// The code also checks if the caret position is within the scrollView bounds to avoid scrolling when caret is | ||
// not on the first line of the scrollView. | ||
|
||
let data = convertStringToDictionary(editor.getCaretPosition()) | ||
|
||
// Checks if caret is on new line with no content | ||
let newLine = data!["newLine"] as! Bool | ||
if let caretPositionNumeric = data!["height"] as? NSNumber { | ||
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. Would it make sense to have some
or
or something. In this second case, it's not really the "caret position" either... Also, stay away from implicitly unwrapped optionals and Objective-C classes like NSNumber when you can. I also like 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. so a few things - 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. and yea, I should just use Int(), I don't really remember why I went that route. |
||
|
||
let caretFloat = CGFloat(caretPositionNumeric) - scrollView.contentOffset.y | ||
|
||
var CGFloatCaretPositionNumeric = CGFloat(caretPositionNumeric.floatValue) | ||
if newLine { | ||
if (caretFloat + 24.0) > (scrollView.bounds.size.height) { | ||
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. What is the significance of 24? Will it change if people change to have some custom CSS instead? 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. Yea, so basically the value returned by the JS caret position method will either give you the point at the bottom of the caret or the top of the caret (you could have it return both in a dict). so depending on whether the caret is above or below, one of them depending on how you code it will have to add or subtract the caret height to get the proper x,y value. 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. @cjwirth If the user can change the font size, then I need to add a new method that gets the current caret height, and uses that value instead of the hardcoded |
||
let bottomOffset = CGPointMake(0, (CGFloat(caretPositionNumeric.floatValue) - (scrollView.bounds.size.height + scrollView.contentOffset.y)) + scrollView.contentOffset.y + 28.0) | ||
scrollView.setContentOffset(bottomOffset, animated: true) | ||
} else if (CGFloatCaretPositionNumeric < scrollView.contentOffset.y) && scrollView.contentOffset.y > 0 { | ||
let bottomOffset = CGPointMake(scrollView.contentOffset.x, CGFloatCaretPositionNumeric) | ||
scrollView.setContentOffset(bottomOffset, animated: true) | ||
} | ||
} else { | ||
if CGFloatCaretPositionNumeric + 24.0 > scrollView.bounds.size.height { | ||
let bottomOffset = CGPointMake(0, ((CGFloatCaretPositionNumeric - scrollView.bounds.size.height) + scrollView.contentOffset.y + 28.0)) | ||
scrollView.setContentOffset(bottomOffset, animated: true) | ||
} else if (CGFloatCaretPositionNumeric < 0) { | ||
var amount = scrollView.contentOffset.y + CGFloatCaretPositionNumeric | ||
amount = amount < 0 ? 0 : amount | ||
let bottomOffset = CGPointMake(scrollView.contentOffset.x, amount) | ||
scrollView.setContentOffset(bottomOffset, animated: true) | ||
} | ||
} | ||
} | ||
|
||
} | ||
|
||
private func convertStringToDictionary(text: String) -> [String:AnyObject]? { | ||
if let data = text.dataUsingEncoding(NSUTF8StringEncoding) { | ||
do { | ||
return try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [String:AnyObject] | ||
} catch let error as NSError { | ||
print(error) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
/** | ||
Runs some JavaScript on the UIWebView and returns the result | ||
If there is no result, returns an empty string | ||
|
@@ -513,6 +588,7 @@ extension RichEditorView { | |
updateHeight() | ||
} | ||
else if method.hasPrefix("input") { | ||
fixScrollView(self) | ||
let content = runJS("RE.getHtml()") | ||
contentHTML = content | ||
updateHeight() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,8 +36,8 @@ class KeyboardManager: NSObject { | |
Starts monitoring for keyboard notifications in order to show/hide the toolbar | ||
*/ | ||
func beginMonitoring() { | ||
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(KeyboardManager.keyboardWillShowOrHide(_:)), name: UIKeyboardWillShowNotification, object: nil) | ||
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(KeyboardManager.keyboardWillShowOrHide(_:)), name: UIKeyboardWillHideNotification, object: nil) | ||
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("KeyboardManager.keyboardWillShowOrHide:"), name: UIKeyboardWillShowNotification, object: nil) | ||
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. The project is already using Swift 2.2 selector syntax, doesn't it make sense to use here too? 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. Nice catch. Now that Xcode 7.3 official is out, I think we should migrate everything to being Swift 2.2 only. Although -- this file is just in the sample project. Do you think we should make a major version change if we have changes which break in Xcode 7.2 but work in 7.3? I feel like I want to say yes... 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. Yea, I simply switched it back since 7.3 wasn't out and switching it back every time i updated a branch was getting annoying 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. Yeah it would be nice to have these in Xcode 7.3 form |
||
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("KeyboardManager.keyboardWillShowOrHide:"), name: UIKeyboardWillHideNotification, object: nil) | ||
} | ||
|
||
/** | ||
|
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.
Is there a reason all the nodes have to be wrapped in divs? I think I told you that it was a specification for the project that I made this on, I don't really remember. I'm not saying you need to take it out, I'm just curious.
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.
without text nodes being wrapped in divs, it causes issues for calculating where the caret is. I remember you telling me they were supposed to be divs, no?