diff options
20 files changed, 183 insertions, 224 deletions
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index f2bc2ec157a..4ecbf195b64 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -10,7 +10,6 @@ function requireAll(context) { return context.keys().map(context); } window.$ = window.jQuery = require('jquery'); require('jquery-ui/ui/autocomplete'); -require('jquery-ui/ui/datepicker'); require('jquery-ui/ui/draggable'); require('jquery-ui/ui/effect-highlight'); require('jquery-ui/ui/sortable'); @@ -35,6 +34,7 @@ require('bootstrap/js/transition'); require('bootstrap/js/tooltip'); require('bootstrap/js/popover'); require('select2/select2.js'); +window.Pikaday = require('pikaday'); window._ = require('underscore'); window.Dropzone = require('dropzone'); window.Sortable = require('vendor/Sortable'); diff --git a/app/assets/javascripts/boards/filters/due_date_filters.js.es6 b/app/assets/javascripts/boards/filters/due_date_filters.js.es6 index 7e192e90fe6..ac2966cef5d 100644 --- a/app/assets/javascripts/boards/filters/due_date_filters.js.es6 +++ b/app/assets/javascripts/boards/filters/due_date_filters.js.es6 @@ -1,6 +1,7 @@ /* global Vue */ +/* global dateFormat */ Vue.filter('due-date', (value) => { const date = new Date(value); - return $.datepicker.formatDate('M d, yy', date); + return dateFormat(date, 'mmm d, yyyy'); }); diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index f8efca76b13..7eec2d39a9c 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -97,6 +97,7 @@ const ShortcutsBlob = require('./shortcuts_blob'); break; case 'projects:milestones:new': case 'projects:milestones:edit': + case 'projects:milestones:update': new ZenMode(); new gl.DueDateSelectors(); new gl.GLForm($('.milestone-form')); diff --git a/app/assets/javascripts/due_date_select.js.es6 b/app/assets/javascripts/due_date_select.js.es6 index d81d4cf8425..ab5ce23d261 100644 --- a/app/assets/javascripts/due_date_select.js.es6 +++ b/app/assets/javascripts/due_date_select.js.es6 @@ -1,4 +1,6 @@ /* 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 */ +/* global Pikaday */ (function(global) { class DueDateSelect { @@ -25,11 +27,14 @@ this.initGlDropdown(); this.initRemoveDueDate(); this.initDatePicker(); - this.initStopPropagation(); } initGlDropdown() { this.$dropdown.glDropdown({ + opened: () => { + const calendar = this.$datePicker.data('pikaday'); + calendar.show(); + }, hidden: () => { this.$selectbox.hide(); this.$value.css('display', ''); @@ -38,25 +43,37 @@ } initDatePicker() { - this.$datePicker.datepicker({ - dateFormat: 'yy-mm-dd', - defaultDate: $("input[name='" + this.fieldName + "']").val(), - altField: "input[name='" + this.fieldName + "']", - onSelect: () => { + const $dueDateInput = $(`input[name='${this.fieldName}']`); + + const calendar = new Pikaday({ + field: $dueDateInput.get(0), + theme: 'gitlab-theme', + format: 'YYYY-MM-DD', + onSelect: (dateText) => { + const formattedDate = dateFormat(new Date(dateText), 'yyyy-mm-dd'); + + $dueDateInput.val(formattedDate); + if (this.$dropdown.hasClass('js-issue-boards-due-date')) { - gl.issueBoards.BoardsStore.detail.issue.dueDate = $(`input[name='${this.fieldName}']`).val(); + gl.issueBoards.BoardsStore.detail.issue.dueDate = $dueDateInput.val(); this.updateIssueBoardIssue(); } else { - return this.saveDueDate(true); + this.saveDueDate(true); } } }); + + this.$datePicker.append(calendar.el); + this.$datePicker.data('pikaday', calendar); } initRemoveDueDate() { this.$block.on('click', '.js-remove-due-date', (e) => { + const calendar = this.$datePicker.data('pikaday'); e.preventDefault(); + calendar.setDate(null); + if (this.$dropdown.hasClass('js-issue-boards-due-date')) { gl.issueBoards.BoardsStore.detail.issue.dueDate = ''; this.updateIssueBoardIssue(); @@ -67,12 +84,6 @@ }); } - initStopPropagation() { - $(document).off('click', '.ui-datepicker-header a').on('click', '.ui-datepicker-header a', (e) => { - return e.stopImmediatePropagation(); - }); - } - saveDueDate(isDropdown) { this.parseSelectedDate(); this.prepSelectedDate(); @@ -86,7 +97,7 @@ // Construct Date object manually to avoid buggy dateString support within Date constructor const dateArray = this.rawSelectedDate.split('-').map(v => parseInt(v, 10)); const dateObj = new Date(dateArray[0], dateArray[1] - 1, dateArray[2]); - this.displayedDate = $.datepicker.formatDate('M d, yy', dateObj); + this.displayedDate = dateFormat(dateObj, 'mmm d, yyyy'); } else { this.displayedDate = 'No due date'; } @@ -153,14 +164,24 @@ } initMilestoneDatePicker() { - $('.datepicker').datepicker({ - dateFormat: 'yy-mm-dd' + $('.datepicker').each(function() { + const $datePicker = $(this); + const calendar = new Pikaday({ + field: $datePicker.get(0), + theme: 'gitlab-theme', + format: 'YYYY-MM-DD', + onSelect(dateText) { + $datePicker.val(dateFormat(new Date(dateText), 'yyyy-mm-dd')); + } + }); + + $datePicker.data('pikaday', calendar); }); $('.js-clear-due-date,.js-clear-start-date').on('click', (e) => { e.preventDefault(); - const datepicker = $(e.target).siblings('.datepicker'); - $.datepicker._clearDate(datepicker); + const calendar = $(e.target).siblings('.datepicker').data('pikaday'); + calendar.setDate(null); }); } diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index d9101b55c7f..77fa662892a 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -437,7 +437,7 @@ } }; - GitLabDropdown.prototype.opened = function() { + GitLabDropdown.prototype.opened = function(e) { var contentHtml; this.resetRows(); this.addArrowKeyEvent(); @@ -457,6 +457,10 @@ this.positionMenuAbove(); } + if (this.options.opened) { + this.options.opened.call(this, e); + } + return this.dropdown.trigger('shown.gl.dropdown'); }; diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js index 293b856dc4d..2ec545db665 100644 --- a/app/assets/javascripts/issuable_form.js +++ b/app/assets/javascripts/issuable_form.js @@ -3,6 +3,8 @@ /* global UsersSelect */ /* global ZenMode */ /* global Autosave */ +/* global dateFormat */ +/* global Pikaday */ (function() { var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; @@ -13,7 +15,7 @@ IssuableForm.prototype.wipRegex = /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i; function IssuableForm(form) { - var $issuableDueDate; + var $issuableDueDate, calendar; this.form = form; this.toggleWip = bind(this.toggleWip, this); this.renderWipExplanation = bind(this.renderWipExplanation, this); @@ -35,12 +37,14 @@ this.initMoveDropdown(); $issuableDueDate = $('#issuable-due-date'); if ($issuableDueDate.length) { - $('.datepicker').datepicker({ - dateFormat: 'yy-mm-dd', - onSelect: function(dateText, inst) { - return $issuableDueDate.val(dateText); + calendar = new Pikaday({ + field: $issuableDueDate.get(0), + theme: 'gitlab-theme', + format: 'YYYY-MM-DD', + onSelect: function(dateText) { + $issuableDueDate.val(dateFormat(new Date(dateText), 'yyyy-mm-dd')); } - }).datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $issuableDueDate.val())); + }); } } diff --git a/app/assets/javascripts/member_expiration_date.js.es6 b/app/assets/javascripts/member_expiration_date.js.es6 index bf6c0ec2798..f57d4a20498 100644 --- a/app/assets/javascripts/member_expiration_date.js.es6 +++ b/app/assets/javascripts/member_expiration_date.js.es6 @@ -1,3 +1,5 @@ +/* global Pikaday */ +/* global dateFormat */ (() => { // 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 @@ -11,21 +13,34 @@ } const inputs = $(selector); - inputs.datepicker({ - dateFormat: 'yy-mm-dd', - minDate: 1, - onSelect: function onSelect() { - $(this).trigger('change'); - toggleClearInput.call(this); - }, + inputs.each((i, el) => { + const $input = $(el); + + const calendar = new Pikaday({ + field: $input.get(0), + theme: 'gitlab-theme', + format: 'YYYY-MM-DD', + minDate: new Date(), + onSelect(dateText) { + $input.val(dateFormat(new Date(dateText), 'yyyy-mm-dd')); + + $input.trigger('change'); + + toggleClearInput.call($input); + }, + }); + + $input.data('pikaday', calendar); }); inputs.next('.js-clear-input').on('click', function clicked(event) { event.preventDefault(); const input = $(this).closest('.clearable-input').find(selector); - input.datepicker('setDate', null) - .trigger('change'); + const calendar = input.data('pikaday'); + + calendar.setDate(null); + input.trigger('change'); toggleClearInput.call(input); }); diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 8b93665d085..1dcd1f8a6fc 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -2,7 +2,6 @@ * This is a manifest file that'll automatically include all the stylesheets available in this directory * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at * the top of the compiled file, but it's generally better to create a new file per style scope. - *= require jquery-ui/datepicker *= require jquery-ui/autocomplete *= require jquery.atwho *= require select2 @@ -19,6 +18,8 @@ * directory. */ +@import "../../../node_modules/pikaday/scss/pikaday"; + /* * GitLab UI framework */ diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss index 1d2d1bfc0d7..d485e75a434 100644 --- a/app/assets/stylesheets/framework/calendar.scss +++ b/app/assets/stylesheets/framework/calendar.scss @@ -43,3 +43,56 @@ float: right; font-size: 12px; } + +.pika-single.gitlab-theme { + .pika-label { + color: $gl-text-color-secondary; + font-size: 14px; + font-weight: normal; + } + + th { + padding: 2px 0; + color: $note-disabled-comment-color; + font-weight: normal; + text-transform: lowercase; + border-top: 1px solid $calendar-border-color; + } + + abbr { + cursor: default; + } + + td { + border: 1px solid $calendar-border-color; + + &:first-child { + border-left: 0; + } + + &:last-child { + border-right: 0; + } + } + + .pika-day { + border-radius: 0; + background-color: $white-light; + text-align: center; + } + + .is-today { + .pika-day { + color: inherit; + font-weight: normal; + } + } + + .is-selected .pika-day, + .pika-day:hover, + .is-today .pika-day:hover { + background: $gl-primary; + color: $white-light; + box-shadow: none; + } +} diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index ca5861bf3e6..facfb7f9920 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -502,119 +502,16 @@ max-height: 230px; } - .ui-widget { - table { - margin: 0; - } - - &.ui-datepicker-inline { - padding: 0 10px; - border: 0; - width: 100%; - } - - .ui-datepicker-header { - padding: 0 8px 10px; - border: 0; - - .ui-icon { - background: none; - font-size: 20px; - text-indent: 0; - - &::before { - display: block; - position: relative; - top: -2px; - color: $dropdown-title-btn-color; - font: normal normal normal 14px/1 FontAwesome; - font-size: inherit; - text-rendering: auto; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - } - } - } - - .ui-datepicker-calendar { - .ui-state-hover, - .ui-state-active { - color: $white-light; - border: 0; - } - } - - .ui-datepicker-prev, - .ui-datepicker-next { - top: 0; - height: 15px; - cursor: pointer; - - &:hover { - background-color: transparent; - border: 0; - - .ui-icon::before { - color: $md-link-color; - } - } - } - - .ui-datepicker-prev { - left: 0; - - .ui-icon::before { - content: '\f104'; - text-align: left; - } - } - - .ui-datepicker-next { - right: 0; - - .ui-icon::before { - content: '\f105'; - text-align: right; - } - } - - td { - padding: 0; - border: 1px solid $calendar-border-color; - - &:first-child { - border-left: 0; - } - - &:last-child { - border-right: 0; - } - - a { - line-height: 17px; - border: 0; - border-radius: 0; - } - } - - .ui-datepicker-title { - color: $gl-text-color; - font-size: 14px; - line-height: 1; - font-weight: normal; - } - } - - th { - padding: 2px 0; - color: $note-disabled-comment-color; - font-weight: normal; - text-transform: lowercase; - border-top: 1px solid $calendar-border-color; + .pika-single { + position: relative!important; + top: 0!important; + border: 0; + box-shadow: none; } - .ui-datepicker-unselectable { - background-color: $gray-light; + .pika-lendar { + margin-top: -5px; + margin-bottom: 0; } } diff --git a/app/assets/stylesheets/framework/jquery.scss b/app/assets/stylesheets/framework/jquery.scss index 8487384aed6..d335fedefe2 100644 --- a/app/assets/stylesheets/framework/jquery.scss +++ b/app/assets/stylesheets/framework/jquery.scss @@ -2,42 +2,6 @@ font-family: $regular_font; font-size: $font-size-base; - &.ui-datepicker, - &.ui-datepicker-inline { - border: 1px solid $jq-ui-border; - padding: 10px; - width: 270px; - - .ui-datepicker-header { - background: $white-light; - border-color: $jq-ui-border; - - .ui-datepicker-prev, - .ui-datepicker-next { - top: 4px; - } - - .ui-datepicker-prev { - left: 2px; - } - - .ui-datepicker-next { - right: 2px; - } - - .ui-state-hover { - background: transparent; - border: 0; - cursor: pointer; - } - } - - .ui-datepicker-calendar td a { - padding: 5px; - text-align: center; - } - } - &.ui-autocomplete { border-color: $jq-ui-border; padding: 0; @@ -59,14 +23,4 @@ border: 0; background: transparent; } - - .ui-datepicker-calendar { - .ui-state-active, - .ui-state-hover, - .ui-state-focus { - border: 1px solid $gl-primary; - background: $gl-primary; - color: $white-light; - } - } } diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 722b3006f7c..8031c4467a4 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -201,10 +201,6 @@ color: $note-disabled-comment-color; } -.datepicker.personal-access-tokens-expires-at .ui-state-disabled span { - text-align: center; -} - .created-personal-access-token-container { #created-personal-access-token { width: 90%; diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml index 60a561c9f9c..2c006e1712d 100644 --- a/app/views/profiles/personal_access_tokens/index.html.haml +++ b/app/views/profiles/personal_access_tokens/index.html.haml @@ -85,11 +85,17 @@ :javascript - var date = $('#personal_access_token_expires_at').val(); - - var datepicker = $(".datepicker").datepicker({ - dateFormat: "yy-mm-dd", - minDate: 0 + var $dateField = $('#personal_access_token_expires_at'); + var date = $dateField.val(); + + new Pikaday({ + field: $dateField.get(0), + theme: 'gitlab-theme', + format: 'YYYY-MM-DD', + minDate: new Date(), + onSelect: function(dateText) { + $dateField.val(dateFormat(new Date(dateText), 'yyyy-mm-dd')); + } }); $("#created-personal-access-token").click(function() { diff --git a/app/views/shared/milestones/_form_dates.html.haml b/app/views/shared/milestones/_form_dates.html.haml index 748b10a1298..ed94773ef89 100644 --- a/app/views/shared/milestones/_form_dates.html.haml +++ b/app/views/shared/milestones/_form_dates.html.haml @@ -10,6 +10,3 @@ .col-sm-10 = f.text_field :due_date, class: "datepicker form-control", placeholder: "Select due date" %a.inline.prepend-top-5.js-clear-due-date{ href: "#" } Clear due date - -:javascript - new gl.DueDateSelectors(); diff --git a/changelogs/unreleased/remove-jquery-ui-datepicker.yml b/changelogs/unreleased/remove-jquery-ui-datepicker.yml new file mode 100644 index 00000000000..cd00690d774 --- /dev/null +++ b/changelogs/unreleased/remove-jquery-ui-datepicker.yml @@ -0,0 +1,4 @@ +--- +title: Replaced jQuery UI datepicker +merge_request: +author: diff --git a/package.json b/package.json index 9581d966237..a25e09e4cf2 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "jquery-ujs": "1.2.1", "json-loader": "^0.5.4", "mousetrap": "1.4.6", + "pikaday": "^1.5.1", "select2": "3.5.2-browserify", "stats-webpack-plugin": "^0.4.2", "underscore": "1.8.3", diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index 9cc50167395..bad6b56a18a 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -241,7 +241,7 @@ describe 'Issue Boards', feature: true, js: true do page.within('.due_date') do click_link 'Edit' - click_link Date.today.day + click_button Date.today.day wait_for_vue_resource diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 394eb31aff8..755162a1eb5 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -78,8 +78,8 @@ describe 'Issues', feature: true do fill_in 'issue_description', with: 'bug description' find('#issuable-due-date').click - page.within '.ui-datepicker' do - click_link date.day + page.within '.pika-single' do + click_button date.day end expect(find('#issuable-due-date').value).to eq date.to_s @@ -110,8 +110,8 @@ describe 'Issues', feature: true do fill_in 'issue_description', with: 'bug description' find('#issuable-due-date').click - page.within '.ui-datepicker' do - click_link date.day + page.within '.pika-single' do + click_button date.day end expect(find('#issuable-due-date').value).to eq date.to_s @@ -624,8 +624,8 @@ describe 'Issues', feature: true do page.within '.due_date' do click_link 'Edit' - page.within '.ui-datepicker-calendar' do - click_link date.day + page.within '.pika-single' do + click_button date.day end wait_for_ajax @@ -635,11 +635,13 @@ describe 'Issues', feature: true do end it 'removes due date from issue' do + date = Date.today.at_beginning_of_month + 2.days + page.within '.due_date' do click_link 'Edit' - page.within '.ui-datepicker-calendar' do - first('.ui-state-default').click + page.within '.pika-single' do + click_button date.day end wait_for_ajax diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb index 55a01057c83..eb7b8a24669 100644 --- a/spec/features/profiles/personal_access_tokens_spec.rb +++ b/spec/features/profiles/personal_access_tokens_spec.rb @@ -34,7 +34,7 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do # Set date to 1st of next month find_field("Expires at").trigger('focus') - find("a[title='Next']").click + find(".pika-next").click click_on "1" # Scopes diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb index f136d9ce0fa..c3f45be6e4b 100644 --- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb +++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb @@ -14,15 +14,16 @@ feature 'Projects > Members > Master adds member with expiration date', feature: login_as(master) end - scenario 'expiration date is displayed in the members list', js: true do + scenario 'expiration date is displayed in the members list' do travel_to Time.zone.parse('2016-08-06 08:00') do - visit namespace_project_settings_members_path(project.namespace, project) + date = 4.days.from_now + visit namespace_project_project_members_path(project.namespace, project) + page.within '.users-project-form' do select2(new_member.id, from: '#user_ids', multiple: true) - fill_in 'expires_at', with: '2016-08-10' + fill_in 'expires_at', with: date.to_s(:medium) + click_on 'Add to project' end - find('.users-project-form').click - click_on 'Add to project' page.within "#project_member_#{new_member.project_members.first.id}" do expect(page).to have_content('Expires in 4 days') @@ -32,11 +33,12 @@ feature 'Projects > Members > Master adds member with expiration date', feature: scenario 'change expiration date' do travel_to Time.zone.parse('2016-08-06 08:00') do - project.team.add_users([new_member.id], :developer, expires_at: '2016-09-06') + date = 3.days.from_now + project.team.add_users([new_member.id], :developer, expires_at: Date.today.to_s(:medium)) visit namespace_project_project_members_path(project.namespace, project) page.within "#project_member_#{new_member.project_members.first.id}" do - find('.js-access-expiration-date').set '2016-08-09' + find('.js-access-expiration-date').set date.to_s(:medium) wait_for_ajax expect(page).to have_content('Expires in 3 days') end |