mirror of
https://github.com/kiwix/kiwix-apple.git
synced 2025-09-25 21:05:09 -04:00
js toc visible detection
This commit is contained in:
parent
ed9c927189
commit
795d129234
@ -27,8 +27,16 @@ class JS {
|
||||
return webView.stringByEvaluatingJavaScriptFromString("document.title")
|
||||
}
|
||||
|
||||
class func startTOCCallBack(webView: UIWebView) {
|
||||
webView.stringByEvaluatingJavaScriptFromString("startCallBack()")
|
||||
}
|
||||
|
||||
class func stopTOCCallBack(webView: UIWebView) {
|
||||
webView.stringByEvaluatingJavaScriptFromString("stopCallBack()")
|
||||
}
|
||||
|
||||
class func getTableOfContents(webView: UIWebView) -> [HTMLHeading] {
|
||||
let jString = "(new TableOfContents()).headerObjects;"
|
||||
let jString = "getTableOfContents().headerObjects;"
|
||||
guard let elements = webView.context.evaluateScript(jString).toArray() as? [[String: String]] else {return [HTMLHeading]()}
|
||||
var headings = [HTMLHeading]()
|
||||
for element in elements {
|
||||
|
@ -18,6 +18,12 @@ extension MainController: UIWebViewDelegate, SFSafariViewControllerDelegate, LPT
|
||||
|
||||
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
|
||||
guard let url = request.URL else {return false}
|
||||
guard url.scheme?.caseInsensitiveCompare("pagescroll") != .OrderedSame else {
|
||||
let components = NSURLComponents(string: url.absoluteString!)
|
||||
let ids = components?.queryItems?.filter({$0.name == "header"}).flatMap({$0.value}) ?? [String]()
|
||||
tableOfContentsController?.visibleHeaderIDs = ids
|
||||
return false
|
||||
}
|
||||
guard url.isKiwixURL else {
|
||||
let controller = SFSafariViewController(URL: url)
|
||||
controller.delegate = self
|
||||
|
@ -120,6 +120,8 @@ extension MainController {
|
||||
self.view.layoutIfNeeded()
|
||||
self.dimView.alpha = 0.5
|
||||
}) { (completed) in }
|
||||
|
||||
JS.startTOCCallBack(webView)
|
||||
}
|
||||
|
||||
func hideTableOfContentsController() {
|
||||
@ -133,6 +135,8 @@ extension MainController {
|
||||
self.dimView.hidden = true
|
||||
self.tocVisiualEffectView.hidden = true
|
||||
}
|
||||
|
||||
JS.stopTOCCallBack(webView)
|
||||
}
|
||||
|
||||
func configureTOCViewConstraints() {
|
||||
|
@ -12,6 +12,7 @@ import DZNEmptyDataSet
|
||||
class TableOfContentsController: UIViewController, UITableViewDelegate, UITableViewDataSource, DZNEmptyDataSetSource, DZNEmptyDataSetDelegate {
|
||||
|
||||
@IBOutlet weak var tableView: UITableView!
|
||||
private let visibleHeaderIndicator = UIView()
|
||||
weak var delegate: TableOfContentsDelegate?
|
||||
private var headinglevelMin = 0
|
||||
|
||||
@ -19,9 +20,16 @@ class TableOfContentsController: UIViewController, UITableViewDelegate, UITableV
|
||||
didSet {
|
||||
configurePreferredContentSize()
|
||||
headinglevelMin = max(2, headings.map({$0.level}).minElement() ?? 0)
|
||||
visibleHeaderIDs.removeAll()
|
||||
tableView.reloadData()
|
||||
}
|
||||
}
|
||||
var visibleHeaderIDs = [String]() {
|
||||
didSet {
|
||||
guard oldValue != visibleHeaderIDs else {return}
|
||||
configureVisibleHeaderView(animated: oldValue.count > 0)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
@ -30,6 +38,7 @@ class TableOfContentsController: UIViewController, UITableViewDelegate, UITableV
|
||||
tableView.emptyDataSetSource = self
|
||||
tableView.emptyDataSetDelegate = self
|
||||
tableView.tableFooterView = UIView()
|
||||
visibleHeaderIndicator.backgroundColor = UIColor.redColor()
|
||||
}
|
||||
|
||||
func configurePreferredContentSize() {
|
||||
@ -37,7 +46,47 @@ class TableOfContentsController: UIViewController, UITableViewDelegate, UITableV
|
||||
let width = traitCollection.horizontalSizeClass == .Regular ? 300 : (UIScreen.mainScreen().bounds.width)
|
||||
preferredContentSize = CGSizeMake(width, count == 0 ? 350 : min(CGFloat(count) * 44.0, UIScreen.mainScreen().bounds.height * 0.8))
|
||||
}
|
||||
|
||||
|
||||
func configureVisibleHeaderView(animated animated: Bool) {
|
||||
// no visible header
|
||||
guard visibleHeaderIDs.count > 0 else {
|
||||
visibleHeaderIndicator.removeFromSuperview()
|
||||
return
|
||||
}
|
||||
|
||||
// calculations
|
||||
guard let minIndex = headings.indexOf({$0.id == visibleHeaderIDs.first}),
|
||||
let maxIndex = headings.indexOf({$0.id == visibleHeaderIDs.last}) else {return}
|
||||
let topIndexPath = NSIndexPath(forRow: minIndex, inSection: 0)
|
||||
let bottomIndexPath = NSIndexPath(forRow: maxIndex, inSection: 0)
|
||||
let topCell = tableView(tableView, cellForRowAtIndexPath: topIndexPath)
|
||||
let bottomCell = tableView(tableView, cellForRowAtIndexPath: bottomIndexPath)
|
||||
let top = topCell.frame.origin.y + topCell.frame.height * 0.1
|
||||
let bottom = bottomCell.frame.origin.y + bottomCell.frame.height * 0.9
|
||||
|
||||
// indicator frame
|
||||
if !tableView.subviews.contains(visibleHeaderIndicator) {tableView.addSubview(visibleHeaderIndicator)}
|
||||
if animated {
|
||||
UIView.animateWithDuration(0.1, animations: {
|
||||
self.visibleHeaderIndicator.frame = CGRectMake(0, top, 3, bottom - top)
|
||||
})
|
||||
} else {
|
||||
visibleHeaderIndicator.frame = CGRectMake(0, top, 3, bottom - top)
|
||||
}
|
||||
|
||||
// tableview scroll
|
||||
let topCellVisible = tableView.indexPathsForVisibleRows?.contains(topIndexPath) ?? false
|
||||
let bottomCellVisible = tableView.indexPathsForVisibleRows?.contains(bottomIndexPath) ?? false
|
||||
switch (topCellVisible, bottomCellVisible) {
|
||||
case (true, false):
|
||||
tableView.scrollToRowAtIndexPath(bottomIndexPath, atScrollPosition: .Bottom, animated: animated)
|
||||
case (false, true), (false, false):
|
||||
tableView.scrollToRowAtIndexPath(topIndexPath, atScrollPosition: .Top, animated: animated)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Table view data source
|
||||
|
||||
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
|
||||
|
@ -49,7 +49,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.8.1791</string>
|
||||
<string>1.8.1832</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
@ -21,7 +21,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.8.1791</string>
|
||||
<string>1.8.1832</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionMainStoryboard</key>
|
||||
|
@ -1,3 +1,12 @@
|
||||
var toc = undefined;
|
||||
var visEle = undefined;
|
||||
var visibleHeaderIDs = undefined;
|
||||
|
||||
function getTableOfContents() {
|
||||
toc = toc == undefined ? new TableOfContents() : toc;
|
||||
return toc;
|
||||
}
|
||||
|
||||
function TableOfContents () {
|
||||
this.getHeaderElements = function () {
|
||||
var h1 = document.getElementsByTagName('h1')[0];
|
||||
@ -17,7 +26,7 @@ function TableOfContents () {
|
||||
this.headerElements = this.getHeaderElements();
|
||||
|
||||
this.getHeaderObjects = function () {
|
||||
return this.getHeaderElements().map( elementToObject );
|
||||
return this.headerElements.map( elementToObject );
|
||||
}
|
||||
|
||||
this.headerObjects = this.getHeaderObjects();
|
||||
@ -30,3 +39,96 @@ function TableOfContents () {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
function getVisibleElementsChecker() {
|
||||
visEle = visEle == undefined ? new VisibleElements() : visEle;
|
||||
return visEle;
|
||||
}
|
||||
|
||||
function VisibleElements () {
|
||||
// return a 2d array [[header(h1/h2/h3), p, ul, div]]
|
||||
function getElementGroups() {
|
||||
var groups = [];
|
||||
var group = [document.getElementsByTagName('h1')[0]];
|
||||
var contents = document.getElementById("mw-content-text").children;
|
||||
var headerTags = ['h2', 'h3', 'h4'].map(function(x){ return x.toUpperCase() });
|
||||
|
||||
for (i = 0; i < contents.length; i++) {
|
||||
var element = contents[i];
|
||||
if (headerTags.includes(element.tagName)) {
|
||||
groups.push(group);
|
||||
group = []
|
||||
}
|
||||
group.push(element);
|
||||
}
|
||||
|
||||
groups.push(group);
|
||||
return groups;
|
||||
}
|
||||
|
||||
this.elementGroups = getElementGroups();
|
||||
|
||||
this.getVisibleHeaders = function () {
|
||||
var groups = this.elementGroups;
|
||||
var viewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
|
||||
var visibleHeaders = [];
|
||||
|
||||
for (i = 0; i < groups.length; i++) {
|
||||
var group = groups[i];
|
||||
var header = group[0];
|
||||
var groupVisible = false;
|
||||
|
||||
for (j = 0; j < group.length; j++) {
|
||||
var element = group[j];
|
||||
var rect = element.getBoundingClientRect();
|
||||
var isVisible = !(rect.bottom < 0 || rect.top - viewHeight >= 0);
|
||||
groupVisible = groupVisible || isVisible;
|
||||
if (isVisible) { // if found an element visible in group, break and check the next group
|
||||
visibleHeaders.push(header)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If found visible groups already, but current group is not visible
|
||||
// It means we are checking area below the visible area, should break
|
||||
if (visibleHeaders.length > 0 && !groupVisible) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return visibleHeaders;
|
||||
}
|
||||
|
||||
this.getVisibleHeaderIDs = function () {
|
||||
return this.getVisibleHeaders().map(function(x){ return x.id });
|
||||
}
|
||||
}
|
||||
|
||||
function startCallBack() {
|
||||
function arraysEqual(a, b) {
|
||||
if (a === b) return true;
|
||||
if (a == null || b == null) return false;
|
||||
if (a.length != b.length) return false;
|
||||
|
||||
for (var i = 0; i < a.length; ++i) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
visibleHeaderIDs = getVisibleElementsChecker().getVisibleHeaderIDs();
|
||||
window.onscroll = function() {
|
||||
var newVisibleHeaderIDs = getVisibleElementsChecker().getVisibleHeaderIDs();
|
||||
if (!arraysEqual(visibleHeaderIDs, newVisibleHeaderIDs)) {
|
||||
visibleHeaderIDs = newVisibleHeaderIDs;
|
||||
console.log(visibleHeaderIDs);
|
||||
|
||||
var parameter = visibleHeaderIDs.map(function(x){ return 'header=' + x }).join('&');
|
||||
window.location = 'pagescroll:scrollEnded?' + parameter;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function stopCallBack() {
|
||||
window.onscroll = undefined;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user