From 941c11824a191216fd43805b910cdbd172157cb0 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 18 Oct 2017 11:31:01 +0000 Subject: [PATCH] Fix Pikaday --- .../boards/components/board_sidebar.js | 3 +- app/assets/javascripts/dispatcher.js | 5 +- app/assets/javascripts/due_date_select.js | 52 +++++++++---------- .../javascripts/init_issuable_sidebar.js | 4 +- app/assets/javascripts/issuable_form.js | 8 +-- app/assets/javascripts/lib/utils/datefix.js | 33 +++++++++--- app/assets/javascripts/main.js | 2 - .../javascripts/member_expiration_date.js | 9 ++-- changelogs/unreleased/38986-due-date.yml | 5 ++ config/dependency_decisions.yml | 7 +++ package.json | 2 +- spec/javascripts/lib/utils/datefix_spec.js | 29 +++++++++++ yarn.lock | 6 +-- 13 files changed, 115 insertions(+), 50 deletions(-) create mode 100644 changelogs/unreleased/38986-due-date.yml create mode 100644 spec/javascripts/lib/utils/datefix_spec.js diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index 7f3afefc9cc..c1f902a785a 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -9,6 +9,7 @@ import Flash from '../../flash'; import eventHub from '../../sidebar/event_hub'; import AssigneeTitle from '../../sidebar/components/assignees/assignee_title'; import Assignees from '../../sidebar/components/assignees/assignees'; +import DueDateSelectors from '../../due_date_select'; import './sidebar/remove_issue'; const Store = gl.issueBoards.BoardsStore; @@ -113,7 +114,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({ mounted () { new IssuableContext(this.currentUser); new MilestoneSelect(); - new gl.DueDateSelectors(); + new DueDateSelectors(); new LabelsSelect(); new Sidebar(); gl.Subscription.bindAll('.subscription'); diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index b66652db33b..2885923aeda 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -86,6 +86,7 @@ import ShortcutsIssuable from './shortcuts_issuable'; import U2FAuthenticate from './u2f/authenticate'; import Members from './members'; import memberExpirationDate from './member_expiration_date'; +import DueDateSelectors from './due_date_select'; (function() { var Dispatcher; @@ -232,7 +233,7 @@ import memberExpirationDate from './member_expiration_date'; case 'groups:milestones:edit': case 'groups:milestones:update': new ZenMode(); - new gl.DueDateSelectors(); + new DueDateSelectors(); new GLForm($('.milestone-form'), true); break; case 'projects:compare:show': @@ -532,7 +533,7 @@ import memberExpirationDate from './member_expiration_date'; break; case 'profiles:personal_access_tokens:index': case 'admin:impersonation_tokens:index': - new gl.DueDateSelectors(); + new DueDateSelectors(); break; case 'projects:clusters:show': import(/* webpackChunkName: "clusters" */ './clusters') diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js index ee71728184f..ada985913bb 100644 --- a/app/assets/javascripts/due_date_select.js +++ b/app/assets/javascripts/due_date_select.js @@ -1,8 +1,7 @@ -/* eslint-disable wrap-iife, func-names, space-before-function-paren, comma-dangle, prefer-template, consistent-return, class-methods-use-this, arrow-body-style, no-unused-vars, no-underscore-dangle, no-new, max-len, no-sequences, no-unused-expressions, no-param-reassign */ /* global dateFormat */ import Pikaday from 'pikaday'; -import DateFix from './lib/utils/datefix'; +import { parsePikadayDate, pikadayToString } from './lib/utils/datefix'; class DueDateSelect { constructor({ $dropdown, $loading } = {}) { @@ -17,8 +16,8 @@ class DueDateSelect { this.$value = $block.find('.value'); this.$valueContent = $block.find('.value-content'); this.$sidebarValue = $('.js-due-date-sidebar-value', $block); - this.fieldName = $dropdown.data('field-name'), - this.abilityName = $dropdown.data('ability-name'), + this.fieldName = $dropdown.data('field-name'); + this.abilityName = $dropdown.data('ability-name'); this.issueUpdateURL = $dropdown.data('issue-update'); this.rawSelectedDate = null; @@ -39,20 +38,20 @@ class DueDateSelect { hidden: () => { this.$selectbox.hide(); this.$value.css('display', ''); - } + }, }); } initDatePicker() { const $dueDateInput = $(`input[name='${this.fieldName}']`); - const dateFix = DateFix.dashedFix($dueDateInput.val()); const calendar = new Pikaday({ field: $dueDateInput.get(0), theme: 'gitlab-theme', format: 'yyyy-mm-dd', + parse: dateString => parsePikadayDate(dateString), + toString: date => pikadayToString(date), onSelect: (dateText) => { - const formattedDate = dateFormat(new Date(dateText), 'yyyy-mm-dd'); - $dueDateInput.val(formattedDate); + $dueDateInput.val(calendar.toString(dateText)); if (this.$dropdown.hasClass('js-issue-boards-due-date')) { gl.issueBoards.BoardsStore.detail.issue.dueDate = $dueDateInput.val(); @@ -60,10 +59,10 @@ class DueDateSelect { } else { this.saveDueDate(true); } - } + }, }); - calendar.setDate(dateFix); + calendar.setDate(parsePikadayDate($dueDateInput.val())); this.$datePicker.append(calendar.el); this.$datePicker.data('pikaday', calendar); } @@ -79,8 +78,8 @@ class DueDateSelect { gl.issueBoards.BoardsStore.detail.issue.dueDate = ''; this.updateIssueBoardIssue(); } else { - $("input[name='" + this.fieldName + "']").val(''); - return this.saveDueDate(false); + $(`input[name='${this.fieldName}']`).val(''); + this.saveDueDate(false); } }); } @@ -111,7 +110,7 @@ class DueDateSelect { this.datePayload = datePayload; } - updateIssueBoardIssue () { + updateIssueBoardIssue() { this.$loading.fadeIn(); this.$dropdown.trigger('loading.gl.dropdown'); this.$selectbox.hide(); @@ -149,8 +148,8 @@ class DueDateSelect { return selectedDateValue.length ? $('.js-remove-due-date-holder').removeClass('hidden') : $('.js-remove-due-date-holder').addClass('hidden'); - } - }).done((data) => { + }, + }).done(() => { if (isDropdown) { this.$dropdown.trigger('loaded.gl.dropdown'); this.$dropdown.dropdown('toggle'); @@ -160,27 +159,28 @@ class DueDateSelect { } } -class DueDateSelectors { +export default class DueDateSelectors { constructor() { this.initMilestoneDatePicker(); this.initIssuableSelect(); } - + // eslint-disable-next-line class-methods-use-this initMilestoneDatePicker() { - $('.datepicker').each(function() { + $('.datepicker').each(function initPikadayMilestone() { const $datePicker = $(this); - const dateFix = DateFix.dashedFix($datePicker.val()); const calendar = new Pikaday({ field: $datePicker.get(0), theme: 'gitlab-theme animate-picker', format: 'yyyy-mm-dd', container: $datePicker.parent().get(0), + parse: dateString => parsePikadayDate(dateString), + toString: date => pikadayToString(date), onSelect(dateText) { - $datePicker.val(dateFormat(new Date(dateText), 'yyyy-mm-dd')); - } + $datePicker.val(calendar.toString(dateText)); + }, }); - calendar.setDate(dateFix); + calendar.setDate(parsePikadayDate($datePicker.val())); $datePicker.data('pikaday', calendar); }); @@ -191,19 +191,17 @@ class DueDateSelectors { calendar.setDate(null); }); } - + // eslint-disable-next-line class-methods-use-this initIssuableSelect() { const $loading = $('.js-issuable-update .due_date').find('.block-loading').hide(); $('.js-due-date-select').each((i, dropdown) => { const $dropdown = $(dropdown); + // eslint-disable-next-line no-new new DueDateSelect({ $dropdown, - $loading + $loading, }); }); } } - -window.gl = window.gl || {}; -window.gl.DueDateSelectors = DueDateSelectors; diff --git a/app/assets/javascripts/init_issuable_sidebar.js b/app/assets/javascripts/init_issuable_sidebar.js index 29e3d2ea94e..32a1a269f9a 100644 --- a/app/assets/javascripts/init_issuable_sidebar.js +++ b/app/assets/javascripts/init_issuable_sidebar.js @@ -4,6 +4,8 @@ /* global IssuableContext */ /* global Sidebar */ +import DueDateSelectors from './due_date_select'; + export default () => { const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML); @@ -13,6 +15,6 @@ export default () => { new LabelsSelect(); new IssuableContext(sidebarOptions.currentUser); gl.Subscription.bindAll('.subscription'); - new gl.DueDateSelectors(); + new DueDateSelectors(); window.sidebar = new Sidebar(); }; diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js index 470c39c6f76..cd2562bc6a9 100644 --- a/app/assets/javascripts/issuable_form.js +++ b/app/assets/javascripts/issuable_form.js @@ -1,12 +1,12 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-useless-escape, no-new, quotes, object-shorthand, no-unused-vars, comma-dangle, no-alert, consistent-return, no-else-return, prefer-template, one-var, one-var-declaration-per-line, curly, max-len */ /* global GitLab */ /* global Autosave */ -/* global dateFormat */ import Pikaday from 'pikaday'; import UsersSelect from './users_select'; import GfmAutoComplete from './gfm_auto_complete'; import ZenMode from './zen_mode'; +import { parsePikadayDate, pikadayToString } from './lib/utils/datefix'; (function() { this.IssuableForm = (function() { @@ -38,11 +38,13 @@ import ZenMode from './zen_mode'; theme: 'gitlab-theme animate-picker', format: 'yyyy-mm-dd', container: $issuableDueDate.parent().get(0), + parse: dateString => parsePikadayDate(dateString), + toString: date => pikadayToString(date), onSelect: function(dateText) { - $issuableDueDate.val(dateFormat(new Date(dateText), 'yyyy-mm-dd')); + $issuableDueDate.val(calendar.toString(dateText)); } }); - calendar.setDate(new Date($issuableDueDate.val())); + calendar.setDate(parsePikadayDate($issuableDueDate.val())); } } diff --git a/app/assets/javascripts/lib/utils/datefix.js b/app/assets/javascripts/lib/utils/datefix.js index 990dc3f6d1a..e98c9068367 100644 --- a/app/assets/javascripts/lib/utils/datefix.js +++ b/app/assets/javascripts/lib/utils/datefix.js @@ -1,8 +1,29 @@ -const DateFix = { - dashedFix(val) { - const [y, m, d] = val.split('-'); - return new Date(y, m - 1, d); - }, + +export const pad = (val, len = 2) => (`0${val}`).slice(-len); + +/** + * Formats dates in Pickaday + * @param {String} dateString Date in yyyy-mm-dd format + * @return {Date} UTC format + */ +export const parsePikadayDate = (dateString) => { + const parts = dateString.split('-'); + const year = parseInt(parts[0], 10); + const month = parseInt(parts[1] - 1, 10); + const day = parseInt(parts[2], 10); + + return new Date(year, month, day); }; -export default DateFix; +/** + * Used `onSelect` method in pickaday + * @param {Date} date UTC format + * @return {String} Date formated in yyyy-mm-dd + */ +export const pikadayToString = (date) => { + const day = pad(date.getDate()); + const month = pad(date.getMonth() + 1); + const year = date.getFullYear(); + + return `${year}-${month}-${day}`; +}; diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 2fc47d5963b..4cf07e99161 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -51,8 +51,6 @@ import './confirm_danger_modal'; import './copy_as_gfm'; import './copy_to_clipboard'; import './diff'; -import './dropzone_input'; -import './due_date_select'; import './files_comment_button'; import Flash, { removeFlashClickListener } from './flash'; import './gl_dropdown'; diff --git a/app/assets/javascripts/member_expiration_date.js b/app/assets/javascripts/member_expiration_date.js index 26b24fdafda..84e70e35bad 100644 --- a/app/assets/javascripts/member_expiration_date.js +++ b/app/assets/javascripts/member_expiration_date.js @@ -1,6 +1,5 @@ -/* global dateFormat */ - import Pikaday from 'pikaday'; +import { parsePikadayDate, pikadayToString } from './lib/utils/datefix'; // Add datepickers to all `js-access-expiration-date` elements. If those elements are // children of an element with the `clearable-input` class, and have a sibling @@ -22,8 +21,10 @@ export default function memberExpirationDate(selector = '.js-access-expiration-d format: 'yyyy-mm-dd', minDate: new Date(), container: $input.parent().get(0), + parse: dateString => parsePikadayDate(dateString), + toString: date => pikadayToString(date), onSelect(dateText) { - $input.val(dateFormat(new Date(dateText), 'yyyy-mm-dd')); + $input.val(calendar.toString(dateText)); $input.trigger('change'); @@ -31,7 +32,7 @@ export default function memberExpirationDate(selector = '.js-access-expiration-d }, }); - calendar.setDate(new Date($input.val())); + calendar.setDate(parsePikadayDate($input.val())); $input.data('pikaday', calendar); }); diff --git a/changelogs/unreleased/38986-due-date.yml b/changelogs/unreleased/38986-due-date.yml new file mode 100644 index 00000000000..7799b8d297e --- /dev/null +++ b/changelogs/unreleased/38986-due-date.yml @@ -0,0 +1,5 @@ +--- +title: Fix timezone bug in Pikaday and upgrade Pikaday version +merge_request: +author: +type: fixed diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml index db31b01a7d2..3af7f7bd5c0 100644 --- a/config/dependency_decisions.yml +++ b/config/dependency_decisions.yml @@ -464,3 +464,10 @@ :why: Our own library - https://gitlab.com/gitlab-org/gitlab-svgs :versions: [] :when: 2017-09-19 14:36:32.795496000 Z +- - :license + - pikaday + - MIT + - :who: + :why: + :versions: [] + :when: 2017-10-17 17:46:12.367554000 Z diff --git a/package.json b/package.json index 620b39766f4..057cd8f7bc7 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "monaco-editor": "0.10.0", "mousetrap": "^1.4.6", "name-all-modules-plugin": "^1.0.1", - "pikaday": "^1.5.1", + "pikaday": "^1.6.1", "prismjs": "^1.6.0", "raphael": "^2.2.7", "raven-js": "^3.14.0", diff --git a/spec/javascripts/lib/utils/datefix_spec.js b/spec/javascripts/lib/utils/datefix_spec.js new file mode 100644 index 00000000000..0b9fde2be67 --- /dev/null +++ b/spec/javascripts/lib/utils/datefix_spec.js @@ -0,0 +1,29 @@ +import { pad, parsePikadayDate, pikadayToString } from '~/lib/utils/datefix'; + +describe('datefix', () => { + describe('pad', () => { + it('should add a 0 when length is smaller than 2', () => { + expect(pad(2)).toEqual('02'); + }); + + it('should not add a zero when lenght matches the default', () => { + expect(pad(12)).toEqual('12'); + }); + + it('should add a 0 when lenght is smaller than the provided', () => { + expect(pad(12, 3)).toEqual('012'); + }); + }); + + describe('parsePikadayDate', () => { + it('should return a UTC date', () => { + expect(parsePikadayDate('2020-01-29')).toEqual(new Date('2020-01-29')); + }); + }); + + describe('pikadayToString', () => { + it('should format a UTC date into yyyy-mm-dd format', () => { + expect(pikadayToString(new Date('2020-01-29'))).toEqual('2020-01-29'); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index b830278eab0..91ffbe5d4b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4677,9 +4677,9 @@ pify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" -pikaday@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/pikaday/-/pikaday-1.5.1.tgz#0a48549bc1a14ea1d08c44074d761bc2f2bfcfd3" +pikaday@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/pikaday/-/pikaday-1.6.1.tgz#b91bcb9b8539cedd8d6d08e4e7465e12095671b0" optionalDependencies: moment "2.x"