arctic-ui 0.1.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.
- checksums.yaml +7 -0
- data/.circleci/config.yml +36 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +98 -0
- data/LICENSE.txt +21 -0
- data/README.md +28 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/core-api.gemspec +41 -0
- data/documentation/.editorconfig +15 -0
- data/documentation/.gitignore +24 -0
- data/documentation/.travis.yml +10 -0
- data/documentation/CHANGELOG.md +137 -0
- data/documentation/CODE_OF_CONDUCT.md +46 -0
- data/documentation/Gemfile +11 -0
- data/documentation/Gemfile.lock +129 -0
- data/documentation/LICENSE +13 -0
- data/documentation/Procfile +1 -0
- data/documentation/README.md +118 -0
- data/documentation/config.rb +57 -0
- data/documentation/deploy.sh +215 -0
- data/documentation/font-selection.json +148 -0
- data/documentation/lib/multilang.rb +16 -0
- data/documentation/lib/nesting_unique_head.rb +22 -0
- data/documentation/lib/toc_data.rb +30 -0
- data/documentation/lib/unique_head.rb +24 -0
- data/documentation/source/fonts/slate.eot +0 -0
- data/documentation/source/fonts/slate.svg +14 -0
- data/documentation/source/fonts/slate.ttf +0 -0
- data/documentation/source/fonts/slate.woff +0 -0
- data/documentation/source/fonts/slate.woff2 +0 -0
- data/documentation/source/images/logo.png +0 -0
- data/documentation/source/images/navbar.png +0 -0
- data/documentation/source/includes/_errors.md +17 -0
- data/documentation/source/index.html.md +179 -0
- data/documentation/source/javascripts/all.js +2 -0
- data/documentation/source/javascripts/all_nosearch.js +16 -0
- data/documentation/source/javascripts/app/_lang.js +164 -0
- data/documentation/source/javascripts/app/_search.js +98 -0
- data/documentation/source/javascripts/app/_toc.js +114 -0
- data/documentation/source/javascripts/lib/_energize.js +169 -0
- data/documentation/source/javascripts/lib/_imagesloaded.min.js +7 -0
- data/documentation/source/javascripts/lib/_jquery.highlight.js +108 -0
- data/documentation/source/javascripts/lib/_jquery.js +9831 -0
- data/documentation/source/javascripts/lib/_lunr.js +1910 -0
- data/documentation/source/layouts/layout.erb +116 -0
- data/documentation/source/stylesheets/_icon-font.scss +38 -0
- data/documentation/source/stylesheets/_normalize.scss +427 -0
- data/documentation/source/stylesheets/_rtl.scss +140 -0
- data/documentation/source/stylesheets/_variables.scss +103 -0
- data/documentation/source/stylesheets/_variables2.scss +147 -0
- data/documentation/source/stylesheets/print.css.scss +148 -0
- data/documentation/source/stylesheets/screen.css.scss +707 -0
- data/lib/arctic/ui.rb +21 -0
- data/lib/arctic/ui/api.rb +69 -0
- data/lib/arctic/ui/configuration.rb +26 -0
- data/lib/arctic/ui/version.rb +5 -0
- metadata +288 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
//= require ./lib/_energize
|
2
|
+
//= require ./app/_toc
|
3
|
+
//= require ./app/_lang
|
4
|
+
|
5
|
+
$(function() {
|
6
|
+
loadToc($('#toc'), '.toc-link', '.toc-list-h2', 10);
|
7
|
+
setupLanguages($('body').data('languages'));
|
8
|
+
$('.content').imagesLoaded( function() {
|
9
|
+
window.recacheHeights();
|
10
|
+
window.refreshToc();
|
11
|
+
});
|
12
|
+
});
|
13
|
+
|
14
|
+
window.onpopstate = function() {
|
15
|
+
activateLanguage(getLanguageFromQueryString());
|
16
|
+
};
|
@@ -0,0 +1,164 @@
|
|
1
|
+
//= require ../lib/_jquery
|
2
|
+
|
3
|
+
/*
|
4
|
+
Copyright 2008-2013 Concur Technologies, Inc.
|
5
|
+
|
6
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
7
|
+
not use this file except in compliance with the License. You may obtain
|
8
|
+
a copy of the License at
|
9
|
+
|
10
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
|
12
|
+
Unless required by applicable law or agreed to in writing, software
|
13
|
+
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
14
|
+
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
15
|
+
License for the specific language governing permissions and limitations
|
16
|
+
under the License.
|
17
|
+
*/
|
18
|
+
;(function () {
|
19
|
+
'use strict';
|
20
|
+
|
21
|
+
var languages = [];
|
22
|
+
|
23
|
+
window.setupLanguages = setupLanguages;
|
24
|
+
window.activateLanguage = activateLanguage;
|
25
|
+
window.getLanguageFromQueryString = getLanguageFromQueryString;
|
26
|
+
|
27
|
+
function activateLanguage(language) {
|
28
|
+
if (!language) return;
|
29
|
+
if (language === "") return;
|
30
|
+
|
31
|
+
$(".lang-selector a").removeClass('active');
|
32
|
+
$(".lang-selector a[data-language-name='" + language + "']").addClass('active');
|
33
|
+
for (var i=0; i < languages.length; i++) {
|
34
|
+
$(".highlight.tab-" + languages[i]).hide();
|
35
|
+
$(".lang-specific." + languages[i]).hide();
|
36
|
+
}
|
37
|
+
$(".highlight.tab-" + language).show();
|
38
|
+
$(".lang-specific." + language).show();
|
39
|
+
|
40
|
+
window.recacheHeights();
|
41
|
+
|
42
|
+
// scroll to the new location of the position
|
43
|
+
if ($(window.location.hash).get(0)) {
|
44
|
+
$(window.location.hash).get(0).scrollIntoView(true);
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
// parseURL and stringifyURL are from https://github.com/sindresorhus/query-string
|
49
|
+
// MIT licensed
|
50
|
+
// https://github.com/sindresorhus/query-string/blob/7bee64c16f2da1a326579e96977b9227bf6da9e6/license
|
51
|
+
function parseURL(str) {
|
52
|
+
if (typeof str !== 'string') {
|
53
|
+
return {};
|
54
|
+
}
|
55
|
+
|
56
|
+
str = str.trim().replace(/^(\?|#|&)/, '');
|
57
|
+
|
58
|
+
if (!str) {
|
59
|
+
return {};
|
60
|
+
}
|
61
|
+
|
62
|
+
return str.split('&').reduce(function (ret, param) {
|
63
|
+
var parts = param.replace(/\+/g, ' ').split('=');
|
64
|
+
var key = parts[0];
|
65
|
+
var val = parts[1];
|
66
|
+
|
67
|
+
key = decodeURIComponent(key);
|
68
|
+
// missing `=` should be `null`:
|
69
|
+
// http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
|
70
|
+
val = val === undefined ? null : decodeURIComponent(val);
|
71
|
+
|
72
|
+
if (!ret.hasOwnProperty(key)) {
|
73
|
+
ret[key] = val;
|
74
|
+
} else if (Array.isArray(ret[key])) {
|
75
|
+
ret[key].push(val);
|
76
|
+
} else {
|
77
|
+
ret[key] = [ret[key], val];
|
78
|
+
}
|
79
|
+
|
80
|
+
return ret;
|
81
|
+
}, {});
|
82
|
+
};
|
83
|
+
|
84
|
+
function stringifyURL(obj) {
|
85
|
+
return obj ? Object.keys(obj).sort().map(function (key) {
|
86
|
+
var val = obj[key];
|
87
|
+
|
88
|
+
if (Array.isArray(val)) {
|
89
|
+
return val.sort().map(function (val2) {
|
90
|
+
return encodeURIComponent(key) + '=' + encodeURIComponent(val2);
|
91
|
+
}).join('&');
|
92
|
+
}
|
93
|
+
|
94
|
+
return encodeURIComponent(key) + '=' + encodeURIComponent(val);
|
95
|
+
}).join('&') : '';
|
96
|
+
};
|
97
|
+
|
98
|
+
// gets the language set in the query string
|
99
|
+
function getLanguageFromQueryString() {
|
100
|
+
if (location.search.length >= 1) {
|
101
|
+
var language = parseURL(location.search).language;
|
102
|
+
if (language) {
|
103
|
+
return language;
|
104
|
+
} else if (jQuery.inArray(location.search.substr(1), languages) != -1) {
|
105
|
+
return location.search.substr(1);
|
106
|
+
}
|
107
|
+
}
|
108
|
+
|
109
|
+
return false;
|
110
|
+
}
|
111
|
+
|
112
|
+
// returns a new query string with the new language in it
|
113
|
+
function generateNewQueryString(language) {
|
114
|
+
var url = parseURL(location.search);
|
115
|
+
if (url.language) {
|
116
|
+
url.language = language;
|
117
|
+
return stringifyURL(url);
|
118
|
+
}
|
119
|
+
return language;
|
120
|
+
}
|
121
|
+
|
122
|
+
// if a button is clicked, add the state to the history
|
123
|
+
function pushURL(language) {
|
124
|
+
if (!history) { return; }
|
125
|
+
var hash = window.location.hash;
|
126
|
+
if (hash) {
|
127
|
+
hash = hash.replace(/^#+/, '');
|
128
|
+
}
|
129
|
+
history.pushState({}, '', '?' + generateNewQueryString(language) + '#' + hash);
|
130
|
+
|
131
|
+
// save language as next default
|
132
|
+
localStorage.setItem("language", language);
|
133
|
+
}
|
134
|
+
|
135
|
+
function setupLanguages(l) {
|
136
|
+
var defaultLanguage = localStorage.getItem("language");
|
137
|
+
|
138
|
+
languages = l;
|
139
|
+
|
140
|
+
var presetLanguage = getLanguageFromQueryString();
|
141
|
+
if (presetLanguage) {
|
142
|
+
// the language is in the URL, so use that language!
|
143
|
+
activateLanguage(presetLanguage);
|
144
|
+
|
145
|
+
localStorage.setItem("language", presetLanguage);
|
146
|
+
} else if ((defaultLanguage !== null) && (jQuery.inArray(defaultLanguage, languages) != -1)) {
|
147
|
+
// the language was the last selected one saved in localstorage, so use that language!
|
148
|
+
activateLanguage(defaultLanguage);
|
149
|
+
} else {
|
150
|
+
// no language selected, so use the default
|
151
|
+
activateLanguage(languages[0]);
|
152
|
+
}
|
153
|
+
}
|
154
|
+
|
155
|
+
// if we click on a language tab, activate that language
|
156
|
+
$(function() {
|
157
|
+
$(".lang-selector a").on("click", function() {
|
158
|
+
var language = $(this).data("language-name");
|
159
|
+
pushURL(language);
|
160
|
+
activateLanguage(language);
|
161
|
+
return false;
|
162
|
+
});
|
163
|
+
});
|
164
|
+
})();
|
@@ -0,0 +1,98 @@
|
|
1
|
+
//= require ../lib/_lunr
|
2
|
+
//= require ../lib/_jquery
|
3
|
+
//= require ../lib/_jquery.highlight
|
4
|
+
;(function () {
|
5
|
+
'use strict';
|
6
|
+
|
7
|
+
var content, searchResults;
|
8
|
+
var highlightOpts = { element: 'span', className: 'search-highlight' };
|
9
|
+
var searchDelay = 0;
|
10
|
+
var timeoutHandle = 0;
|
11
|
+
|
12
|
+
var index = new lunr.Index();
|
13
|
+
|
14
|
+
index.ref('id');
|
15
|
+
index.field('title', { boost: 10 });
|
16
|
+
index.field('body');
|
17
|
+
index.pipeline.add(lunr.trimmer, lunr.stopWordFilter);
|
18
|
+
|
19
|
+
$(populate);
|
20
|
+
$(bind);
|
21
|
+
|
22
|
+
function populate() {
|
23
|
+
$('h1, h2').each(function() {
|
24
|
+
var title = $(this);
|
25
|
+
var body = title.nextUntil('h1, h2');
|
26
|
+
index.add({
|
27
|
+
id: title.prop('id'),
|
28
|
+
title: title.text(),
|
29
|
+
body: body.text()
|
30
|
+
});
|
31
|
+
});
|
32
|
+
|
33
|
+
determineSearchDelay();
|
34
|
+
}
|
35
|
+
function determineSearchDelay() {
|
36
|
+
if(index.tokenStore.length>5000) {
|
37
|
+
searchDelay = 300;
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
function bind() {
|
42
|
+
content = $('.content');
|
43
|
+
searchResults = $('.search-results');
|
44
|
+
|
45
|
+
$('#input-search').on('keyup',function(e) {
|
46
|
+
var wait = function() {
|
47
|
+
return function(executingFunction, waitTime){
|
48
|
+
clearTimeout(timeoutHandle);
|
49
|
+
timeoutHandle = setTimeout(executingFunction, waitTime);
|
50
|
+
};
|
51
|
+
}();
|
52
|
+
wait(function(){
|
53
|
+
search(e);
|
54
|
+
}, searchDelay );
|
55
|
+
});
|
56
|
+
}
|
57
|
+
|
58
|
+
function search(event) {
|
59
|
+
|
60
|
+
var searchInput = $('#input-search')[0];
|
61
|
+
|
62
|
+
unhighlight();
|
63
|
+
searchResults.addClass('visible');
|
64
|
+
|
65
|
+
// ESC clears the field
|
66
|
+
if (event.keyCode === 27) searchInput.value = '';
|
67
|
+
|
68
|
+
if (searchInput.value) {
|
69
|
+
var results = index.search(searchInput.value).filter(function(r) {
|
70
|
+
return r.score > 0.0001;
|
71
|
+
});
|
72
|
+
|
73
|
+
if (results.length) {
|
74
|
+
searchResults.empty();
|
75
|
+
$.each(results, function (index, result) {
|
76
|
+
var elem = document.getElementById(result.ref);
|
77
|
+
searchResults.append("<li><a href='#" + result.ref + "'>" + $(elem).text() + "</a></li>");
|
78
|
+
});
|
79
|
+
highlight.call(searchInput);
|
80
|
+
} else {
|
81
|
+
searchResults.html('<li></li>');
|
82
|
+
$('.search-results li').text('No Results Found for "' + searchInput.value + '"');
|
83
|
+
}
|
84
|
+
} else {
|
85
|
+
unhighlight();
|
86
|
+
searchResults.removeClass('visible');
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
function highlight() {
|
91
|
+
if (this.value) content.highlight(this.value, highlightOpts);
|
92
|
+
}
|
93
|
+
|
94
|
+
function unhighlight() {
|
95
|
+
content.unhighlight(highlightOpts);
|
96
|
+
}
|
97
|
+
})();
|
98
|
+
|
@@ -0,0 +1,114 @@
|
|
1
|
+
//= require ../lib/_jquery
|
2
|
+
//= require ../lib/_imagesloaded.min
|
3
|
+
;(function () {
|
4
|
+
'use strict';
|
5
|
+
|
6
|
+
var loaded = false;
|
7
|
+
|
8
|
+
var debounce = function(func, waitTime) {
|
9
|
+
var timeout = false;
|
10
|
+
return function() {
|
11
|
+
if (timeout === false) {
|
12
|
+
setTimeout(function() {
|
13
|
+
func();
|
14
|
+
timeout = false;
|
15
|
+
}, waitTime);
|
16
|
+
timeout = true;
|
17
|
+
}
|
18
|
+
};
|
19
|
+
};
|
20
|
+
|
21
|
+
var closeToc = function() {
|
22
|
+
$(".toc-wrapper").removeClass('open');
|
23
|
+
$("#nav-button").removeClass('open');
|
24
|
+
};
|
25
|
+
|
26
|
+
function loadToc($toc, tocLinkSelector, tocListSelector, scrollOffset) {
|
27
|
+
var headerHeights = {};
|
28
|
+
var pageHeight = 0;
|
29
|
+
var windowHeight = 0;
|
30
|
+
var originalTitle = document.title;
|
31
|
+
|
32
|
+
var recacheHeights = function() {
|
33
|
+
headerHeights = {};
|
34
|
+
pageHeight = $(document).height();
|
35
|
+
windowHeight = $(window).height();
|
36
|
+
|
37
|
+
$toc.find(tocLinkSelector).each(function() {
|
38
|
+
var targetId = $(this).attr('href');
|
39
|
+
if (targetId[0] === "#") {
|
40
|
+
headerHeights[targetId] = $(targetId).offset().top;
|
41
|
+
}
|
42
|
+
});
|
43
|
+
};
|
44
|
+
|
45
|
+
var refreshToc = function() {
|
46
|
+
var currentTop = $(document).scrollTop() + scrollOffset;
|
47
|
+
|
48
|
+
if (currentTop + windowHeight >= pageHeight) {
|
49
|
+
// at bottom of page, so just select last header by making currentTop very large
|
50
|
+
// this fixes the problem where the last header won't ever show as active if its content
|
51
|
+
// is shorter than the window height
|
52
|
+
currentTop = pageHeight + 1000;
|
53
|
+
}
|
54
|
+
|
55
|
+
var best = null;
|
56
|
+
for (var name in headerHeights) {
|
57
|
+
if ((headerHeights[name] < currentTop && headerHeights[name] > headerHeights[best]) || best === null) {
|
58
|
+
best = name;
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
// Catch the initial load case
|
63
|
+
if (currentTop == scrollOffset && !loaded) {
|
64
|
+
best = window.location.hash;
|
65
|
+
loaded = true;
|
66
|
+
}
|
67
|
+
|
68
|
+
var $best = $toc.find("[href='" + best + "']").first();
|
69
|
+
if (!$best.hasClass("active")) {
|
70
|
+
// .active is applied to the ToC link we're currently on, and its parent <ul>s selected by tocListSelector
|
71
|
+
// .active-expanded is applied to the ToC links that are parents of this one
|
72
|
+
$toc.find(".active").removeClass("active");
|
73
|
+
$toc.find(".active-parent").removeClass("active-parent");
|
74
|
+
$best.addClass("active");
|
75
|
+
$best.parents(tocListSelector).addClass("active").siblings(tocLinkSelector).addClass('active-parent');
|
76
|
+
$best.siblings(tocListSelector).addClass("active");
|
77
|
+
$toc.find(tocListSelector).filter(":not(.active)").slideUp(150);
|
78
|
+
$toc.find(tocListSelector).filter(".active").slideDown(150);
|
79
|
+
// TODO remove classnames
|
80
|
+
document.title = $best.data("title") + " – " + originalTitle;
|
81
|
+
}
|
82
|
+
};
|
83
|
+
|
84
|
+
var makeToc = function() {
|
85
|
+
recacheHeights();
|
86
|
+
refreshToc();
|
87
|
+
|
88
|
+
$("#nav-button").click(function() {
|
89
|
+
$(".toc-wrapper").toggleClass('open');
|
90
|
+
$("#nav-button").toggleClass('open');
|
91
|
+
return false;
|
92
|
+
});
|
93
|
+
$(".page-wrapper").click(closeToc);
|
94
|
+
$(".toc-link").click(closeToc);
|
95
|
+
|
96
|
+
// reload immediately after scrolling on toc click
|
97
|
+
$toc.find(tocLinkSelector).click(function() {
|
98
|
+
setTimeout(function() {
|
99
|
+
refreshToc();
|
100
|
+
}, 0);
|
101
|
+
});
|
102
|
+
|
103
|
+
$(window).scroll(debounce(refreshToc, 200));
|
104
|
+
$(window).resize(debounce(recacheHeights, 200));
|
105
|
+
};
|
106
|
+
|
107
|
+
makeToc();
|
108
|
+
|
109
|
+
window.recacheHeights = recacheHeights;
|
110
|
+
window.refreshToc = refreshToc;
|
111
|
+
}
|
112
|
+
|
113
|
+
window.loadToc = loadToc;
|
114
|
+
})();
|
@@ -0,0 +1,169 @@
|
|
1
|
+
/**
|
2
|
+
* energize.js v0.1.0
|
3
|
+
*
|
4
|
+
* Speeds up click events on mobile devices.
|
5
|
+
* https://github.com/davidcalhoun/energize.js
|
6
|
+
*/
|
7
|
+
|
8
|
+
(function() { // Sandbox
|
9
|
+
/**
|
10
|
+
* Don't add to non-touch devices, which don't need to be sped up
|
11
|
+
*/
|
12
|
+
if(!('ontouchstart' in window)) return;
|
13
|
+
|
14
|
+
var lastClick = {},
|
15
|
+
isThresholdReached, touchstart, touchmove, touchend,
|
16
|
+
click, closest;
|
17
|
+
|
18
|
+
/**
|
19
|
+
* isThresholdReached
|
20
|
+
*
|
21
|
+
* Compare touchstart with touchend xy coordinates,
|
22
|
+
* and only fire simulated click event if the coordinates
|
23
|
+
* are nearby. (don't want clicking to be confused with a swipe)
|
24
|
+
*/
|
25
|
+
isThresholdReached = function(startXY, xy) {
|
26
|
+
return Math.abs(startXY[0] - xy[0]) > 5 || Math.abs(startXY[1] - xy[1]) > 5;
|
27
|
+
};
|
28
|
+
|
29
|
+
/**
|
30
|
+
* touchstart
|
31
|
+
*
|
32
|
+
* Save xy coordinates when the user starts touching the screen
|
33
|
+
*/
|
34
|
+
touchstart = function(e) {
|
35
|
+
this.startXY = [e.touches[0].clientX, e.touches[0].clientY];
|
36
|
+
this.threshold = false;
|
37
|
+
};
|
38
|
+
|
39
|
+
/**
|
40
|
+
* touchmove
|
41
|
+
*
|
42
|
+
* Check if the user is scrolling past the threshold.
|
43
|
+
* Have to check here because touchend will not always fire
|
44
|
+
* on some tested devices (Kindle Fire?)
|
45
|
+
*/
|
46
|
+
touchmove = function(e) {
|
47
|
+
// NOOP if the threshold has already been reached
|
48
|
+
if(this.threshold) return false;
|
49
|
+
|
50
|
+
this.threshold = isThresholdReached(this.startXY, [e.touches[0].clientX, e.touches[0].clientY]);
|
51
|
+
};
|
52
|
+
|
53
|
+
/**
|
54
|
+
* touchend
|
55
|
+
*
|
56
|
+
* If the user didn't scroll past the threshold between
|
57
|
+
* touchstart and touchend, fire a simulated click.
|
58
|
+
*
|
59
|
+
* (This will fire before a native click)
|
60
|
+
*/
|
61
|
+
touchend = function(e) {
|
62
|
+
// Don't fire a click if the user scrolled past the threshold
|
63
|
+
if(this.threshold || isThresholdReached(this.startXY, [e.changedTouches[0].clientX, e.changedTouches[0].clientY])) {
|
64
|
+
return;
|
65
|
+
}
|
66
|
+
|
67
|
+
/**
|
68
|
+
* Create and fire a click event on the target element
|
69
|
+
* https://developer.mozilla.org/en/DOM/event.initMouseEvent
|
70
|
+
*/
|
71
|
+
var touch = e.changedTouches[0],
|
72
|
+
evt = document.createEvent('MouseEvents');
|
73
|
+
evt.initMouseEvent('click', true, true, window, 0, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
|
74
|
+
evt.simulated = true; // distinguish from a normal (nonsimulated) click
|
75
|
+
e.target.dispatchEvent(evt);
|
76
|
+
};
|
77
|
+
|
78
|
+
/**
|
79
|
+
* click
|
80
|
+
*
|
81
|
+
* Because we've already fired a click event in touchend,
|
82
|
+
* we need to listed for all native click events here
|
83
|
+
* and suppress them as necessary.
|
84
|
+
*/
|
85
|
+
click = function(e) {
|
86
|
+
/**
|
87
|
+
* Prevent ghost clicks by only allowing clicks we created
|
88
|
+
* in the click event we fired (look for e.simulated)
|
89
|
+
*/
|
90
|
+
var time = Date.now(),
|
91
|
+
timeDiff = time - lastClick.time,
|
92
|
+
x = e.clientX,
|
93
|
+
y = e.clientY,
|
94
|
+
xyDiff = [Math.abs(lastClick.x - x), Math.abs(lastClick.y - y)],
|
95
|
+
target = closest(e.target, 'A') || e.target, // needed for standalone apps
|
96
|
+
nodeName = target.nodeName,
|
97
|
+
isLink = nodeName === 'A',
|
98
|
+
standAlone = window.navigator.standalone && isLink && e.target.getAttribute("href");
|
99
|
+
|
100
|
+
lastClick.time = time;
|
101
|
+
lastClick.x = x;
|
102
|
+
lastClick.y = y;
|
103
|
+
|
104
|
+
/**
|
105
|
+
* Unfortunately Android sometimes fires click events without touch events (seen on Kindle Fire),
|
106
|
+
* so we have to add more logic to determine the time of the last click. Not perfect...
|
107
|
+
*
|
108
|
+
* Older, simpler check: if((!e.simulated) || standAlone)
|
109
|
+
*/
|
110
|
+
if((!e.simulated && (timeDiff < 500 || (timeDiff < 1500 && xyDiff[0] < 50 && xyDiff[1] < 50))) || standAlone) {
|
111
|
+
e.preventDefault();
|
112
|
+
e.stopPropagation();
|
113
|
+
if(!standAlone) return false;
|
114
|
+
}
|
115
|
+
|
116
|
+
/**
|
117
|
+
* Special logic for standalone web apps
|
118
|
+
* See http://stackoverflow.com/questions/2898740/iphone-safari-web-app-opens-links-in-new-window
|
119
|
+
*/
|
120
|
+
if(standAlone) {
|
121
|
+
window.location = target.getAttribute("href");
|
122
|
+
}
|
123
|
+
|
124
|
+
/**
|
125
|
+
* Add an energize-focus class to the targeted link (mimics :focus behavior)
|
126
|
+
* TODO: test and/or remove? Does this work?
|
127
|
+
*/
|
128
|
+
if(!target || !target.classList) return;
|
129
|
+
target.classList.add("energize-focus");
|
130
|
+
window.setTimeout(function(){
|
131
|
+
target.classList.remove("energize-focus");
|
132
|
+
}, 150);
|
133
|
+
};
|
134
|
+
|
135
|
+
/**
|
136
|
+
* closest
|
137
|
+
* @param {HTMLElement} node current node to start searching from.
|
138
|
+
* @param {string} tagName the (uppercase) name of the tag you're looking for.
|
139
|
+
*
|
140
|
+
* Find the closest ancestor tag of a given node.
|
141
|
+
*
|
142
|
+
* Starts at node and goes up the DOM tree looking for a
|
143
|
+
* matching nodeName, continuing until hitting document.body
|
144
|
+
*/
|
145
|
+
closest = function(node, tagName){
|
146
|
+
var curNode = node;
|
147
|
+
|
148
|
+
while(curNode !== document.body) { // go up the dom until we find the tag we're after
|
149
|
+
if(!curNode || curNode.nodeName === tagName) { return curNode; } // found
|
150
|
+
curNode = curNode.parentNode; // not found, so keep going up
|
151
|
+
}
|
152
|
+
|
153
|
+
return null; // not found
|
154
|
+
};
|
155
|
+
|
156
|
+
/**
|
157
|
+
* Add all delegated event listeners
|
158
|
+
*
|
159
|
+
* All the events we care about bubble up to document,
|
160
|
+
* so we can take advantage of event delegation.
|
161
|
+
*
|
162
|
+
* Note: no need to wait for DOMContentLoaded here
|
163
|
+
*/
|
164
|
+
document.addEventListener('touchstart', touchstart, false);
|
165
|
+
document.addEventListener('touchmove', touchmove, false);
|
166
|
+
document.addEventListener('touchend', touchend, false);
|
167
|
+
document.addEventListener('click', click, true); // TODO: why does this use capture?
|
168
|
+
|
169
|
+
})();
|