├── README.md
├── chrome
├── background.js
├── github-time-travel.js
├── manifest.json
└── pikaday
│ ├── pikaday.css
│ └── pikaday.js
└── firefox
├── data
├── github-time-travel.js
└── pikaday
│ ├── pikaday.css
│ └── pikaday.js
├── index.js
└── package.json
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Github Time Travel
4 |
5 | 
6 |
7 | Browser extension for browsing commits by date on Github. Only works for public repos.
8 |
9 | - Firefox: https://addons.mozilla.org/en-US/firefox/addon/github-time-travel/
10 | - Chrome: https://chrome.google.com/webstore/detail/github-time-travel/gdaikofkgknonbdhgomechckcjlcpgpg
11 |
--------------------------------------------------------------------------------
/chrome/background.js:
--------------------------------------------------------------------------------
1 |
2 | chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
3 | var re = /.*github\.com\/(.*)\/(.*)\/commits.*/,
4 | m = re.exec(changeInfo.url);
5 |
6 | if (m === null) return;
7 |
8 | chrome.tabs.executeScript({
9 | code: 'setDomChangeTimeout(attachDateButton, "commit-group-title", 2000);'
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/chrome/github-time-travel.js:
--------------------------------------------------------------------------------
1 |
2 | function setDomChangeTimeout (callback, klass, timeout) {
3 | // A function like setTimeout that watches a DOM element (by class name)
4 | // for change. The callback is called as soon as the DOM element
5 | // changes. If it doesn't change, the callback is called at timeout.
6 |
7 | // This is the fastest way to get a callback when the new DOM settles.
8 | // Right now, it naively uses innerHTML, but for this use, it's good enough.
9 |
10 | var start = performance.now(),
11 | startContent = '',
12 | startElements = document.getElementsByClassName(klass);
13 |
14 | if (startElements.length > 0) {
15 | startContent = startElements[0].innerHTML;
16 | }
17 |
18 | var interval = setInterval(function () {
19 | if (performance.now() - start > timeout) {
20 | clearInterval(interval);
21 | callback();
22 | } else {
23 | var endElements = document.getElementsByClassName(klass);
24 |
25 | if (endElements.length > 0) {
26 | if (endElements[0].innerHTML !== startContent) {
27 | clearInterval(interval);
28 | callback();
29 | }
30 | }
31 | }
32 | }, 50);
33 | }
34 |
35 | function attachDateButton () {
36 | // Extract the Github username and repo from a commit log URL.
37 | // If the DOM element does not already exist, create an element to hold
38 | // the datepicker and append it to the .file-navigation DOM element
39 | // after styling it like a Github button.
40 |
41 | // Then, create the datepicker with a callback to make an API call on select.
42 | // Attach the listener to the API call to handle the response.
43 |
44 | var re = /.*github\.com\/(.*)\/(.*)\/commits.*/,
45 | m = re.exec(document.location.href);
46 |
47 | if (m === null) return;
48 | if (document.getElementsByClassName('datepicker-button').length > 0) return;
49 |
50 | var username = m[1],
51 | repo = m[2],
52 | api = 'https://api.github.com/repos/' + username + '/' + repo + '/commits',
53 | date = getCommitDate(),
54 | el = document.createElement('span');
55 |
56 | el.className = 'btn btn-sm select-menu-button datepicker-button';
57 | el.innerHTML = 'Date: ' + date + ' ';
58 |
59 | document.getElementsByClassName('file-navigation')[0].appendChild(el);
60 |
61 | new Pikaday({
62 | field: el,
63 | defaultDate: new Date(date),
64 | setDefaultDate: true,
65 | onSelect: function (date) {
66 | var req = new XMLHttpRequest(),
67 | url = api + '?per_page=1&until=' + date.toISOString().split('T')[0];
68 |
69 | req.addEventListener('load', reqListener);
70 | req.open('get', url, true);
71 | req.send();
72 | }
73 | });
74 | }
75 |
76 | function getCommitDate () {
77 | // Find first .commit-group-title in page, extract the date from the text.
78 | // Return 'unknown' if no commit group titles are found.
79 |
80 | var commitGroups = document.getElementsByClassName('commit-group-title');
81 |
82 | if (commitGroups.length > 0) {
83 | var re = /Commits on (.*)/,
84 | m = re.exec(commitGroups[0].innerHTML),
85 | date = m[1];
86 |
87 | return date;
88 | } else {
89 | return 'unknown';
90 | }
91 | }
92 |
93 | function reqListener () {
94 | // Listen for a response from the Github Commits API
95 | // https://developer.github.com/v3/repos/commits/
96 | // Parse the JSON response and extract the HTML link from the first commit
97 | // if commits were returned.
98 | // Change 'commit' to 'commits' in the URL to browse the commit log.
99 |
100 | var parsed = JSON.parse(this.responseText);
101 |
102 | if (parsed.length > 0) {
103 | var tree_url = parsed[0].html_url.replace('/commit/', '/commits/');
104 | document.location.href = tree_url;
105 | }
106 | }
107 |
108 | attachDateButton();
109 |
--------------------------------------------------------------------------------
/chrome/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Github Time Travel",
4 | "version": "1.0.1",
5 | "description": "Browse commits by date on Github",
6 | "author": "Nathan Cahill",
7 | "permissions": ["https://*.github.com/", "tabs"],
8 | "background": {
9 | "persistent": false,
10 | "scripts": ["background.js"]
11 | },
12 | "content_scripts": [{
13 | "matches": ["https://*.github.com/*/*"],
14 | "css": ["pikaday/pikaday.css"],
15 | "js": ["pikaday/pikaday.js", "github-time-travel.js"]
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/chrome/pikaday/pikaday.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 |
3 | /*!
4 | * Pikaday
5 | * Copyright © 2014 David Bushell | BSD & MIT license | http://dbushell.com/
6 | */
7 |
8 | .pika-single {
9 | z-index: 9999;
10 | display: block;
11 | position: relative;
12 | color: #333;
13 | background: #fff;
14 | border: 1px solid #ccc;
15 | border-bottom-color: #bbb;
16 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
17 | }
18 |
19 | /*
20 | clear child float (pika-lendar), using the famous micro clearfix hack
21 | http://nicolasgallagher.com/micro-clearfix-hack/
22 | */
23 | .pika-single:before,
24 | .pika-single:after {
25 | content: " ";
26 | display: table;
27 | }
28 | .pika-single:after { clear: both }
29 | .pika-single { *zoom: 1 }
30 |
31 | .pika-single.is-hidden {
32 | display: none;
33 | }
34 |
35 | .pika-single.is-bound {
36 | position: absolute;
37 | box-shadow: 0 5px 15px -5px rgba(0,0,0,.5);
38 | }
39 |
40 | .pika-lendar {
41 | float: left;
42 | width: 240px;
43 | margin: 8px;
44 | }
45 |
46 | .pika-title {
47 | position: relative;
48 | text-align: center;
49 | }
50 |
51 | .pika-label {
52 | display: inline-block;
53 | *display: inline;
54 | position: relative;
55 | z-index: 9999;
56 | overflow: hidden;
57 | margin: 0;
58 | padding: 5px 3px;
59 | font-size: 14px;
60 | line-height: 20px;
61 | font-weight: bold;
62 | background-color: #fff;
63 | }
64 | .pika-title select {
65 | cursor: pointer;
66 | position: absolute;
67 | z-index: 9998;
68 | margin: 0;
69 | left: 0;
70 | top: 5px;
71 | filter: alpha(opacity=0);
72 | opacity: 0;
73 | }
74 |
75 | .pika-prev,
76 | .pika-next {
77 | display: block;
78 | cursor: pointer;
79 | position: relative;
80 | outline: none;
81 | border: 0;
82 | padding: 0;
83 | width: 20px;
84 | height: 30px;
85 | /* hide text using text-indent trick, using width value (it's enough) */
86 | text-indent: 20px;
87 | white-space: nowrap;
88 | overflow: hidden;
89 | background-color: transparent;
90 | background-position: center center;
91 | background-repeat: no-repeat;
92 | background-size: 75% 75%;
93 | opacity: .5;
94 | *position: absolute;
95 | *top: 0;
96 | }
97 |
98 | .pika-prev:hover,
99 | .pika-next:hover {
100 | opacity: 1;
101 | }
102 |
103 | .pika-prev,
104 | .is-rtl .pika-next {
105 | float: left;
106 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAUklEQVR42u3VMQoAIBADQf8Pgj+OD9hG2CtONJB2ymQkKe0HbwAP0xucDiQWARITIDEBEnMgMQ8S8+AqBIl6kKgHiXqQqAeJepBo/z38J/U0uAHlaBkBl9I4GwAAAABJRU5ErkJggg==');
107 | *left: 0;
108 | }
109 |
110 | .pika-next,
111 | .is-rtl .pika-prev {
112 | float: right;
113 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAU0lEQVR42u3VOwoAMAgE0dwfAnNjU26bYkBCFGwfiL9VVWoO+BJ4Gf3gtsEKKoFBNTCoCAYVwaAiGNQGMUHMkjGbgjk2mIONuXo0nC8XnCf1JXgArVIZAQh5TKYAAAAASUVORK5CYII=');
114 | *right: 0;
115 | }
116 |
117 | .pika-prev.is-disabled,
118 | .pika-next.is-disabled {
119 | cursor: default;
120 | opacity: .2;
121 | }
122 |
123 | .pika-select {
124 | display: inline-block;
125 | *display: inline;
126 | }
127 |
128 | .pika-table {
129 | width: 100%;
130 | border-collapse: collapse;
131 | border-spacing: 0;
132 | border: 0;
133 | }
134 |
135 | .pika-table th,
136 | .pika-table td {
137 | width: 14.285714285714286%;
138 | padding: 0;
139 | }
140 |
141 | .pika-table th {
142 | color: #999;
143 | font-size: 12px;
144 | line-height: 25px;
145 | font-weight: bold;
146 | text-align: center;
147 | }
148 |
149 | .pika-button {
150 | cursor: pointer;
151 | display: block;
152 | box-sizing: border-box;
153 | -moz-box-sizing: border-box;
154 | outline: none;
155 | border: 0;
156 | margin: 0;
157 | width: 100%;
158 | padding: 5px;
159 | color: #666;
160 | font-size: 12px;
161 | line-height: 15px;
162 | text-align: right;
163 | background: #f5f5f5;
164 | }
165 |
166 | .pika-week {
167 | font-size: 11px;
168 | color: #999;
169 | }
170 |
171 | .is-today .pika-button {
172 | color: #33aaff;
173 | font-weight: bold;
174 | }
175 |
176 | .is-selected .pika-button {
177 | color: #fff;
178 | font-weight: bold;
179 | background: #33aaff;
180 | box-shadow: inset 0 1px 3px #178fe5;
181 | border-radius: 3px;
182 | }
183 |
184 | .is-inrange .pika-button {
185 | background: #D5E9F7;
186 | }
187 |
188 | .is-startrange .pika-button {
189 | color: #fff;
190 | background: #6CB31D;
191 | box-shadow: none;
192 | border-radius: 3px;
193 | }
194 |
195 | .is-endrange .pika-button {
196 | color: #fff;
197 | background: #33aaff;
198 | box-shadow: none;
199 | border-radius: 3px;
200 | }
201 |
202 | .is-disabled .pika-button {
203 | pointer-events: none;
204 | cursor: default;
205 | color: #999;
206 | opacity: .3;
207 | }
208 |
209 | .pika-button:hover {
210 | color: #fff;
211 | background: #ff8000;
212 | box-shadow: none;
213 | border-radius: 3px;
214 | }
215 |
216 | /* styling for abbr */
217 | .pika-table abbr {
218 | border-bottom: none;
219 | cursor: help;
220 | }
221 |
222 |
--------------------------------------------------------------------------------
/chrome/pikaday/pikaday.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Pikaday
3 | *
4 | * Copyright © 2014 David Bushell | BSD & MIT license | https://github.com/dbushell/Pikaday
5 | */
6 |
7 | (function (root, factory)
8 | {
9 | 'use strict';
10 |
11 | var moment;
12 | if (typeof exports === 'object') {
13 | // CommonJS module
14 | // Load moment.js as an optional dependency
15 | try { moment = require('moment'); } catch (e) {}
16 | module.exports = factory(moment);
17 | } else if (typeof define === 'function' && define.amd) {
18 | // AMD. Register as an anonymous module.
19 | define(function (req)
20 | {
21 | // Load moment.js as an optional dependency
22 | var id = 'moment';
23 | try { moment = req(id); } catch (e) {}
24 | return factory(moment);
25 | });
26 | } else {
27 | root.Pikaday = factory(root.moment);
28 | }
29 | }(this, function (moment)
30 | {
31 | 'use strict';
32 |
33 | /**
34 | * feature detection and helper functions
35 | */
36 | var hasMoment = typeof moment === 'function',
37 |
38 | hasEventListeners = !!window.addEventListener,
39 |
40 | document = window.document,
41 |
42 | sto = window.setTimeout,
43 |
44 | addEvent = function(el, e, callback, capture)
45 | {
46 | if (hasEventListeners) {
47 | el.addEventListener(e, callback, !!capture);
48 | } else {
49 | el.attachEvent('on' + e, callback);
50 | }
51 | },
52 |
53 | removeEvent = function(el, e, callback, capture)
54 | {
55 | if (hasEventListeners) {
56 | el.removeEventListener(e, callback, !!capture);
57 | } else {
58 | el.detachEvent('on' + e, callback);
59 | }
60 | },
61 |
62 | fireEvent = function(el, eventName, data)
63 | {
64 | var ev;
65 |
66 | if (document.createEvent) {
67 | ev = document.createEvent('HTMLEvents');
68 | ev.initEvent(eventName, true, false);
69 | ev = extend(ev, data);
70 | el.dispatchEvent(ev);
71 | } else if (document.createEventObject) {
72 | ev = document.createEventObject();
73 | ev = extend(ev, data);
74 | el.fireEvent('on' + eventName, ev);
75 | }
76 | },
77 |
78 | trim = function(str)
79 | {
80 | return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g,'');
81 | },
82 |
83 | hasClass = function(el, cn)
84 | {
85 | return (' ' + el.className + ' ').indexOf(' ' + cn + ' ') !== -1;
86 | },
87 |
88 | addClass = function(el, cn)
89 | {
90 | if (!hasClass(el, cn)) {
91 | el.className = (el.className === '') ? cn : el.className + ' ' + cn;
92 | }
93 | },
94 |
95 | removeClass = function(el, cn)
96 | {
97 | el.className = trim((' ' + el.className + ' ').replace(' ' + cn + ' ', ' '));
98 | },
99 |
100 | isArray = function(obj)
101 | {
102 | return (/Array/).test(Object.prototype.toString.call(obj));
103 | },
104 |
105 | isDate = function(obj)
106 | {
107 | return (/Date/).test(Object.prototype.toString.call(obj)) && !isNaN(obj.getTime());
108 | },
109 |
110 | isWeekend = function(date)
111 | {
112 | var day = date.getDay();
113 | return day === 0 || day === 6;
114 | },
115 |
116 | isLeapYear = function(year)
117 | {
118 | // solution by Matti Virkkunen: http://stackoverflow.com/a/4881951
119 | return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
120 | },
121 |
122 | getDaysInMonth = function(year, month)
123 | {
124 | return [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
125 | },
126 |
127 | setToStartOfDay = function(date)
128 | {
129 | if (isDate(date)) date.setHours(0,0,0,0);
130 | },
131 |
132 | compareDates = function(a,b)
133 | {
134 | // weak date comparison (use setToStartOfDay(date) to ensure correct result)
135 | return a.getTime() === b.getTime();
136 | },
137 |
138 | extend = function(to, from, overwrite)
139 | {
140 | var prop, hasProp;
141 | for (prop in from) {
142 | hasProp = to[prop] !== undefined;
143 | if (hasProp && typeof from[prop] === 'object' && from[prop] !== null && from[prop].nodeName === undefined) {
144 | if (isDate(from[prop])) {
145 | if (overwrite) {
146 | to[prop] = new Date(from[prop].getTime());
147 | }
148 | }
149 | else if (isArray(from[prop])) {
150 | if (overwrite) {
151 | to[prop] = from[prop].slice(0);
152 | }
153 | } else {
154 | to[prop] = extend({}, from[prop], overwrite);
155 | }
156 | } else if (overwrite || !hasProp) {
157 | to[prop] = from[prop];
158 | }
159 | }
160 | return to;
161 | },
162 |
163 | adjustCalendar = function(calendar) {
164 | if (calendar.month < 0) {
165 | calendar.year -= Math.ceil(Math.abs(calendar.month)/12);
166 | calendar.month += 12;
167 | }
168 | if (calendar.month > 11) {
169 | calendar.year += Math.floor(Math.abs(calendar.month)/12);
170 | calendar.month -= 12;
171 | }
172 | return calendar;
173 | },
174 |
175 | /**
176 | * defaults and localisation
177 | */
178 | defaults = {
179 |
180 | // bind the picker to a form field
181 | field: null,
182 |
183 | // automatically show/hide the picker on `field` focus (default `true` if `field` is set)
184 | bound: undefined,
185 |
186 | // position of the datepicker, relative to the field (default to bottom & left)
187 | // ('bottom' & 'left' keywords are not used, 'top' & 'right' are modifier on the bottom/left position)
188 | position: 'bottom left',
189 |
190 | // automatically fit in the viewport even if it means repositioning from the position option
191 | reposition: true,
192 |
193 | // the default output format for `.toString()` and `field` value
194 | format: 'YYYY-MM-DD',
195 |
196 | // the initial date to view when first opened
197 | defaultDate: null,
198 |
199 | // make the `defaultDate` the initial selected value
200 | setDefaultDate: false,
201 |
202 | // first day of week (0: Sunday, 1: Monday etc)
203 | firstDay: 0,
204 |
205 | // the minimum/earliest date that can be selected
206 | minDate: null,
207 | // the maximum/latest date that can be selected
208 | maxDate: null,
209 |
210 | // number of years either side, or array of upper/lower range
211 | yearRange: 10,
212 |
213 | // show week numbers at head of row
214 | showWeekNumber: false,
215 |
216 | // used internally (don't config outside)
217 | minYear: 0,
218 | maxYear: 9999,
219 | minMonth: undefined,
220 | maxMonth: undefined,
221 |
222 | startRange: null,
223 | endRange: null,
224 |
225 | isRTL: false,
226 |
227 | // Additional text to append to the year in the calendar title
228 | yearSuffix: '',
229 |
230 | // Render the month after year in the calendar title
231 | showMonthAfterYear: false,
232 |
233 | // how many months are visible
234 | numberOfMonths: 1,
235 |
236 | // when numberOfMonths is used, this will help you to choose where the main calendar will be (default `left`, can be set to `right`)
237 | // only used for the first display or when a selected date is not visible
238 | mainCalendar: 'left',
239 |
240 | // Specify a DOM element to render the calendar in
241 | container: undefined,
242 |
243 | // internationalization
244 | i18n: {
245 | previousMonth : 'Previous Month',
246 | nextMonth : 'Next Month',
247 | months : ['January','February','March','April','May','June','July','August','September','October','November','December'],
248 | weekdays : ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
249 | weekdaysShort : ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']
250 | },
251 |
252 | // Theme Classname
253 | theme: null,
254 |
255 | // callback function
256 | onSelect: null,
257 | onOpen: null,
258 | onClose: null,
259 | onDraw: null
260 | },
261 |
262 |
263 | /**
264 | * templating functions to abstract HTML rendering
265 | */
266 | renderDayName = function(opts, day, abbr)
267 | {
268 | day += opts.firstDay;
269 | while (day >= 7) {
270 | day -= 7;
271 | }
272 | return abbr ? opts.i18n.weekdaysShort[day] : opts.i18n.weekdays[day];
273 | },
274 |
275 | renderDay = function(opts)
276 | {
277 | if (opts.isEmpty) {
278 | return '
| ';
279 | }
280 | var arr = [];
281 | if (opts.isDisabled) {
282 | arr.push('is-disabled');
283 | }
284 | if (opts.isToday) {
285 | arr.push('is-today');
286 | }
287 | if (opts.isSelected) {
288 | arr.push('is-selected');
289 | }
290 | if (opts.isInRange) {
291 | arr.push('is-inrange');
292 | }
293 | if (opts.isStartRange) {
294 | arr.push('is-startrange');
295 | }
296 | if (opts.isEndRange) {
297 | arr.push('is-endrange');
298 | }
299 | return '' +
300 | '' +
304 | ' | ';
305 | },
306 |
307 | renderWeek = function (d, m, y) {
308 | // Lifted from http://javascript.about.com/library/blweekyear.htm, lightly modified.
309 | var onejan = new Date(y, 0, 1),
310 | weekNum = Math.ceil((((new Date(y, m, d) - onejan) / 86400000) + onejan.getDay()+1)/7);
311 | return '' + weekNum + ' | ';
312 | },
313 |
314 | renderRow = function(days, isRTL)
315 | {
316 | return '' + (isRTL ? days.reverse() : days).join('') + '
';
317 | },
318 |
319 | renderBody = function(rows)
320 | {
321 | return '' + rows.join('') + '';
322 | },
323 |
324 | renderHead = function(opts)
325 | {
326 | var i, arr = [];
327 | if (opts.showWeekNumber) {
328 | arr.push(' | ');
329 | }
330 | for (i = 0; i < 7; i++) {
331 | arr.push('' + renderDayName(opts, i, true) + ' | ');
332 | }
333 | return '' + (opts.isRTL ? arr.reverse() : arr).join('') + '';
334 | },
335 |
336 | renderTitle = function(instance, c, year, month, refYear)
337 | {
338 | var i, j, arr,
339 | opts = instance._o,
340 | isMinYear = year === opts.minYear,
341 | isMaxYear = year === opts.maxYear,
342 | html = '',
343 | monthHtml,
344 | yearHtml,
345 | prev = true,
346 | next = true;
347 |
348 | for (arr = [], i = 0; i < 12; i++) {
349 | arr.push('
');
353 | }
354 | monthHtml = '
' + opts.i18n.months[month] + '
';
355 |
356 | if (isArray(opts.yearRange)) {
357 | i = opts.yearRange[0];
358 | j = opts.yearRange[1] + 1;
359 | } else {
360 | i = year - opts.yearRange;
361 | j = 1 + year + opts.yearRange;
362 | }
363 |
364 | for (arr = []; i < j && i <= opts.maxYear; i++) {
365 | if (i >= opts.minYear) {
366 | arr.push('
');
367 | }
368 | }
369 | yearHtml = '
' + year + opts.yearSuffix + '
';
370 |
371 | if (opts.showMonthAfterYear) {
372 | html += yearHtml + monthHtml;
373 | } else {
374 | html += monthHtml + yearHtml;
375 | }
376 |
377 | if (isMinYear && (month === 0 || opts.minMonth >= month)) {
378 | prev = false;
379 | }
380 |
381 | if (isMaxYear && (month === 11 || opts.maxMonth <= month)) {
382 | next = false;
383 | }
384 |
385 | if (c === 0) {
386 | html += '
';
387 | }
388 | if (c === (instance._o.numberOfMonths - 1) ) {
389 | html += '
';
390 | }
391 |
392 | return html += '
';
393 | },
394 |
395 | renderTable = function(opts, data)
396 | {
397 | return '' + renderHead(opts) + renderBody(data) + '
';
398 | },
399 |
400 |
401 | /**
402 | * Pikaday constructor
403 | */
404 | Pikaday = function(options)
405 | {
406 | var self = this,
407 | opts = self.config(options);
408 |
409 | self._onMouseDown = function(e)
410 | {
411 | if (!self._v) {
412 | return;
413 | }
414 | e = e || window.event;
415 | var target = e.target || e.srcElement;
416 | if (!target) {
417 | return;
418 | }
419 |
420 | if (!hasClass(target.parentNode, 'is-disabled')) {
421 | if (hasClass(target, 'pika-button') && !hasClass(target, 'is-empty')) {
422 | self.setDate(new Date(target.getAttribute('data-pika-year'), target.getAttribute('data-pika-month'), target.getAttribute('data-pika-day')));
423 | if (opts.bound) {
424 | sto(function() {
425 | self.hide();
426 | if (opts.field) {
427 | opts.field.blur();
428 | }
429 | }, 100);
430 | }
431 | return;
432 | }
433 | else if (hasClass(target, 'pika-prev')) {
434 | self.prevMonth();
435 | }
436 | else if (hasClass(target, 'pika-next')) {
437 | self.nextMonth();
438 | }
439 | }
440 | if (!hasClass(target, 'pika-select')) {
441 | if (e.preventDefault) {
442 | e.preventDefault();
443 | } else {
444 | e.returnValue = false;
445 | return false;
446 | }
447 | } else {
448 | self._c = true;
449 | }
450 | };
451 |
452 | self._onChange = function(e)
453 | {
454 | e = e || window.event;
455 | var target = e.target || e.srcElement;
456 | if (!target) {
457 | return;
458 | }
459 | if (hasClass(target, 'pika-select-month')) {
460 | self.gotoMonth(target.value);
461 | }
462 | else if (hasClass(target, 'pika-select-year')) {
463 | self.gotoYear(target.value);
464 | }
465 | };
466 |
467 | self._onInputChange = function(e)
468 | {
469 | var date;
470 |
471 | if (e.firedBy === self) {
472 | return;
473 | }
474 | if (hasMoment) {
475 | date = moment(opts.field.value, opts.format);
476 | date = (date && date.isValid()) ? date.toDate() : null;
477 | }
478 | else {
479 | date = new Date(Date.parse(opts.field.value));
480 | }
481 | if (isDate(date)) {
482 | self.setDate(date);
483 | }
484 | if (!self._v) {
485 | self.show();
486 | }
487 | };
488 |
489 | self._onInputFocus = function()
490 | {
491 | self.show();
492 | };
493 |
494 | self._onInputClick = function()
495 | {
496 | self.show();
497 | };
498 |
499 | self._onInputBlur = function()
500 | {
501 | // IE allows pika div to gain focus; catch blur the input field
502 | var pEl = document.activeElement;
503 | do {
504 | if (hasClass(pEl, 'pika-single')) {
505 | return;
506 | }
507 | }
508 | while ((pEl = pEl.parentNode));
509 |
510 | if (!self._c) {
511 | self._b = sto(function() {
512 | self.hide();
513 | }, 50);
514 | }
515 | self._c = false;
516 | };
517 |
518 | self._onClick = function(e)
519 | {
520 | e = e || window.event;
521 | var target = e.target || e.srcElement,
522 | pEl = target;
523 | if (!target) {
524 | return;
525 | }
526 | if (!hasEventListeners && hasClass(target, 'pika-select')) {
527 | if (!target.onchange) {
528 | target.setAttribute('onchange', 'return;');
529 | addEvent(target, 'change', self._onChange);
530 | }
531 | }
532 | do {
533 | if (hasClass(pEl, 'pika-single') || pEl === opts.trigger) {
534 | return;
535 | }
536 | }
537 | while ((pEl = pEl.parentNode));
538 | if (self._v && target !== opts.trigger && pEl !== opts.trigger) {
539 | self.hide();
540 | }
541 | };
542 |
543 | self.el = document.createElement('div');
544 | self.el.className = 'pika-single' + (opts.isRTL ? ' is-rtl' : '') + (opts.theme ? ' ' + opts.theme : '');
545 |
546 | addEvent(self.el, 'ontouchend' in document ? 'touchend' : 'mousedown', self._onMouseDown, true);
547 | addEvent(self.el, 'change', self._onChange);
548 |
549 | if (opts.field) {
550 | if (opts.container) {
551 | opts.container.appendChild(self.el);
552 | } else if (opts.bound) {
553 | document.body.appendChild(self.el);
554 | } else {
555 | opts.field.parentNode.insertBefore(self.el, opts.field.nextSibling);
556 | }
557 | addEvent(opts.field, 'change', self._onInputChange);
558 |
559 | if (!opts.defaultDate) {
560 | if (hasMoment && opts.field.value) {
561 | opts.defaultDate = moment(opts.field.value, opts.format).toDate();
562 | } else {
563 | opts.defaultDate = new Date(Date.parse(opts.field.value));
564 | }
565 | opts.setDefaultDate = true;
566 | }
567 | }
568 |
569 | var defDate = opts.defaultDate;
570 |
571 | if (isDate(defDate)) {
572 | if (opts.setDefaultDate) {
573 | self.setDate(defDate, true);
574 | } else {
575 | self.gotoDate(defDate);
576 | }
577 | } else {
578 | self.gotoDate(new Date());
579 | }
580 |
581 | if (opts.bound) {
582 | this.hide();
583 | self.el.className += ' is-bound';
584 | addEvent(opts.trigger, 'click', self._onInputClick);
585 | addEvent(opts.trigger, 'focus', self._onInputFocus);
586 | addEvent(opts.trigger, 'blur', self._onInputBlur);
587 | } else {
588 | this.show();
589 | }
590 | };
591 |
592 |
593 | /**
594 | * public Pikaday API
595 | */
596 | Pikaday.prototype = {
597 |
598 |
599 | /**
600 | * configure functionality
601 | */
602 | config: function(options)
603 | {
604 | if (!this._o) {
605 | this._o = extend({}, defaults, true);
606 | }
607 |
608 | var opts = extend(this._o, options, true);
609 |
610 | opts.isRTL = !!opts.isRTL;
611 |
612 | opts.field = (opts.field && opts.field.nodeName) ? opts.field : null;
613 |
614 | opts.theme = (typeof opts.theme) === 'string' && opts.theme ? opts.theme : null;
615 |
616 | opts.bound = !!(opts.bound !== undefined ? opts.field && opts.bound : opts.field);
617 |
618 | opts.trigger = (opts.trigger && opts.trigger.nodeName) ? opts.trigger : opts.field;
619 |
620 | opts.disableWeekends = !!opts.disableWeekends;
621 |
622 | opts.disableDayFn = (typeof opts.disableDayFn) === 'function' ? opts.disableDayFn : null;
623 |
624 | var nom = parseInt(opts.numberOfMonths, 10) || 1;
625 | opts.numberOfMonths = nom > 4 ? 4 : nom;
626 |
627 | if (!isDate(opts.minDate)) {
628 | opts.minDate = false;
629 | }
630 | if (!isDate(opts.maxDate)) {
631 | opts.maxDate = false;
632 | }
633 | if ((opts.minDate && opts.maxDate) && opts.maxDate < opts.minDate) {
634 | opts.maxDate = opts.minDate = false;
635 | }
636 | if (opts.minDate) {
637 | this.setMinDate(opts.minDate);
638 | }
639 | if (opts.maxDate) {
640 | setToStartOfDay(opts.maxDate);
641 | opts.maxYear = opts.maxDate.getFullYear();
642 | opts.maxMonth = opts.maxDate.getMonth();
643 | }
644 |
645 | if (isArray(opts.yearRange)) {
646 | var fallback = new Date().getFullYear() - 10;
647 | opts.yearRange[0] = parseInt(opts.yearRange[0], 10) || fallback;
648 | opts.yearRange[1] = parseInt(opts.yearRange[1], 10) || fallback;
649 | } else {
650 | opts.yearRange = Math.abs(parseInt(opts.yearRange, 10)) || defaults.yearRange;
651 | if (opts.yearRange > 100) {
652 | opts.yearRange = 100;
653 | }
654 | }
655 |
656 | return opts;
657 | },
658 |
659 | /**
660 | * return a formatted string of the current selection (using Moment.js if available)
661 | */
662 | toString: function(format)
663 | {
664 | return !isDate(this._d) ? '' : hasMoment ? moment(this._d).format(format || this._o.format) : this._d.toDateString();
665 | },
666 |
667 | /**
668 | * return a Moment.js object of the current selection (if available)
669 | */
670 | getMoment: function()
671 | {
672 | return hasMoment ? moment(this._d) : null;
673 | },
674 |
675 | /**
676 | * set the current selection from a Moment.js object (if available)
677 | */
678 | setMoment: function(date, preventOnSelect)
679 | {
680 | if (hasMoment && moment.isMoment(date)) {
681 | this.setDate(date.toDate(), preventOnSelect);
682 | }
683 | },
684 |
685 | /**
686 | * return a Date object of the current selection
687 | */
688 | getDate: function()
689 | {
690 | return isDate(this._d) ? new Date(this._d.getTime()) : null;
691 | },
692 |
693 | /**
694 | * set the current selection
695 | */
696 | setDate: function(date, preventOnSelect)
697 | {
698 | if (!date) {
699 | this._d = null;
700 |
701 | if (this._o.field) {
702 | this._o.field.value = '';
703 | fireEvent(this._o.field, 'change', { firedBy: this });
704 | }
705 |
706 | return this.draw();
707 | }
708 | if (typeof date === 'string') {
709 | date = new Date(Date.parse(date));
710 | }
711 | if (!isDate(date)) {
712 | return;
713 | }
714 |
715 | var min = this._o.minDate,
716 | max = this._o.maxDate;
717 |
718 | if (isDate(min) && date < min) {
719 | date = min;
720 | } else if (isDate(max) && date > max) {
721 | date = max;
722 | }
723 |
724 | this._d = new Date(date.getTime());
725 | setToStartOfDay(this._d);
726 | this.gotoDate(this._d);
727 |
728 | if (this._o.field) {
729 | this._o.field.value = this.toString();
730 | fireEvent(this._o.field, 'change', { firedBy: this });
731 | }
732 | if (!preventOnSelect && typeof this._o.onSelect === 'function') {
733 | this._o.onSelect.call(this, this.getDate());
734 | }
735 | },
736 |
737 | /**
738 | * change view to a specific date
739 | */
740 | gotoDate: function(date)
741 | {
742 | var newCalendar = true;
743 |
744 | if (!isDate(date)) {
745 | return;
746 | }
747 |
748 | if (this.calendars) {
749 | var firstVisibleDate = new Date(this.calendars[0].year, this.calendars[0].month, 1),
750 | lastVisibleDate = new Date(this.calendars[this.calendars.length-1].year, this.calendars[this.calendars.length-1].month, 1),
751 | visibleDate = date.getTime();
752 | // get the end of the month
753 | lastVisibleDate.setMonth(lastVisibleDate.getMonth()+1);
754 | lastVisibleDate.setDate(lastVisibleDate.getDate()-1);
755 | newCalendar = (visibleDate < firstVisibleDate.getTime() || lastVisibleDate.getTime() < visibleDate);
756 | }
757 |
758 | if (newCalendar) {
759 | this.calendars = [{
760 | month: date.getMonth(),
761 | year: date.getFullYear()
762 | }];
763 | if (this._o.mainCalendar === 'right') {
764 | this.calendars[0].month += 1 - this._o.numberOfMonths;
765 | }
766 | }
767 |
768 | this.adjustCalendars();
769 | },
770 |
771 | adjustCalendars: function() {
772 | this.calendars[0] = adjustCalendar(this.calendars[0]);
773 | for (var c = 1; c < this._o.numberOfMonths; c++) {
774 | this.calendars[c] = adjustCalendar({
775 | month: this.calendars[0].month + c,
776 | year: this.calendars[0].year
777 | });
778 | }
779 | this.draw();
780 | },
781 |
782 | gotoToday: function()
783 | {
784 | this.gotoDate(new Date());
785 | },
786 |
787 | /**
788 | * change view to a specific month (zero-index, e.g. 0: January)
789 | */
790 | gotoMonth: function(month)
791 | {
792 | if (!isNaN(month)) {
793 | this.calendars[0].month = parseInt(month, 10);
794 | this.adjustCalendars();
795 | }
796 | },
797 |
798 | nextMonth: function()
799 | {
800 | this.calendars[0].month++;
801 | this.adjustCalendars();
802 | },
803 |
804 | prevMonth: function()
805 | {
806 | this.calendars[0].month--;
807 | this.adjustCalendars();
808 | },
809 |
810 | /**
811 | * change view to a specific full year (e.g. "2012")
812 | */
813 | gotoYear: function(year)
814 | {
815 | if (!isNaN(year)) {
816 | this.calendars[0].year = parseInt(year, 10);
817 | this.adjustCalendars();
818 | }
819 | },
820 |
821 | /**
822 | * change the minDate
823 | */
824 | setMinDate: function(value)
825 | {
826 | setToStartOfDay(value);
827 | this._o.minDate = value;
828 | this._o.minYear = value.getFullYear();
829 | this._o.minMonth = value.getMonth();
830 | },
831 |
832 | /**
833 | * change the maxDate
834 | */
835 | setMaxDate: function(value)
836 | {
837 | this._o.maxDate = value;
838 | },
839 |
840 | setStartRange: function(value)
841 | {
842 | this._o.startRange = value;
843 | },
844 |
845 | setEndRange: function(value)
846 | {
847 | this._o.endRange = value;
848 | },
849 |
850 | /**
851 | * refresh the HTML
852 | */
853 | draw: function(force)
854 | {
855 | if (!this._v && !force) {
856 | return;
857 | }
858 | var opts = this._o,
859 | minYear = opts.minYear,
860 | maxYear = opts.maxYear,
861 | minMonth = opts.minMonth,
862 | maxMonth = opts.maxMonth,
863 | html = '';
864 |
865 | if (this._y <= minYear) {
866 | this._y = minYear;
867 | if (!isNaN(minMonth) && this._m < minMonth) {
868 | this._m = minMonth;
869 | }
870 | }
871 | if (this._y >= maxYear) {
872 | this._y = maxYear;
873 | if (!isNaN(maxMonth) && this._m > maxMonth) {
874 | this._m = maxMonth;
875 | }
876 | }
877 |
878 | for (var c = 0; c < opts.numberOfMonths; c++) {
879 | html += '' + renderTitle(this, c, this.calendars[c].year, this.calendars[c].month, this.calendars[0].year) + this.render(this.calendars[c].year, this.calendars[c].month) + '
';
880 | }
881 |
882 | this.el.innerHTML = html;
883 |
884 | if (opts.bound) {
885 | if(opts.field.type !== 'hidden') {
886 | sto(function() {
887 | opts.trigger.focus();
888 | }, 1);
889 | }
890 | }
891 |
892 | if (typeof this._o.onDraw === 'function') {
893 | var self = this;
894 | sto(function() {
895 | self._o.onDraw.call(self);
896 | }, 0);
897 | }
898 | },
899 |
900 | adjustPosition: function()
901 | {
902 | var field, pEl, width, height, viewportWidth, viewportHeight, scrollTop, left, top, clientRect;
903 |
904 | if (this._o.container) return;
905 |
906 | this.el.style.position = 'absolute';
907 |
908 | field = this._o.trigger;
909 | pEl = field;
910 | width = this.el.offsetWidth;
911 | height = this.el.offsetHeight;
912 | viewportWidth = window.innerWidth || document.documentElement.clientWidth;
913 | viewportHeight = window.innerHeight || document.documentElement.clientHeight;
914 | scrollTop = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
915 |
916 | if (typeof field.getBoundingClientRect === 'function') {
917 | clientRect = field.getBoundingClientRect();
918 | left = clientRect.left + window.pageXOffset;
919 | top = clientRect.bottom + window.pageYOffset;
920 | } else {
921 | left = pEl.offsetLeft;
922 | top = pEl.offsetTop + pEl.offsetHeight;
923 | while((pEl = pEl.offsetParent)) {
924 | left += pEl.offsetLeft;
925 | top += pEl.offsetTop;
926 | }
927 | }
928 |
929 | // default position is bottom & left
930 | if ((this._o.reposition && left + width > viewportWidth) ||
931 | (
932 | this._o.position.indexOf('right') > -1 &&
933 | left - width + field.offsetWidth > 0
934 | )
935 | ) {
936 | left = left - width + field.offsetWidth;
937 | }
938 | if ((this._o.reposition && top + height > viewportHeight + scrollTop) ||
939 | (
940 | this._o.position.indexOf('top') > -1 &&
941 | top - height - field.offsetHeight > 0
942 | )
943 | ) {
944 | top = top - height - field.offsetHeight;
945 | }
946 |
947 | this.el.style.left = left + 'px';
948 | this.el.style.top = top + 'px';
949 | },
950 |
951 | /**
952 | * render HTML for a particular month
953 | */
954 | render: function(year, month)
955 | {
956 | var opts = this._o,
957 | now = new Date(),
958 | days = getDaysInMonth(year, month),
959 | before = new Date(year, month, 1).getDay(),
960 | data = [],
961 | row = [];
962 | setToStartOfDay(now);
963 | if (opts.firstDay > 0) {
964 | before -= opts.firstDay;
965 | if (before < 0) {
966 | before += 7;
967 | }
968 | }
969 | var cells = days + before,
970 | after = cells;
971 | while(after > 7) {
972 | after -= 7;
973 | }
974 | cells += 7 - after;
975 | for (var i = 0, r = 0; i < cells; i++)
976 | {
977 | var dayConfig,
978 | day = new Date(year, month, 1 + (i - before)),
979 | isSelected = isDate(this._d) ? compareDates(day, this._d) : false,
980 | isToday = compareDates(day, now),
981 | isEmpty = i < before || i >= (days + before),
982 | isStartRange = opts.startRange && compareDates(opts.startRange, day),
983 | isEndRange = opts.endRange && compareDates(opts.endRange, day),
984 | isInRange = opts.startRange && opts.endRange && opts.startRange < day && day < opts.endRange,
985 | isDisabled = (opts.minDate && day < opts.minDate) ||
986 | (opts.maxDate && day > opts.maxDate) ||
987 | (opts.disableWeekends && isWeekend(day)) ||
988 | (opts.disableDayFn && opts.disableDayFn(day)),
989 | dayConfig = {
990 | day: 1 + (i - before),
991 | month: month,
992 | year: year,
993 | isSelected: isSelected,
994 | isToday: isToday,
995 | isDisabled: isDisabled,
996 | isEmpty: isEmpty,
997 | isStartRange: isStartRange,
998 | isEndRange: isEndRange,
999 | isInRange: isInRange
1000 | };
1001 |
1002 | row.push(renderDay(dayConfig));
1003 |
1004 | if (++r === 7) {
1005 | if (opts.showWeekNumber) {
1006 | row.unshift(renderWeek(i - before, month, year));
1007 | }
1008 | data.push(renderRow(row, opts.isRTL));
1009 | row = [];
1010 | r = 0;
1011 | }
1012 | }
1013 | return renderTable(opts, data);
1014 | },
1015 |
1016 | isVisible: function()
1017 | {
1018 | return this._v;
1019 | },
1020 |
1021 | show: function()
1022 | {
1023 | if (!this._v) {
1024 | removeClass(this.el, 'is-hidden');
1025 | this._v = true;
1026 | this.draw();
1027 | if (this._o.bound) {
1028 | addEvent(document, 'click', this._onClick);
1029 | this.adjustPosition();
1030 | }
1031 | if (typeof this._o.onOpen === 'function') {
1032 | this._o.onOpen.call(this);
1033 | }
1034 | }
1035 | },
1036 |
1037 | hide: function()
1038 | {
1039 | var v = this._v;
1040 | if (v !== false) {
1041 | if (this._o.bound) {
1042 | removeEvent(document, 'click', this._onClick);
1043 | }
1044 | this.el.style.position = 'static'; // reset
1045 | this.el.style.left = 'auto';
1046 | this.el.style.top = 'auto';
1047 | addClass(this.el, 'is-hidden');
1048 | this._v = false;
1049 | if (v !== undefined && typeof this._o.onClose === 'function') {
1050 | this._o.onClose.call(this);
1051 | }
1052 | }
1053 | },
1054 |
1055 | /**
1056 | * GAME OVER
1057 | */
1058 | destroy: function()
1059 | {
1060 | this.hide();
1061 | removeEvent(this.el, 'mousedown', this._onMouseDown, true);
1062 | removeEvent(this.el, 'change', this._onChange);
1063 | if (this._o.field) {
1064 | removeEvent(this._o.field, 'change', this._onInputChange);
1065 | if (this._o.bound) {
1066 | removeEvent(this._o.trigger, 'click', this._onInputClick);
1067 | removeEvent(this._o.trigger, 'focus', this._onInputFocus);
1068 | removeEvent(this._o.trigger, 'blur', this._onInputBlur);
1069 | }
1070 | }
1071 | if (this.el.parentNode) {
1072 | this.el.parentNode.removeChild(this.el);
1073 | }
1074 | }
1075 |
1076 | };
1077 |
1078 | return Pikaday;
1079 |
1080 | }));
1081 |
--------------------------------------------------------------------------------
/firefox/data/github-time-travel.js:
--------------------------------------------------------------------------------
1 |
2 | var pushState = history.pushState;
3 |
4 | function pushStatePatch (state) {
5 | // Github uses pushState for navigation within a repo (actually pjax, but
6 | // pushState is the underlying API). We have to add an onpushstate listener
7 | // to add our functionality to the DOM when the page changes.
8 | // This requires monkey-patching the window.history.pushState function.
9 |
10 | if (typeof history.onpushstate == "function") {
11 | history.onpushstate({state: state});
12 | }
13 |
14 | return pushState.apply(history, arguments);
15 | }
16 |
17 | history.onpushstate = function (state) {
18 | // When navigation happens, we have to wait for the DOM to change before
19 | // attaching the date button.
20 |
21 | setDomChangeTimeout(attachDateButton, 'commit-group-title', 2000);
22 | }
23 |
24 | function setDomChangeTimeout (callback, klass, timeout) {
25 | // A function like setTimeout that watches a DOM element (by class name)
26 | // for change. The callback is called as soon as the DOM element
27 | // changes. If it doesn't change, the callback is called at timeout.
28 |
29 | // This is the fastest way to get a callback when the new DOM settles.
30 | // Right now, it naively uses innerHTML, but for this use, it's good enough.
31 |
32 | var start = performance.now(),
33 | startContent = '',
34 | startElements = document.getElementsByClassName(klass);
35 |
36 | if (startElements.length > 0) {
37 | startContent = startElements[0].innerHTML;
38 | }
39 |
40 | var interval = setInterval(function () {
41 | if (performance.now() - start > timeout) {
42 | clearInterval(interval);
43 | callback();
44 | } else {
45 | var endElements = document.getElementsByClassName(klass);
46 |
47 | if (endElements.length > 0) {
48 | if (endElements[0].innerHTML !== startContent) {
49 | clearInterval(interval);
50 | callback();
51 | }
52 | }
53 | }
54 | }, 50);
55 | }
56 |
57 | function attachDateButton () {
58 | // Extract the Github username and repo from a commit log URL.
59 | // If the DOM element does not already exist, create an element to hold
60 | // the datepicker and append it to the .file-navigation DOM element
61 | // after styling it like a Github button.
62 |
63 | // Then, create the datepicker with a callback to make an API call on select.
64 | // Attach the listener to the API call to handle the response.
65 |
66 | var re = /.*github\.com\/(.*)\/(.*)\/commits.*/,
67 | m = re.exec(document.location.href);
68 |
69 | if (m === null) return;
70 | if (document.getElementsByClassName('datepicker-button').length > 0) return;
71 |
72 | var username = m[1],
73 | repo = m[2],
74 | api = 'https://api.github.com/repos/' + username + '/' + repo + '/commits',
75 | date = getCommitDate(),
76 | el = document.createElement('span');
77 |
78 | el.className = 'btn btn-sm select-menu-button datepicker-button';
79 | el.innerHTML = 'Date: ' + date + ' ';
80 |
81 | document.getElementsByClassName('file-navigation')[0].appendChild(el);
82 |
83 | new Pikaday({
84 | field: el,
85 | defaultDate: new Date(date),
86 | setDefaultDate: true,
87 | onSelect: function (date) {
88 | var req = new XMLHttpRequest(),
89 | url = api + '?per_page=1&until=' + date.toISOString().split('T')[0];
90 |
91 | req.addEventListener('load', reqListener);
92 | req.open('get', url, true);
93 | req.send();
94 | }
95 | });
96 | }
97 |
98 | function getCommitDate () {
99 | // Find first .commit-group-title in page, extract the date from the text.
100 | // Return 'unknown' if no commit group titles are found.
101 |
102 | var commitGroups = document.getElementsByClassName('commit-group-title');
103 |
104 | if (commitGroups.length > 0) {
105 | var re = /Commits on (.*)/,
106 | m = re.exec(commitGroups[0].innerHTML),
107 | date = m[1];
108 |
109 | return date;
110 | } else {
111 | return 'unknown';
112 | }
113 | }
114 |
115 | function reqListener () {
116 | // Listen for a response from the Github Commits API
117 | // https://developer.github.com/v3/repos/commits/
118 | // Parse the JSON response and extract the HTML link from the first commit
119 | // if commits were returned.
120 | // Change 'commit' to 'commits' in the URL to browse the commit log.
121 |
122 | var parsed = JSON.parse(this.responseText);
123 |
124 | if (parsed.length > 0) {
125 | var tree_url = parsed[0].html_url.replace('/commit/', '/commits/');
126 | document.location.href = tree_url;
127 | }
128 | }
129 |
130 | attachDateButton();
131 | exportFunction(pushStatePatch, unsafeWindow.history, {defineAs: 'pushState', allowCallbacks: true});
132 |
--------------------------------------------------------------------------------
/firefox/data/pikaday/pikaday.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 |
3 | /*!
4 | * Pikaday
5 | * Copyright © 2014 David Bushell | BSD & MIT license | http://dbushell.com/
6 | */
7 |
8 | .pika-single {
9 | z-index: 9999;
10 | display: block;
11 | position: relative;
12 | color: #333;
13 | background: #fff;
14 | border: 1px solid #ccc;
15 | border-bottom-color: #bbb;
16 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
17 | }
18 |
19 | /*
20 | clear child float (pika-lendar), using the famous micro clearfix hack
21 | http://nicolasgallagher.com/micro-clearfix-hack/
22 | */
23 | .pika-single:before,
24 | .pika-single:after {
25 | content: " ";
26 | display: table;
27 | }
28 | .pika-single:after { clear: both }
29 | .pika-single { *zoom: 1 }
30 |
31 | .pika-single.is-hidden {
32 | display: none;
33 | }
34 |
35 | .pika-single.is-bound {
36 | position: absolute;
37 | box-shadow: 0 5px 15px -5px rgba(0,0,0,.5);
38 | }
39 |
40 | .pika-lendar {
41 | float: left;
42 | width: 240px;
43 | margin: 8px;
44 | }
45 |
46 | .pika-title {
47 | position: relative;
48 | text-align: center;
49 | }
50 |
51 | .pika-label {
52 | display: inline-block;
53 | *display: inline;
54 | position: relative;
55 | z-index: 9999;
56 | overflow: hidden;
57 | margin: 0;
58 | padding: 5px 3px;
59 | font-size: 14px;
60 | line-height: 20px;
61 | font-weight: bold;
62 | background-color: #fff;
63 | }
64 | .pika-title select {
65 | cursor: pointer;
66 | position: absolute;
67 | z-index: 9998;
68 | margin: 0;
69 | left: 0;
70 | top: 5px;
71 | filter: alpha(opacity=0);
72 | opacity: 0;
73 | }
74 |
75 | .pika-prev,
76 | .pika-next {
77 | display: block;
78 | cursor: pointer;
79 | position: relative;
80 | outline: none;
81 | border: 0;
82 | padding: 0;
83 | width: 20px;
84 | height: 30px;
85 | /* hide text using text-indent trick, using width value (it's enough) */
86 | text-indent: 20px;
87 | white-space: nowrap;
88 | overflow: hidden;
89 | background-color: transparent;
90 | background-position: center center;
91 | background-repeat: no-repeat;
92 | background-size: 75% 75%;
93 | opacity: .5;
94 | *position: absolute;
95 | *top: 0;
96 | }
97 |
98 | .pika-prev:hover,
99 | .pika-next:hover {
100 | opacity: 1;
101 | }
102 |
103 | .pika-prev,
104 | .is-rtl .pika-next {
105 | float: left;
106 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAUklEQVR42u3VMQoAIBADQf8Pgj+OD9hG2CtONJB2ymQkKe0HbwAP0xucDiQWARITIDEBEnMgMQ8S8+AqBIl6kKgHiXqQqAeJepBo/z38J/U0uAHlaBkBl9I4GwAAAABJRU5ErkJggg==');
107 | *left: 0;
108 | }
109 |
110 | .pika-next,
111 | .is-rtl .pika-prev {
112 | float: right;
113 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAU0lEQVR42u3VOwoAMAgE0dwfAnNjU26bYkBCFGwfiL9VVWoO+BJ4Gf3gtsEKKoFBNTCoCAYVwaAiGNQGMUHMkjGbgjk2mIONuXo0nC8XnCf1JXgArVIZAQh5TKYAAAAASUVORK5CYII=');
114 | *right: 0;
115 | }
116 |
117 | .pika-prev.is-disabled,
118 | .pika-next.is-disabled {
119 | cursor: default;
120 | opacity: .2;
121 | }
122 |
123 | .pika-select {
124 | display: inline-block;
125 | *display: inline;
126 | }
127 |
128 | .pika-table {
129 | width: 100%;
130 | border-collapse: collapse;
131 | border-spacing: 0;
132 | border: 0;
133 | }
134 |
135 | .pika-table th,
136 | .pika-table td {
137 | width: 14.285714285714286%;
138 | padding: 0;
139 | }
140 |
141 | .pika-table th {
142 | color: #999;
143 | font-size: 12px;
144 | line-height: 25px;
145 | font-weight: bold;
146 | text-align: center;
147 | }
148 |
149 | .pika-button {
150 | cursor: pointer;
151 | display: block;
152 | box-sizing: border-box;
153 | -moz-box-sizing: border-box;
154 | outline: none;
155 | border: 0;
156 | margin: 0;
157 | width: 100%;
158 | padding: 5px;
159 | color: #666;
160 | font-size: 12px;
161 | line-height: 15px;
162 | text-align: right;
163 | background: #f5f5f5;
164 | }
165 |
166 | .pika-week {
167 | font-size: 11px;
168 | color: #999;
169 | }
170 |
171 | .is-today .pika-button {
172 | color: #33aaff;
173 | font-weight: bold;
174 | }
175 |
176 | .is-selected .pika-button {
177 | color: #fff;
178 | font-weight: bold;
179 | background: #33aaff;
180 | box-shadow: inset 0 1px 3px #178fe5;
181 | border-radius: 3px;
182 | }
183 |
184 | .is-inrange .pika-button {
185 | background: #D5E9F7;
186 | }
187 |
188 | .is-startrange .pika-button {
189 | color: #fff;
190 | background: #6CB31D;
191 | box-shadow: none;
192 | border-radius: 3px;
193 | }
194 |
195 | .is-endrange .pika-button {
196 | color: #fff;
197 | background: #33aaff;
198 | box-shadow: none;
199 | border-radius: 3px;
200 | }
201 |
202 | .is-disabled .pika-button {
203 | pointer-events: none;
204 | cursor: default;
205 | color: #999;
206 | opacity: .3;
207 | }
208 |
209 | .pika-button:hover {
210 | color: #fff;
211 | background: #ff8000;
212 | box-shadow: none;
213 | border-radius: 3px;
214 | }
215 |
216 | /* styling for abbr */
217 | .pika-table abbr {
218 | border-bottom: none;
219 | cursor: help;
220 | }
221 |
222 |
--------------------------------------------------------------------------------
/firefox/data/pikaday/pikaday.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Pikaday
3 | *
4 | * Copyright © 2014 David Bushell | BSD & MIT license | https://github.com/dbushell/Pikaday
5 | */
6 |
7 | (function (root, factory)
8 | {
9 | 'use strict';
10 |
11 | var moment;
12 | if (typeof exports === 'object') {
13 | // CommonJS module
14 | // Load moment.js as an optional dependency
15 | try { moment = require('moment'); } catch (e) {}
16 | module.exports = factory(moment);
17 | } else if (typeof define === 'function' && define.amd) {
18 | // AMD. Register as an anonymous module.
19 | define(function (req)
20 | {
21 | // Load moment.js as an optional dependency
22 | var id = 'moment';
23 | try { moment = req(id); } catch (e) {}
24 | return factory(moment);
25 | });
26 | } else {
27 | root.Pikaday = factory(root.moment);
28 | }
29 | }(this, function (moment)
30 | {
31 | 'use strict';
32 |
33 | /**
34 | * feature detection and helper functions
35 | */
36 | var hasMoment = typeof moment === 'function',
37 |
38 | hasEventListeners = !!window.addEventListener,
39 |
40 | document = window.document,
41 |
42 | sto = window.setTimeout,
43 |
44 | addEvent = function(el, e, callback, capture)
45 | {
46 | if (hasEventListeners) {
47 | el.addEventListener(e, callback, !!capture);
48 | } else {
49 | el.attachEvent('on' + e, callback);
50 | }
51 | },
52 |
53 | removeEvent = function(el, e, callback, capture)
54 | {
55 | if (hasEventListeners) {
56 | el.removeEventListener(e, callback, !!capture);
57 | } else {
58 | el.detachEvent('on' + e, callback);
59 | }
60 | },
61 |
62 | fireEvent = function(el, eventName, data)
63 | {
64 | var ev;
65 |
66 | if (document.createEvent) {
67 | ev = document.createEvent('HTMLEvents');
68 | ev.initEvent(eventName, true, false);
69 | ev = extend(ev, data);
70 | el.dispatchEvent(ev);
71 | } else if (document.createEventObject) {
72 | ev = document.createEventObject();
73 | ev = extend(ev, data);
74 | el.fireEvent('on' + eventName, ev);
75 | }
76 | },
77 |
78 | trim = function(str)
79 | {
80 | return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g,'');
81 | },
82 |
83 | hasClass = function(el, cn)
84 | {
85 | return (' ' + el.className + ' ').indexOf(' ' + cn + ' ') !== -1;
86 | },
87 |
88 | addClass = function(el, cn)
89 | {
90 | if (!hasClass(el, cn)) {
91 | el.className = (el.className === '') ? cn : el.className + ' ' + cn;
92 | }
93 | },
94 |
95 | removeClass = function(el, cn)
96 | {
97 | el.className = trim((' ' + el.className + ' ').replace(' ' + cn + ' ', ' '));
98 | },
99 |
100 | isArray = function(obj)
101 | {
102 | return (/Array/).test(Object.prototype.toString.call(obj));
103 | },
104 |
105 | isDate = function(obj)
106 | {
107 | return (/Date/).test(Object.prototype.toString.call(obj)) && !isNaN(obj.getTime());
108 | },
109 |
110 | isWeekend = function(date)
111 | {
112 | var day = date.getDay();
113 | return day === 0 || day === 6;
114 | },
115 |
116 | isLeapYear = function(year)
117 | {
118 | // solution by Matti Virkkunen: http://stackoverflow.com/a/4881951
119 | return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
120 | },
121 |
122 | getDaysInMonth = function(year, month)
123 | {
124 | return [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
125 | },
126 |
127 | setToStartOfDay = function(date)
128 | {
129 | if (isDate(date)) date.setHours(0,0,0,0);
130 | },
131 |
132 | compareDates = function(a,b)
133 | {
134 | // weak date comparison (use setToStartOfDay(date) to ensure correct result)
135 | return a.getTime() === b.getTime();
136 | },
137 |
138 | extend = function(to, from, overwrite)
139 | {
140 | var prop, hasProp;
141 | for (prop in from) {
142 | hasProp = to[prop] !== undefined;
143 | if (hasProp && typeof from[prop] === 'object' && from[prop] !== null && from[prop].nodeName === undefined) {
144 | if (isDate(from[prop])) {
145 | if (overwrite) {
146 | to[prop] = new Date(from[prop].getTime());
147 | }
148 | }
149 | else if (isArray(from[prop])) {
150 | if (overwrite) {
151 | to[prop] = from[prop].slice(0);
152 | }
153 | } else {
154 | to[prop] = extend({}, from[prop], overwrite);
155 | }
156 | } else if (overwrite || !hasProp) {
157 | to[prop] = from[prop];
158 | }
159 | }
160 | return to;
161 | },
162 |
163 | adjustCalendar = function(calendar) {
164 | if (calendar.month < 0) {
165 | calendar.year -= Math.ceil(Math.abs(calendar.month)/12);
166 | calendar.month += 12;
167 | }
168 | if (calendar.month > 11) {
169 | calendar.year += Math.floor(Math.abs(calendar.month)/12);
170 | calendar.month -= 12;
171 | }
172 | return calendar;
173 | },
174 |
175 | /**
176 | * defaults and localisation
177 | */
178 | defaults = {
179 |
180 | // bind the picker to a form field
181 | field: null,
182 |
183 | // automatically show/hide the picker on `field` focus (default `true` if `field` is set)
184 | bound: undefined,
185 |
186 | // position of the datepicker, relative to the field (default to bottom & left)
187 | // ('bottom' & 'left' keywords are not used, 'top' & 'right' are modifier on the bottom/left position)
188 | position: 'bottom left',
189 |
190 | // automatically fit in the viewport even if it means repositioning from the position option
191 | reposition: true,
192 |
193 | // the default output format for `.toString()` and `field` value
194 | format: 'YYYY-MM-DD',
195 |
196 | // the initial date to view when first opened
197 | defaultDate: null,
198 |
199 | // make the `defaultDate` the initial selected value
200 | setDefaultDate: false,
201 |
202 | // first day of week (0: Sunday, 1: Monday etc)
203 | firstDay: 0,
204 |
205 | // the minimum/earliest date that can be selected
206 | minDate: null,
207 | // the maximum/latest date that can be selected
208 | maxDate: null,
209 |
210 | // number of years either side, or array of upper/lower range
211 | yearRange: 10,
212 |
213 | // show week numbers at head of row
214 | showWeekNumber: false,
215 |
216 | // used internally (don't config outside)
217 | minYear: 0,
218 | maxYear: 9999,
219 | minMonth: undefined,
220 | maxMonth: undefined,
221 |
222 | startRange: null,
223 | endRange: null,
224 |
225 | isRTL: false,
226 |
227 | // Additional text to append to the year in the calendar title
228 | yearSuffix: '',
229 |
230 | // Render the month after year in the calendar title
231 | showMonthAfterYear: false,
232 |
233 | // how many months are visible
234 | numberOfMonths: 1,
235 |
236 | // when numberOfMonths is used, this will help you to choose where the main calendar will be (default `left`, can be set to `right`)
237 | // only used for the first display or when a selected date is not visible
238 | mainCalendar: 'left',
239 |
240 | // Specify a DOM element to render the calendar in
241 | container: undefined,
242 |
243 | // internationalization
244 | i18n: {
245 | previousMonth : 'Previous Month',
246 | nextMonth : 'Next Month',
247 | months : ['January','February','March','April','May','June','July','August','September','October','November','December'],
248 | weekdays : ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
249 | weekdaysShort : ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']
250 | },
251 |
252 | // Theme Classname
253 | theme: null,
254 |
255 | // callback function
256 | onSelect: null,
257 | onOpen: null,
258 | onClose: null,
259 | onDraw: null
260 | },
261 |
262 |
263 | /**
264 | * templating functions to abstract HTML rendering
265 | */
266 | renderDayName = function(opts, day, abbr)
267 | {
268 | day += opts.firstDay;
269 | while (day >= 7) {
270 | day -= 7;
271 | }
272 | return abbr ? opts.i18n.weekdaysShort[day] : opts.i18n.weekdays[day];
273 | },
274 |
275 | renderDay = function(opts)
276 | {
277 | if (opts.isEmpty) {
278 | return ' | ';
279 | }
280 | var arr = [];
281 | if (opts.isDisabled) {
282 | arr.push('is-disabled');
283 | }
284 | if (opts.isToday) {
285 | arr.push('is-today');
286 | }
287 | if (opts.isSelected) {
288 | arr.push('is-selected');
289 | }
290 | if (opts.isInRange) {
291 | arr.push('is-inrange');
292 | }
293 | if (opts.isStartRange) {
294 | arr.push('is-startrange');
295 | }
296 | if (opts.isEndRange) {
297 | arr.push('is-endrange');
298 | }
299 | return '' +
300 | '' +
304 | ' | ';
305 | },
306 |
307 | renderWeek = function (d, m, y) {
308 | // Lifted from http://javascript.about.com/library/blweekyear.htm, lightly modified.
309 | var onejan = new Date(y, 0, 1),
310 | weekNum = Math.ceil((((new Date(y, m, d) - onejan) / 86400000) + onejan.getDay()+1)/7);
311 | return '' + weekNum + ' | ';
312 | },
313 |
314 | renderRow = function(days, isRTL)
315 | {
316 | return '' + (isRTL ? days.reverse() : days).join('') + '
';
317 | },
318 |
319 | renderBody = function(rows)
320 | {
321 | return '' + rows.join('') + '';
322 | },
323 |
324 | renderHead = function(opts)
325 | {
326 | var i, arr = [];
327 | if (opts.showWeekNumber) {
328 | arr.push(' | ');
329 | }
330 | for (i = 0; i < 7; i++) {
331 | arr.push('' + renderDayName(opts, i, true) + ' | ');
332 | }
333 | return '' + (opts.isRTL ? arr.reverse() : arr).join('') + '';
334 | },
335 |
336 | renderTitle = function(instance, c, year, month, refYear)
337 | {
338 | var i, j, arr,
339 | opts = instance._o,
340 | isMinYear = year === opts.minYear,
341 | isMaxYear = year === opts.maxYear,
342 | html = '',
343 | monthHtml,
344 | yearHtml,
345 | prev = true,
346 | next = true;
347 |
348 | for (arr = [], i = 0; i < 12; i++) {
349 | arr.push('
');
353 | }
354 | monthHtml = '
' + opts.i18n.months[month] + '
';
355 |
356 | if (isArray(opts.yearRange)) {
357 | i = opts.yearRange[0];
358 | j = opts.yearRange[1] + 1;
359 | } else {
360 | i = year - opts.yearRange;
361 | j = 1 + year + opts.yearRange;
362 | }
363 |
364 | for (arr = []; i < j && i <= opts.maxYear; i++) {
365 | if (i >= opts.minYear) {
366 | arr.push('
');
367 | }
368 | }
369 | yearHtml = '
' + year + opts.yearSuffix + '
';
370 |
371 | if (opts.showMonthAfterYear) {
372 | html += yearHtml + monthHtml;
373 | } else {
374 | html += monthHtml + yearHtml;
375 | }
376 |
377 | if (isMinYear && (month === 0 || opts.minMonth >= month)) {
378 | prev = false;
379 | }
380 |
381 | if (isMaxYear && (month === 11 || opts.maxMonth <= month)) {
382 | next = false;
383 | }
384 |
385 | if (c === 0) {
386 | html += '
';
387 | }
388 | if (c === (instance._o.numberOfMonths - 1) ) {
389 | html += '
';
390 | }
391 |
392 | return html += '
';
393 | },
394 |
395 | renderTable = function(opts, data)
396 | {
397 | return '' + renderHead(opts) + renderBody(data) + '
';
398 | },
399 |
400 |
401 | /**
402 | * Pikaday constructor
403 | */
404 | Pikaday = function(options)
405 | {
406 | var self = this,
407 | opts = self.config(options);
408 |
409 | self._onMouseDown = function(e)
410 | {
411 | if (!self._v) {
412 | return;
413 | }
414 | e = e || window.event;
415 | var target = e.target || e.srcElement;
416 | if (!target) {
417 | return;
418 | }
419 |
420 | if (!hasClass(target.parentNode, 'is-disabled')) {
421 | if (hasClass(target, 'pika-button') && !hasClass(target, 'is-empty')) {
422 | self.setDate(new Date(target.getAttribute('data-pika-year'), target.getAttribute('data-pika-month'), target.getAttribute('data-pika-day')));
423 | if (opts.bound) {
424 | sto(function() {
425 | self.hide();
426 | if (opts.field) {
427 | opts.field.blur();
428 | }
429 | }, 100);
430 | }
431 | return;
432 | }
433 | else if (hasClass(target, 'pika-prev')) {
434 | self.prevMonth();
435 | }
436 | else if (hasClass(target, 'pika-next')) {
437 | self.nextMonth();
438 | }
439 | }
440 | if (!hasClass(target, 'pika-select')) {
441 | if (e.preventDefault) {
442 | e.preventDefault();
443 | } else {
444 | e.returnValue = false;
445 | return false;
446 | }
447 | } else {
448 | self._c = true;
449 | }
450 | };
451 |
452 | self._onChange = function(e)
453 | {
454 | e = e || window.event;
455 | var target = e.target || e.srcElement;
456 | if (!target) {
457 | return;
458 | }
459 | if (hasClass(target, 'pika-select-month')) {
460 | self.gotoMonth(target.value);
461 | }
462 | else if (hasClass(target, 'pika-select-year')) {
463 | self.gotoYear(target.value);
464 | }
465 | };
466 |
467 | self._onInputChange = function(e)
468 | {
469 | var date;
470 |
471 | if (e.firedBy === self) {
472 | return;
473 | }
474 | if (hasMoment) {
475 | date = moment(opts.field.value, opts.format);
476 | date = (date && date.isValid()) ? date.toDate() : null;
477 | }
478 | else {
479 | date = new Date(Date.parse(opts.field.value));
480 | }
481 | if (isDate(date)) {
482 | self.setDate(date);
483 | }
484 | if (!self._v) {
485 | self.show();
486 | }
487 | };
488 |
489 | self._onInputFocus = function()
490 | {
491 | self.show();
492 | };
493 |
494 | self._onInputClick = function()
495 | {
496 | self.show();
497 | };
498 |
499 | self._onInputBlur = function()
500 | {
501 | // IE allows pika div to gain focus; catch blur the input field
502 | var pEl = document.activeElement;
503 | do {
504 | if (hasClass(pEl, 'pika-single')) {
505 | return;
506 | }
507 | }
508 | while ((pEl = pEl.parentNode));
509 |
510 | if (!self._c) {
511 | self._b = sto(function() {
512 | self.hide();
513 | }, 50);
514 | }
515 | self._c = false;
516 | };
517 |
518 | self._onClick = function(e)
519 | {
520 | e = e || window.event;
521 | var target = e.target || e.srcElement,
522 | pEl = target;
523 | if (!target) {
524 | return;
525 | }
526 | if (!hasEventListeners && hasClass(target, 'pika-select')) {
527 | if (!target.onchange) {
528 | target.setAttribute('onchange', 'return;');
529 | addEvent(target, 'change', self._onChange);
530 | }
531 | }
532 | do {
533 | if (hasClass(pEl, 'pika-single') || pEl === opts.trigger) {
534 | return;
535 | }
536 | }
537 | while ((pEl = pEl.parentNode));
538 | if (self._v && target !== opts.trigger && pEl !== opts.trigger) {
539 | self.hide();
540 | }
541 | };
542 |
543 | self.el = document.createElement('div');
544 | self.el.className = 'pika-single' + (opts.isRTL ? ' is-rtl' : '') + (opts.theme ? ' ' + opts.theme : '');
545 |
546 | addEvent(self.el, 'ontouchend' in document ? 'touchend' : 'mousedown', self._onMouseDown, true);
547 | addEvent(self.el, 'change', self._onChange);
548 |
549 | if (opts.field) {
550 | if (opts.container) {
551 | opts.container.appendChild(self.el);
552 | } else if (opts.bound) {
553 | document.body.appendChild(self.el);
554 | } else {
555 | opts.field.parentNode.insertBefore(self.el, opts.field.nextSibling);
556 | }
557 | addEvent(opts.field, 'change', self._onInputChange);
558 |
559 | if (!opts.defaultDate) {
560 | if (hasMoment && opts.field.value) {
561 | opts.defaultDate = moment(opts.field.value, opts.format).toDate();
562 | } else {
563 | opts.defaultDate = new Date(Date.parse(opts.field.value));
564 | }
565 | opts.setDefaultDate = true;
566 | }
567 | }
568 |
569 | var defDate = opts.defaultDate;
570 |
571 | if (isDate(defDate)) {
572 | if (opts.setDefaultDate) {
573 | self.setDate(defDate, true);
574 | } else {
575 | self.gotoDate(defDate);
576 | }
577 | } else {
578 | self.gotoDate(new Date());
579 | }
580 |
581 | if (opts.bound) {
582 | this.hide();
583 | self.el.className += ' is-bound';
584 | addEvent(opts.trigger, 'click', self._onInputClick);
585 | addEvent(opts.trigger, 'focus', self._onInputFocus);
586 | addEvent(opts.trigger, 'blur', self._onInputBlur);
587 | } else {
588 | this.show();
589 | }
590 | };
591 |
592 |
593 | /**
594 | * public Pikaday API
595 | */
596 | Pikaday.prototype = {
597 |
598 |
599 | /**
600 | * configure functionality
601 | */
602 | config: function(options)
603 | {
604 | if (!this._o) {
605 | this._o = extend({}, defaults, true);
606 | }
607 |
608 | var opts = extend(this._o, options, true);
609 |
610 | opts.isRTL = !!opts.isRTL;
611 |
612 | opts.field = (opts.field && opts.field.nodeName) ? opts.field : null;
613 |
614 | opts.theme = (typeof opts.theme) === 'string' && opts.theme ? opts.theme : null;
615 |
616 | opts.bound = !!(opts.bound !== undefined ? opts.field && opts.bound : opts.field);
617 |
618 | opts.trigger = (opts.trigger && opts.trigger.nodeName) ? opts.trigger : opts.field;
619 |
620 | opts.disableWeekends = !!opts.disableWeekends;
621 |
622 | opts.disableDayFn = (typeof opts.disableDayFn) === 'function' ? opts.disableDayFn : null;
623 |
624 | var nom = parseInt(opts.numberOfMonths, 10) || 1;
625 | opts.numberOfMonths = nom > 4 ? 4 : nom;
626 |
627 | if (!isDate(opts.minDate)) {
628 | opts.minDate = false;
629 | }
630 | if (!isDate(opts.maxDate)) {
631 | opts.maxDate = false;
632 | }
633 | if ((opts.minDate && opts.maxDate) && opts.maxDate < opts.minDate) {
634 | opts.maxDate = opts.minDate = false;
635 | }
636 | if (opts.minDate) {
637 | this.setMinDate(opts.minDate);
638 | }
639 | if (opts.maxDate) {
640 | setToStartOfDay(opts.maxDate);
641 | opts.maxYear = opts.maxDate.getFullYear();
642 | opts.maxMonth = opts.maxDate.getMonth();
643 | }
644 |
645 | if (isArray(opts.yearRange)) {
646 | var fallback = new Date().getFullYear() - 10;
647 | opts.yearRange[0] = parseInt(opts.yearRange[0], 10) || fallback;
648 | opts.yearRange[1] = parseInt(opts.yearRange[1], 10) || fallback;
649 | } else {
650 | opts.yearRange = Math.abs(parseInt(opts.yearRange, 10)) || defaults.yearRange;
651 | if (opts.yearRange > 100) {
652 | opts.yearRange = 100;
653 | }
654 | }
655 |
656 | return opts;
657 | },
658 |
659 | /**
660 | * return a formatted string of the current selection (using Moment.js if available)
661 | */
662 | toString: function(format)
663 | {
664 | return !isDate(this._d) ? '' : hasMoment ? moment(this._d).format(format || this._o.format) : this._d.toDateString();
665 | },
666 |
667 | /**
668 | * return a Moment.js object of the current selection (if available)
669 | */
670 | getMoment: function()
671 | {
672 | return hasMoment ? moment(this._d) : null;
673 | },
674 |
675 | /**
676 | * set the current selection from a Moment.js object (if available)
677 | */
678 | setMoment: function(date, preventOnSelect)
679 | {
680 | if (hasMoment && moment.isMoment(date)) {
681 | this.setDate(date.toDate(), preventOnSelect);
682 | }
683 | },
684 |
685 | /**
686 | * return a Date object of the current selection
687 | */
688 | getDate: function()
689 | {
690 | return isDate(this._d) ? new Date(this._d.getTime()) : null;
691 | },
692 |
693 | /**
694 | * set the current selection
695 | */
696 | setDate: function(date, preventOnSelect)
697 | {
698 | if (!date) {
699 | this._d = null;
700 |
701 | if (this._o.field) {
702 | this._o.field.value = '';
703 | fireEvent(this._o.field, 'change', { firedBy: this });
704 | }
705 |
706 | return this.draw();
707 | }
708 | if (typeof date === 'string') {
709 | date = new Date(Date.parse(date));
710 | }
711 | if (!isDate(date)) {
712 | return;
713 | }
714 |
715 | var min = this._o.minDate,
716 | max = this._o.maxDate;
717 |
718 | if (isDate(min) && date < min) {
719 | date = min;
720 | } else if (isDate(max) && date > max) {
721 | date = max;
722 | }
723 |
724 | this._d = new Date(date.getTime());
725 | setToStartOfDay(this._d);
726 | this.gotoDate(this._d);
727 |
728 | if (this._o.field) {
729 | this._o.field.value = this.toString();
730 | fireEvent(this._o.field, 'change', { firedBy: this });
731 | }
732 | if (!preventOnSelect && typeof this._o.onSelect === 'function') {
733 | this._o.onSelect.call(this, this.getDate());
734 | }
735 | },
736 |
737 | /**
738 | * change view to a specific date
739 | */
740 | gotoDate: function(date)
741 | {
742 | var newCalendar = true;
743 |
744 | if (!isDate(date)) {
745 | return;
746 | }
747 |
748 | if (this.calendars) {
749 | var firstVisibleDate = new Date(this.calendars[0].year, this.calendars[0].month, 1),
750 | lastVisibleDate = new Date(this.calendars[this.calendars.length-1].year, this.calendars[this.calendars.length-1].month, 1),
751 | visibleDate = date.getTime();
752 | // get the end of the month
753 | lastVisibleDate.setMonth(lastVisibleDate.getMonth()+1);
754 | lastVisibleDate.setDate(lastVisibleDate.getDate()-1);
755 | newCalendar = (visibleDate < firstVisibleDate.getTime() || lastVisibleDate.getTime() < visibleDate);
756 | }
757 |
758 | if (newCalendar) {
759 | this.calendars = [{
760 | month: date.getMonth(),
761 | year: date.getFullYear()
762 | }];
763 | if (this._o.mainCalendar === 'right') {
764 | this.calendars[0].month += 1 - this._o.numberOfMonths;
765 | }
766 | }
767 |
768 | this.adjustCalendars();
769 | },
770 |
771 | adjustCalendars: function() {
772 | this.calendars[0] = adjustCalendar(this.calendars[0]);
773 | for (var c = 1; c < this._o.numberOfMonths; c++) {
774 | this.calendars[c] = adjustCalendar({
775 | month: this.calendars[0].month + c,
776 | year: this.calendars[0].year
777 | });
778 | }
779 | this.draw();
780 | },
781 |
782 | gotoToday: function()
783 | {
784 | this.gotoDate(new Date());
785 | },
786 |
787 | /**
788 | * change view to a specific month (zero-index, e.g. 0: January)
789 | */
790 | gotoMonth: function(month)
791 | {
792 | if (!isNaN(month)) {
793 | this.calendars[0].month = parseInt(month, 10);
794 | this.adjustCalendars();
795 | }
796 | },
797 |
798 | nextMonth: function()
799 | {
800 | this.calendars[0].month++;
801 | this.adjustCalendars();
802 | },
803 |
804 | prevMonth: function()
805 | {
806 | this.calendars[0].month--;
807 | this.adjustCalendars();
808 | },
809 |
810 | /**
811 | * change view to a specific full year (e.g. "2012")
812 | */
813 | gotoYear: function(year)
814 | {
815 | if (!isNaN(year)) {
816 | this.calendars[0].year = parseInt(year, 10);
817 | this.adjustCalendars();
818 | }
819 | },
820 |
821 | /**
822 | * change the minDate
823 | */
824 | setMinDate: function(value)
825 | {
826 | setToStartOfDay(value);
827 | this._o.minDate = value;
828 | this._o.minYear = value.getFullYear();
829 | this._o.minMonth = value.getMonth();
830 | },
831 |
832 | /**
833 | * change the maxDate
834 | */
835 | setMaxDate: function(value)
836 | {
837 | this._o.maxDate = value;
838 | },
839 |
840 | setStartRange: function(value)
841 | {
842 | this._o.startRange = value;
843 | },
844 |
845 | setEndRange: function(value)
846 | {
847 | this._o.endRange = value;
848 | },
849 |
850 | /**
851 | * refresh the HTML
852 | */
853 | draw: function(force)
854 | {
855 | if (!this._v && !force) {
856 | return;
857 | }
858 | var opts = this._o,
859 | minYear = opts.minYear,
860 | maxYear = opts.maxYear,
861 | minMonth = opts.minMonth,
862 | maxMonth = opts.maxMonth,
863 | html = '';
864 |
865 | if (this._y <= minYear) {
866 | this._y = minYear;
867 | if (!isNaN(minMonth) && this._m < minMonth) {
868 | this._m = minMonth;
869 | }
870 | }
871 | if (this._y >= maxYear) {
872 | this._y = maxYear;
873 | if (!isNaN(maxMonth) && this._m > maxMonth) {
874 | this._m = maxMonth;
875 | }
876 | }
877 |
878 | for (var c = 0; c < opts.numberOfMonths; c++) {
879 | html += '' + renderTitle(this, c, this.calendars[c].year, this.calendars[c].month, this.calendars[0].year) + this.render(this.calendars[c].year, this.calendars[c].month) + '
';
880 | }
881 |
882 | this.el.innerHTML = html;
883 |
884 | if (opts.bound) {
885 | if(opts.field.type !== 'hidden') {
886 | sto(function() {
887 | opts.trigger.focus();
888 | }, 1);
889 | }
890 | }
891 |
892 | if (typeof this._o.onDraw === 'function') {
893 | var self = this;
894 | sto(function() {
895 | self._o.onDraw.call(self);
896 | }, 0);
897 | }
898 | },
899 |
900 | adjustPosition: function()
901 | {
902 | var field, pEl, width, height, viewportWidth, viewportHeight, scrollTop, left, top, clientRect;
903 |
904 | if (this._o.container) return;
905 |
906 | this.el.style.position = 'absolute';
907 |
908 | field = this._o.trigger;
909 | pEl = field;
910 | width = this.el.offsetWidth;
911 | height = this.el.offsetHeight;
912 | viewportWidth = window.innerWidth || document.documentElement.clientWidth;
913 | viewportHeight = window.innerHeight || document.documentElement.clientHeight;
914 | scrollTop = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
915 |
916 | if (typeof field.getBoundingClientRect === 'function') {
917 | clientRect = field.getBoundingClientRect();
918 | left = clientRect.left + window.pageXOffset;
919 | top = clientRect.bottom + window.pageYOffset;
920 | } else {
921 | left = pEl.offsetLeft;
922 | top = pEl.offsetTop + pEl.offsetHeight;
923 | while((pEl = pEl.offsetParent)) {
924 | left += pEl.offsetLeft;
925 | top += pEl.offsetTop;
926 | }
927 | }
928 |
929 | // default position is bottom & left
930 | if ((this._o.reposition && left + width > viewportWidth) ||
931 | (
932 | this._o.position.indexOf('right') > -1 &&
933 | left - width + field.offsetWidth > 0
934 | )
935 | ) {
936 | left = left - width + field.offsetWidth;
937 | }
938 | if ((this._o.reposition && top + height > viewportHeight + scrollTop) ||
939 | (
940 | this._o.position.indexOf('top') > -1 &&
941 | top - height - field.offsetHeight > 0
942 | )
943 | ) {
944 | top = top - height - field.offsetHeight;
945 | }
946 |
947 | this.el.style.left = left + 'px';
948 | this.el.style.top = top + 'px';
949 | },
950 |
951 | /**
952 | * render HTML for a particular month
953 | */
954 | render: function(year, month)
955 | {
956 | var opts = this._o,
957 | now = new Date(),
958 | days = getDaysInMonth(year, month),
959 | before = new Date(year, month, 1).getDay(),
960 | data = [],
961 | row = [];
962 | setToStartOfDay(now);
963 | if (opts.firstDay > 0) {
964 | before -= opts.firstDay;
965 | if (before < 0) {
966 | before += 7;
967 | }
968 | }
969 | var cells = days + before,
970 | after = cells;
971 | while(after > 7) {
972 | after -= 7;
973 | }
974 | cells += 7 - after;
975 | for (var i = 0, r = 0; i < cells; i++)
976 | {
977 | var dayConfig,
978 | day = new Date(year, month, 1 + (i - before)),
979 | isSelected = isDate(this._d) ? compareDates(day, this._d) : false,
980 | isToday = compareDates(day, now),
981 | isEmpty = i < before || i >= (days + before),
982 | isStartRange = opts.startRange && compareDates(opts.startRange, day),
983 | isEndRange = opts.endRange && compareDates(opts.endRange, day),
984 | isInRange = opts.startRange && opts.endRange && opts.startRange < day && day < opts.endRange,
985 | isDisabled = (opts.minDate && day < opts.minDate) ||
986 | (opts.maxDate && day > opts.maxDate) ||
987 | (opts.disableWeekends && isWeekend(day)) ||
988 | (opts.disableDayFn && opts.disableDayFn(day)),
989 | dayConfig = {
990 | day: 1 + (i - before),
991 | month: month,
992 | year: year,
993 | isSelected: isSelected,
994 | isToday: isToday,
995 | isDisabled: isDisabled,
996 | isEmpty: isEmpty,
997 | isStartRange: isStartRange,
998 | isEndRange: isEndRange,
999 | isInRange: isInRange
1000 | };
1001 |
1002 | row.push(renderDay(dayConfig));
1003 |
1004 | if (++r === 7) {
1005 | if (opts.showWeekNumber) {
1006 | row.unshift(renderWeek(i - before, month, year));
1007 | }
1008 | data.push(renderRow(row, opts.isRTL));
1009 | row = [];
1010 | r = 0;
1011 | }
1012 | }
1013 | return renderTable(opts, data);
1014 | },
1015 |
1016 | isVisible: function()
1017 | {
1018 | return this._v;
1019 | },
1020 |
1021 | show: function()
1022 | {
1023 | if (!this._v) {
1024 | removeClass(this.el, 'is-hidden');
1025 | this._v = true;
1026 | this.draw();
1027 | if (this._o.bound) {
1028 | addEvent(document, 'click', this._onClick);
1029 | this.adjustPosition();
1030 | }
1031 | if (typeof this._o.onOpen === 'function') {
1032 | this._o.onOpen.call(this);
1033 | }
1034 | }
1035 | },
1036 |
1037 | hide: function()
1038 | {
1039 | var v = this._v;
1040 | if (v !== false) {
1041 | if (this._o.bound) {
1042 | removeEvent(document, 'click', this._onClick);
1043 | }
1044 | this.el.style.position = 'static'; // reset
1045 | this.el.style.left = 'auto';
1046 | this.el.style.top = 'auto';
1047 | addClass(this.el, 'is-hidden');
1048 | this._v = false;
1049 | if (v !== undefined && typeof this._o.onClose === 'function') {
1050 | this._o.onClose.call(this);
1051 | }
1052 | }
1053 | },
1054 |
1055 | /**
1056 | * GAME OVER
1057 | */
1058 | destroy: function()
1059 | {
1060 | this.hide();
1061 | removeEvent(this.el, 'mousedown', this._onMouseDown, true);
1062 | removeEvent(this.el, 'change', this._onChange);
1063 | if (this._o.field) {
1064 | removeEvent(this._o.field, 'change', this._onInputChange);
1065 | if (this._o.bound) {
1066 | removeEvent(this._o.trigger, 'click', this._onInputClick);
1067 | removeEvent(this._o.trigger, 'focus', this._onInputFocus);
1068 | removeEvent(this._o.trigger, 'blur', this._onInputBlur);
1069 | }
1070 | }
1071 | if (this.el.parentNode) {
1072 | this.el.parentNode.removeChild(this.el);
1073 | }
1074 | }
1075 |
1076 | };
1077 |
1078 | return Pikaday;
1079 |
1080 | }));
1081 |
--------------------------------------------------------------------------------
/firefox/index.js:
--------------------------------------------------------------------------------
1 |
2 | var pageMod = require("sdk/page-mod");
3 |
4 | pageMod.PageMod({
5 | include: [/.*github\.com\/.*\/.*/],
6 | contentScriptFile: ['./pikaday/pikaday.js', './github-time-travel.js'],
7 | contentStyleFile: ['./pikaday/pikaday.css']
8 | });
9 |
--------------------------------------------------------------------------------
/firefox/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Github Time Travel",
3 | "name": "github-commit-dates",
4 | "version": "1.0.1",
5 | "description": "Browse commits by date on Github",
6 | "main": "index.js",
7 | "author": "Nathan Cahill",
8 | "engines": {
9 | "firefox": ">=38.0a1"
10 | },
11 | "license": "MIT"
12 | }
13 |
--------------------------------------------------------------------------------