"]
35 | }
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/chrome/pikaday.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 |
3 | /*!
4 | * Pikaday
5 | * Copyright © 2014 David Bushell | BSD & MIT license | https://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 |
30 | .pika-single.is-hidden {
31 | display: none;
32 | }
33 |
34 | .pika-single.is-bound {
35 | position: absolute;
36 | box-shadow: 0 5px 15px -5px rgba(0,0,0,.5);
37 | }
38 |
39 | .pika-lendar {
40 | float: left;
41 | width: 240px;
42 | margin: 8px;
43 | }
44 |
45 | .pika-title {
46 | position: relative;
47 | text-align: center;
48 | }
49 |
50 | .pika-label {
51 | display: inline-block;
52 | position: relative;
53 | z-index: 9999;
54 | overflow: hidden;
55 | margin: 0;
56 | padding: 5px 3px;
57 | font-size: 14px;
58 | line-height: 20px;
59 | font-weight: bold;
60 | background-color: #fff;
61 | }
62 | .pika-title select {
63 | cursor: pointer;
64 | position: absolute;
65 | z-index: 9998;
66 | margin: 0;
67 | left: 0;
68 | top: 5px;
69 | opacity: 0;
70 | }
71 |
72 | .pika-prev,
73 | .pika-next {
74 | display: block;
75 | cursor: pointer;
76 | position: relative;
77 | outline: none;
78 | border: 0;
79 | padding: 0;
80 | width: 20px;
81 | height: 30px;
82 | /* hide text using text-indent trick, using width value (it's enough) */
83 | text-indent: 20px;
84 | white-space: nowrap;
85 | overflow: hidden;
86 | background-color: transparent;
87 | background-position: center center;
88 | background-repeat: no-repeat;
89 | background-size: 75% 75%;
90 | opacity: .5;
91 | }
92 |
93 | .pika-prev:hover,
94 | .pika-next:hover {
95 | opacity: 1;
96 | }
97 |
98 | .pika-prev,
99 | .is-rtl .pika-next {
100 | float: left;
101 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAUklEQVR42u3VMQoAIBADQf8Pgj+OD9hG2CtONJB2ymQkKe0HbwAP0xucDiQWARITIDEBEnMgMQ8S8+AqBIl6kKgHiXqQqAeJepBo/z38J/U0uAHlaBkBl9I4GwAAAABJRU5ErkJggg==');
102 | }
103 |
104 | .pika-next,
105 | .is-rtl .pika-prev {
106 | float: right;
107 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAU0lEQVR42u3VOwoAMAgE0dwfAnNjU26bYkBCFGwfiL9VVWoO+BJ4Gf3gtsEKKoFBNTCoCAYVwaAiGNQGMUHMkjGbgjk2mIONuXo0nC8XnCf1JXgArVIZAQh5TKYAAAAASUVORK5CYII=');
108 | }
109 |
110 | .pika-prev.is-disabled,
111 | .pika-next.is-disabled {
112 | cursor: default;
113 | opacity: .2;
114 | }
115 |
116 | .pika-select {
117 | display: inline-block;
118 | }
119 |
120 | .pika-table {
121 | width: 100%;
122 | border-collapse: collapse;
123 | border-spacing: 0;
124 | border: 0;
125 | }
126 |
127 | .pika-table th,
128 | .pika-table td {
129 | width: 14.285714285714286%;
130 | padding: 0;
131 | }
132 |
133 | .pika-table th {
134 | color: #999;
135 | font-size: 12px;
136 | line-height: 25px;
137 | font-weight: bold;
138 | text-align: center;
139 | }
140 |
141 | .pika-button {
142 | cursor: pointer;
143 | display: block;
144 | box-sizing: border-box;
145 | -moz-box-sizing: border-box;
146 | outline: none;
147 | border: 0;
148 | margin: 0;
149 | width: 100%;
150 | padding: 5px;
151 | color: #666;
152 | font-size: 12px;
153 | line-height: 15px;
154 | text-align: right;
155 | background: #f5f5f5;
156 | height: initial;
157 | }
158 |
159 | .pika-week {
160 | font-size: 11px;
161 | color: #999;
162 | }
163 |
164 | .is-today .pika-button {
165 | color: #33aaff;
166 | font-weight: bold;
167 | }
168 |
169 | .is-selected .pika-button,
170 | .has-event .pika-button {
171 | color: #fff;
172 | font-weight: bold;
173 | background: #33aaff;
174 | box-shadow: inset 0 1px 3px #178fe5;
175 | border-radius: 3px;
176 | }
177 |
178 | .has-event .pika-button {
179 | background: #005da9;
180 | box-shadow: inset 0 1px 3px #0076c9;
181 | }
182 |
183 | .is-disabled .pika-button,
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 | .is-outside-current-month .pika-button {
210 | color: #999;
211 | opacity: .3;
212 | }
213 |
214 | .is-selection-disabled {
215 | pointer-events: none;
216 | cursor: default;
217 | }
218 |
219 | .pika-button:hover,
220 | .pika-row.pick-whole-week:hover .pika-button {
221 | color: #fff;
222 | background: #ff8000;
223 | box-shadow: none;
224 | border-radius: 3px;
225 | }
226 |
227 | /* styling for abbr */
228 | .pika-table abbr {
229 | border-bottom: none;
230 | cursor: help;
231 | }
232 |
--------------------------------------------------------------------------------
/chrome/pikaday.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Pikaday
3 | *
4 | * Copyright © 2014 David Bushell | BSD & MIT license | https://github.com/Pikaday/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 | trim = function(str)
63 | {
64 | return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g,'');
65 | },
66 |
67 | hasClass = function(el, cn)
68 | {
69 | return (' ' + el.className + ' ').indexOf(' ' + cn + ' ') !== -1;
70 | },
71 |
72 | addClass = function(el, cn)
73 | {
74 | if (!hasClass(el, cn)) {
75 | el.className = (el.className === '') ? cn : el.className + ' ' + cn;
76 | }
77 | },
78 |
79 | removeClass = function(el, cn)
80 | {
81 | el.className = trim((' ' + el.className + ' ').replace(' ' + cn + ' ', ' '));
82 | },
83 |
84 | isArray = function(obj)
85 | {
86 | return (/Array/).test(Object.prototype.toString.call(obj));
87 | },
88 |
89 | isDate = function(obj)
90 | {
91 | return (/Date/).test(Object.prototype.toString.call(obj)) && !isNaN(obj.getTime());
92 | },
93 |
94 | isWeekend = function(date)
95 | {
96 | var day = date.getDay();
97 | return day === 0 || day === 6;
98 | },
99 |
100 | isLeapYear = function(year)
101 | {
102 | // solution lifted from date.js (MIT license): https://github.com/datejs/Datejs
103 | return ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0);
104 | },
105 |
106 | getDaysInMonth = function(year, month)
107 | {
108 | return [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
109 | },
110 |
111 | setToStartOfDay = function(date)
112 | {
113 | if (isDate(date)) date.setHours(0,0,0,0);
114 | },
115 |
116 | compareDates = function(a,b)
117 | {
118 | // weak date comparison (use setToStartOfDay(date) to ensure correct result)
119 | return a.getTime() === b.getTime();
120 | },
121 |
122 | extend = function(to, from, overwrite)
123 | {
124 | var prop, hasProp;
125 | for (prop in from) {
126 | hasProp = to[prop] !== undefined;
127 | if (hasProp && typeof from[prop] === 'object' && from[prop] !== null && from[prop].nodeName === undefined) {
128 | if (isDate(from[prop])) {
129 | if (overwrite) {
130 | to[prop] = new Date(from[prop].getTime());
131 | }
132 | }
133 | else if (isArray(from[prop])) {
134 | if (overwrite) {
135 | to[prop] = from[prop].slice(0);
136 | }
137 | } else {
138 | to[prop] = extend({}, from[prop], overwrite);
139 | }
140 | } else if (overwrite || !hasProp) {
141 | to[prop] = from[prop];
142 | }
143 | }
144 | return to;
145 | },
146 |
147 | fireEvent = function(el, eventName, data)
148 | {
149 | var ev;
150 |
151 | if (document.createEvent) {
152 | ev = document.createEvent('HTMLEvents');
153 | ev.initEvent(eventName, true, false);
154 | ev = extend(ev, data);
155 | el.dispatchEvent(ev);
156 | } else if (document.createEventObject) {
157 | ev = document.createEventObject();
158 | ev = extend(ev, data);
159 | el.fireEvent('on' + eventName, ev);
160 | }
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 | // data-attribute on the input field with an aria assistance text (only applied when `bound` is set)
187 | ariaLabel: 'Use the arrow keys to pick a date',
188 |
189 | // position of the datepicker, relative to the field (default to bottom & left)
190 | // ('bottom' & 'left' keywords are not used, 'top' & 'right' are modifier on the bottom/left position)
191 | position: 'bottom left',
192 |
193 | // automatically fit in the viewport even if it means repositioning from the position option
194 | reposition: true,
195 |
196 | // the default output format for `.toString()` and `field` value
197 | format: 'YYYY-MM-DD',
198 |
199 | // the toString function which gets passed a current date object and format
200 | // and returns a string
201 | toString: null,
202 |
203 | // used to create date object from current input string
204 | parse: null,
205 |
206 | // the initial date to view when first opened
207 | defaultDate: null,
208 |
209 | // make the `defaultDate` the initial selected value
210 | setDefaultDate: false,
211 |
212 | // first day of week (0: Sunday, 1: Monday etc)
213 | firstDay: 0,
214 |
215 | // minimum number of days in the week that gets week number one
216 | // default ISO 8601, week 01 is the week with the first Thursday (4)
217 | firstWeekOfYearMinDays: 4,
218 |
219 | // the default flag for moment's strict date parsing
220 | formatStrict: false,
221 |
222 | // the minimum/earliest date that can be selected
223 | minDate: null,
224 | // the maximum/latest date that can be selected
225 | maxDate: null,
226 |
227 | // number of years either side, or array of upper/lower range
228 | yearRange: 10,
229 |
230 | // show week numbers at head of row
231 | showWeekNumber: false,
232 |
233 | // Week picker mode
234 | pickWholeWeek: false,
235 |
236 | // used internally (don't config outside)
237 | minYear: 0,
238 | maxYear: 9999,
239 | minMonth: undefined,
240 | maxMonth: undefined,
241 |
242 | startRange: null,
243 | endRange: null,
244 |
245 | isRTL: false,
246 |
247 | // Additional text to append to the year in the calendar title
248 | yearSuffix: '',
249 |
250 | // Render the month after year in the calendar title
251 | showMonthAfterYear: false,
252 |
253 | // Render days of the calendar grid that fall in the next or previous month
254 | showDaysInNextAndPreviousMonths: false,
255 |
256 | // Allows user to select days that fall in the next or previous month
257 | enableSelectionDaysInNextAndPreviousMonths: false,
258 |
259 | // how many months are visible
260 | numberOfMonths: 1,
261 |
262 | // when numberOfMonths is used, this will help you to choose where the main calendar will be (default `left`, can be set to `right`)
263 | // only used for the first display or when a selected date is not visible
264 | mainCalendar: 'left',
265 |
266 | // Specify a DOM element to render the calendar in
267 | container: undefined,
268 |
269 | // Blur field when date is selected
270 | blurFieldOnSelect : true,
271 |
272 | // internationalization
273 | i18n: {
274 | previousMonth : 'Previous Month',
275 | nextMonth : 'Next Month',
276 | months : ['January','February','March','April','May','June','July','August','September','October','November','December'],
277 | weekdays : ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
278 | weekdaysShort : ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']
279 | },
280 |
281 | // Theme Classname
282 | theme: null,
283 |
284 | // events array
285 | events: [],
286 |
287 | // callback function
288 | onSelect: null,
289 | onOpen: null,
290 | onClose: null,
291 | onDraw: null,
292 |
293 | // Enable keyboard input
294 | keyboardInput: true
295 | },
296 |
297 |
298 | /**
299 | * templating functions to abstract HTML rendering
300 | */
301 | renderDayName = function(opts, day, abbr)
302 | {
303 | day += opts.firstDay;
304 | while (day >= 7) {
305 | day -= 7;
306 | }
307 | return abbr ? opts.i18n.weekdaysShort[day] : opts.i18n.weekdays[day];
308 | },
309 |
310 | renderDay = function(opts)
311 | {
312 | var arr = [];
313 | var ariaSelected = 'false';
314 | if (opts.isEmpty) {
315 | if (opts.showDaysInNextAndPreviousMonths) {
316 | arr.push('is-outside-current-month');
317 |
318 | if(!opts.enableSelectionDaysInNextAndPreviousMonths) {
319 | arr.push('is-selection-disabled');
320 | }
321 |
322 | } else {
323 | return ' | ';
324 | }
325 | }
326 | if (opts.isDisabled) {
327 | arr.push('is-disabled');
328 | }
329 | if (opts.isToday) {
330 | arr.push('is-today');
331 | }
332 | if (opts.isSelected) {
333 | arr.push('is-selected');
334 | ariaSelected = 'true';
335 | }
336 | if (opts.hasEvent) {
337 | arr.push('has-event');
338 | }
339 | if (opts.isInRange) {
340 | arr.push('is-inrange');
341 | }
342 | if (opts.isStartRange) {
343 | arr.push('is-startrange');
344 | }
345 | if (opts.isEndRange) {
346 | arr.push('is-endrange');
347 | }
348 | return '' +
349 | '' +
353 | ' | ';
354 | },
355 |
356 | isoWeek = function(date, firstWeekOfYearMinDays) {
357 | // Ensure we're at the start of the day.
358 | date.setHours(0, 0, 0, 0);
359 |
360 | // Thursday in current week decides the year because January 4th
361 | // is always in the first week according to ISO8601.
362 | var yearDay = date.getDate(),
363 | weekDay = date.getDay(),
364 | dayInFirstWeek = firstWeekOfYearMinDays,
365 | dayShift = dayInFirstWeek - 1, // counting starts at 0
366 | daysPerWeek = 7,
367 | prevWeekDay = function(day) { return (day + daysPerWeek - 1) % daysPerWeek; };
368 |
369 | // Adjust to Thursday in week 1 and count number of weeks from date to week 1.
370 | date.setDate(yearDay + dayShift - prevWeekDay(weekDay));
371 |
372 | var jan4th = new Date(date.getFullYear(), 0, dayInFirstWeek),
373 | msPerDay = 24 * 60 * 60 * 1000,
374 | daysBetween = (date.getTime() - jan4th.getTime()) / msPerDay,
375 | weekNum = 1 + Math.round((daysBetween - dayShift + prevWeekDay(jan4th.getDay())) / daysPerWeek);
376 |
377 | return weekNum;
378 | },
379 |
380 | renderWeek = function (d, m, y, firstWeekOfYearMinDays) {
381 | var date = new Date(y, m, d),
382 | week = hasMoment ? moment(date).isoWeek() : isoWeek(date, firstWeekOfYearMinDays);
383 |
384 | return '' + week + ' | ';
385 | },
386 |
387 | renderRow = function(days, isRTL, pickWholeWeek, isRowSelected)
388 | {
389 | return '' + (isRTL ? days.reverse() : days).join('') + '
';
390 | },
391 |
392 | renderBody = function(rows)
393 | {
394 | return '' + rows.join('') + '';
395 | },
396 |
397 | renderHead = function(opts)
398 | {
399 | var i, arr = [];
400 | if (opts.showWeekNumber) {
401 | arr.push(' | ');
402 | }
403 | for (i = 0; i < 7; i++) {
404 | arr.push('' + renderDayName(opts, i, true) + ' | ');
405 | }
406 | return '' + (opts.isRTL ? arr.reverse() : arr).join('') + '
';
407 | },
408 |
409 | renderTitle = function(instance, c, year, month, refYear, randId)
410 | {
411 | var i, j, arr,
412 | opts = instance._o,
413 | isMinYear = year === opts.minYear,
414 | isMaxYear = year === opts.maxYear,
415 | html = '',
416 | monthHtml,
417 | yearHtml,
418 | prev = true,
419 | next = true;
420 |
421 | for (arr = [], i = 0; i < 12; i++) {
422 | arr.push('
');
426 | }
427 |
428 | monthHtml = '
' + opts.i18n.months[month] + '
';
429 |
430 | if (isArray(opts.yearRange)) {
431 | i = opts.yearRange[0];
432 | j = opts.yearRange[1] + 1;
433 | } else {
434 | i = year - opts.yearRange;
435 | j = 1 + year + opts.yearRange;
436 | }
437 |
438 | for (arr = []; i < j && i <= opts.maxYear; i++) {
439 | if (i >= opts.minYear) {
440 | arr.push('
');
441 | }
442 | }
443 | yearHtml = '
' + year + opts.yearSuffix + '
';
444 |
445 | if (opts.showMonthAfterYear) {
446 | html += yearHtml + monthHtml;
447 | } else {
448 | html += monthHtml + yearHtml;
449 | }
450 |
451 | if (isMinYear && (month === 0 || opts.minMonth >= month)) {
452 | prev = false;
453 | }
454 |
455 | if (isMaxYear && (month === 11 || opts.maxMonth <= month)) {
456 | next = false;
457 | }
458 |
459 | if (c === 0) {
460 | html += '
';
461 | }
462 | if (c === (instance._o.numberOfMonths - 1) ) {
463 | html += '
';
464 | }
465 |
466 | return html += '
';
467 | },
468 |
469 | renderTable = function(opts, data, randId)
470 | {
471 | return '' + renderHead(opts) + renderBody(data) + '
';
472 | },
473 |
474 |
475 | /**
476 | * Pikaday constructor
477 | */
478 | Pikaday = function(options)
479 | {
480 | var self = this,
481 | opts = self.config(options);
482 |
483 | self._onMouseDown = function(e)
484 | {
485 | if (!self._v) {
486 | return;
487 | }
488 | e = e || window.event;
489 | var target = e.target || e.srcElement;
490 | if (!target) {
491 | return;
492 | }
493 |
494 | if (!hasClass(target, 'is-disabled')) {
495 | if (hasClass(target, 'pika-button') && !hasClass(target, 'is-empty') && !hasClass(target.parentNode, 'is-disabled')) {
496 | self.setDate(new Date(target.getAttribute('data-pika-year'), target.getAttribute('data-pika-month'), target.getAttribute('data-pika-day')));
497 | if (opts.bound) {
498 | sto(function() {
499 | self.hide();
500 | if (opts.blurFieldOnSelect && opts.field) {
501 | opts.field.blur();
502 | }
503 | }, 100);
504 | }
505 | }
506 | else if (hasClass(target, 'pika-prev')) {
507 | self.prevMonth();
508 | }
509 | else if (hasClass(target, 'pika-next')) {
510 | self.nextMonth();
511 | }
512 | }
513 | if (!hasClass(target, 'pika-select')) {
514 | // if this is touch event prevent mouse events emulation
515 | if (e.preventDefault) {
516 | e.preventDefault();
517 | } else {
518 | e.returnValue = false;
519 | return false;
520 | }
521 | } else {
522 | self._c = true;
523 | }
524 | };
525 |
526 | self._onChange = function(e)
527 | {
528 | e = e || window.event;
529 | var target = e.target || e.srcElement;
530 | if (!target) {
531 | return;
532 | }
533 | if (hasClass(target, 'pika-select-month')) {
534 | self.gotoMonth(target.value);
535 | }
536 | else if (hasClass(target, 'pika-select-year')) {
537 | self.gotoYear(target.value);
538 | }
539 | };
540 |
541 | self._onKeyChange = function(e)
542 | {
543 | e = e || window.event;
544 |
545 | if (self.isVisible()) {
546 |
547 | switch(e.keyCode){
548 | case 13:
549 | case 27:
550 | if (opts.field) {
551 | opts.field.blur();
552 | }
553 | break;
554 | case 37:
555 | self.adjustDate('subtract', 1);
556 | break;
557 | case 38:
558 | self.adjustDate('subtract', 7);
559 | break;
560 | case 39:
561 | self.adjustDate('add', 1);
562 | break;
563 | case 40:
564 | self.adjustDate('add', 7);
565 | break;
566 | case 8:
567 | case 46:
568 | self.setDate(null);
569 | break;
570 | }
571 | }
572 | };
573 |
574 | self._parseFieldValue = function()
575 | {
576 | if (opts.parse) {
577 | return opts.parse(opts.field.value, opts.format);
578 | } else if (hasMoment) {
579 | var date = moment(opts.field.value, opts.format, opts.formatStrict);
580 | return (date && date.isValid()) ? date.toDate() : null;
581 | } else {
582 | return new Date(Date.parse(opts.field.value));
583 | }
584 | };
585 |
586 | self._onInputChange = function(e)
587 | {
588 | var date;
589 |
590 | if (e.firedBy === self) {
591 | return;
592 | }
593 | date = self._parseFieldValue();
594 | if (isDate(date)) {
595 | self.setDate(date);
596 | }
597 | if (!self._v) {
598 | self.show();
599 | }
600 | };
601 |
602 | self._onInputFocus = function()
603 | {
604 | self.show();
605 | };
606 |
607 | self._onInputClick = function()
608 | {
609 | self.show();
610 | };
611 |
612 | self._onInputBlur = function()
613 | {
614 | // IE allows pika div to gain focus; catch blur the input field
615 | var pEl = document.activeElement;
616 | do {
617 | if (hasClass(pEl, 'pika-single')) {
618 | return;
619 | }
620 | }
621 | while ((pEl = pEl.parentNode));
622 |
623 | if (!self._c) {
624 | self._b = sto(function() {
625 | self.hide();
626 | }, 50);
627 | }
628 | self._c = false;
629 | };
630 |
631 | self._onClick = function(e)
632 | {
633 | e = e || window.event;
634 | var target = e.target || e.srcElement,
635 | pEl = target;
636 | if (!target) {
637 | return;
638 | }
639 | if (!hasEventListeners && hasClass(target, 'pika-select')) {
640 | if (!target.onchange) {
641 | target.setAttribute('onchange', 'return;');
642 | addEvent(target, 'change', self._onChange);
643 | }
644 | }
645 | do {
646 | if (hasClass(pEl, 'pika-single') || pEl === opts.trigger) {
647 | return;
648 | }
649 | }
650 | while ((pEl = pEl.parentNode));
651 | if (self._v && target !== opts.trigger && pEl !== opts.trigger) {
652 | self.hide();
653 | }
654 | };
655 |
656 | self.el = document.createElement('div');
657 | self.el.className = 'pika-single' + (opts.isRTL ? ' is-rtl' : '') + (opts.theme ? ' ' + opts.theme : '');
658 |
659 | addEvent(self.el, 'mousedown', self._onMouseDown, true);
660 | addEvent(self.el, 'touchend', self._onMouseDown, true);
661 | addEvent(self.el, 'change', self._onChange);
662 |
663 | if (opts.keyboardInput) {
664 | addEvent(document, 'keydown', self._onKeyChange);
665 | }
666 |
667 | if (opts.field) {
668 | if (opts.container) {
669 | opts.container.appendChild(self.el);
670 | } else if (opts.bound) {
671 | document.body.appendChild(self.el);
672 | } else {
673 | opts.field.parentNode.insertBefore(self.el, opts.field.nextSibling);
674 | }
675 | addEvent(opts.field, 'change', self._onInputChange);
676 |
677 | if (!opts.defaultDate) {
678 | opts.defaultDate = self._parseFieldValue();
679 | opts.setDefaultDate = true;
680 | }
681 | }
682 |
683 | var defDate = opts.defaultDate;
684 |
685 | if (isDate(defDate)) {
686 | if (opts.setDefaultDate) {
687 | self.setDate(defDate, true);
688 | } else {
689 | self.gotoDate(defDate);
690 | }
691 | } else {
692 | self.gotoDate(new Date());
693 | }
694 |
695 | if (opts.bound) {
696 | this.hide();
697 | self.el.className += ' is-bound';
698 | addEvent(opts.trigger, 'click', self._onInputClick);
699 | addEvent(opts.trigger, 'focus', self._onInputFocus);
700 | addEvent(opts.trigger, 'blur', self._onInputBlur);
701 | } else {
702 | this.show();
703 | }
704 | };
705 |
706 |
707 | /**
708 | * public Pikaday API
709 | */
710 | Pikaday.prototype = {
711 |
712 |
713 | /**
714 | * configure functionality
715 | */
716 | config: function(options)
717 | {
718 | if (!this._o) {
719 | this._o = extend({}, defaults, true);
720 | }
721 |
722 | var opts = extend(this._o, options, true);
723 |
724 | opts.isRTL = !!opts.isRTL;
725 |
726 | opts.field = (opts.field && opts.field.nodeName) ? opts.field : null;
727 |
728 | opts.theme = (typeof opts.theme) === 'string' && opts.theme ? opts.theme : null;
729 |
730 | opts.bound = !!(opts.bound !== undefined ? opts.field && opts.bound : opts.field);
731 |
732 | opts.trigger = (opts.trigger && opts.trigger.nodeName) ? opts.trigger : opts.field;
733 |
734 | opts.disableWeekends = !!opts.disableWeekends;
735 |
736 | opts.disableDayFn = (typeof opts.disableDayFn) === 'function' ? opts.disableDayFn : null;
737 |
738 | var nom = parseInt(opts.numberOfMonths, 10) || 1;
739 | opts.numberOfMonths = nom > 4 ? 4 : nom;
740 |
741 | if (!isDate(opts.minDate)) {
742 | opts.minDate = false;
743 | }
744 | if (!isDate(opts.maxDate)) {
745 | opts.maxDate = false;
746 | }
747 | if ((opts.minDate && opts.maxDate) && opts.maxDate < opts.minDate) {
748 | opts.maxDate = opts.minDate = false;
749 | }
750 | if (opts.minDate) {
751 | this.setMinDate(opts.minDate);
752 | }
753 | if (opts.maxDate) {
754 | this.setMaxDate(opts.maxDate);
755 | }
756 |
757 | if (isArray(opts.yearRange)) {
758 | var fallback = new Date().getFullYear() - 10;
759 | opts.yearRange[0] = parseInt(opts.yearRange[0], 10) || fallback;
760 | opts.yearRange[1] = parseInt(opts.yearRange[1], 10) || fallback;
761 | } else {
762 | opts.yearRange = Math.abs(parseInt(opts.yearRange, 10)) || defaults.yearRange;
763 | if (opts.yearRange > 100) {
764 | opts.yearRange = 100;
765 | }
766 | }
767 |
768 | return opts;
769 | },
770 |
771 | /**
772 | * return a formatted string of the current selection (using Moment.js if available)
773 | */
774 | toString: function(format)
775 | {
776 | format = format || this._o.format;
777 | if (!isDate(this._d)) {
778 | return '';
779 | }
780 | if (this._o.toString) {
781 | return this._o.toString(this._d, format);
782 | }
783 | if (hasMoment) {
784 | return moment(this._d).format(format);
785 | }
786 | return this._d.toDateString();
787 | },
788 |
789 | /**
790 | * return a Moment.js object of the current selection (if available)
791 | */
792 | getMoment: function()
793 | {
794 | return hasMoment ? moment(this._d) : null;
795 | },
796 |
797 | /**
798 | * set the current selection from a Moment.js object (if available)
799 | */
800 | setMoment: function(date, preventOnSelect)
801 | {
802 | if (hasMoment && moment.isMoment(date)) {
803 | this.setDate(date.toDate(), preventOnSelect);
804 | }
805 | },
806 |
807 | /**
808 | * return a Date object of the current selection
809 | */
810 | getDate: function()
811 | {
812 | return isDate(this._d) ? new Date(this._d.getTime()) : null;
813 | },
814 |
815 | /**
816 | * set the current selection
817 | */
818 | setDate: function(date, preventOnSelect)
819 | {
820 | if (!date) {
821 | this._d = null;
822 |
823 | if (this._o.field) {
824 | this._o.field.value = '';
825 | fireEvent(this._o.field, 'change', { firedBy: this });
826 | }
827 |
828 | return this.draw();
829 | }
830 | if (typeof date === 'string') {
831 | date = new Date(Date.parse(date));
832 | }
833 | if (!isDate(date)) {
834 | return;
835 | }
836 |
837 | var min = this._o.minDate,
838 | max = this._o.maxDate;
839 |
840 | if (isDate(min) && date < min) {
841 | date = min;
842 | } else if (isDate(max) && date > max) {
843 | date = max;
844 | }
845 |
846 | this._d = new Date(date.getTime());
847 | setToStartOfDay(this._d);
848 | this.gotoDate(this._d);
849 |
850 | if (this._o.field) {
851 | this._o.field.value = this.toString();
852 | fireEvent(this._o.field, 'change', { firedBy: this });
853 | }
854 | if (!preventOnSelect && typeof this._o.onSelect === 'function') {
855 | this._o.onSelect.call(this, this.getDate());
856 | }
857 | },
858 |
859 | /**
860 | * clear and reset the date
861 | */
862 | clear: function()
863 | {
864 | this.setDate(null);
865 | },
866 |
867 | /**
868 | * change view to a specific date
869 | */
870 | gotoDate: function(date)
871 | {
872 | var newCalendar = true;
873 |
874 | if (!isDate(date)) {
875 | return;
876 | }
877 |
878 | if (this.calendars) {
879 | var firstVisibleDate = new Date(this.calendars[0].year, this.calendars[0].month, 1),
880 | lastVisibleDate = new Date(this.calendars[this.calendars.length-1].year, this.calendars[this.calendars.length-1].month, 1),
881 | visibleDate = date.getTime();
882 | // get the end of the month
883 | lastVisibleDate.setMonth(lastVisibleDate.getMonth()+1);
884 | lastVisibleDate.setDate(lastVisibleDate.getDate()-1);
885 | newCalendar = (visibleDate < firstVisibleDate.getTime() || lastVisibleDate.getTime() < visibleDate);
886 | }
887 |
888 | if (newCalendar) {
889 | this.calendars = [{
890 | month: date.getMonth(),
891 | year: date.getFullYear()
892 | }];
893 | if (this._o.mainCalendar === 'right') {
894 | this.calendars[0].month += 1 - this._o.numberOfMonths;
895 | }
896 | }
897 |
898 | this.adjustCalendars();
899 | },
900 |
901 | adjustDate: function(sign, days) {
902 |
903 | var day = this.getDate() || new Date();
904 | var difference = parseInt(days)*24*60*60*1000;
905 |
906 | var newDay;
907 |
908 | if (sign === 'add') {
909 | newDay = new Date(day.valueOf() + difference);
910 | } else if (sign === 'subtract') {
911 | newDay = new Date(day.valueOf() - difference);
912 | }
913 |
914 | this.setDate(newDay);
915 | },
916 |
917 | adjustCalendars: function() {
918 | this.calendars[0] = adjustCalendar(this.calendars[0]);
919 | for (var c = 1; c < this._o.numberOfMonths; c++) {
920 | this.calendars[c] = adjustCalendar({
921 | month: this.calendars[0].month + c,
922 | year: this.calendars[0].year
923 | });
924 | }
925 | this.draw();
926 | },
927 |
928 | gotoToday: function()
929 | {
930 | this.gotoDate(new Date());
931 | },
932 |
933 | /**
934 | * change view to a specific month (zero-index, e.g. 0: January)
935 | */
936 | gotoMonth: function(month)
937 | {
938 | if (!isNaN(month)) {
939 | this.calendars[0].month = parseInt(month, 10);
940 | this.adjustCalendars();
941 | }
942 | },
943 |
944 | nextMonth: function()
945 | {
946 | this.calendars[0].month++;
947 | this.adjustCalendars();
948 | },
949 |
950 | prevMonth: function()
951 | {
952 | this.calendars[0].month--;
953 | this.adjustCalendars();
954 | },
955 |
956 | /**
957 | * change view to a specific full year (e.g. "2012")
958 | */
959 | gotoYear: function(year)
960 | {
961 | if (!isNaN(year)) {
962 | this.calendars[0].year = parseInt(year, 10);
963 | this.adjustCalendars();
964 | }
965 | },
966 |
967 | /**
968 | * change the minDate
969 | */
970 | setMinDate: function(value)
971 | {
972 | if(value instanceof Date) {
973 | setToStartOfDay(value);
974 | this._o.minDate = value;
975 | this._o.minYear = value.getFullYear();
976 | this._o.minMonth = value.getMonth();
977 | } else {
978 | this._o.minDate = defaults.minDate;
979 | this._o.minYear = defaults.minYear;
980 | this._o.minMonth = defaults.minMonth;
981 | this._o.startRange = defaults.startRange;
982 | }
983 |
984 | this.draw();
985 | },
986 |
987 | /**
988 | * change the maxDate
989 | */
990 | setMaxDate: function(value)
991 | {
992 | if(value instanceof Date) {
993 | setToStartOfDay(value);
994 | this._o.maxDate = value;
995 | this._o.maxYear = value.getFullYear();
996 | this._o.maxMonth = value.getMonth();
997 | } else {
998 | this._o.maxDate = defaults.maxDate;
999 | this._o.maxYear = defaults.maxYear;
1000 | this._o.maxMonth = defaults.maxMonth;
1001 | this._o.endRange = defaults.endRange;
1002 | }
1003 |
1004 | this.draw();
1005 | },
1006 |
1007 | setStartRange: function(value)
1008 | {
1009 | this._o.startRange = value;
1010 | },
1011 |
1012 | setEndRange: function(value)
1013 | {
1014 | this._o.endRange = value;
1015 | },
1016 |
1017 | /**
1018 | * refresh the HTML
1019 | */
1020 | draw: function(force)
1021 | {
1022 | if (!this._v && !force) {
1023 | return;
1024 | }
1025 | var opts = this._o,
1026 | minYear = opts.minYear,
1027 | maxYear = opts.maxYear,
1028 | minMonth = opts.minMonth,
1029 | maxMonth = opts.maxMonth,
1030 | html = '',
1031 | randId;
1032 |
1033 | if (this._y <= minYear) {
1034 | this._y = minYear;
1035 | if (!isNaN(minMonth) && this._m < minMonth) {
1036 | this._m = minMonth;
1037 | }
1038 | }
1039 | if (this._y >= maxYear) {
1040 | this._y = maxYear;
1041 | if (!isNaN(maxMonth) && this._m > maxMonth) {
1042 | this._m = maxMonth;
1043 | }
1044 | }
1045 |
1046 | for (var c = 0; c < opts.numberOfMonths; c++) {
1047 | randId = 'pika-title-' + Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 2);
1048 | html += '' + renderTitle(this, c, this.calendars[c].year, this.calendars[c].month, this.calendars[0].year, randId) + this.render(this.calendars[c].year, this.calendars[c].month, randId) + '
';
1049 | }
1050 |
1051 | this.el.innerHTML = html;
1052 |
1053 | if (opts.bound) {
1054 | if(opts.field.type !== 'hidden') {
1055 | sto(function() {
1056 | opts.trigger.focus();
1057 | }, 1);
1058 | }
1059 | }
1060 |
1061 | if (typeof this._o.onDraw === 'function') {
1062 | this._o.onDraw(this);
1063 | }
1064 |
1065 | if (opts.bound) {
1066 | // let the screen reader user know to use arrow keys
1067 | opts.field.setAttribute('aria-label', opts.ariaLabel);
1068 | }
1069 | },
1070 |
1071 | adjustPosition: function()
1072 | {
1073 | var field, pEl, width, height, viewportWidth, viewportHeight, scrollTop, left, top, clientRect, leftAligned, bottomAligned;
1074 |
1075 | if (this._o.container) return;
1076 |
1077 | this.el.style.position = 'absolute';
1078 |
1079 | field = this._o.trigger;
1080 | pEl = field;
1081 | width = this.el.offsetWidth;
1082 | height = this.el.offsetHeight;
1083 | viewportWidth = window.innerWidth || document.documentElement.clientWidth;
1084 | viewportHeight = window.innerHeight || document.documentElement.clientHeight;
1085 | scrollTop = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
1086 | leftAligned = true;
1087 | bottomAligned = true;
1088 |
1089 | if (typeof field.getBoundingClientRect === 'function') {
1090 | clientRect = field.getBoundingClientRect();
1091 | left = clientRect.left + window.pageXOffset;
1092 | top = clientRect.bottom + window.pageYOffset;
1093 | } else {
1094 | left = pEl.offsetLeft;
1095 | top = pEl.offsetTop + pEl.offsetHeight;
1096 | while((pEl = pEl.offsetParent)) {
1097 | left += pEl.offsetLeft;
1098 | top += pEl.offsetTop;
1099 | }
1100 | }
1101 |
1102 | // default position is bottom & left
1103 | if ((this._o.reposition && left + width > viewportWidth) ||
1104 | (
1105 | this._o.position.indexOf('right') > -1 &&
1106 | left - width + field.offsetWidth > 0
1107 | )
1108 | ) {
1109 | left = left - width + field.offsetWidth;
1110 | leftAligned = false;
1111 | }
1112 | if ((this._o.reposition && top + height > viewportHeight + scrollTop) ||
1113 | (
1114 | this._o.position.indexOf('top') > -1 &&
1115 | top - height - field.offsetHeight > 0
1116 | )
1117 | ) {
1118 | top = top - height - field.offsetHeight;
1119 | bottomAligned = false;
1120 | }
1121 |
1122 | this.el.style.left = left + 'px';
1123 | this.el.style.top = top + 'px';
1124 |
1125 | addClass(this.el, leftAligned ? 'left-aligned' : 'right-aligned');
1126 | addClass(this.el, bottomAligned ? 'bottom-aligned' : 'top-aligned');
1127 | removeClass(this.el, !leftAligned ? 'left-aligned' : 'right-aligned');
1128 | removeClass(this.el, !bottomAligned ? 'bottom-aligned' : 'top-aligned');
1129 | },
1130 |
1131 | /**
1132 | * render HTML for a particular month
1133 | */
1134 | render: function(year, month, randId)
1135 | {
1136 | var opts = this._o,
1137 | now = new Date(),
1138 | days = getDaysInMonth(year, month),
1139 | before = new Date(year, month, 1).getDay(),
1140 | data = [],
1141 | row = [];
1142 | setToStartOfDay(now);
1143 | if (opts.firstDay > 0) {
1144 | before -= opts.firstDay;
1145 | if (before < 0) {
1146 | before += 7;
1147 | }
1148 | }
1149 | var previousMonth = month === 0 ? 11 : month - 1,
1150 | nextMonth = month === 11 ? 0 : month + 1,
1151 | yearOfPreviousMonth = month === 0 ? year - 1 : year,
1152 | yearOfNextMonth = month === 11 ? year + 1 : year,
1153 | daysInPreviousMonth = getDaysInMonth(yearOfPreviousMonth, previousMonth);
1154 | var cells = days + before,
1155 | after = cells;
1156 | while(after > 7) {
1157 | after -= 7;
1158 | }
1159 | cells += 7 - after;
1160 | var isWeekSelected = false;
1161 | for (var i = 0, r = 0; i < cells; i++)
1162 | {
1163 | var day = new Date(year, month, 1 + (i - before)),
1164 | isSelected = isDate(this._d) ? compareDates(day, this._d) : false,
1165 | isToday = compareDates(day, now),
1166 | hasEvent = opts.events.indexOf(day.toDateString()) !== -1 ? true : false,
1167 | isEmpty = i < before || i >= (days + before),
1168 | dayNumber = 1 + (i - before),
1169 | monthNumber = month,
1170 | yearNumber = year,
1171 | isStartRange = opts.startRange && compareDates(opts.startRange, day),
1172 | isEndRange = opts.endRange && compareDates(opts.endRange, day),
1173 | isInRange = opts.startRange && opts.endRange && opts.startRange < day && day < opts.endRange,
1174 | isDisabled = (opts.minDate && day < opts.minDate) ||
1175 | (opts.maxDate && day > opts.maxDate) ||
1176 | (opts.disableWeekends && isWeekend(day)) ||
1177 | (opts.disableDayFn && opts.disableDayFn(day));
1178 |
1179 | if (isEmpty) {
1180 | if (i < before) {
1181 | dayNumber = daysInPreviousMonth + dayNumber;
1182 | monthNumber = previousMonth;
1183 | yearNumber = yearOfPreviousMonth;
1184 | } else {
1185 | dayNumber = dayNumber - days;
1186 | monthNumber = nextMonth;
1187 | yearNumber = yearOfNextMonth;
1188 | }
1189 | }
1190 |
1191 | var dayConfig = {
1192 | day: dayNumber,
1193 | month: monthNumber,
1194 | year: yearNumber,
1195 | hasEvent: hasEvent,
1196 | isSelected: isSelected,
1197 | isToday: isToday,
1198 | isDisabled: isDisabled,
1199 | isEmpty: isEmpty,
1200 | isStartRange: isStartRange,
1201 | isEndRange: isEndRange,
1202 | isInRange: isInRange,
1203 | showDaysInNextAndPreviousMonths: opts.showDaysInNextAndPreviousMonths,
1204 | enableSelectionDaysInNextAndPreviousMonths: opts.enableSelectionDaysInNextAndPreviousMonths
1205 | };
1206 |
1207 | if (opts.pickWholeWeek && isSelected) {
1208 | isWeekSelected = true;
1209 | }
1210 |
1211 | row.push(renderDay(dayConfig));
1212 |
1213 | if (++r === 7) {
1214 | if (opts.showWeekNumber) {
1215 | row.unshift(renderWeek(i - before, month, year, opts.firstWeekOfYearMinDays));
1216 | }
1217 | data.push(renderRow(row, opts.isRTL, opts.pickWholeWeek, isWeekSelected));
1218 | row = [];
1219 | r = 0;
1220 | isWeekSelected = false;
1221 | }
1222 | }
1223 | return renderTable(opts, data, randId);
1224 | },
1225 |
1226 | isVisible: function()
1227 | {
1228 | return this._v;
1229 | },
1230 |
1231 | show: function()
1232 | {
1233 | if (!this.isVisible()) {
1234 | this._v = true;
1235 | this.draw();
1236 | removeClass(this.el, 'is-hidden');
1237 | if (this._o.bound) {
1238 | addEvent(document, 'click', this._onClick);
1239 | this.adjustPosition();
1240 | }
1241 | if (typeof this._o.onOpen === 'function') {
1242 | this._o.onOpen.call(this);
1243 | }
1244 | }
1245 | },
1246 |
1247 | hide: function()
1248 | {
1249 | var v = this._v;
1250 | if (v !== false) {
1251 | if (this._o.bound) {
1252 | removeEvent(document, 'click', this._onClick);
1253 | }
1254 |
1255 | if (!this._o.container) {
1256 | this.el.style.position = 'static'; // reset
1257 | this.el.style.left = 'auto';
1258 | this.el.style.top = 'auto';
1259 | }
1260 | addClass(this.el, 'is-hidden');
1261 | this._v = false;
1262 | if (v !== undefined && typeof this._o.onClose === 'function') {
1263 | this._o.onClose.call(this);
1264 | }
1265 | }
1266 | },
1267 |
1268 | /**
1269 | * GAME OVER
1270 | */
1271 | destroy: function()
1272 | {
1273 | var opts = this._o;
1274 |
1275 | this.hide();
1276 | removeEvent(this.el, 'mousedown', this._onMouseDown, true);
1277 | removeEvent(this.el, 'touchend', this._onMouseDown, true);
1278 | removeEvent(this.el, 'change', this._onChange);
1279 | if (opts.keyboardInput) {
1280 | removeEvent(document, 'keydown', this._onKeyChange);
1281 | }
1282 | if (opts.field) {
1283 | removeEvent(opts.field, 'change', this._onInputChange);
1284 | if (opts.bound) {
1285 | removeEvent(opts.trigger, 'click', this._onInputClick);
1286 | removeEvent(opts.trigger, 'focus', this._onInputFocus);
1287 | removeEvent(opts.trigger, 'blur', this._onInputBlur);
1288 | }
1289 | }
1290 | if (this.el.parentNode) {
1291 | this.el.parentNode.removeChild(this.el);
1292 | }
1293 | }
1294 |
1295 | };
1296 |
1297 | return Pikaday;
1298 | }));
1299 |
--------------------------------------------------------------------------------
/chrome/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Wiki Journey
5 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
78 |
79 |
80 |
81 |
82 | d.children ? Object.values(d.children) : []);
35 |
36 | const dx = 64;
37 | const dy = 192;
38 | const leftTextOffset = 128; // Estimated offset to accommodate leftmost text
39 | const width = (root.height + 1) * dy + leftTextOffset;
40 | const tree = d3.tree().nodeSize([dx, dy]);
41 |
42 | root.sort((a, b) => d3.ascending(a.data.title, b.data.title));
43 | tree(root);
44 |
45 | let x0 = Infinity;
46 | let x1 = -x0;
47 | root.each(d => {
48 | if (d.x > x1) x1 = d.x;
49 | if (d.x < x0) x0 = d.x;
50 | });
51 |
52 | const height = x1 - x0 + dx * 2;
53 |
54 | // Since you're in a browser extension popup, use d3.select instead of d3.create
55 | const svg = d3.select("#graph").append("svg")
56 | .attr("width", width)
57 | .attr("height", height)
58 | .attr("viewBox", [-leftTextOffset, x0 - dx, width + leftTextOffset, height]) // Adjust viewBox by leftTextOffset
59 | .style("font", "18px sans-serif");
60 |
61 | const link = svg.append("g")
62 | .attr("fill", "none")
63 | .attr("stroke", "#555")
64 | .attr("stroke-opacity", 0.4)
65 | .attr("stroke-width", 1.5)
66 | .selectAll("path")
67 | .data(root.links())
68 | .join("path")
69 | .attr("d", d3.linkHorizontal()
70 | .x(d => d.y)
71 | .y(d => d.x));
72 |
73 | const node = svg.append("g")
74 | .attr("stroke-linejoin", "round")
75 | .attr("stroke-width", 3)
76 | .selectAll("g")
77 | .data(root.descendants())
78 | .join("g")
79 | .attr("transform", d => `translate(${d.y},${d.x})`);
80 |
81 | node.append("circle")
82 | .attr("fill", d => d.children ? "#555" : "#999")
83 | .attr("r", 2.5);
84 |
85 | node.append("text")
86 | .attr("dy", "0.31em")
87 | .attr("x", d => d.children ? -6 : 6)
88 | .attr("text-anchor", d => d.children ? "end" : "start")
89 | .text(d => d.data.title)
90 | .attr("fill", "black") // Set the text color
91 | .clone(true).lower()
92 | .attr("stroke", "white"); // Remove the stroke or set to a contrasting color if needed
93 |
94 |
95 | // Add this SVG to your popup
96 | document.querySelector("#graph").appendChild(svg.node());
97 | }
98 | const today = new Date();
99 | document.getElementById('datePicker').value = toLocalISOString(today); // Set date picker value to today
100 | loadJourney(toLocalISOString(today));
101 | });
102 |
--------------------------------------------------------------------------------
/chrome/wikiJourneyChrome.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demegire/wiki-journey/e6dfb2d17f4dcd58c17cfa152597abfc0f2dff84/chrome/wikiJourneyChrome.zip
--------------------------------------------------------------------------------
/firefox/LinBiolinum_aS.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demegire/wiki-journey/e6dfb2d17f4dcd58c17cfa152597abfc0f2dff84/firefox/LinBiolinum_aS.ttf
--------------------------------------------------------------------------------
/firefox/background.js:
--------------------------------------------------------------------------------
1 | let articlesTree = {};
2 | let lastJourneyDate = '';
3 |
4 | function toLocalISOString(date) {
5 | var localOffset = date.getTimezoneOffset() * 60000; // offset in milliseconds
6 | var localTime = new Date(date.getTime() - localOffset);
7 | return localTime.toISOString().split('T')[0];
8 | }
9 |
10 | function addArticleToTree(title, url, parentUrl) {
11 | // If there's no parent, this is a root article
12 | if (!parentUrl) {
13 | articlesTree[url] = articlesTree[url] || { title, children: {} };
14 | } else {
15 | // Recursively search for the parent node in the tree
16 | const parentNode = findParentNode(articlesTree, parentUrl);
17 | if (parentNode) {
18 | // Add the article under its parent
19 | parentNode.children[url] = parentNode.children[url] || { title, children: {} };
20 | } else {
21 | // If the parent node is not found, treat it as a root article
22 | articlesTree[url] = articlesTree[url] || { title, children: {} };
23 | }
24 | }
25 | // Save the updated tree to storage
26 | saveCurrentDayJourney()
27 | }
28 |
29 | function findParentNode(tree, parentUrl) {
30 | for (const url in tree) {
31 | if (url === parentUrl) {
32 | return tree[url];
33 | }
34 | const foundInChildren = findParentNode(tree[url].children, parentUrl);
35 | if (foundInChildren) {
36 | return foundInChildren;
37 | }
38 | }
39 | return null;
40 | }
41 |
42 | async function handleMessage(request, sender, sendResponse) {
43 | if (request.article) {
44 | await checkNewDay(); // Ensure this completes before proceeding
45 | const { title, url, parent } = request.article;
46 | addArticleToTree(title, url, parent);
47 | }
48 | }
49 |
50 |
51 | function saveCurrentDayJourney() {
52 | const dateString = toLocalISOString(new Date());
53 | const key = `journey_${dateString}`;
54 | browser.storage.local.set({ [key]: articlesTree });
55 | }
56 |
57 | // Load the articles tree from storage or start a new one each day
58 | function loadOrInitializeTree() {
59 | const dateString = toLocalISOString(new Date());
60 | const key = `journey_${dateString}`;
61 | browser.storage.local.get([key], (result) => {
62 | if (result[key]) {
63 | articlesTree = result[key];
64 | } else {
65 | articlesTree = {}; // If there's no entry for today, start a new tree
66 | browser.storage.local.set({ [key]: articlesTree });
67 | }
68 | });
69 | }
70 |
71 | function checkNewDay() {
72 | const dateString = toLocalISOString(new Date());
73 | const key = `journey_${dateString}`;
74 | return new Promise((resolve, reject) => {
75 | browser.storage.local.get([key], (result) => {
76 | if (result[key]) {
77 | articlesTree = result[key];
78 | resolve();
79 | } else {
80 | articlesTree = {}; // If there's no entry for today, start a new tree
81 | browser.storage.local.set({ [key]: articlesTree }, resolve);
82 | }
83 | });
84 | });
85 | }
86 |
87 | loadOrInitializeTree();
88 |
89 | browser.runtime.onMessage.addListener(handleMessage);
90 |
--------------------------------------------------------------------------------
/firefox/buttons.js:
--------------------------------------------------------------------------------
1 | document.getElementById('shareButton').addEventListener('click', function() {
2 | function drawImageWithText() {
3 | const svgElement = document.querySelector('svg');
4 | const {width, height} = svgElement.getBoundingClientRect();
5 |
6 | // Create a canvas element to render the SVG
7 | const canvas = document.createElement('canvas');
8 | canvas.width = width;
9 | canvas.height = height;
10 | const ctx = canvas.getContext('2d');
11 |
12 | // Fill the canvas with a white background
13 | ctx.fillStyle = 'white'; // Set fill color to white
14 | ctx.fillRect(0, 0, canvas.width, canvas.height); // Fill the entire canvas
15 | ctx.font = '16px "LinBiolinum_aS"';
16 |
17 |
18 | // Use a Blob to convert SVG to a URL that can be drawn on Canvas
19 | let data = new XMLSerializer().serializeToString(svgElement);
20 | // Assume viewBox is like "viewBox = minX minY width height"
21 | const viewBox = svgElement.getAttribute('viewBox');
22 | if (viewBox) {
23 | const viewBoxValues = viewBox.split(',');
24 | if (viewBoxValues.length === 4) {
25 | minX = parseFloat(viewBoxValues[0]);
26 | minY = parseFloat(viewBoxValues[1]);
27 | }
28 | }
29 |
30 | // Adjust the watermark position based on viewBox
31 | const watermarkString = `Wiki Journey`;
32 | data = data.replace('', `${watermarkString}`);
33 | const svgBlob = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
34 | const url = URL.createObjectURL(svgBlob);
35 |
36 | const image = new Image();
37 | image.onload = function() {
38 | // Draw the SVG onto the Canvas
39 | ctx.drawImage(image, 0, 0);
40 | URL.revokeObjectURL(url);
41 |
42 | // Convert Canvas to PNG and trigger download
43 | canvas.toBlob(function(blob) {
44 | // Create a new URL for the blob
45 | const blobUrl = URL.createObjectURL(blob);
46 |
47 | // Create a temporary anchor element and trigger the download
48 | const downloadLink = document.createElement('a');
49 | downloadLink.href = blobUrl;
50 | downloadLink.download = 'wikijourney.png'; // Specify the download file name
51 | document.body.appendChild(downloadLink); // Append to the document
52 | downloadLink.click(); // Trigger the download
53 |
54 | // Clean up by revoking the blob URL and removing the temporary link
55 | URL.revokeObjectURL(blobUrl);
56 | document.body.removeChild(downloadLink);
57 | }, 'image/png'); // Specify PNG format here
58 | };
59 | image.src = url;
60 | }
61 |
62 | if ('fonts' in document) {
63 | document.fonts.load('16px "LinBiolinum_aS"').then(function () {
64 | // This ensures the font is available for the canvas
65 | drawImageWithText();
66 | });
67 | } else {
68 | // Fallback for browsers that do not support Font Loading API
69 | drawImageWithText();
70 | }
71 | });
72 | document.getElementById('infoButton').addEventListener('click', function() {
73 | const url = 'https://demegire.github.io/wikijourney'; // Replace with the URL you want to redirect to
74 | window.open(url, '_blank'); // Open in a new tab
75 | });
76 |
77 |
--------------------------------------------------------------------------------
/firefox/content.js:
--------------------------------------------------------------------------------
1 | function isValidWikipediaArticle() {
2 | // Check if the URL follows the typical pattern for Wikipedia articles
3 | // This regex matches the main article pages but excludes special pages, user pages, etc.
4 | const urlRegex = /^https?:\/\/[a-z]+\.wikipedia\.org\/(?:wiki|zh-cn|zh-hk|zh-mo|zh-my|zh-sg|zh-tw)\/(?!Special:|User:|Wikipedia:|File:|MediaWiki:|Template:|Help:|Category:|Portal:|Draft:|TimedText:|Module:|Gadget:|Gadget_definition:|Education_Program:|Topic:|Book:|Special:Search|Special:RecentChanges).+/;
5 | return urlRegex.test(window.location.href);
6 | }
7 |
8 | function sendArticleInfo() {
9 | if (isValidWikipediaArticle()) {
10 | const title = document.querySelector('h1').innerText;
11 | const url = window.location.href;
12 | const parent = document.referrer.includes("wikipedia.org") && isValidWikipediaArticle(document.referrer) ? document.referrer : null;
13 |
14 | browser.runtime.sendMessage({
15 | article: { title, url, parent }
16 | });
17 | }
18 | }
19 |
20 | // Execute when the script loads
21 | sendArticleInfo();
22 |
--------------------------------------------------------------------------------
/firefox/downloads.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demegire/wiki-journey/e6dfb2d17f4dcd58c17cfa152597abfc0f2dff84/firefox/downloads.png
--------------------------------------------------------------------------------
/firefox/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demegire/wiki-journey/e6dfb2d17f4dcd58c17cfa152597abfc0f2dff84/firefox/icon.png
--------------------------------------------------------------------------------
/firefox/info.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demegire/wiki-journey/e6dfb2d17f4dcd58c17cfa152597abfc0f2dff84/firefox/info.png
--------------------------------------------------------------------------------
/firefox/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Wiki Journey",
4 | "version": "1.2",
5 | "description": "Visualizes your daily Wikipedia adventures",
6 | "permissions": [
7 | "tabs",
8 | "storage",
9 | "*://*.wikipedia.org/*"
10 | ],
11 | "background": {
12 | "scripts": ["background.js"]
13 | },
14 | "content_scripts": [
15 | {
16 | "matches": ["*://*.wikipedia.org/*"],
17 | "js": ["content.js"]
18 | }
19 | ],
20 | "browser_action": {
21 | "default_popup": "popup.html",
22 | "default_icon": "icon.png"
23 | },
24 | "icons": {
25 | "48": "icon.png"
26 | },
27 | "content_security_policy": "script-src 'self'; object-src 'self'",
28 | "web_accessible_resources": [
29 | "d3.v7.min.js",
30 | "pikaday.js",
31 | "LinBiolinum_aS.ttf"
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/firefox/pikaday.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 |
3 | /*!
4 | * Pikaday
5 | * Copyright © 2014 David Bushell | BSD & MIT license | https://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 |
30 | .pika-single.is-hidden {
31 | display: none;
32 | }
33 |
34 | .pika-single.is-bound {
35 | position: absolute;
36 | box-shadow: 0 5px 15px -5px rgba(0,0,0,.5);
37 | }
38 |
39 | .pika-lendar {
40 | float: left;
41 | width: 240px;
42 | margin: 8px;
43 | }
44 |
45 | .pika-title {
46 | position: relative;
47 | text-align: center;
48 | }
49 |
50 | .pika-label {
51 | display: inline-block;
52 | position: relative;
53 | z-index: 9999;
54 | overflow: hidden;
55 | margin: 0;
56 | padding: 5px 3px;
57 | font-size: 14px;
58 | line-height: 20px;
59 | font-weight: bold;
60 | background-color: #fff;
61 | }
62 | .pika-title select {
63 | cursor: pointer;
64 | position: absolute;
65 | z-index: 9998;
66 | margin: 0;
67 | left: 0;
68 | top: 5px;
69 | opacity: 0;
70 | }
71 |
72 | .pika-prev,
73 | .pika-next {
74 | display: block;
75 | cursor: pointer;
76 | position: relative;
77 | outline: none;
78 | border: 0;
79 | padding: 0;
80 | width: 20px;
81 | height: 30px;
82 | /* hide text using text-indent trick, using width value (it's enough) */
83 | text-indent: 20px;
84 | white-space: nowrap;
85 | overflow: hidden;
86 | background-color: transparent;
87 | background-position: center center;
88 | background-repeat: no-repeat;
89 | background-size: 75% 75%;
90 | opacity: .5;
91 | }
92 |
93 | .pika-prev:hover,
94 | .pika-next:hover {
95 | opacity: 1;
96 | }
97 |
98 | .pika-prev,
99 | .is-rtl .pika-next {
100 | float: left;
101 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAUklEQVR42u3VMQoAIBADQf8Pgj+OD9hG2CtONJB2ymQkKe0HbwAP0xucDiQWARITIDEBEnMgMQ8S8+AqBIl6kKgHiXqQqAeJepBo/z38J/U0uAHlaBkBl9I4GwAAAABJRU5ErkJggg==');
102 | }
103 |
104 | .pika-next,
105 | .is-rtl .pika-prev {
106 | float: right;
107 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAU0lEQVR42u3VOwoAMAgE0dwfAnNjU26bYkBCFGwfiL9VVWoO+BJ4Gf3gtsEKKoFBNTCoCAYVwaAiGNQGMUHMkjGbgjk2mIONuXo0nC8XnCf1JXgArVIZAQh5TKYAAAAASUVORK5CYII=');
108 | }
109 |
110 | .pika-prev.is-disabled,
111 | .pika-next.is-disabled {
112 | cursor: default;
113 | opacity: .2;
114 | }
115 |
116 | .pika-select {
117 | display: inline-block;
118 | }
119 |
120 | .pika-table {
121 | width: 100%;
122 | border-collapse: collapse;
123 | border-spacing: 0;
124 | border: 0;
125 | }
126 |
127 | .pika-table th,
128 | .pika-table td {
129 | width: 14.285714285714286%;
130 | padding: 0;
131 | }
132 |
133 | .pika-table th {
134 | color: #999;
135 | font-size: 12px;
136 | line-height: 25px;
137 | font-weight: bold;
138 | text-align: center;
139 | }
140 |
141 | .pika-button {
142 | cursor: pointer;
143 | display: block;
144 | box-sizing: border-box;
145 | -moz-box-sizing: border-box;
146 | outline: none;
147 | border: 0;
148 | margin: 0;
149 | width: 100%;
150 | padding: 5px;
151 | color: #666;
152 | font-size: 12px;
153 | line-height: 15px;
154 | text-align: right;
155 | background: #f5f5f5;
156 | height: initial;
157 | }
158 |
159 | .pika-week {
160 | font-size: 11px;
161 | color: #999;
162 | }
163 |
164 | .is-today .pika-button {
165 | color: #33aaff;
166 | font-weight: bold;
167 | }
168 |
169 | .is-selected .pika-button,
170 | .has-event .pika-button {
171 | color: #fff;
172 | font-weight: bold;
173 | background: #33aaff;
174 | box-shadow: inset 0 1px 3px #178fe5;
175 | border-radius: 3px;
176 | }
177 |
178 | .has-event .pika-button {
179 | background: #005da9;
180 | box-shadow: inset 0 1px 3px #0076c9;
181 | }
182 |
183 | .is-disabled .pika-button,
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 | .is-outside-current-month .pika-button {
210 | color: #999;
211 | opacity: .3;
212 | }
213 |
214 | .is-selection-disabled {
215 | pointer-events: none;
216 | cursor: default;
217 | }
218 |
219 | .pika-button:hover,
220 | .pika-row.pick-whole-week:hover .pika-button {
221 | color: #fff;
222 | background: #ff8000;
223 | box-shadow: none;
224 | border-radius: 3px;
225 | }
226 |
227 | /* styling for abbr */
228 | .pika-table abbr {
229 | border-bottom: none;
230 | cursor: help;
231 | }
232 |
--------------------------------------------------------------------------------
/firefox/pikaday.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Pikaday
3 | *
4 | * Copyright © 2014 David Bushell | BSD & MIT license | https://github.com/Pikaday/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 | trim = function(str)
63 | {
64 | return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g,'');
65 | },
66 |
67 | hasClass = function(el, cn)
68 | {
69 | return (' ' + el.className + ' ').indexOf(' ' + cn + ' ') !== -1;
70 | },
71 |
72 | addClass = function(el, cn)
73 | {
74 | if (!hasClass(el, cn)) {
75 | el.className = (el.className === '') ? cn : el.className + ' ' + cn;
76 | }
77 | },
78 |
79 | removeClass = function(el, cn)
80 | {
81 | el.className = trim((' ' + el.className + ' ').replace(' ' + cn + ' ', ' '));
82 | },
83 |
84 | isArray = function(obj)
85 | {
86 | return (/Array/).test(Object.prototype.toString.call(obj));
87 | },
88 |
89 | isDate = function(obj)
90 | {
91 | return (/Date/).test(Object.prototype.toString.call(obj)) && !isNaN(obj.getTime());
92 | },
93 |
94 | isWeekend = function(date)
95 | {
96 | var day = date.getDay();
97 | return day === 0 || day === 6;
98 | },
99 |
100 | isLeapYear = function(year)
101 | {
102 | // solution lifted from date.js (MIT license): https://github.com/datejs/Datejs
103 | return ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0);
104 | },
105 |
106 | getDaysInMonth = function(year, month)
107 | {
108 | return [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
109 | },
110 |
111 | setToStartOfDay = function(date)
112 | {
113 | if (isDate(date)) date.setHours(0,0,0,0);
114 | },
115 |
116 | compareDates = function(a,b)
117 | {
118 | // weak date comparison (use setToStartOfDay(date) to ensure correct result)
119 | return a.getTime() === b.getTime();
120 | },
121 |
122 | extend = function(to, from, overwrite)
123 | {
124 | var prop, hasProp;
125 | for (prop in from) {
126 | hasProp = to[prop] !== undefined;
127 | if (hasProp && typeof from[prop] === 'object' && from[prop] !== null && from[prop].nodeName === undefined) {
128 | if (isDate(from[prop])) {
129 | if (overwrite) {
130 | to[prop] = new Date(from[prop].getTime());
131 | }
132 | }
133 | else if (isArray(from[prop])) {
134 | if (overwrite) {
135 | to[prop] = from[prop].slice(0);
136 | }
137 | } else {
138 | to[prop] = extend({}, from[prop], overwrite);
139 | }
140 | } else if (overwrite || !hasProp) {
141 | to[prop] = from[prop];
142 | }
143 | }
144 | return to;
145 | },
146 |
147 | fireEvent = function(el, eventName, data)
148 | {
149 | var ev;
150 |
151 | if (document.createEvent) {
152 | ev = document.createEvent('HTMLEvents');
153 | ev.initEvent(eventName, true, false);
154 | ev = extend(ev, data);
155 | el.dispatchEvent(ev);
156 | } else if (document.createEventObject) {
157 | ev = document.createEventObject();
158 | ev = extend(ev, data);
159 | el.fireEvent('on' + eventName, ev);
160 | }
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 | // data-attribute on the input field with an aria assistance text (only applied when `bound` is set)
187 | ariaLabel: 'Use the arrow keys to pick a date',
188 |
189 | // position of the datepicker, relative to the field (default to bottom & left)
190 | // ('bottom' & 'left' keywords are not used, 'top' & 'right' are modifier on the bottom/left position)
191 | position: 'bottom left',
192 |
193 | // automatically fit in the viewport even if it means repositioning from the position option
194 | reposition: true,
195 |
196 | // the default output format for `.toString()` and `field` value
197 | format: 'YYYY-MM-DD',
198 |
199 | // the toString function which gets passed a current date object and format
200 | // and returns a string
201 | toString: null,
202 |
203 | // used to create date object from current input string
204 | parse: null,
205 |
206 | // the initial date to view when first opened
207 | defaultDate: null,
208 |
209 | // make the `defaultDate` the initial selected value
210 | setDefaultDate: false,
211 |
212 | // first day of week (0: Sunday, 1: Monday etc)
213 | firstDay: 0,
214 |
215 | // minimum number of days in the week that gets week number one
216 | // default ISO 8601, week 01 is the week with the first Thursday (4)
217 | firstWeekOfYearMinDays: 4,
218 |
219 | // the default flag for moment's strict date parsing
220 | formatStrict: false,
221 |
222 | // the minimum/earliest date that can be selected
223 | minDate: null,
224 | // the maximum/latest date that can be selected
225 | maxDate: null,
226 |
227 | // number of years either side, or array of upper/lower range
228 | yearRange: 10,
229 |
230 | // show week numbers at head of row
231 | showWeekNumber: false,
232 |
233 | // Week picker mode
234 | pickWholeWeek: false,
235 |
236 | // used internally (don't config outside)
237 | minYear: 0,
238 | maxYear: 9999,
239 | minMonth: undefined,
240 | maxMonth: undefined,
241 |
242 | startRange: null,
243 | endRange: null,
244 |
245 | isRTL: false,
246 |
247 | // Additional text to append to the year in the calendar title
248 | yearSuffix: '',
249 |
250 | // Render the month after year in the calendar title
251 | showMonthAfterYear: false,
252 |
253 | // Render days of the calendar grid that fall in the next or previous month
254 | showDaysInNextAndPreviousMonths: false,
255 |
256 | // Allows user to select days that fall in the next or previous month
257 | enableSelectionDaysInNextAndPreviousMonths: false,
258 |
259 | // how many months are visible
260 | numberOfMonths: 1,
261 |
262 | // when numberOfMonths is used, this will help you to choose where the main calendar will be (default `left`, can be set to `right`)
263 | // only used for the first display or when a selected date is not visible
264 | mainCalendar: 'left',
265 |
266 | // Specify a DOM element to render the calendar in
267 | container: undefined,
268 |
269 | // Blur field when date is selected
270 | blurFieldOnSelect : true,
271 |
272 | // internationalization
273 | i18n: {
274 | previousMonth : 'Previous Month',
275 | nextMonth : 'Next Month',
276 | months : ['January','February','March','April','May','June','July','August','September','October','November','December'],
277 | weekdays : ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
278 | weekdaysShort : ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']
279 | },
280 |
281 | // Theme Classname
282 | theme: null,
283 |
284 | // events array
285 | events: [],
286 |
287 | // callback function
288 | onSelect: null,
289 | onOpen: null,
290 | onClose: null,
291 | onDraw: null,
292 |
293 | // Enable keyboard input
294 | keyboardInput: true
295 | },
296 |
297 |
298 | /**
299 | * templating functions to abstract HTML rendering
300 | */
301 | renderDayName = function(opts, day, abbr)
302 | {
303 | day += opts.firstDay;
304 | while (day >= 7) {
305 | day -= 7;
306 | }
307 | return abbr ? opts.i18n.weekdaysShort[day] : opts.i18n.weekdays[day];
308 | },
309 |
310 | renderDay = function(opts)
311 | {
312 | var arr = [];
313 | var ariaSelected = 'false';
314 | if (opts.isEmpty) {
315 | if (opts.showDaysInNextAndPreviousMonths) {
316 | arr.push('is-outside-current-month');
317 |
318 | if(!opts.enableSelectionDaysInNextAndPreviousMonths) {
319 | arr.push('is-selection-disabled');
320 | }
321 |
322 | } else {
323 | return ' | ';
324 | }
325 | }
326 | if (opts.isDisabled) {
327 | arr.push('is-disabled');
328 | }
329 | if (opts.isToday) {
330 | arr.push('is-today');
331 | }
332 | if (opts.isSelected) {
333 | arr.push('is-selected');
334 | ariaSelected = 'true';
335 | }
336 | if (opts.hasEvent) {
337 | arr.push('has-event');
338 | }
339 | if (opts.isInRange) {
340 | arr.push('is-inrange');
341 | }
342 | if (opts.isStartRange) {
343 | arr.push('is-startrange');
344 | }
345 | if (opts.isEndRange) {
346 | arr.push('is-endrange');
347 | }
348 | return '' +
349 | '' +
353 | ' | ';
354 | },
355 |
356 | isoWeek = function(date, firstWeekOfYearMinDays) {
357 | // Ensure we're at the start of the day.
358 | date.setHours(0, 0, 0, 0);
359 |
360 | // Thursday in current week decides the year because January 4th
361 | // is always in the first week according to ISO8601.
362 | var yearDay = date.getDate(),
363 | weekDay = date.getDay(),
364 | dayInFirstWeek = firstWeekOfYearMinDays,
365 | dayShift = dayInFirstWeek - 1, // counting starts at 0
366 | daysPerWeek = 7,
367 | prevWeekDay = function(day) { return (day + daysPerWeek - 1) % daysPerWeek; };
368 |
369 | // Adjust to Thursday in week 1 and count number of weeks from date to week 1.
370 | date.setDate(yearDay + dayShift - prevWeekDay(weekDay));
371 |
372 | var jan4th = new Date(date.getFullYear(), 0, dayInFirstWeek),
373 | msPerDay = 24 * 60 * 60 * 1000,
374 | daysBetween = (date.getTime() - jan4th.getTime()) / msPerDay,
375 | weekNum = 1 + Math.round((daysBetween - dayShift + prevWeekDay(jan4th.getDay())) / daysPerWeek);
376 |
377 | return weekNum;
378 | },
379 |
380 | renderWeek = function (d, m, y, firstWeekOfYearMinDays) {
381 | var date = new Date(y, m, d),
382 | week = hasMoment ? moment(date).isoWeek() : isoWeek(date, firstWeekOfYearMinDays);
383 |
384 | return '' + week + ' | ';
385 | },
386 |
387 | renderRow = function(days, isRTL, pickWholeWeek, isRowSelected)
388 | {
389 | return '' + (isRTL ? days.reverse() : days).join('') + '
';
390 | },
391 |
392 | renderBody = function(rows)
393 | {
394 | return '' + rows.join('') + '';
395 | },
396 |
397 | renderHead = function(opts)
398 | {
399 | var i, arr = [];
400 | if (opts.showWeekNumber) {
401 | arr.push(' | ');
402 | }
403 | for (i = 0; i < 7; i++) {
404 | arr.push('' + renderDayName(opts, i, true) + ' | ');
405 | }
406 | return '' + (opts.isRTL ? arr.reverse() : arr).join('') + '
';
407 | },
408 |
409 | renderTitle = function(instance, c, year, month, refYear, randId)
410 | {
411 | var i, j, arr,
412 | opts = instance._o,
413 | isMinYear = year === opts.minYear,
414 | isMaxYear = year === opts.maxYear,
415 | html = '',
416 | monthHtml,
417 | yearHtml,
418 | prev = true,
419 | next = true;
420 |
421 | for (arr = [], i = 0; i < 12; i++) {
422 | arr.push('
');
426 | }
427 |
428 | monthHtml = '
' + opts.i18n.months[month] + '
';
429 |
430 | if (isArray(opts.yearRange)) {
431 | i = opts.yearRange[0];
432 | j = opts.yearRange[1] + 1;
433 | } else {
434 | i = year - opts.yearRange;
435 | j = 1 + year + opts.yearRange;
436 | }
437 |
438 | for (arr = []; i < j && i <= opts.maxYear; i++) {
439 | if (i >= opts.minYear) {
440 | arr.push('
');
441 | }
442 | }
443 | yearHtml = '
' + year + opts.yearSuffix + '
';
444 |
445 | if (opts.showMonthAfterYear) {
446 | html += yearHtml + monthHtml;
447 | } else {
448 | html += monthHtml + yearHtml;
449 | }
450 |
451 | if (isMinYear && (month === 0 || opts.minMonth >= month)) {
452 | prev = false;
453 | }
454 |
455 | if (isMaxYear && (month === 11 || opts.maxMonth <= month)) {
456 | next = false;
457 | }
458 |
459 | if (c === 0) {
460 | html += '
';
461 | }
462 | if (c === (instance._o.numberOfMonths - 1) ) {
463 | html += '
';
464 | }
465 |
466 | return html += '
';
467 | },
468 |
469 | renderTable = function(opts, data, randId)
470 | {
471 | return '' + renderHead(opts) + renderBody(data) + '
';
472 | },
473 |
474 |
475 | /**
476 | * Pikaday constructor
477 | */
478 | Pikaday = function(options)
479 | {
480 | var self = this,
481 | opts = self.config(options);
482 |
483 | self._onMouseDown = function(e)
484 | {
485 | if (!self._v) {
486 | return;
487 | }
488 | e = e || window.event;
489 | var target = e.target || e.srcElement;
490 | if (!target) {
491 | return;
492 | }
493 |
494 | if (!hasClass(target, 'is-disabled')) {
495 | if (hasClass(target, 'pika-button') && !hasClass(target, 'is-empty') && !hasClass(target.parentNode, 'is-disabled')) {
496 | self.setDate(new Date(target.getAttribute('data-pika-year'), target.getAttribute('data-pika-month'), target.getAttribute('data-pika-day')));
497 | if (opts.bound) {
498 | sto(function() {
499 | self.hide();
500 | if (opts.blurFieldOnSelect && opts.field) {
501 | opts.field.blur();
502 | }
503 | }, 100);
504 | }
505 | }
506 | else if (hasClass(target, 'pika-prev')) {
507 | self.prevMonth();
508 | }
509 | else if (hasClass(target, 'pika-next')) {
510 | self.nextMonth();
511 | }
512 | }
513 | if (!hasClass(target, 'pika-select')) {
514 | // if this is touch event prevent mouse events emulation
515 | if (e.preventDefault) {
516 | e.preventDefault();
517 | } else {
518 | e.returnValue = false;
519 | return false;
520 | }
521 | } else {
522 | self._c = true;
523 | }
524 | };
525 |
526 | self._onChange = function(e)
527 | {
528 | e = e || window.event;
529 | var target = e.target || e.srcElement;
530 | if (!target) {
531 | return;
532 | }
533 | if (hasClass(target, 'pika-select-month')) {
534 | self.gotoMonth(target.value);
535 | }
536 | else if (hasClass(target, 'pika-select-year')) {
537 | self.gotoYear(target.value);
538 | }
539 | };
540 |
541 | self._onKeyChange = function(e)
542 | {
543 | e = e || window.event;
544 |
545 | if (self.isVisible()) {
546 |
547 | switch(e.keyCode){
548 | case 13:
549 | case 27:
550 | if (opts.field) {
551 | opts.field.blur();
552 | }
553 | break;
554 | case 37:
555 | self.adjustDate('subtract', 1);
556 | break;
557 | case 38:
558 | self.adjustDate('subtract', 7);
559 | break;
560 | case 39:
561 | self.adjustDate('add', 1);
562 | break;
563 | case 40:
564 | self.adjustDate('add', 7);
565 | break;
566 | case 8:
567 | case 46:
568 | self.setDate(null);
569 | break;
570 | }
571 | }
572 | };
573 |
574 | self._parseFieldValue = function()
575 | {
576 | if (opts.parse) {
577 | return opts.parse(opts.field.value, opts.format);
578 | } else if (hasMoment) {
579 | var date = moment(opts.field.value, opts.format, opts.formatStrict);
580 | return (date && date.isValid()) ? date.toDate() : null;
581 | } else {
582 | return new Date(Date.parse(opts.field.value));
583 | }
584 | };
585 |
586 | self._onInputChange = function(e)
587 | {
588 | var date;
589 |
590 | if (e.firedBy === self) {
591 | return;
592 | }
593 | date = self._parseFieldValue();
594 | if (isDate(date)) {
595 | self.setDate(date);
596 | }
597 | if (!self._v) {
598 | self.show();
599 | }
600 | };
601 |
602 | self._onInputFocus = function()
603 | {
604 | self.show();
605 | };
606 |
607 | self._onInputClick = function()
608 | {
609 | self.show();
610 | };
611 |
612 | self._onInputBlur = function()
613 | {
614 | // IE allows pika div to gain focus; catch blur the input field
615 | var pEl = document.activeElement;
616 | do {
617 | if (hasClass(pEl, 'pika-single')) {
618 | return;
619 | }
620 | }
621 | while ((pEl = pEl.parentNode));
622 |
623 | if (!self._c) {
624 | self._b = sto(function() {
625 | self.hide();
626 | }, 50);
627 | }
628 | self._c = false;
629 | };
630 |
631 | self._onClick = function(e)
632 | {
633 | e = e || window.event;
634 | var target = e.target || e.srcElement,
635 | pEl = target;
636 | if (!target) {
637 | return;
638 | }
639 | if (!hasEventListeners && hasClass(target, 'pika-select')) {
640 | if (!target.onchange) {
641 | target.setAttribute('onchange', 'return;');
642 | addEvent(target, 'change', self._onChange);
643 | }
644 | }
645 | do {
646 | if (hasClass(pEl, 'pika-single') || pEl === opts.trigger) {
647 | return;
648 | }
649 | }
650 | while ((pEl = pEl.parentNode));
651 | if (self._v && target !== opts.trigger && pEl !== opts.trigger) {
652 | self.hide();
653 | }
654 | };
655 |
656 | self.el = document.createElement('div');
657 | self.el.className = 'pika-single' + (opts.isRTL ? ' is-rtl' : '') + (opts.theme ? ' ' + opts.theme : '');
658 |
659 | addEvent(self.el, 'mousedown', self._onMouseDown, true);
660 | addEvent(self.el, 'touchend', self._onMouseDown, true);
661 | addEvent(self.el, 'change', self._onChange);
662 |
663 | if (opts.keyboardInput) {
664 | addEvent(document, 'keydown', self._onKeyChange);
665 | }
666 |
667 | if (opts.field) {
668 | if (opts.container) {
669 | opts.container.appendChild(self.el);
670 | } else if (opts.bound) {
671 | document.body.appendChild(self.el);
672 | } else {
673 | opts.field.parentNode.insertBefore(self.el, opts.field.nextSibling);
674 | }
675 | addEvent(opts.field, 'change', self._onInputChange);
676 |
677 | if (!opts.defaultDate) {
678 | opts.defaultDate = self._parseFieldValue();
679 | opts.setDefaultDate = true;
680 | }
681 | }
682 |
683 | var defDate = opts.defaultDate;
684 |
685 | if (isDate(defDate)) {
686 | if (opts.setDefaultDate) {
687 | self.setDate(defDate, true);
688 | } else {
689 | self.gotoDate(defDate);
690 | }
691 | } else {
692 | self.gotoDate(new Date());
693 | }
694 |
695 | if (opts.bound) {
696 | this.hide();
697 | self.el.className += ' is-bound';
698 | addEvent(opts.trigger, 'click', self._onInputClick);
699 | addEvent(opts.trigger, 'focus', self._onInputFocus);
700 | addEvent(opts.trigger, 'blur', self._onInputBlur);
701 | } else {
702 | this.show();
703 | }
704 | };
705 |
706 |
707 | /**
708 | * public Pikaday API
709 | */
710 | Pikaday.prototype = {
711 |
712 |
713 | /**
714 | * configure functionality
715 | */
716 | config: function(options)
717 | {
718 | if (!this._o) {
719 | this._o = extend({}, defaults, true);
720 | }
721 |
722 | var opts = extend(this._o, options, true);
723 |
724 | opts.isRTL = !!opts.isRTL;
725 |
726 | opts.field = (opts.field && opts.field.nodeName) ? opts.field : null;
727 |
728 | opts.theme = (typeof opts.theme) === 'string' && opts.theme ? opts.theme : null;
729 |
730 | opts.bound = !!(opts.bound !== undefined ? opts.field && opts.bound : opts.field);
731 |
732 | opts.trigger = (opts.trigger && opts.trigger.nodeName) ? opts.trigger : opts.field;
733 |
734 | opts.disableWeekends = !!opts.disableWeekends;
735 |
736 | opts.disableDayFn = (typeof opts.disableDayFn) === 'function' ? opts.disableDayFn : null;
737 |
738 | var nom = parseInt(opts.numberOfMonths, 10) || 1;
739 | opts.numberOfMonths = nom > 4 ? 4 : nom;
740 |
741 | if (!isDate(opts.minDate)) {
742 | opts.minDate = false;
743 | }
744 | if (!isDate(opts.maxDate)) {
745 | opts.maxDate = false;
746 | }
747 | if ((opts.minDate && opts.maxDate) && opts.maxDate < opts.minDate) {
748 | opts.maxDate = opts.minDate = false;
749 | }
750 | if (opts.minDate) {
751 | this.setMinDate(opts.minDate);
752 | }
753 | if (opts.maxDate) {
754 | this.setMaxDate(opts.maxDate);
755 | }
756 |
757 | if (isArray(opts.yearRange)) {
758 | var fallback = new Date().getFullYear() - 10;
759 | opts.yearRange[0] = parseInt(opts.yearRange[0], 10) || fallback;
760 | opts.yearRange[1] = parseInt(opts.yearRange[1], 10) || fallback;
761 | } else {
762 | opts.yearRange = Math.abs(parseInt(opts.yearRange, 10)) || defaults.yearRange;
763 | if (opts.yearRange > 100) {
764 | opts.yearRange = 100;
765 | }
766 | }
767 |
768 | return opts;
769 | },
770 |
771 | /**
772 | * return a formatted string of the current selection (using Moment.js if available)
773 | */
774 | toString: function(format)
775 | {
776 | format = format || this._o.format;
777 | if (!isDate(this._d)) {
778 | return '';
779 | }
780 | if (this._o.toString) {
781 | return this._o.toString(this._d, format);
782 | }
783 | if (hasMoment) {
784 | return moment(this._d).format(format);
785 | }
786 | return this._d.toDateString();
787 | },
788 |
789 | /**
790 | * return a Moment.js object of the current selection (if available)
791 | */
792 | getMoment: function()
793 | {
794 | return hasMoment ? moment(this._d) : null;
795 | },
796 |
797 | /**
798 | * set the current selection from a Moment.js object (if available)
799 | */
800 | setMoment: function(date, preventOnSelect)
801 | {
802 | if (hasMoment && moment.isMoment(date)) {
803 | this.setDate(date.toDate(), preventOnSelect);
804 | }
805 | },
806 |
807 | /**
808 | * return a Date object of the current selection
809 | */
810 | getDate: function()
811 | {
812 | return isDate(this._d) ? new Date(this._d.getTime()) : null;
813 | },
814 |
815 | /**
816 | * set the current selection
817 | */
818 | setDate: function(date, preventOnSelect)
819 | {
820 | if (!date) {
821 | this._d = null;
822 |
823 | if (this._o.field) {
824 | this._o.field.value = '';
825 | fireEvent(this._o.field, 'change', { firedBy: this });
826 | }
827 |
828 | return this.draw();
829 | }
830 | if (typeof date === 'string') {
831 | date = new Date(Date.parse(date));
832 | }
833 | if (!isDate(date)) {
834 | return;
835 | }
836 |
837 | var min = this._o.minDate,
838 | max = this._o.maxDate;
839 |
840 | if (isDate(min) && date < min) {
841 | date = min;
842 | } else if (isDate(max) && date > max) {
843 | date = max;
844 | }
845 |
846 | this._d = new Date(date.getTime());
847 | setToStartOfDay(this._d);
848 | this.gotoDate(this._d);
849 |
850 | if (this._o.field) {
851 | this._o.field.value = this.toString();
852 | fireEvent(this._o.field, 'change', { firedBy: this });
853 | }
854 | if (!preventOnSelect && typeof this._o.onSelect === 'function') {
855 | this._o.onSelect.call(this, this.getDate());
856 | }
857 | },
858 |
859 | /**
860 | * clear and reset the date
861 | */
862 | clear: function()
863 | {
864 | this.setDate(null);
865 | },
866 |
867 | /**
868 | * change view to a specific date
869 | */
870 | gotoDate: function(date)
871 | {
872 | var newCalendar = true;
873 |
874 | if (!isDate(date)) {
875 | return;
876 | }
877 |
878 | if (this.calendars) {
879 | var firstVisibleDate = new Date(this.calendars[0].year, this.calendars[0].month, 1),
880 | lastVisibleDate = new Date(this.calendars[this.calendars.length-1].year, this.calendars[this.calendars.length-1].month, 1),
881 | visibleDate = date.getTime();
882 | // get the end of the month
883 | lastVisibleDate.setMonth(lastVisibleDate.getMonth()+1);
884 | lastVisibleDate.setDate(lastVisibleDate.getDate()-1);
885 | newCalendar = (visibleDate < firstVisibleDate.getTime() || lastVisibleDate.getTime() < visibleDate);
886 | }
887 |
888 | if (newCalendar) {
889 | this.calendars = [{
890 | month: date.getMonth(),
891 | year: date.getFullYear()
892 | }];
893 | if (this._o.mainCalendar === 'right') {
894 | this.calendars[0].month += 1 - this._o.numberOfMonths;
895 | }
896 | }
897 |
898 | this.adjustCalendars();
899 | },
900 |
901 | adjustDate: function(sign, days) {
902 |
903 | var day = this.getDate() || new Date();
904 | var difference = parseInt(days)*24*60*60*1000;
905 |
906 | var newDay;
907 |
908 | if (sign === 'add') {
909 | newDay = new Date(day.valueOf() + difference);
910 | } else if (sign === 'subtract') {
911 | newDay = new Date(day.valueOf() - difference);
912 | }
913 |
914 | this.setDate(newDay);
915 | },
916 |
917 | adjustCalendars: function() {
918 | this.calendars[0] = adjustCalendar(this.calendars[0]);
919 | for (var c = 1; c < this._o.numberOfMonths; c++) {
920 | this.calendars[c] = adjustCalendar({
921 | month: this.calendars[0].month + c,
922 | year: this.calendars[0].year
923 | });
924 | }
925 | this.draw();
926 | },
927 |
928 | gotoToday: function()
929 | {
930 | this.gotoDate(new Date());
931 | },
932 |
933 | /**
934 | * change view to a specific month (zero-index, e.g. 0: January)
935 | */
936 | gotoMonth: function(month)
937 | {
938 | if (!isNaN(month)) {
939 | this.calendars[0].month = parseInt(month, 10);
940 | this.adjustCalendars();
941 | }
942 | },
943 |
944 | nextMonth: function()
945 | {
946 | this.calendars[0].month++;
947 | this.adjustCalendars();
948 | },
949 |
950 | prevMonth: function()
951 | {
952 | this.calendars[0].month--;
953 | this.adjustCalendars();
954 | },
955 |
956 | /**
957 | * change view to a specific full year (e.g. "2012")
958 | */
959 | gotoYear: function(year)
960 | {
961 | if (!isNaN(year)) {
962 | this.calendars[0].year = parseInt(year, 10);
963 | this.adjustCalendars();
964 | }
965 | },
966 |
967 | /**
968 | * change the minDate
969 | */
970 | setMinDate: function(value)
971 | {
972 | if(value instanceof Date) {
973 | setToStartOfDay(value);
974 | this._o.minDate = value;
975 | this._o.minYear = value.getFullYear();
976 | this._o.minMonth = value.getMonth();
977 | } else {
978 | this._o.minDate = defaults.minDate;
979 | this._o.minYear = defaults.minYear;
980 | this._o.minMonth = defaults.minMonth;
981 | this._o.startRange = defaults.startRange;
982 | }
983 |
984 | this.draw();
985 | },
986 |
987 | /**
988 | * change the maxDate
989 | */
990 | setMaxDate: function(value)
991 | {
992 | if(value instanceof Date) {
993 | setToStartOfDay(value);
994 | this._o.maxDate = value;
995 | this._o.maxYear = value.getFullYear();
996 | this._o.maxMonth = value.getMonth();
997 | } else {
998 | this._o.maxDate = defaults.maxDate;
999 | this._o.maxYear = defaults.maxYear;
1000 | this._o.maxMonth = defaults.maxMonth;
1001 | this._o.endRange = defaults.endRange;
1002 | }
1003 |
1004 | this.draw();
1005 | },
1006 |
1007 | setStartRange: function(value)
1008 | {
1009 | this._o.startRange = value;
1010 | },
1011 |
1012 | setEndRange: function(value)
1013 | {
1014 | this._o.endRange = value;
1015 | },
1016 |
1017 | /**
1018 | * refresh the HTML
1019 | */
1020 | draw: function(force)
1021 | {
1022 | if (!this._v && !force) {
1023 | return;
1024 | }
1025 | var opts = this._o,
1026 | minYear = opts.minYear,
1027 | maxYear = opts.maxYear,
1028 | minMonth = opts.minMonth,
1029 | maxMonth = opts.maxMonth,
1030 | html = '',
1031 | randId;
1032 |
1033 | if (this._y <= minYear) {
1034 | this._y = minYear;
1035 | if (!isNaN(minMonth) && this._m < minMonth) {
1036 | this._m = minMonth;
1037 | }
1038 | }
1039 | if (this._y >= maxYear) {
1040 | this._y = maxYear;
1041 | if (!isNaN(maxMonth) && this._m > maxMonth) {
1042 | this._m = maxMonth;
1043 | }
1044 | }
1045 |
1046 | for (var c = 0; c < opts.numberOfMonths; c++) {
1047 | randId = 'pika-title-' + Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 2);
1048 | html += '' + renderTitle(this, c, this.calendars[c].year, this.calendars[c].month, this.calendars[0].year, randId) + this.render(this.calendars[c].year, this.calendars[c].month, randId) + '
';
1049 | }
1050 |
1051 | this.el.innerHTML = html;
1052 |
1053 | if (opts.bound) {
1054 | if(opts.field.type !== 'hidden') {
1055 | sto(function() {
1056 | opts.trigger.focus();
1057 | }, 1);
1058 | }
1059 | }
1060 |
1061 | if (typeof this._o.onDraw === 'function') {
1062 | this._o.onDraw(this);
1063 | }
1064 |
1065 | if (opts.bound) {
1066 | // let the screen reader user know to use arrow keys
1067 | opts.field.setAttribute('aria-label', opts.ariaLabel);
1068 | }
1069 | },
1070 |
1071 | adjustPosition: function()
1072 | {
1073 | var field, pEl, width, height, viewportWidth, viewportHeight, scrollTop, left, top, clientRect, leftAligned, bottomAligned;
1074 |
1075 | if (this._o.container) return;
1076 |
1077 | this.el.style.position = 'absolute';
1078 |
1079 | field = this._o.trigger;
1080 | pEl = field;
1081 | width = this.el.offsetWidth;
1082 | height = this.el.offsetHeight;
1083 | viewportWidth = window.innerWidth || document.documentElement.clientWidth;
1084 | viewportHeight = window.innerHeight || document.documentElement.clientHeight;
1085 | scrollTop = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
1086 | leftAligned = true;
1087 | bottomAligned = true;
1088 |
1089 | if (typeof field.getBoundingClientRect === 'function') {
1090 | clientRect = field.getBoundingClientRect();
1091 | left = clientRect.left + window.pageXOffset;
1092 | top = clientRect.bottom + window.pageYOffset;
1093 | } else {
1094 | left = pEl.offsetLeft;
1095 | top = pEl.offsetTop + pEl.offsetHeight;
1096 | while((pEl = pEl.offsetParent)) {
1097 | left += pEl.offsetLeft;
1098 | top += pEl.offsetTop;
1099 | }
1100 | }
1101 |
1102 | // default position is bottom & left
1103 | if ((this._o.reposition && left + width > viewportWidth) ||
1104 | (
1105 | this._o.position.indexOf('right') > -1 &&
1106 | left - width + field.offsetWidth > 0
1107 | )
1108 | ) {
1109 | left = left - width + field.offsetWidth;
1110 | leftAligned = false;
1111 | }
1112 | if ((this._o.reposition && top + height > viewportHeight + scrollTop) ||
1113 | (
1114 | this._o.position.indexOf('top') > -1 &&
1115 | top - height - field.offsetHeight > 0
1116 | )
1117 | ) {
1118 | top = top - height - field.offsetHeight;
1119 | bottomAligned = false;
1120 | }
1121 |
1122 | this.el.style.left = left + 'px';
1123 | this.el.style.top = top + 'px';
1124 |
1125 | addClass(this.el, leftAligned ? 'left-aligned' : 'right-aligned');
1126 | addClass(this.el, bottomAligned ? 'bottom-aligned' : 'top-aligned');
1127 | removeClass(this.el, !leftAligned ? 'left-aligned' : 'right-aligned');
1128 | removeClass(this.el, !bottomAligned ? 'bottom-aligned' : 'top-aligned');
1129 | },
1130 |
1131 | /**
1132 | * render HTML for a particular month
1133 | */
1134 | render: function(year, month, randId)
1135 | {
1136 | var opts = this._o,
1137 | now = new Date(),
1138 | days = getDaysInMonth(year, month),
1139 | before = new Date(year, month, 1).getDay(),
1140 | data = [],
1141 | row = [];
1142 | setToStartOfDay(now);
1143 | if (opts.firstDay > 0) {
1144 | before -= opts.firstDay;
1145 | if (before < 0) {
1146 | before += 7;
1147 | }
1148 | }
1149 | var previousMonth = month === 0 ? 11 : month - 1,
1150 | nextMonth = month === 11 ? 0 : month + 1,
1151 | yearOfPreviousMonth = month === 0 ? year - 1 : year,
1152 | yearOfNextMonth = month === 11 ? year + 1 : year,
1153 | daysInPreviousMonth = getDaysInMonth(yearOfPreviousMonth, previousMonth);
1154 | var cells = days + before,
1155 | after = cells;
1156 | while(after > 7) {
1157 | after -= 7;
1158 | }
1159 | cells += 7 - after;
1160 | var isWeekSelected = false;
1161 | for (var i = 0, r = 0; i < cells; i++)
1162 | {
1163 | var day = new Date(year, month, 1 + (i - before)),
1164 | isSelected = isDate(this._d) ? compareDates(day, this._d) : false,
1165 | isToday = compareDates(day, now),
1166 | hasEvent = opts.events.indexOf(day.toDateString()) !== -1 ? true : false,
1167 | isEmpty = i < before || i >= (days + before),
1168 | dayNumber = 1 + (i - before),
1169 | monthNumber = month,
1170 | yearNumber = year,
1171 | isStartRange = opts.startRange && compareDates(opts.startRange, day),
1172 | isEndRange = opts.endRange && compareDates(opts.endRange, day),
1173 | isInRange = opts.startRange && opts.endRange && opts.startRange < day && day < opts.endRange,
1174 | isDisabled = (opts.minDate && day < opts.minDate) ||
1175 | (opts.maxDate && day > opts.maxDate) ||
1176 | (opts.disableWeekends && isWeekend(day)) ||
1177 | (opts.disableDayFn && opts.disableDayFn(day));
1178 |
1179 | if (isEmpty) {
1180 | if (i < before) {
1181 | dayNumber = daysInPreviousMonth + dayNumber;
1182 | monthNumber = previousMonth;
1183 | yearNumber = yearOfPreviousMonth;
1184 | } else {
1185 | dayNumber = dayNumber - days;
1186 | monthNumber = nextMonth;
1187 | yearNumber = yearOfNextMonth;
1188 | }
1189 | }
1190 |
1191 | var dayConfig = {
1192 | day: dayNumber,
1193 | month: monthNumber,
1194 | year: yearNumber,
1195 | hasEvent: hasEvent,
1196 | isSelected: isSelected,
1197 | isToday: isToday,
1198 | isDisabled: isDisabled,
1199 | isEmpty: isEmpty,
1200 | isStartRange: isStartRange,
1201 | isEndRange: isEndRange,
1202 | isInRange: isInRange,
1203 | showDaysInNextAndPreviousMonths: opts.showDaysInNextAndPreviousMonths,
1204 | enableSelectionDaysInNextAndPreviousMonths: opts.enableSelectionDaysInNextAndPreviousMonths
1205 | };
1206 |
1207 | if (opts.pickWholeWeek && isSelected) {
1208 | isWeekSelected = true;
1209 | }
1210 |
1211 | row.push(renderDay(dayConfig));
1212 |
1213 | if (++r === 7) {
1214 | if (opts.showWeekNumber) {
1215 | row.unshift(renderWeek(i - before, month, year, opts.firstWeekOfYearMinDays));
1216 | }
1217 | data.push(renderRow(row, opts.isRTL, opts.pickWholeWeek, isWeekSelected));
1218 | row = [];
1219 | r = 0;
1220 | isWeekSelected = false;
1221 | }
1222 | }
1223 | return renderTable(opts, data, randId);
1224 | },
1225 |
1226 | isVisible: function()
1227 | {
1228 | return this._v;
1229 | },
1230 |
1231 | show: function()
1232 | {
1233 | if (!this.isVisible()) {
1234 | this._v = true;
1235 | this.draw();
1236 | removeClass(this.el, 'is-hidden');
1237 | if (this._o.bound) {
1238 | addEvent(document, 'click', this._onClick);
1239 | this.adjustPosition();
1240 | }
1241 | if (typeof this._o.onOpen === 'function') {
1242 | this._o.onOpen.call(this);
1243 | }
1244 | }
1245 | },
1246 |
1247 | hide: function()
1248 | {
1249 | var v = this._v;
1250 | if (v !== false) {
1251 | if (this._o.bound) {
1252 | removeEvent(document, 'click', this._onClick);
1253 | }
1254 |
1255 | if (!this._o.container) {
1256 | this.el.style.position = 'static'; // reset
1257 | this.el.style.left = 'auto';
1258 | this.el.style.top = 'auto';
1259 | }
1260 | addClass(this.el, 'is-hidden');
1261 | this._v = false;
1262 | if (v !== undefined && typeof this._o.onClose === 'function') {
1263 | this._o.onClose.call(this);
1264 | }
1265 | }
1266 | },
1267 |
1268 | /**
1269 | * GAME OVER
1270 | */
1271 | destroy: function()
1272 | {
1273 | var opts = this._o;
1274 |
1275 | this.hide();
1276 | removeEvent(this.el, 'mousedown', this._onMouseDown, true);
1277 | removeEvent(this.el, 'touchend', this._onMouseDown, true);
1278 | removeEvent(this.el, 'change', this._onChange);
1279 | if (opts.keyboardInput) {
1280 | removeEvent(document, 'keydown', this._onKeyChange);
1281 | }
1282 | if (opts.field) {
1283 | removeEvent(opts.field, 'change', this._onInputChange);
1284 | if (opts.bound) {
1285 | removeEvent(opts.trigger, 'click', this._onInputClick);
1286 | removeEvent(opts.trigger, 'focus', this._onInputFocus);
1287 | removeEvent(opts.trigger, 'blur', this._onInputBlur);
1288 | }
1289 | }
1290 | if (this.el.parentNode) {
1291 | this.el.parentNode.removeChild(this.el);
1292 | }
1293 | }
1294 |
1295 | };
1296 |
1297 | return Pikaday;
1298 | }));
1299 |
--------------------------------------------------------------------------------
/firefox/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Wiki Journey
5 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
78 |
79 |
80 |
81 |
82 | d.children ? Object.values(d.children) : []);
35 |
36 | const dx = 64;
37 | const dy = 192;
38 | const leftTextOffset = 128; // Estimated offset to accommodate leftmost text
39 | const width = (root.height + 1) * dy + leftTextOffset;
40 | const tree = d3.tree().nodeSize([dx, dy]);
41 |
42 | root.sort((a, b) => d3.ascending(a.data.title, b.data.title));
43 | tree(root);
44 |
45 | let x0 = Infinity;
46 | let x1 = -x0;
47 | root.each(d => {
48 | if (d.x > x1) x1 = d.x;
49 | if (d.x < x0) x0 = d.x;
50 | });
51 |
52 | const height = x1 - x0 + dx * 2;
53 |
54 | // Since you're in a browser extension popup, use d3.select instead of d3.create
55 | const svg = d3.select("#graph").append("svg")
56 | .attr("width", width)
57 | .attr("height", height)
58 | .attr("viewBox", [-leftTextOffset, x0 - dx, width + leftTextOffset, height]) // Adjust viewBox by leftTextOffset
59 | .style("font", "18px sans-serif");
60 |
61 | const link = svg.append("g")
62 | .attr("fill", "none")
63 | .attr("stroke", "#555")
64 | .attr("stroke-opacity", 0.4)
65 | .attr("stroke-width", 1.5)
66 | .selectAll("path")
67 | .data(root.links())
68 | .join("path")
69 | .attr("d", d3.linkHorizontal()
70 | .x(d => d.y)
71 | .y(d => d.x));
72 |
73 | const node = svg.append("g")
74 | .attr("stroke-linejoin", "round")
75 | .attr("stroke-width", 3)
76 | .selectAll("g")
77 | .data(root.descendants())
78 | .join("g")
79 | .attr("transform", d => `translate(${d.y},${d.x})`);
80 |
81 | node.append("circle")
82 | .attr("fill", d => d.children ? "#555" : "#999")
83 | .attr("r", 2.5);
84 |
85 | node.append("text")
86 | .attr("dy", "0.31em")
87 | .attr("x", d => d.children ? -6 : 6)
88 | .attr("text-anchor", d => d.children ? "end" : "start")
89 | .text(d => d.data.title)
90 | .attr("fill", "black") // Set the text color
91 | .clone(true).lower()
92 | .attr("stroke", "white"); // Remove the stroke or set to a contrasting color if needed
93 |
94 |
95 | // Add this SVG to your popup
96 | document.querySelector("#graph").appendChild(svg.node());
97 | }
98 | const today = new Date();
99 | document.getElementById('datePicker').value = toLocalISOString(today); // Set date picker value to today
100 | loadJourney(toLocalISOString(today));
101 | });
102 |
--------------------------------------------------------------------------------
/firefox/wikiJourneyFirefox.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demegire/wiki-journey/e6dfb2d17f4dcd58c17cfa152597abfc0f2dff84/firefox/wikiJourneyFirefox.zip
--------------------------------------------------------------------------------