diff --git a/classes/plugin/AdminController.php b/classes/plugin/AdminController.php index 9b0c99bf..7c5f59d4 100644 --- a/classes/plugin/AdminController.php +++ b/classes/plugin/AdminController.php @@ -943,6 +943,11 @@ class AdminController extends AdminBaseController public function taskSafeUpgradeRestore() { if (!$this->authorizeTask('install grav', ['admin.super'])) { + $this->sendJsonResponse([ + 'status' => 'error', + 'message' => $this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') + ]); + return false; } @@ -950,28 +955,28 @@ class AdminController extends AdminBaseController $snapshotId = isset($post['snapshot']) ? (string)$post['snapshot'] : ''; if ($snapshotId === '') { - $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.RESTORE_GRAV_INVALID'), 'error'); - $this->setRedirect('/tools/restore-grav'); + $this->sendJsonResponse([ + 'status' => 'error', + 'message' => $this->admin::translate('PLUGIN_ADMIN.RESTORE_GRAV_INVALID') + ]); - return false; + return true; } $manager = $this->getSafeUpgradeManager(); - $result = $manager->restoreSnapshot($snapshotId); + $result = $manager->queueRestore($snapshotId); + $status = $result['status'] ?? 'error'; - if (($result['status'] ?? 'error') === 'success') { - $manifest = $result['manifest'] ?? []; - $version = $manifest['source_version'] ?? $manifest['target_version'] ?? 'unknown'; - $this->admin->setMessage( - sprintf($this->admin::translate('PLUGIN_ADMIN.RESTORE_GRAV_SUCCESS'), $snapshotId, $version), - 'info' - ); - } else { - $message = $result['message'] ?? $this->admin::translate('PLUGIN_ADMIN.RESTORE_GRAV_FAILED'); - $this->admin->setMessage($message, 'error'); + $response = [ + 'status' => $status === 'error' ? 'error' : 'success', + 'data' => $result, + ]; + + if (!empty($result['message'])) { + $response['message'] = $result['message']; } - $this->setRedirect('/tools/restore-grav'); + $this->sendJsonResponse($response); return true; } diff --git a/classes/plugin/SafeUpgradeManager.php b/classes/plugin/SafeUpgradeManager.php index f57cc5af..be9d0096 100644 --- a/classes/plugin/SafeUpgradeManager.php +++ b/classes/plugin/SafeUpgradeManager.php @@ -199,6 +199,30 @@ class SafeUpgradeManager ]; } + public function queueRestore(string $snapshotId): array + { + $snapshotId = trim($snapshotId); + if ($snapshotId === '') { + return [ + 'status' => 'error', + 'message' => 'Snapshot identifier is required.', + ]; + } + + $manifestPath = GRAV_ROOT . '/user/data/upgrades/' . $snapshotId . '.json'; + if (!is_file($manifestPath)) { + return [ + 'status' => 'error', + 'message' => sprintf('Snapshot %s not found.', $snapshotId), + ]; + } + + return $this->queue([ + 'operation' => 'restore', + 'snapshot_id' => $snapshotId, + ]); + } + /** * @param array $snapshotIds * @return array @@ -458,6 +482,10 @@ class SafeUpgradeManager public function queue(array $options = []): array { + $operation = $options['operation'] ?? 'upgrade'; + $options['operation'] = $operation; + + $this->resetProgress(); $jobId = $this->generateJobId(); $this->setJobId($jobId); @@ -487,7 +515,8 @@ class SafeUpgradeManager $this->log(sprintf('Queued safe upgrade job %s', $jobId)); - $this->setProgress('queued', 'Waiting for upgrade worker...', 0, ['job_id' => $jobId, 'status' => 'queued']); + $queueMessage = $operation === 'restore' ? 'Waiting for restore worker...' : 'Waiting for upgrade worker...'; + $this->setProgress('queued', $queueMessage, 0, ['job_id' => $jobId, 'status' => 'queued', 'operation' => $operation]); if (!function_exists('proc_open')) { $message = 'proc_open() is disabled on this server; unable to run safe upgrade worker.'; @@ -495,12 +524,13 @@ class SafeUpgradeManager 'status' => 'error', 'error' => $message, ]); - $this->setProgress('error', $message, null, ['job_id' => $jobId]); + $this->setProgress('error', $message, null, ['job_id' => $jobId, 'operation' => $operation]); $this->clearJobContext(); return [ 'status' => 'error', 'message' => $message, + 'operation' => $operation, ]; } @@ -554,12 +584,13 @@ class SafeUpgradeManager 'status' => 'error', 'error' => $message, ]); - $this->setProgress('error', $message, null, ['job_id' => $jobId]); + $this->setProgress('error', $message, null, ['job_id' => $jobId, 'operation' => $operation]); $this->clearJobContext(); return [ 'status' => 'error', 'message' => $message, + 'operation' => $operation, ]; } @@ -577,6 +608,7 @@ class SafeUpgradeManager 'progress' => $this->getProgress(), 'job' => $this->readManifest(), 'context' => $this->buildStatusContext(), + 'operation' => $operation, ]; } @@ -813,6 +845,57 @@ class SafeUpgradeManager ]; } + public function runRestore(array $options): array + { + $snapshotId = isset($options['snapshot_id']) ? (string)$options['snapshot_id'] : ''; + if ($snapshotId === '') { + return $this->errorResult('Snapshot identifier is required.', ['operation' => 'restore']); + } + + $this->setProgress('restoring', sprintf('Restoring snapshot %s...', $snapshotId), null, [ + 'operation' => 'restore', + 'snapshot' => $snapshotId, + ]); + + $result = $this->restoreSnapshot($snapshotId); + if (($result['status'] ?? 'error') !== 'success') { + $message = $result['message'] ?? 'Snapshot restore failed.'; + + return $this->errorResult($message, [ + 'operation' => 'restore', + 'snapshot' => $snapshotId, + ]); + } + + $manifest = $result['manifest'] ?? []; + $version = $manifest['source_version'] ?? $manifest['target_version'] ?? null; + + $this->setProgress('complete', sprintf('Snapshot %s restored.', $snapshotId), 100, [ + 'operation' => 'restore', + 'snapshot' => $snapshotId, + 'version' => $version, + ]); + + if ($this->jobManifestPath) { + $this->updateJob([ + 'result' => [ + 'status' => 'success', + 'snapshot' => $snapshotId, + 'version' => $version, + 'manifest' => $manifest, + ], + ]); + } + + return [ + 'status' => 'success', + 'snapshot' => $snapshotId, + 'version' => $version, + 'manifest' => $manifest, + 'context' => $this->buildStatusContext(), + ]; + } + /** * Retrieve current progress payload. * diff --git a/languages/en.yaml b/languages/en.yaml index 7b7431db..17b57c09 100644 --- a/languages/en.yaml +++ b/languages/en.yaml @@ -852,6 +852,9 @@ PLUGIN_ADMIN: RESTORE_GRAV_NONE: "No safe upgrade snapshots are currently available." RESTORE_GRAV_INVALID: "Select at least one snapshot before continuing." RESTORE_GRAV_SUCCESS: "Snapshot %s restored (Grav %s)." + RESTORE_GRAV_SUCCESS_MESSAGE: "Snapshot %1$s restored (Grav %2$s)." + RESTORE_GRAV_SUCCESS_SIMPLE: "Snapshot %s restored." + RESTORE_GRAV_RUNNING: "Restoring snapshot %s..." RESTORE_GRAV_FAILED: "Unable to restore the selected snapshot." RESTORE_GRAV_DELETE_SUCCESS: "%d snapshot(s) deleted." RESTORE_GRAV_DELETE_FAILED: "Failed to delete one or more snapshots." diff --git a/themes/grav/app/tools/index.js b/themes/grav/app/tools/index.js index 37d4b65e..931c6308 100644 --- a/themes/grav/app/tools/index.js +++ b/themes/grav/app/tools/index.js @@ -1 +1,2 @@ import './logs'; +import './restore'; diff --git a/themes/grav/app/tools/restore.js b/themes/grav/app/tools/restore.js new file mode 100644 index 00000000..8b5205b7 --- /dev/null +++ b/themes/grav/app/tools/restore.js @@ -0,0 +1,139 @@ +import $ from 'jquery'; +import { config, translations } from 'grav-config'; +import request from '../utils/request'; +import toastr from '../utils/toastr'; + +const paramSep = config.param_sep; +const task = `task${paramSep}`; +const nonce = `admin-nonce${paramSep}${config.admin_nonce}`; +const base = `${config.base_url_relative}/update.json`; + +const urls = { + restore: `${base}/${task}safeUpgradeRestore/${nonce}`, + status: `${base}/${task}safeUpgradeStatus/${nonce}`, +}; + +class RestoreManager { + constructor() { + this.job = null; + this.pollTimer = null; + + $(document).on('click', '[data-restore-snapshot]', (event) => { + event.preventDefault(); + const button = $(event.currentTarget); + if (this.job) { + return; + } + this.startRestore(button); + }); + } + + startRestore(button) { + const snapshot = button.data('restore-snapshot'); + if (!snapshot) { + return; + } + + button.prop('disabled', true).addClass('is-loading'); + + const body = { snapshot }; + request(urls.restore, { method: 'post', body }, (response) => { + button.prop('disabled', false).removeClass('is-loading'); + + if (!response) { + toastr.error(translations.PLUGIN_ADMIN?.RESTORE_GRAV_FAILED || 'Snapshot restore failed.'); + return; + } + + if (response.status === 'error') { + toastr.error(response.message || translations.PLUGIN_ADMIN?.RESTORE_GRAV_FAILED || 'Snapshot restore failed.'); + return; + } + + const data = response.data || {}; + const jobId = data.job_id || (data.job && data.job.id); + if (!jobId) { + const message = response.message || translations.PLUGIN_ADMIN?.RESTORE_GRAV_FAILED || 'Snapshot restore failed.'; + toastr.error(message); + return; + } + + this.job = { + id: jobId, + snapshot, + }; + + const runningMessage = translations.PLUGIN_ADMIN?.RESTORE_GRAV_RUNNING + ? translations.PLUGIN_ADMIN.RESTORE_GRAV_RUNNING.replace('%s', snapshot) + : `Restoring snapshot ${snapshot}...`; + toastr.info(runningMessage); + this.schedulePoll(); + }); + } + + schedulePoll(delay = 1200) { + this.clearPoll(); + this.pollTimer = setTimeout(() => this.pollStatus(), delay); + } + + clearPoll() { + if (this.pollTimer) { + clearTimeout(this.pollTimer); + this.pollTimer = null; + } + } + + pollStatus() { + if (!this.job) { + return; + } + + const jobId = this.job.id; + request(`${urls.status}?job=${encodeURIComponent(jobId)}`, { silentErrors: true }, (response) => { + if (!response || response.status !== 'success') { + this.schedulePoll(); + return; + } + + const data = response.data || {}; + const job = data.job || {}; + const progress = data.progress || {}; + + const stage = progress.stage || null; + const status = job.status || progress.status || null; + + if (stage === 'error' || status === 'error') { + const message = job.error || progress.message || translations.PLUGIN_ADMIN?.RESTORE_GRAV_FAILED || 'Snapshot restore failed.'; + toastr.error(message); + this.job = null; + this.clearPoll(); + return; + } + + if (stage === 'complete' || status === 'success') { + const snapshot = progress.snapshot || this.job.snapshot; + const version = (job.result && job.result.version) || progress.version || ''; + let successMessage; + if (translations.PLUGIN_ADMIN?.RESTORE_GRAV_SUCCESS_MESSAGE && version) { + successMessage = translations.PLUGIN_ADMIN.RESTORE_GRAV_SUCCESS_MESSAGE.replace('%1$s', snapshot).replace('%2$s', version); + } else if (translations.PLUGIN_ADMIN?.RESTORE_GRAV_SUCCESS_SIMPLE) { + successMessage = translations.PLUGIN_ADMIN.RESTORE_GRAV_SUCCESS_SIMPLE.replace('%s', snapshot); + } else { + successMessage = version ? `Snapshot ${snapshot} restored (Grav ${version}).` : `Snapshot ${snapshot} restored.`; + } + toastr.success(successMessage); + this.job = null; + this.clearPoll(); + setTimeout(() => window.location.reload(), 1500); + return; + } + + this.schedulePoll(); + }); + } +} + +// Initialize restore manager when tools view loads. +$(document).ready(() => { + new RestoreManager(); +}); diff --git a/themes/grav/js/admin.min.js b/themes/grav/js/admin.min.js index 1ed570ed..a5cf6a27 100644 --- a/themes/grav/js/admin.min.js +++ b/themes/grav/js/admin.min.js @@ -324,2361 +324,7 @@ webpackContext.id = 35358; /***/ }), -/***/ 41669: -/***/ ((module) => { - -"use strict"; -module.exports = jQuery; - -/***/ }), - -/***/ 61954: -/***/ ((module) => { - -"use strict"; -module.exports = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII="; - -/***/ }), - -/***/ 70086: -/***/ ((module, exports, __webpack_require__) => { - -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } -/*! version : 4.17.47 - ========================================================= - bootstrap-datetimejs - https://github.com/Eonasdan/bootstrap-datetimepicker - Copyright (c) 2015 Jonathan Peterson - ========================================================= - */ -/* - The MIT License (MIT) - - Copyright (c) 2015 Jonathan Peterson - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - */ -/* global define:false */ -/* global jQuery:false */ -/* global moment:false */ -(function (factory) { - 'use strict'; - - if (true) { - // AMD is used - Register as an anonymous module. - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(41669), __webpack_require__(95093)], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); - } else // removed by dead control flow -{} -})(function ($, moment) { - 'use strict'; - - if (!moment) { - throw new Error('bootstrap-datetimepicker requires Moment.js to be loaded first'); - } - var dateTimePicker = function dateTimePicker(element, options) { - var picker = {}; - var date; - var viewDate; - var unset = true; - var input; - var component = false; - var widget = false; - var use24Hours; - var minViewModeNumber = 0; - var actualFormat; - var parseFormats; - var currentViewMode; - var datePickerModes = [{ - clsName: 'days', - navFnc: 'M', - navStep: 1 - }, { - clsName: 'months', - navFnc: 'y', - navStep: 1 - }, { - clsName: 'years', - navFnc: 'y', - navStep: 10 - }, { - clsName: 'decades', - navFnc: 'y', - navStep: 100 - }]; - var viewModes = ['days', 'months', 'years', 'decades']; - var verticalModes = ['top', 'bottom', 'auto']; - var horizontalModes = ['left', 'right', 'auto']; - var toolbarPlacements = ['default', 'top', 'bottom']; - var keyMap = { - 'up': 38, - 38: 'up', - 'down': 40, - 40: 'down', - 'left': 37, - 37: 'left', - 'right': 39, - 39: 'right', - 'tab': 9, - 9: 'tab', - 'escape': 27, - 27: 'escape', - 'enter': 13, - 13: 'enter', - 'pageUp': 33, - 33: 'pageUp', - 'pageDown': 34, - 34: 'pageDown', - 'shift': 16, - 16: 'shift', - 'control': 17, - 17: 'control', - 'space': 32, - 32: 'space', - 't': 84, - 84: 't', - 'delete': 46, - 46: 'delete' - }; - var keyState = {}; - - /** ****************************************************************************** - * - * Private functions - * - ********************************************************************************/ - - var hasTimeZone = function hasTimeZone() { - return moment.tz !== undefined && options.timeZone !== undefined && options.timeZone !== null && options.timeZone !== ''; - }; - var getMoment = function getMoment(d) { - var returnMoment; - if (d === undefined || d === null) { - returnMoment = moment(); // TODO should this use format? and locale? - } else if (moment.isDate(d) || moment.isMoment(d)) { - // If the date that is passed in is already a Date() or moment() object, - // pass it directly to moment. - returnMoment = moment(d); - } else if (hasTimeZone()) { - // There is a string to parse and a default time zone - // parse with the tz function which takes a default time zone if it is not in the format string - returnMoment = moment.tz(d, parseFormats, options.useStrict, options.timeZone); - } else { - returnMoment = moment(d, parseFormats, options.useStrict); - } - if (hasTimeZone()) { - returnMoment.tz(options.timeZone); - } - return returnMoment; - }; - var isEnabled = function isEnabled(granularity) { - if (typeof granularity !== 'string' || granularity.length > 1) { - throw new TypeError('isEnabled expects a single character string parameter'); - } - switch (granularity) { - case 'y': - return actualFormat.indexOf('Y') !== -1; - case 'M': - return actualFormat.indexOf('M') !== -1; - case 'd': - return actualFormat.toLowerCase().indexOf('d') !== -1; - case 'h': - case 'H': - return actualFormat.toLowerCase().indexOf('h') !== -1; - case 'm': - return actualFormat.indexOf('m') !== -1; - case 's': - return actualFormat.indexOf('s') !== -1; - default: - return false; - } - }; - var hasTime = function hasTime() { - return isEnabled('h') || isEnabled('m') || isEnabled('s'); - }; - var hasDate = function hasDate() { - return isEnabled('y') || isEnabled('M') || isEnabled('d'); - }; - var getDatePickerTemplate = function getDatePickerTemplate() { - var headTemplate = $('').append($('').append($('').addClass('prev').attr('data-action', 'previous').append($('').addClass(options.icons.previous))).append($('').addClass('picker-switch').attr('data-action', 'pickerSwitch').attr('colspan', options.calendarWeeks ? '6' : '5')).append($('').addClass('next').attr('data-action', 'next').append($('').addClass(options.icons.next)))); - var contTemplate = $('').append($('').append($('').attr('colspan', options.calendarWeeks ? '8' : '7'))); - return [$('
').addClass('datepicker-days').append($('').addClass('table-condensed').append(headTemplate).append($(''))), $('
').addClass('datepicker-months').append($('
').addClass('table-condensed').append(headTemplate.clone()).append(contTemplate.clone())), $('
').addClass('datepicker-years').append($('
').addClass('table-condensed').append(headTemplate.clone()).append(contTemplate.clone())), $('
').addClass('datepicker-decades').append($('
').addClass('table-condensed').append(headTemplate.clone()).append(contTemplate.clone()))]; - }; - var getTimePickerMainTemplate = function getTimePickerMainTemplate() { - var topRow = $(''); - var middleRow = $(''); - var bottomRow = $(''); - if (isEnabled('h')) { - topRow.append($('
').append($('').attr({ - href: '#', - tabindex: '-1', - 'title': options.tooltips.incrementHour - }).addClass('btn').attr('data-action', 'incrementHours').append($('').addClass(options.icons.up)))); - middleRow.append($('').append($('').addClass('timepicker-hour').attr({ - 'data-time-component': 'hours', - 'title': options.tooltips.pickHour - }).attr('data-action', 'showHours'))); - bottomRow.append($('').append($('').attr({ - href: '#', - tabindex: '-1', - 'title': options.tooltips.decrementHour - }).addClass('btn').attr('data-action', 'decrementHours').append($('').addClass(options.icons.down)))); - } - if (isEnabled('m')) { - if (isEnabled('h')) { - topRow.append($('').addClass('separator')); - middleRow.append($('').addClass('separator').html(':')); - bottomRow.append($('').addClass('separator')); - } - topRow.append($('').append($('').attr({ - href: '#', - tabindex: '-1', - 'title': options.tooltips.incrementMinute - }).addClass('btn').attr('data-action', 'incrementMinutes').append($('').addClass(options.icons.up)))); - middleRow.append($('').append($('').addClass('timepicker-minute').attr({ - 'data-time-component': 'minutes', - 'title': options.tooltips.pickMinute - }).attr('data-action', 'showMinutes'))); - bottomRow.append($('').append($('').attr({ - href: '#', - tabindex: '-1', - 'title': options.tooltips.decrementMinute - }).addClass('btn').attr('data-action', 'decrementMinutes').append($('').addClass(options.icons.down)))); - } - if (isEnabled('s')) { - if (isEnabled('m')) { - topRow.append($('').addClass('separator')); - middleRow.append($('').addClass('separator').html(':')); - bottomRow.append($('').addClass('separator')); - } - topRow.append($('').append($('').attr({ - href: '#', - tabindex: '-1', - 'title': options.tooltips.incrementSecond - }).addClass('btn').attr('data-action', 'incrementSeconds').append($('').addClass(options.icons.up)))); - middleRow.append($('').append($('').addClass('timepicker-second').attr({ - 'data-time-component': 'seconds', - 'title': options.tooltips.pickSecond - }).attr('data-action', 'showSeconds'))); - bottomRow.append($('').append($('').attr({ - href: '#', - tabindex: '-1', - 'title': options.tooltips.decrementSecond - }).addClass('btn').attr('data-action', 'decrementSeconds').append($('').addClass(options.icons.down)))); - } - if (!use24Hours) { - topRow.append($('').addClass('separator')); - middleRow.append($('').append($('').addClass('separator')); - } - return $('
').addClass('timepicker-picker').append($('').addClass('table-condensed').append([topRow, middleRow, bottomRow])); - }; - var getTimePickerTemplate = function getTimePickerTemplate() { - var hoursView = $('
').addClass('timepicker-hours').append($('
').addClass('table-condensed')); - var minutesView = $('
').addClass('timepicker-minutes').append($('
').addClass('table-condensed')); - var secondsView = $('
').addClass('timepicker-seconds').append($('
').addClass('table-condensed')); - var ret = [getTimePickerMainTemplate()]; - if (isEnabled('h')) { - ret.push(hoursView); - } - if (isEnabled('m')) { - ret.push(minutesView); - } - if (isEnabled('s')) { - ret.push(secondsView); - } - return ret; - }; - var getToolbar = function getToolbar() { - var row = []; - if (options.showTodayButton) { - row.push($('
').append($('').attr({ - 'data-action': 'today', - 'title': options.tooltips.today - }).append($('').addClass(options.icons.today)))); - } - if (!options.sideBySide && hasDate() && hasTime()) { - row.push($('').append($('').attr({ - 'data-action': 'togglePicker', - 'title': options.tooltips.selectTime - }).append($('').addClass(options.icons.time)))); - } - if (options.showClear) { - row.push($('').append($('').attr({ - 'data-action': 'clear', - 'title': options.tooltips.clear - }).append($('').addClass(options.icons.clear)))); - } - if (options.showClose) { - row.push($('').append($('').attr({ - 'data-action': 'close', - 'title': options.tooltips.close - }).append($('').addClass(options.icons.close)))); - } - return $('').addClass('table-condensed').append($('').append($('').append(row))); - }; - var getTemplate = function getTemplate() { - var template = $('
').addClass('bootstrap-datetimepicker-widget dropdown-menu'); - var dateView = $('
').addClass('datepicker').append(getDatePickerTemplate()); - var timeView = $('
').addClass('timepicker').append(getTimePickerTemplate()); - var content = $('
    ').addClass('list-unstyled'); - var toolbar = $('
  • ').addClass('picker-switch' + (options.collapse ? ' accordion-toggle' : '')).append(getToolbar()); - if (options.inline) { - template.removeClass('dropdown-menu'); - } - if (use24Hours) { - template.addClass('usetwentyfour'); - } - if (isEnabled('s') && !use24Hours) { - template.addClass('wider'); - } - if (options.sideBySide && hasDate() && hasTime()) { - template.addClass('timepicker-sbs'); - if (options.toolbarPlacement === 'top') { - template.append(toolbar); - } - template.append($('
    ').addClass('row').append(dateView.addClass('col-md-6')).append(timeView.addClass('col-md-6'))); - if (options.toolbarPlacement === 'bottom') { - template.append(toolbar); - } - return template; - } - if (options.toolbarPlacement === 'top') { - content.append(toolbar); - } - if (hasDate()) { - content.append($('
  • ').addClass(options.collapse && hasTime() ? 'collapse in' : '').append(dateView)); - } - if (options.toolbarPlacement === 'default') { - content.append(toolbar); - } - if (hasTime()) { - content.append($('
  • ').addClass(options.collapse && hasDate() ? 'collapse' : '').append(timeView)); - } - if (options.toolbarPlacement === 'bottom') { - content.append(toolbar); - } - return template.append(content); - }; - var dataToOptions = function dataToOptions() { - var eData; - var dataOptions = {}; - if (element.is('input') || options.inline) { - eData = element.data(); - } else { - eData = element.find('input').data(); - } - if (eData.dateOptions && eData.dateOptions instanceof Object) { - dataOptions = $.extend(true, dataOptions, eData.dateOptions); - } - $.each(options, function (key) { - var attributeName = 'date' + key.charAt(0).toUpperCase() + key.slice(1); - if (eData[attributeName] !== undefined) { - dataOptions[key] = eData[attributeName]; - } - }); - return dataOptions; - }; - var place = function place() { - var position = (component || element).position(); - var offset = (component || element).offset(); - var vertical = options.widgetPositioning.vertical; - var horizontal = options.widgetPositioning.horizontal; - var parent; - if (options.widgetParent) { - parent = options.widgetParent.append(widget); - } else if (element.is('input')) { - parent = element.after(widget).parent(); - } else if (options.inline) { - parent = element.append(widget); - return; - } else { - parent = element; - element.children().first().after(widget); - } - - // Top and bottom logic - if (vertical === 'auto') { - if (offset.top + widget.height() * 1.5 >= $(window).height() + $(window).scrollTop() && widget.height() + element.outerHeight() < offset.top) { - vertical = 'top'; - } else { - vertical = 'bottom'; - } - } - - // Left and right logic - if (horizontal === 'auto') { - if (parent.width() < offset.left + widget.outerWidth() / 2 && offset.left + widget.outerWidth() > $(window).width()) { - horizontal = 'right'; - } else { - horizontal = 'left'; - } - } - if (vertical === 'top') { - widget.addClass('top').removeClass('bottom'); - } else { - widget.addClass('bottom').removeClass('top'); - } - if (horizontal === 'right') { - widget.addClass('pull-right'); - } else { - widget.removeClass('pull-right'); - } - - // find the first parent element that has a non-static css positioning - if (parent.css('position') === 'static') { - parent = parent.parents().filter(function () { - return $(this).css('position') !== 'static'; - }).first(); - } - if (parent.length === 0) { - throw new Error('datetimepicker component should be placed within a non-static positioned container'); - } - widget.css({ - top: vertical === 'top' ? 'auto' : position.top + element.outerHeight(), - bottom: vertical === 'top' ? parent.outerHeight() - (parent === element ? 0 : position.top) : 'auto', - left: horizontal === 'left' ? parent === element ? 0 : position.left : 'auto', - right: horizontal === 'left' ? 'auto' : parent.outerWidth() - element.outerWidth() - (parent === element ? 0 : position.left) - }); - }; - var notifyEvent = function notifyEvent(e) { - if (e.type === 'dp.change' && (e.date && e.date.isSame(e.oldDate) || !e.date && !e.oldDate)) { - return; - } - element.trigger(e); - }; - var viewUpdate = function viewUpdate(e) { - if (e === 'y') { - e = 'YYYY'; - } - notifyEvent({ - type: 'dp.update', - change: e, - viewDate: viewDate.clone() - }); - }; - var showMode = function showMode(dir) { - if (!widget) { - return; - } - if (dir) { - currentViewMode = Math.max(minViewModeNumber, Math.min(3, currentViewMode + dir)); - } - widget.find('.datepicker > div').hide().filter('.datepicker-' + datePickerModes[currentViewMode].clsName).show(); - }; - var fillDow = function fillDow() { - var row = $('
'); - var currentDate = viewDate.clone().startOf('w').startOf('d'); - if (options.calendarWeeks === true) { - row.append($(''); - if (options.calendarWeeks) { - row.append(''); - } - html.push(row); - } - clsNames = ['day']; - if (currentDate.isBefore(viewDate, 'M')) { - clsNames.push('old'); - } - if (currentDate.isAfter(viewDate, 'M')) { - clsNames.push('new'); - } - if (currentDate.isSame(date, 'd') && !unset) { - clsNames.push('active'); - } - if (!isValid(currentDate, 'd')) { - clsNames.push('disabled'); - } - if (currentDate.isSame(getMoment(), 'd')) { - clsNames.push('today'); - } - if (currentDate.day() === 0 || currentDate.day() === 6) { - clsNames.push('weekend'); - } - notifyEvent({ - type: 'dp.classify', - date: currentDate, - classNames: clsNames - }); - row.append(''); - currentDate.add(1, 'd'); - } - daysView.find('tbody').empty().append(html); - updateMonths(); - updateYears(); - updateDecades(); - }; - var fillHours = function fillHours() { - var table = widget.find('.timepicker-hours table'); - var currentHour = viewDate.clone().startOf('d'); - var html = []; - var row = $(''); - if (viewDate.hour() > 11 && !use24Hours) { - currentHour.hour(12); - } - while (currentHour.isSame(viewDate, 'd') && (use24Hours || viewDate.hour() < 12 && currentHour.hour() < 12 || viewDate.hour() > 11)) { - if (currentHour.hour() % 4 === 0) { - row = $(''); - html.push(row); - } - row.append(''); - currentHour.add(1, 'h'); - } - table.empty().append(html); - }; - var fillMinutes = function fillMinutes() { - var table = widget.find('.timepicker-minutes table'); - var currentMinute = viewDate.clone().startOf('h'); - var html = []; - var row = $(''); - var step = options.stepping === 1 ? 5 : options.stepping; - while (viewDate.isSame(currentMinute, 'h')) { - if (currentMinute.minute() % (step * 4) === 0) { - row = $(''); - html.push(row); - } - row.append(''); - currentMinute.add(step, 'm'); - } - table.empty().append(html); - }; - var fillSeconds = function fillSeconds() { - var table = widget.find('.timepicker-seconds table'); - var currentSecond = viewDate.clone().startOf('m'); - var html = []; - var row = $(''); - while (viewDate.isSame(currentSecond, 'm')) { - if (currentSecond.second() % 20 === 0) { - row = $(''); - html.push(row); - } - row.append(''); - currentSecond.add(5, 's'); - } - table.empty().append(html); - }; - var fillTime = function fillTime() { - var toggle; - var newDate; - var timeComponents = widget.find('.timepicker span[data-time-component]'); - if (!use24Hours) { - toggle = widget.find('.timepicker [data-action=togglePeriod]'); - newDate = date.clone().add(date.hours() >= 12 ? -12 : 12, 'h'); - toggle.text(date.format('A')); - if (isValid(newDate, 'h')) { - toggle.removeClass('disabled'); - } else { - toggle.addClass('disabled'); - } - } - timeComponents.filter('[data-time-component=hours]').text(date.format(use24Hours ? 'HH' : 'hh')); - timeComponents.filter('[data-time-component=minutes]').text(date.format('mm')); - timeComponents.filter('[data-time-component=seconds]').text(date.format('ss')); - fillHours(); - fillMinutes(); - fillSeconds(); - }; - var update = function update() { - if (!widget) { - return; - } - fillDate(); - fillTime(); - }; - var setValue = function setValue(targetMoment) { - var oldDate = unset ? null : date; - - // case of calling setValue(null or false) - if (!targetMoment) { - unset = true; - input.val(''); - element.data('date', ''); - notifyEvent({ - type: 'dp.change', - date: false, - oldDate: oldDate - }); - update(); - return; - } - targetMoment = targetMoment.clone().locale(options.locale); - if (hasTimeZone()) { - targetMoment.tz(options.timeZone); - } - if (options.stepping !== 1) { - targetMoment.minutes(Math.round(targetMoment.minutes() / options.stepping) * options.stepping).seconds(0); - while (options.minDate && targetMoment.isBefore(options.minDate)) { - targetMoment.add(options.stepping, 'minutes'); - } - } - if (isValid(targetMoment)) { - date = targetMoment; - viewDate = date.clone(); - input.val(date.format(actualFormat)); - element.data('date', date.format(actualFormat)); - unset = false; - update(); - notifyEvent({ - type: 'dp.change', - date: date.clone(), - oldDate: oldDate - }); - } else { - if (!options.keepInvalid) { - input.val(unset ? '' : date.format(actualFormat)); - } else { - notifyEvent({ - type: 'dp.change', - date: targetMoment, - oldDate: oldDate - }); - } - notifyEvent({ - type: 'dp.error', - date: targetMoment, - oldDate: oldDate - }); - } - }; - - /** - * Hides the widget. Possibly will emit dp.hide - */ - var hide = function hide() { - var transitioning = false; - if (!widget) { - return picker; - } - // Ignore event if in the middle of a picker transition - widget.find('.collapse').each(function () { - var collapseData = $(this).data('collapse'); - if (collapseData && collapseData.transitioning) { - transitioning = true; - return false; - } - return true; - }); - if (transitioning) { - return picker; - } - if (component && component.hasClass('btn')) { - component.toggleClass('active'); - } - widget.hide(); - $(window).off('resize', place); - widget.off('click', '[data-action]'); - widget.off('mousedown', false); - widget.remove(); - widget = false; - notifyEvent({ - type: 'dp.hide', - date: date.clone() - }); - input.blur(); - viewDate = date.clone(); - return picker; - }; - var clear = function clear() { - setValue(null); - }; - var parseInputDate = function parseInputDate(inputDate) { - if (options.parseInputDate === undefined) { - if (!moment.isMoment(inputDate) || inputDate instanceof Date) { - inputDate = getMoment(inputDate); - } - } else { - inputDate = options.parseInputDate(inputDate); - } - // inputDate.locale(options.locale); - return inputDate; - }; - - /** ****************************************************************************** - * - * Widget UI interaction functions - * - ********************************************************************************/ - var actions = { - next: function next() { - var navFnc = datePickerModes[currentViewMode].navFnc; - viewDate.add(datePickerModes[currentViewMode].navStep, navFnc); - fillDate(); - viewUpdate(navFnc); - }, - previous: function previous() { - var navFnc = datePickerModes[currentViewMode].navFnc; - viewDate.subtract(datePickerModes[currentViewMode].navStep, navFnc); - fillDate(); - viewUpdate(navFnc); - }, - pickerSwitch: function pickerSwitch() { - showMode(1); - }, - selectMonth: function selectMonth(e) { - var month = $(e.target).closest('tbody').find('span').index($(e.target)); - viewDate.month(month); - if (currentViewMode === minViewModeNumber) { - setValue(date.clone().year(viewDate.year()).month(viewDate.month())); - if (!options.inline) { - hide(); - } - } else { - showMode(-1); - fillDate(); - } - viewUpdate('M'); - }, - selectYear: function selectYear(e) { - var year = parseInt($(e.target).text(), 10) || 0; - viewDate.year(year); - if (currentViewMode === minViewModeNumber) { - setValue(date.clone().year(viewDate.year())); - if (!options.inline) { - hide(); - } - } else { - showMode(-1); - fillDate(); - } - viewUpdate('YYYY'); - }, - selectDecade: function selectDecade(e) { - var year = parseInt($(e.target).data('selection'), 10) || 0; - viewDate.year(year); - if (currentViewMode === minViewModeNumber) { - setValue(date.clone().year(viewDate.year())); - if (!options.inline) { - hide(); - } - } else { - showMode(-1); - fillDate(); - } - viewUpdate('YYYY'); - }, - selectDay: function selectDay(e) { - var day = viewDate.clone(); - if ($(e.target).is('.old')) { - day.subtract(1, 'M'); - } - if ($(e.target).is('.new')) { - day.add(1, 'M'); - } - setValue(day.date(parseInt($(e.target).text(), 10))); - if (!hasTime() && !options.keepOpen && !options.inline) { - hide(); - } - }, - incrementHours: function incrementHours() { - var newDate = date.clone().add(1, 'h'); - if (isValid(newDate, 'h')) { - setValue(newDate); - } - }, - incrementMinutes: function incrementMinutes() { - var newDate = date.clone().add(options.stepping, 'm'); - if (isValid(newDate, 'm')) { - setValue(newDate); - } - }, - incrementSeconds: function incrementSeconds() { - var newDate = date.clone().add(1, 's'); - if (isValid(newDate, 's')) { - setValue(newDate); - } - }, - decrementHours: function decrementHours() { - var newDate = date.clone().subtract(1, 'h'); - if (isValid(newDate, 'h')) { - setValue(newDate); - } - }, - decrementMinutes: function decrementMinutes() { - var newDate = date.clone().subtract(options.stepping, 'm'); - if (isValid(newDate, 'm')) { - setValue(newDate); - } - }, - decrementSeconds: function decrementSeconds() { - var newDate = date.clone().subtract(1, 's'); - if (isValid(newDate, 's')) { - setValue(newDate); - } - }, - togglePeriod: function togglePeriod() { - setValue(date.clone().add(date.hours() >= 12 ? -12 : 12, 'h')); - }, - togglePicker: function togglePicker(e) { - var $this = $(e.target); - var $parent = $this.closest('ul'); - var expanded = $parent.find('.in'); - var closed = $parent.find('.collapse:not(.in)'); - var collapseData; - if (expanded && expanded.length) { - collapseData = expanded.data('collapse'); - if (collapseData && collapseData.transitioning) { - return; - } - if (expanded.collapse) { - // if collapse plugin is available through bootstrap.js then use it - expanded.collapse('hide'); - closed.collapse('show'); - } else { - // otherwise just toggle in class on the two views - expanded.removeClass('in'); - closed.addClass('in'); - } - if ($this.is('span')) { - $this.toggleClass(options.icons.time + ' ' + options.icons.date); - } else { - $this.find('span').toggleClass(options.icons.time + ' ' + options.icons.date); - } - - // NOTE: uncomment if toggled state will be restored in show() - // if (component) { - // component.find('span').toggleClass(options.icons.time + ' ' + options.icons.date); - // } - } - }, - showPicker: function showPicker() { - widget.find('.timepicker > div:not(.timepicker-picker)').hide(); - widget.find('.timepicker .timepicker-picker').show(); - }, - showHours: function showHours() { - widget.find('.timepicker .timepicker-picker').hide(); - widget.find('.timepicker .timepicker-hours').show(); - }, - showMinutes: function showMinutes() { - widget.find('.timepicker .timepicker-picker').hide(); - widget.find('.timepicker .timepicker-minutes').show(); - }, - showSeconds: function showSeconds() { - widget.find('.timepicker .timepicker-picker').hide(); - widget.find('.timepicker .timepicker-seconds').show(); - }, - selectHour: function selectHour(e) { - var hour = parseInt($(e.target).text(), 10); - if (!use24Hours) { - if (date.hours() >= 12) { - if (hour !== 12) { - hour += 12; - } - } else { - if (hour === 12) { - hour = 0; - } - } - } - setValue(date.clone().hours(hour)); - actions.showPicker.call(picker); - }, - selectMinute: function selectMinute(e) { - setValue(date.clone().minutes(parseInt($(e.target).text(), 10))); - actions.showPicker.call(picker); - }, - selectSecond: function selectSecond(e) { - setValue(date.clone().seconds(parseInt($(e.target).text(), 10))); - actions.showPicker.call(picker); - }, - clear: clear, - today: function today() { - var todaysDate = getMoment(); - if (isValid(todaysDate, 'd')) { - setValue(todaysDate); - } - }, - close: hide - }; - var doAction = function doAction(e) { - if ($(e.currentTarget).is('.disabled')) { - return false; - } - actions[$(e.currentTarget).data('action')].apply(picker, arguments); - return false; - }; - - /** - * Shows the widget. Possibly will emit dp.show and dp.change - */ - var show = function show() { - var currentMoment; - var useCurrentGranularity = { - 'year': function year(m) { - return m.month(0).date(1).hours(0).seconds(0).minutes(0); - }, - 'month': function month(m) { - return m.date(1).hours(0).seconds(0).minutes(0); - }, - 'day': function day(m) { - return m.hours(0).seconds(0).minutes(0); - }, - 'hour': function hour(m) { - return m.seconds(0).minutes(0); - }, - 'minute': function minute(m) { - return m.seconds(0); - } - }; - if (input.prop('disabled') || !options.ignoreReadonly && input.prop('readonly') || widget) { - return picker; - } - if (input.val() !== undefined && input.val().trim().length !== 0) { - setValue(parseInputDate(input.val().trim())); - } else if (unset && options.useCurrent && (options.inline || input.is('input') && input.val().trim().length === 0)) { - currentMoment = getMoment(); - if (typeof options.useCurrent === 'string') { - currentMoment = useCurrentGranularity[options.useCurrent](currentMoment); - } - setValue(currentMoment); - } - widget = getTemplate(); - fillDow(); - fillMonths(); - widget.find('.timepicker-hours').hide(); - widget.find('.timepicker-minutes').hide(); - widget.find('.timepicker-seconds').hide(); - update(); - showMode(); - $(window).on('resize', place); - widget.on('click', '[data-action]', doAction); // this handles clicks on the widget - widget.on('mousedown', false); - if (component && component.hasClass('btn')) { - component.toggleClass('active'); - } - place(); - widget.show(); - if (options.focusOnShow && !input.is(':focus')) { - input.focus(); - } - notifyEvent({ - type: 'dp.show' - }); - return picker; - }; - - /** - * Shows or hides the widget - */ - var toggle = function toggle() { - return widget ? hide() : show(); - }; - var keydown = function keydown(e) { - var handler = null; - var index; - var index2; - var pressedKeys = []; - var pressedModifiers = {}; - var currentKey = e.which; - var keyBindKeys; - var allModifiersPressed; - var pressed = 'p'; - keyState[currentKey] = pressed; - for (index in keyState) { - if (keyState.hasOwnProperty(index) && keyState[index] === pressed) { - pressedKeys.push(index); - if (parseInt(index, 10) !== currentKey) { - pressedModifiers[index] = true; - } - } - } - for (index in options.keyBinds) { - if (options.keyBinds.hasOwnProperty(index) && typeof options.keyBinds[index] === 'function') { - keyBindKeys = index.split(' '); - if (keyBindKeys.length === pressedKeys.length && keyMap[currentKey] === keyBindKeys[keyBindKeys.length - 1]) { - allModifiersPressed = true; - for (index2 = keyBindKeys.length - 2; index2 >= 0; index2--) { - if (!(keyMap[keyBindKeys[index2]] in pressedModifiers)) { - allModifiersPressed = false; - break; - } - } - if (allModifiersPressed) { - handler = options.keyBinds[index]; - break; - } - } - } - } - if (handler) { - handler.call(picker, widget); - e.stopPropagation(); - e.preventDefault(); - } - }; - var keyup = function keyup(e) { - keyState[e.which] = 'r'; - e.stopPropagation(); - e.preventDefault(); - }; - var change = function change(e) { - var val = $(e.target).val().trim(); - var parsedDate = val ? parseInputDate(val) : null; - setValue(parsedDate); - e.stopImmediatePropagation(); - return false; - }; - var attachDatePickerElementEvents = function attachDatePickerElementEvents() { - input.on({ - 'change': change, - 'blur': options.debug ? '' : hide, - 'keydown': keydown, - 'keyup': keyup, - 'focus': options.allowInputToggle ? show : '' - }); - if (element.is('input')) { - input.on({ - 'focus': show - }); - } else if (component) { - component.on('click', toggle); - component.on('mousedown', false); - } - }; - var detachDatePickerElementEvents = function detachDatePickerElementEvents() { - input.off({ - 'change': change, - 'blur': blur, - 'keydown': keydown, - 'keyup': keyup, - 'focus': options.allowInputToggle ? hide : '' - }); - if (element.is('input')) { - input.off({ - 'focus': show - }); - } else if (component) { - component.off('click', toggle); - component.off('mousedown', false); - } - }; - var indexGivenDates = function indexGivenDates(givenDatesArray) { - // Store given enabledDates and disabledDates as keys. - // This way we can check their existence in O(1) time instead of looping through whole array. - // (for example: options.enabledDates['2014-02-27'] === true) - var givenDatesIndexed = {}; - $.each(givenDatesArray, function () { - var dDate = parseInputDate(this); - if (dDate.isValid()) { - givenDatesIndexed[dDate.format('YYYY-MM-DD')] = true; - } - }); - return Object.keys(givenDatesIndexed).length ? givenDatesIndexed : false; - }; - var indexGivenHours = function indexGivenHours(givenHoursArray) { - // Store given enabledHours and disabledHours as keys. - // This way we can check their existence in O(1) time instead of looping through whole array. - // (for example: options.enabledHours['2014-02-27'] === true) - var givenHoursIndexed = {}; - $.each(givenHoursArray, function () { - givenHoursIndexed[this] = true; - }); - return Object.keys(givenHoursIndexed).length ? givenHoursIndexed : false; - }; - var initFormatting = function initFormatting() { - var format = options.format || 'L LT'; - actualFormat = format.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput) { - var newinput = date.localeData().longDateFormat(formatInput) || formatInput; - return newinput.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput2) { - // temp fix for #740 - return date.localeData().longDateFormat(formatInput2) || formatInput2; - }); - }); - parseFormats = options.extraFormats ? options.extraFormats.slice() : []; - if (parseFormats.indexOf(format) < 0 && parseFormats.indexOf(actualFormat) < 0) { - parseFormats.push(actualFormat); - } - use24Hours = actualFormat.toLowerCase().indexOf('a') < 1 && actualFormat.replace(/\[.*?\]/g, '').indexOf('h') < 1; - if (isEnabled('y')) { - minViewModeNumber = 2; - } - if (isEnabled('M')) { - minViewModeNumber = 1; - } - if (isEnabled('d')) { - minViewModeNumber = 0; - } - currentViewMode = Math.max(minViewModeNumber, currentViewMode); - if (!unset) { - setValue(date); - } - }; - - /** ****************************************************************************** - * - * Public API functions - * ===================== - * - * Important: Do not expose direct references to private objects or the options - * object to the outer world. Always return a clone when returning values or make - * a clone when setting a private variable. - * - ********************************************************************************/ - picker.destroy = function () { - // /Destroys the widget and removes all attached event listeners - hide(); - detachDatePickerElementEvents(); - element.removeData('DateTimePicker'); - element.removeData('date'); - }; - picker.toggle = toggle; - picker.show = show; - picker.hide = hide; - picker.disable = function () { - // /Disables the input element, the component is attached to, by adding a disabled="true" attribute to it. - // /If the widget was visible before that call it is hidden. Possibly emits dp.hide - hide(); - if (component && component.hasClass('btn')) { - component.addClass('disabled'); - } - input.prop('disabled', true); - return picker; - }; - picker.enable = function () { - // /Enables the input element, the component is attached to, by removing disabled attribute from it. - if (component && component.hasClass('btn')) { - component.removeClass('disabled'); - } - input.prop('disabled', false); - return picker; - }; - picker.ignoreReadonly = function (ignoreReadonly) { - if (arguments.length === 0) { - return options.ignoreReadonly; - } - if (typeof ignoreReadonly !== 'boolean') { - throw new TypeError('ignoreReadonly () expects a boolean parameter'); - } - options.ignoreReadonly = ignoreReadonly; - return picker; - }; - picker.options = function (newOptions) { - if (arguments.length === 0) { - return $.extend(true, {}, options); - } - if (!(newOptions instanceof Object)) { - throw new TypeError('options() options parameter should be an object'); - } - $.extend(true, options, newOptions); - $.each(options, function (key, value) { - if (picker[key] !== undefined) { - picker[key](value); - } else { - throw new TypeError('option ' + key + ' is not recognized!'); - } - }); - return picker; - }; - picker.date = function (newDate) { - // / - // /Returns the component's model current date, a moment object or null if not set. - // /date.clone() - // / - // / - // /Sets the components model current moment to it. Passing a null value unsets the components model current moment. Parsing of the newDate parameter is made using moment library with the options.format and options.useStrict components configuration. - // /Takes string, Date, moment, null parameter. - // / - if (arguments.length === 0) { - if (unset) { - return null; - } - return date.clone(); - } - if (newDate !== null && typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) { - throw new TypeError('date() parameter must be one of [null, string, moment or Date]'); - } - setValue(newDate === null ? null : parseInputDate(newDate)); - return picker; - }; - picker.format = function (newFormat) { - // /test su - // /info about para - // /returns foo - if (arguments.length === 0) { - return options.format; - } - if (typeof newFormat !== 'string' && (typeof newFormat !== 'boolean' || newFormat !== false)) { - throw new TypeError('format() expects a string or boolean:false parameter ' + newFormat); - } - options.format = newFormat; - if (actualFormat) { - initFormatting(); // reinit formatting - } - return picker; - }; - picker.timeZone = function (newZone) { - if (arguments.length === 0) { - return options.timeZone; - } - if (typeof newZone !== 'string') { - throw new TypeError('newZone() expects a string parameter'); - } - options.timeZone = newZone; - return picker; - }; - picker.dayViewHeaderFormat = function (newFormat) { - if (arguments.length === 0) { - return options.dayViewHeaderFormat; - } - if (typeof newFormat !== 'string') { - throw new TypeError('dayViewHeaderFormat() expects a string parameter'); - } - options.dayViewHeaderFormat = newFormat; - return picker; - }; - picker.extraFormats = function (formats) { - if (arguments.length === 0) { - return options.extraFormats; - } - if (formats !== false && !(formats instanceof Array)) { - throw new TypeError('extraFormats() expects an array or false parameter'); - } - options.extraFormats = formats; - if (parseFormats) { - initFormatting(); // reinit formatting - } - return picker; - }; - picker.disabledDates = function (dates) { - // / - // /Returns an array with the currently set disabled dates on the component. - // /options.disabledDates - // / - // / - // /Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of - // /options.enabledDates if such exist. - // /Takes an [ string or Date or moment ] of values and allows the user to select only from those days. - // / - if (arguments.length === 0) { - return options.disabledDates ? $.extend({}, options.disabledDates) : options.disabledDates; - } - if (!dates) { - options.disabledDates = false; - update(); - return picker; - } - if (!(dates instanceof Array)) { - throw new TypeError('disabledDates() expects an array parameter'); - } - options.disabledDates = indexGivenDates(dates); - options.enabledDates = false; - update(); - return picker; - }; - picker.enabledDates = function (dates) { - // / - // /Returns an array with the currently set enabled dates on the component. - // /options.enabledDates - // / - // / - // /Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of options.disabledDates if such exist. - // /Takes an [ string or Date or moment ] of values and allows the user to select only from those days. - // / - if (arguments.length === 0) { - return options.enabledDates ? $.extend({}, options.enabledDates) : options.enabledDates; - } - if (!dates) { - options.enabledDates = false; - update(); - return picker; - } - if (!(dates instanceof Array)) { - throw new TypeError('enabledDates() expects an array parameter'); - } - options.enabledDates = indexGivenDates(dates); - options.disabledDates = false; - update(); - return picker; - }; - picker.daysOfWeekDisabled = function (daysOfWeekDisabled) { - if (arguments.length === 0) { - return options.daysOfWeekDisabled.splice(0); - } - if (typeof daysOfWeekDisabled === 'boolean' && !daysOfWeekDisabled) { - options.daysOfWeekDisabled = false; - update(); - return picker; - } - if (!(daysOfWeekDisabled instanceof Array)) { - throw new TypeError('daysOfWeekDisabled() expects an array parameter'); - } - options.daysOfWeekDisabled = daysOfWeekDisabled.reduce(function (previousValue, currentValue) { - currentValue = parseInt(currentValue, 10); - if (currentValue > 6 || currentValue < 0 || isNaN(currentValue)) { - return previousValue; - } - if (previousValue.indexOf(currentValue) === -1) { - previousValue.push(currentValue); - } - return previousValue; - }, []).sort(); - if (options.useCurrent && !options.keepInvalid) { - var tries = 0; - while (!isValid(date, 'd')) { - date.add(1, 'd'); - if (tries === 31) { - throw new Error('Tried 31 times to find a valid date'); - } - tries++; - } - setValue(date); - } - update(); - return picker; - }; - picker.maxDate = function (maxDate) { - if (arguments.length === 0) { - return options.maxDate ? options.maxDate.clone() : options.maxDate; - } - if (typeof maxDate === 'boolean' && maxDate === false) { - options.maxDate = false; - update(); - return picker; - } - if (typeof maxDate === 'string') { - if (maxDate === 'now' || maxDate === 'moment') { - maxDate = getMoment(); - } - } - var parsedDate = parseInputDate(maxDate); - if (!parsedDate.isValid()) { - throw new TypeError('maxDate() Could not parse date parameter: ' + maxDate); - } - if (options.minDate && parsedDate.isBefore(options.minDate)) { - throw new TypeError('maxDate() date parameter is before options.minDate: ' + parsedDate.format(actualFormat)); - } - options.maxDate = parsedDate; - if (options.useCurrent && !options.keepInvalid && date.isAfter(maxDate)) { - setValue(options.maxDate); - } - if (viewDate.isAfter(parsedDate)) { - viewDate = parsedDate.clone().subtract(options.stepping, 'm'); - } - update(); - return picker; - }; - picker.minDate = function (minDate) { - if (arguments.length === 0) { - return options.minDate ? options.minDate.clone() : options.minDate; - } - if (typeof minDate === 'boolean' && minDate === false) { - options.minDate = false; - update(); - return picker; - } - if (typeof minDate === 'string') { - if (minDate === 'now' || minDate === 'moment') { - minDate = getMoment(); - } - } - var parsedDate = parseInputDate(minDate); - if (!parsedDate.isValid()) { - throw new TypeError('minDate() Could not parse date parameter: ' + minDate); - } - if (options.maxDate && parsedDate.isAfter(options.maxDate)) { - throw new TypeError('minDate() date parameter is after options.maxDate: ' + parsedDate.format(actualFormat)); - } - options.minDate = parsedDate; - if (options.useCurrent && !options.keepInvalid && date.isBefore(minDate)) { - setValue(options.minDate); - } - if (viewDate.isBefore(parsedDate)) { - viewDate = parsedDate.clone().add(options.stepping, 'm'); - } - update(); - return picker; - }; - picker.defaultDate = function (defaultDate) { - // / - // /Returns a moment with the options.defaultDate option configuration or false if not set - // /date.clone() - // / - // / - // /Will set the picker's inital date. If a boolean:false value is passed the options.defaultDate parameter is cleared. - // /Takes a string, Date, moment, boolean:false - // / - if (arguments.length === 0) { - return options.defaultDate ? options.defaultDate.clone() : options.defaultDate; - } - if (!defaultDate) { - options.defaultDate = false; - return picker; - } - if (typeof defaultDate === 'string') { - if (defaultDate === 'now' || defaultDate === 'moment') { - defaultDate = getMoment(); - } else { - defaultDate = getMoment(defaultDate); - } - } - var parsedDate = parseInputDate(defaultDate); - if (!parsedDate.isValid()) { - throw new TypeError('defaultDate() Could not parse date parameter: ' + defaultDate); - } - if (!isValid(parsedDate)) { - throw new TypeError('defaultDate() date passed is invalid according to component setup validations'); - } - options.defaultDate = parsedDate; - if (options.defaultDate && options.inline || input.val().trim() === '') { - setValue(options.defaultDate); - } - return picker; - }; - picker.locale = function (locale) { - if (arguments.length === 0) { - return options.locale; - } - if (!moment.localeData(locale)) { - throw new TypeError('locale() locale ' + locale + ' is not loaded from moment locales!'); - } - options.locale = locale; - date.locale(options.locale); - viewDate.locale(options.locale); - if (actualFormat) { - initFormatting(); // reinit formatting - } - if (widget) { - hide(); - show(); - } - return picker; - }; - picker.stepping = function (stepping) { - if (arguments.length === 0) { - return options.stepping; - } - stepping = parseInt(stepping, 10); - if (isNaN(stepping) || stepping < 1) { - stepping = 1; - } - options.stepping = stepping; - return picker; - }; - picker.useCurrent = function (useCurrent) { - var useCurrentOptions = ['year', 'month', 'day', 'hour', 'minute']; - if (arguments.length === 0) { - return options.useCurrent; - } - if (typeof useCurrent !== 'boolean' && typeof useCurrent !== 'string') { - throw new TypeError('useCurrent() expects a boolean or string parameter'); - } - if (typeof useCurrent === 'string' && useCurrentOptions.indexOf(useCurrent.toLowerCase()) === -1) { - throw new TypeError('useCurrent() expects a string parameter of ' + useCurrentOptions.join(', ')); - } - options.useCurrent = useCurrent; - return picker; - }; - picker.collapse = function (collapse) { - if (arguments.length === 0) { - return options.collapse; - } - if (typeof collapse !== 'boolean') { - throw new TypeError('collapse() expects a boolean parameter'); - } - if (options.collapse === collapse) { - return picker; - } - options.collapse = collapse; - if (widget) { - hide(); - show(); - } - return picker; - }; - picker.icons = function (icons) { - if (arguments.length === 0) { - return $.extend({}, options.icons); - } - if (!(icons instanceof Object)) { - throw new TypeError('icons() expects parameter to be an Object'); - } - $.extend(options.icons, icons); - if (widget) { - hide(); - show(); - } - return picker; - }; - picker.tooltips = function (tooltips) { - if (arguments.length === 0) { - return $.extend({}, options.tooltips); - } - if (!(tooltips instanceof Object)) { - throw new TypeError('tooltips() expects parameter to be an Object'); - } - $.extend(options.tooltips, tooltips); - if (widget) { - hide(); - show(); - } - return picker; - }; - picker.useStrict = function (useStrict) { - if (arguments.length === 0) { - return options.useStrict; - } - if (typeof useStrict !== 'boolean') { - throw new TypeError('useStrict() expects a boolean parameter'); - } - options.useStrict = useStrict; - return picker; - }; - picker.sideBySide = function (sideBySide) { - if (arguments.length === 0) { - return options.sideBySide; - } - if (typeof sideBySide !== 'boolean') { - throw new TypeError('sideBySide() expects a boolean parameter'); - } - options.sideBySide = sideBySide; - if (widget) { - hide(); - show(); - } - return picker; - }; - picker.viewMode = function (viewMode) { - if (arguments.length === 0) { - return options.viewMode; - } - if (typeof viewMode !== 'string') { - throw new TypeError('viewMode() expects a string parameter'); - } - if (viewModes.indexOf(viewMode) === -1) { - throw new TypeError('viewMode() parameter must be one of (' + viewModes.join(', ') + ') value'); - } - options.viewMode = viewMode; - currentViewMode = Math.max(viewModes.indexOf(viewMode), minViewModeNumber); - showMode(); - return picker; - }; - picker.toolbarPlacement = function (toolbarPlacement) { - if (arguments.length === 0) { - return options.toolbarPlacement; - } - if (typeof toolbarPlacement !== 'string') { - throw new TypeError('toolbarPlacement() expects a string parameter'); - } - if (toolbarPlacements.indexOf(toolbarPlacement) === -1) { - throw new TypeError('toolbarPlacement() parameter must be one of (' + toolbarPlacements.join(', ') + ') value'); - } - options.toolbarPlacement = toolbarPlacement; - if (widget) { - hide(); - show(); - } - return picker; - }; - picker.widgetPositioning = function (widgetPositioning) { - if (arguments.length === 0) { - return $.extend({}, options.widgetPositioning); - } - if ({}.toString.call(widgetPositioning) !== '[object Object]') { - throw new TypeError('widgetPositioning() expects an object variable'); - } - if (widgetPositioning.horizontal) { - if (typeof widgetPositioning.horizontal !== 'string') { - throw new TypeError('widgetPositioning() horizontal variable must be a string'); - } - widgetPositioning.horizontal = widgetPositioning.horizontal.toLowerCase(); - if (horizontalModes.indexOf(widgetPositioning.horizontal) === -1) { - throw new TypeError('widgetPositioning() expects horizontal parameter to be one of (' + horizontalModes.join(', ') + ')'); - } - options.widgetPositioning.horizontal = widgetPositioning.horizontal; - } - if (widgetPositioning.vertical) { - if (typeof widgetPositioning.vertical !== 'string') { - throw new TypeError('widgetPositioning() vertical variable must be a string'); - } - widgetPositioning.vertical = widgetPositioning.vertical.toLowerCase(); - if (verticalModes.indexOf(widgetPositioning.vertical) === -1) { - throw new TypeError('widgetPositioning() expects vertical parameter to be one of (' + verticalModes.join(', ') + ')'); - } - options.widgetPositioning.vertical = widgetPositioning.vertical; - } - update(); - return picker; - }; - picker.calendarWeeks = function (calendarWeeks) { - if (arguments.length === 0) { - return options.calendarWeeks; - } - if (typeof calendarWeeks !== 'boolean') { - throw new TypeError('calendarWeeks() expects parameter to be a boolean value'); - } - options.calendarWeeks = calendarWeeks; - update(); - return picker; - }; - picker.showTodayButton = function (showTodayButton) { - if (arguments.length === 0) { - return options.showTodayButton; - } - if (typeof showTodayButton !== 'boolean') { - throw new TypeError('showTodayButton() expects a boolean parameter'); - } - options.showTodayButton = showTodayButton; - if (widget) { - hide(); - show(); - } - return picker; - }; - picker.showClear = function (showClear) { - if (arguments.length === 0) { - return options.showClear; - } - if (typeof showClear !== 'boolean') { - throw new TypeError('showClear() expects a boolean parameter'); - } - options.showClear = showClear; - if (widget) { - hide(); - show(); - } - return picker; - }; - picker.widgetParent = function (widgetParent) { - if (arguments.length === 0) { - return options.widgetParent; - } - if (typeof widgetParent === 'string') { - widgetParent = $(widgetParent); - } - if (widgetParent !== null && typeof widgetParent !== 'string' && !(widgetParent instanceof $)) { - throw new TypeError('widgetParent() expects a string or a jQuery object parameter'); - } - options.widgetParent = widgetParent; - if (widget) { - hide(); - show(); - } - return picker; - }; - picker.keepOpen = function (keepOpen) { - if (arguments.length === 0) { - return options.keepOpen; - } - if (typeof keepOpen !== 'boolean') { - throw new TypeError('keepOpen() expects a boolean parameter'); - } - options.keepOpen = keepOpen; - return picker; - }; - picker.focusOnShow = function (focusOnShow) { - if (arguments.length === 0) { - return options.focusOnShow; - } - if (typeof focusOnShow !== 'boolean') { - throw new TypeError('focusOnShow() expects a boolean parameter'); - } - options.focusOnShow = focusOnShow; - return picker; - }; - picker.inline = function (inline) { - if (arguments.length === 0) { - return options.inline; - } - if (typeof inline !== 'boolean') { - throw new TypeError('inline() expects a boolean parameter'); - } - options.inline = inline; - return picker; - }; - picker.clear = function () { - clear(); - return picker; - }; - picker.keyBinds = function (keyBinds) { - if (arguments.length === 0) { - return options.keyBinds; - } - options.keyBinds = keyBinds; - return picker; - }; - picker.getMoment = function (d) { - return getMoment(d); - }; - picker.debug = function (debug) { - if (typeof debug !== 'boolean') { - throw new TypeError('debug() expects a boolean parameter'); - } - options.debug = debug; - return picker; - }; - picker.allowInputToggle = function (allowInputToggle) { - if (arguments.length === 0) { - return options.allowInputToggle; - } - if (typeof allowInputToggle !== 'boolean') { - throw new TypeError('allowInputToggle() expects a boolean parameter'); - } - options.allowInputToggle = allowInputToggle; - return picker; - }; - picker.showClose = function (showClose) { - if (arguments.length === 0) { - return options.showClose; - } - if (typeof showClose !== 'boolean') { - throw new TypeError('showClose() expects a boolean parameter'); - } - options.showClose = showClose; - return picker; - }; - picker.keepInvalid = function (keepInvalid) { - if (arguments.length === 0) { - return options.keepInvalid; - } - if (typeof keepInvalid !== 'boolean') { - throw new TypeError('keepInvalid() expects a boolean parameter'); - } - options.keepInvalid = keepInvalid; - return picker; - }; - picker.datepickerInput = function (datepickerInput) { - if (arguments.length === 0) { - return options.datepickerInput; - } - if (typeof datepickerInput !== 'string') { - throw new TypeError('datepickerInput() expects a string parameter'); - } - options.datepickerInput = datepickerInput; - return picker; - }; - picker.parseInputDate = function (parseInputDate) { - if (arguments.length === 0) { - return options.parseInputDate; - } - if (typeof parseInputDate !== 'function') { - throw new TypeError('parseInputDate() sholud be as function'); - } - options.parseInputDate = parseInputDate; - return picker; - }; - picker.disabledTimeIntervals = function (disabledTimeIntervals) { - // / - // /Returns an array with the currently set disabled dates on the component. - // /options.disabledTimeIntervals - // / - // / - // /Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of - // /options.enabledDates if such exist. - // /Takes an [ string or Date or moment ] of values and allows the user to select only from those days. - // / - if (arguments.length === 0) { - return options.disabledTimeIntervals ? $.extend({}, options.disabledTimeIntervals) : options.disabledTimeIntervals; - } - if (!disabledTimeIntervals) { - options.disabledTimeIntervals = false; - update(); - return picker; - } - if (!(disabledTimeIntervals instanceof Array)) { - throw new TypeError('disabledTimeIntervals() expects an array parameter'); - } - options.disabledTimeIntervals = disabledTimeIntervals; - update(); - return picker; - }; - picker.disabledHours = function (hours) { - // / - // /Returns an array with the currently set disabled hours on the component. - // /options.disabledHours - // / - // / - // /Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of - // /options.enabledHours if such exist. - // /Takes an [ int ] of values and disallows the user to select only from those hours. - // / - if (arguments.length === 0) { - return options.disabledHours ? $.extend({}, options.disabledHours) : options.disabledHours; - } - if (!hours) { - options.disabledHours = false; - update(); - return picker; - } - if (!(hours instanceof Array)) { - throw new TypeError('disabledHours() expects an array parameter'); - } - options.disabledHours = indexGivenHours(hours); - options.enabledHours = false; - if (options.useCurrent && !options.keepInvalid) { - var tries = 0; - while (!isValid(date, 'h')) { - date.add(1, 'h'); - if (tries === 24) { - throw new Error('Tried 24 times to find a valid date'); - } - tries++; - } - setValue(date); - } - update(); - return picker; - }; - picker.enabledHours = function (hours) { - // / - // /Returns an array with the currently set enabled hours on the component. - // /options.enabledHours - // / - // / - // /Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of options.disabledHours if such exist. - // /Takes an [ int ] of values and allows the user to select only from those hours. - // / - if (arguments.length === 0) { - return options.enabledHours ? $.extend({}, options.enabledHours) : options.enabledHours; - } - if (!hours) { - options.enabledHours = false; - update(); - return picker; - } - if (!(hours instanceof Array)) { - throw new TypeError('enabledHours() expects an array parameter'); - } - options.enabledHours = indexGivenHours(hours); - options.disabledHours = false; - if (options.useCurrent && !options.keepInvalid) { - var tries = 0; - while (!isValid(date, 'h')) { - date.add(1, 'h'); - if (tries === 24) { - throw new Error('Tried 24 times to find a valid date'); - } - tries++; - } - setValue(date); - } - update(); - return picker; - }; - /** - * Returns the component's model current viewDate, a moment object or null if not set. Passing a null value unsets the components model current moment. Parsing of the newDate parameter is made using moment library with the options.format and options.useStrict components configuration. - * @param {Takes string, viewDate, moment, null parameter.} newDate - * @returns {viewDate.clone()} - */ - picker.viewDate = function (newDate) { - if (arguments.length === 0) { - return viewDate.clone(); - } - if (!newDate) { - viewDate = date.clone(); - return picker; - } - if (typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) { - throw new TypeError('viewDate() parameter must be one of [string, moment or Date]'); - } - viewDate = parseInputDate(newDate); - viewUpdate(); - return picker; - }; - - // initializing element and component attributes - if (element.is('input')) { - input = element; - } else { - input = element.find(options.datepickerInput); - if (input.length === 0) { - input = element.find('input'); - } else if (!input.is('input')) { - throw new Error('CSS class "' + options.datepickerInput + '" cannot be applied to non input element'); - } - } - if (element.hasClass('input-group')) { - // in case there is more then one 'input-group-addon' Issue #48 - if (element.find('.datepickerbutton').length === 0) { - component = element.find('.input-group-addon'); - } else { - component = element.find('.datepickerbutton'); - } - } - if (!options.inline && !input.is('input')) { - throw new Error('Could not initialize DateTimePicker without an input element'); - } - - // Set defaults for date here now instead of in var declaration - date = getMoment(); - viewDate = date.clone(); - $.extend(true, options, dataToOptions()); - picker.options(options); - initFormatting(); - attachDatePickerElementEvents(); - if (input.prop('disabled')) { - picker.disable(); - } - if (input.is('input') && input.val().trim().length !== 0) { - setValue(parseInputDate(input.val().trim())); - } else if (options.defaultDate && input.attr('placeholder') === undefined) { - setValue(options.defaultDate); - } - if (options.inline) { - show(); - } - return picker; - }; - - /** ****************************************************************************** - * - * jQuery plugin constructor and defaults object - * - ********************************************************************************/ - - /** - * See (http://jquery.com/). - * @name jQuery - * @class - * See the jQuery Library (http://jquery.com/) for full details. This just - * documents the function and classes that are added to jQuery by this plug-in. - */ - /** - * See (http://jquery.com/) - * @name fn - * @class - * See the jQuery Library (http://jquery.com/) for full details. This just - * documents the function and classes that are added to jQuery by this plug-in. - * @memberOf jQuery - */ - /** - * Show comments - * @class datetimepicker - * @memberOf jQuery.fn - */ - $.fn.datetimepicker = function (options) { - options = options || {}; - var args = Array.prototype.slice.call(arguments, 1); - var isInstance = true; - var thisMethods = ['destroy', 'hide', 'show', 'toggle']; - var returnValue; - if (_typeof(options) === 'object') { - return this.each(function () { - var $this = $(this); - var _options; - if (!$this.data('DateTimePicker')) { - // create a private copy of the defaults object - _options = $.extend(true, {}, $.fn.datetimepicker.defaults, options); - $this.data('DateTimePicker', dateTimePicker($this, _options)); - } - }); - } else if (typeof options === 'string') { - this.each(function () { - var $this = $(this); - var instance = $this.data('DateTimePicker'); - if (!instance) { - throw new Error('bootstrap-datetimepicker("' + options + '") method was called on an element that is not using DateTimePicker'); - } - returnValue = instance[options].apply(instance, args); - isInstance = returnValue === instance; - }); - if (isInstance || $.inArray(options, thisMethods) > -1) { - return this; - } - return returnValue; - } - throw new TypeError('Invalid arguments for DateTimePicker: ' + options); - }; - $.fn.datetimepicker.defaults = { - timeZone: '', - format: false, - dayViewHeaderFormat: 'MMMM YYYY', - extraFormats: false, - stepping: 1, - minDate: false, - maxDate: false, - useCurrent: true, - collapse: true, - locale: moment.locale(), - defaultDate: false, - disabledDates: false, - enabledDates: false, - icons: { - time: 'glyphicon glyphicon-time', - date: 'glyphicon glyphicon-calendar', - up: 'glyphicon glyphicon-chevron-up', - down: 'glyphicon glyphicon-chevron-down', - previous: 'glyphicon glyphicon-chevron-left', - next: 'glyphicon glyphicon-chevron-right', - today: 'glyphicon glyphicon-screenshot', - clear: 'glyphicon glyphicon-trash', - close: 'glyphicon glyphicon-remove' - }, - tooltips: { - today: 'Go to today', - clear: 'Clear selection', - close: 'Close the picker', - selectMonth: 'Select Month', - prevMonth: 'Previous Month', - nextMonth: 'Next Month', - selectYear: 'Select Year', - prevYear: 'Previous Year', - nextYear: 'Next Year', - selectDecade: 'Select Decade', - prevDecade: 'Previous Decade', - nextDecade: 'Next Decade', - prevCentury: 'Previous Century', - nextCentury: 'Next Century', - pickHour: 'Pick Hour', - incrementHour: 'Increment Hour', - decrementHour: 'Decrement Hour', - pickMinute: 'Pick Minute', - incrementMinute: 'Increment Minute', - decrementMinute: 'Decrement Minute', - pickSecond: 'Pick Second', - incrementSecond: 'Increment Second', - decrementSecond: 'Decrement Second', - togglePeriod: 'Toggle Period', - selectTime: 'Select Time' - }, - useStrict: false, - sideBySide: false, - daysOfWeekDisabled: false, - calendarWeeks: false, - viewMode: 'days', - toolbarPlacement: 'default', - showTodayButton: false, - showClear: false, - showClose: false, - widgetPositioning: { - horizontal: 'auto', - vertical: 'auto' - }, - widgetParent: null, - ignoreReadonly: false, - keepOpen: false, - focusOnShow: true, - inline: false, - keepInvalid: false, - datepickerInput: '.datepickerinput', - keyBinds: { - up: function up(widget) { - if (!widget) { - return; - } - var d = this.date() || this.getMoment(); - if (widget.find('.datepicker').is(':visible')) { - this.date(d.clone().subtract(7, 'd')); - } else { - this.date(d.clone().add(this.stepping(), 'm')); - } - }, - down: function down(widget) { - if (!widget) { - this.show(); - return; - } - var d = this.date() || this.getMoment(); - if (widget.find('.datepicker').is(':visible')) { - this.date(d.clone().add(7, 'd')); - } else { - this.date(d.clone().subtract(this.stepping(), 'm')); - } - }, - 'control up': function control_up(widget) { - if (!widget) { - return; - } - var d = this.date() || this.getMoment(); - if (widget.find('.datepicker').is(':visible')) { - this.date(d.clone().subtract(1, 'y')); - } else { - this.date(d.clone().add(1, 'h')); - } - }, - 'control down': function control_down(widget) { - if (!widget) { - return; - } - var d = this.date() || this.getMoment(); - if (widget.find('.datepicker').is(':visible')) { - this.date(d.clone().add(1, 'y')); - } else { - this.date(d.clone().subtract(1, 'h')); - } - }, - left: function left(widget) { - if (!widget) { - return; - } - var d = this.date() || this.getMoment(); - if (widget.find('.datepicker').is(':visible')) { - this.date(d.clone().subtract(1, 'd')); - } - }, - right: function right(widget) { - if (!widget) { - return; - } - var d = this.date() || this.getMoment(); - if (widget.find('.datepicker').is(':visible')) { - this.date(d.clone().add(1, 'd')); - } - }, - pageUp: function pageUp(widget) { - if (!widget) { - return; - } - var d = this.date() || this.getMoment(); - if (widget.find('.datepicker').is(':visible')) { - this.date(d.clone().subtract(1, 'M')); - } - }, - pageDown: function pageDown(widget) { - if (!widget) { - return; - } - var d = this.date() || this.getMoment(); - if (widget.find('.datepicker').is(':visible')) { - this.date(d.clone().add(1, 'M')); - } - }, - enter: function enter() { - this.hide(); - }, - escape: function escape() { - this.hide(); - }, - // tab: function (widget) { //this break the flow of the form. disabling for now - // var toggle = widget.find('.picker-switch a[data-action="togglePicker"]'); - // if(toggle.length > 0) toggle.click(); - // }, - 'control space': function control_space(widget) { - if (!widget) { - return; - } - if (widget.find('.timepicker').is(':visible')) { - widget.find('.btn[data-action="togglePeriod"]').click(); - } - }, - t: function t() { - this.date(this.getMoment()); - }, - 'delete': function _delete() { - this.clear(); - } - }, - debug: false, - allowInputToggle: false, - disabledTimeIntervals: false, - disabledHours: false, - enabledHours: false, - viewDate: false - }; - return $.fn.datetimepicker; -}); - -/***/ }), - -/***/ 75139: -/***/ ((module) => { - -"use strict"; -module.exports = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC"; - -/***/ }), - -/***/ 75322: +/***/ 36736: /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; @@ -13281,8 +10927,162 @@ external_jQuery_default()(document).on('change', '.logs-content .block-select se var value = target.val(); __webpack_require__.g.location.href = prepareQuery(name, value); }); +;// ./app/tools/restore.js +function restore_typeof(o) { "@babel/helpers - typeof"; return restore_typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, restore_typeof(o); } +function restore_classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } +function restore_defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, restore_toPropertyKey(o.key), o); } } +function restore_createClass(e, r, t) { return r && restore_defineProperties(e.prototype, r), t && restore_defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } +function restore_toPropertyKey(t) { var i = restore_toPrimitive(t, "string"); return "symbol" == restore_typeof(i) ? i : i + ""; } +function restore_toPrimitive(t, r) { if ("object" != restore_typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != restore_typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } + + + + +var paramSep = external_GravAdmin_namespaceObject.config.param_sep; +var task = "task".concat(paramSep); +var nonce = "admin-nonce".concat(paramSep).concat(external_GravAdmin_namespaceObject.config.admin_nonce); +var base = "".concat(external_GravAdmin_namespaceObject.config.base_url_relative, "/update.json"); +var urls = { + restore: "".concat(base, "/").concat(task, "safeUpgradeRestore/").concat(nonce), + status: "".concat(base, "/").concat(task, "safeUpgradeStatus/").concat(nonce) +}; +var RestoreManager = /*#__PURE__*/function () { + function RestoreManager() { + var _this = this; + restore_classCallCheck(this, RestoreManager); + this.job = null; + this.pollTimer = null; + external_jQuery_default()(document).on('click', '[data-restore-snapshot]', function (event) { + event.preventDefault(); + var button = external_jQuery_default()(event.currentTarget); + if (_this.job) { + return; + } + _this.startRestore(button); + }); + } + return restore_createClass(RestoreManager, [{ + key: "startRestore", + value: function startRestore(button) { + var _this2 = this; + var snapshot = button.data('restore-snapshot'); + if (!snapshot) { + return; + } + button.prop('disabled', true).addClass('is-loading'); + var body = { + snapshot: snapshot + }; + utils_request(urls.restore, { + method: 'post', + body: body + }, function (response) { + var _translations$PLUGIN_4; + button.prop('disabled', false).removeClass('is-loading'); + if (!response) { + var _translations$PLUGIN_; + utils_toastr.error(((_translations$PLUGIN_ = external_GravAdmin_namespaceObject.translations.PLUGIN_ADMIN) === null || _translations$PLUGIN_ === void 0 ? void 0 : _translations$PLUGIN_.RESTORE_GRAV_FAILED) || 'Snapshot restore failed.'); + return; + } + if (response.status === 'error') { + var _translations$PLUGIN_2; + utils_toastr.error(response.message || ((_translations$PLUGIN_2 = external_GravAdmin_namespaceObject.translations.PLUGIN_ADMIN) === null || _translations$PLUGIN_2 === void 0 ? void 0 : _translations$PLUGIN_2.RESTORE_GRAV_FAILED) || 'Snapshot restore failed.'); + return; + } + var data = response.data || {}; + var jobId = data.job_id || data.job && data.job.id; + if (!jobId) { + var _translations$PLUGIN_3; + var message = response.message || ((_translations$PLUGIN_3 = external_GravAdmin_namespaceObject.translations.PLUGIN_ADMIN) === null || _translations$PLUGIN_3 === void 0 ? void 0 : _translations$PLUGIN_3.RESTORE_GRAV_FAILED) || 'Snapshot restore failed.'; + utils_toastr.error(message); + return; + } + _this2.job = { + id: jobId, + snapshot: snapshot + }; + var runningMessage = (_translations$PLUGIN_4 = external_GravAdmin_namespaceObject.translations.PLUGIN_ADMIN) !== null && _translations$PLUGIN_4 !== void 0 && _translations$PLUGIN_4.RESTORE_GRAV_RUNNING ? external_GravAdmin_namespaceObject.translations.PLUGIN_ADMIN.RESTORE_GRAV_RUNNING.replace('%s', snapshot) : "Restoring snapshot ".concat(snapshot, "..."); + utils_toastr.info(runningMessage); + _this2.schedulePoll(); + }); + } + }, { + key: "schedulePoll", + value: function schedulePoll() { + var _this3 = this; + var delay = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1200; + this.clearPoll(); + this.pollTimer = setTimeout(function () { + return _this3.pollStatus(); + }, delay); + } + }, { + key: "clearPoll", + value: function clearPoll() { + if (this.pollTimer) { + clearTimeout(this.pollTimer); + this.pollTimer = null; + } + } + }, { + key: "pollStatus", + value: function pollStatus() { + var _this4 = this; + if (!this.job) { + return; + } + var jobId = this.job.id; + utils_request("".concat(urls.status, "?job=").concat(encodeURIComponent(jobId)), { + silentErrors: true + }, function (response) { + if (!response || response.status !== 'success') { + _this4.schedulePoll(); + return; + } + var data = response.data || {}; + var job = data.job || {}; + var progress = data.progress || {}; + var stage = progress.stage || null; + var status = job.status || progress.status || null; + if (stage === 'error' || status === 'error') { + var _translations$PLUGIN_5; + var message = job.error || progress.message || ((_translations$PLUGIN_5 = external_GravAdmin_namespaceObject.translations.PLUGIN_ADMIN) === null || _translations$PLUGIN_5 === void 0 ? void 0 : _translations$PLUGIN_5.RESTORE_GRAV_FAILED) || 'Snapshot restore failed.'; + utils_toastr.error(message); + _this4.job = null; + _this4.clearPoll(); + return; + } + if (stage === 'complete' || status === 'success') { + var _translations$PLUGIN_6, _translations$PLUGIN_7; + var snapshot = progress.snapshot || _this4.job.snapshot; + var version = job.result && job.result.version || progress.version || ''; + var successMessage; + if ((_translations$PLUGIN_6 = external_GravAdmin_namespaceObject.translations.PLUGIN_ADMIN) !== null && _translations$PLUGIN_6 !== void 0 && _translations$PLUGIN_6.RESTORE_GRAV_SUCCESS_MESSAGE && version) { + successMessage = external_GravAdmin_namespaceObject.translations.PLUGIN_ADMIN.RESTORE_GRAV_SUCCESS_MESSAGE.replace('%1$s', snapshot).replace('%2$s', version); + } else if ((_translations$PLUGIN_7 = external_GravAdmin_namespaceObject.translations.PLUGIN_ADMIN) !== null && _translations$PLUGIN_7 !== void 0 && _translations$PLUGIN_7.RESTORE_GRAV_SUCCESS_SIMPLE) { + successMessage = external_GravAdmin_namespaceObject.translations.PLUGIN_ADMIN.RESTORE_GRAV_SUCCESS_SIMPLE.replace('%s', snapshot); + } else { + successMessage = version ? "Snapshot ".concat(snapshot, " restored (Grav ").concat(version, ").") : "Snapshot ".concat(snapshot, " restored."); + } + utils_toastr.success(successMessage); + _this4.job = null; + _this4.clearPoll(); + setTimeout(function () { + return window.location.reload(); + }, 1500); + return; + } + _this4.schedulePoll(); + }); + } + }]); +}(); // Initialize restore manager when tools view loads. +external_jQuery_default()(document).ready(function () { + new RestoreManager(); +}); ;// ./app/tools/index.js + ;// ./app/whitelabel/compile.js @@ -14252,6 +12052,2360 @@ external_jQuery_default()(__webpack_require__.g).on('sidebar_state._grav', funct /***/ }), +/***/ 41669: +/***/ ((module) => { + +"use strict"; +module.exports = jQuery; + +/***/ }), + +/***/ 61954: +/***/ ((module) => { + +"use strict"; +module.exports = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII="; + +/***/ }), + +/***/ 70086: +/***/ ((module, exports, __webpack_require__) => { + +var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } +/*! version : 4.17.47 + ========================================================= + bootstrap-datetimejs + https://github.com/Eonasdan/bootstrap-datetimepicker + Copyright (c) 2015 Jonathan Peterson + ========================================================= + */ +/* + The MIT License (MIT) + + Copyright (c) 2015 Jonathan Peterson + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ +/* global define:false */ +/* global jQuery:false */ +/* global moment:false */ +(function (factory) { + 'use strict'; + + if (true) { + // AMD is used - Register as an anonymous module. + !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(41669), __webpack_require__(95093)], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), + __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? + (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), + __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + } else // removed by dead control flow +{} +})(function ($, moment) { + 'use strict'; + + if (!moment) { + throw new Error('bootstrap-datetimepicker requires Moment.js to be loaded first'); + } + var dateTimePicker = function dateTimePicker(element, options) { + var picker = {}; + var date; + var viewDate; + var unset = true; + var input; + var component = false; + var widget = false; + var use24Hours; + var minViewModeNumber = 0; + var actualFormat; + var parseFormats; + var currentViewMode; + var datePickerModes = [{ + clsName: 'days', + navFnc: 'M', + navStep: 1 + }, { + clsName: 'months', + navFnc: 'y', + navStep: 1 + }, { + clsName: 'years', + navFnc: 'y', + navStep: 10 + }, { + clsName: 'decades', + navFnc: 'y', + navStep: 100 + }]; + var viewModes = ['days', 'months', 'years', 'decades']; + var verticalModes = ['top', 'bottom', 'auto']; + var horizontalModes = ['left', 'right', 'auto']; + var toolbarPlacements = ['default', 'top', 'bottom']; + var keyMap = { + 'up': 38, + 38: 'up', + 'down': 40, + 40: 'down', + 'left': 37, + 37: 'left', + 'right': 39, + 39: 'right', + 'tab': 9, + 9: 'tab', + 'escape': 27, + 27: 'escape', + 'enter': 13, + 13: 'enter', + 'pageUp': 33, + 33: 'pageUp', + 'pageDown': 34, + 34: 'pageDown', + 'shift': 16, + 16: 'shift', + 'control': 17, + 17: 'control', + 'space': 32, + 32: 'space', + 't': 84, + 84: 't', + 'delete': 46, + 46: 'delete' + }; + var keyState = {}; + + /** ****************************************************************************** + * + * Private functions + * + ********************************************************************************/ + + var hasTimeZone = function hasTimeZone() { + return moment.tz !== undefined && options.timeZone !== undefined && options.timeZone !== null && options.timeZone !== ''; + }; + var getMoment = function getMoment(d) { + var returnMoment; + if (d === undefined || d === null) { + returnMoment = moment(); // TODO should this use format? and locale? + } else if (moment.isDate(d) || moment.isMoment(d)) { + // If the date that is passed in is already a Date() or moment() object, + // pass it directly to moment. + returnMoment = moment(d); + } else if (hasTimeZone()) { + // There is a string to parse and a default time zone + // parse with the tz function which takes a default time zone if it is not in the format string + returnMoment = moment.tz(d, parseFormats, options.useStrict, options.timeZone); + } else { + returnMoment = moment(d, parseFormats, options.useStrict); + } + if (hasTimeZone()) { + returnMoment.tz(options.timeZone); + } + return returnMoment; + }; + var isEnabled = function isEnabled(granularity) { + if (typeof granularity !== 'string' || granularity.length > 1) { + throw new TypeError('isEnabled expects a single character string parameter'); + } + switch (granularity) { + case 'y': + return actualFormat.indexOf('Y') !== -1; + case 'M': + return actualFormat.indexOf('M') !== -1; + case 'd': + return actualFormat.toLowerCase().indexOf('d') !== -1; + case 'h': + case 'H': + return actualFormat.toLowerCase().indexOf('h') !== -1; + case 'm': + return actualFormat.indexOf('m') !== -1; + case 's': + return actualFormat.indexOf('s') !== -1; + default: + return false; + } + }; + var hasTime = function hasTime() { + return isEnabled('h') || isEnabled('m') || isEnabled('s'); + }; + var hasDate = function hasDate() { + return isEnabled('y') || isEnabled('M') || isEnabled('d'); + }; + var getDatePickerTemplate = function getDatePickerTemplate() { + var headTemplate = $('').append($('').append($('').append($('').append($('
').addClass('cw').text('#')); - } - while (currentDate.isBefore(viewDate.clone().endOf('w'))) { - row.append($('').addClass('dow').text(currentDate.format('dd'))); - currentDate.add(1, 'd'); - } - widget.find('.datepicker-days thead').append(row); - }; - var isInDisabledDates = function isInDisabledDates(testDate) { - return options.disabledDates[testDate.format('YYYY-MM-DD')] === true; - }; - var isInEnabledDates = function isInEnabledDates(testDate) { - return options.enabledDates[testDate.format('YYYY-MM-DD')] === true; - }; - var isInDisabledHours = function isInDisabledHours(testDate) { - return options.disabledHours[testDate.format('H')] === true; - }; - var isInEnabledHours = function isInEnabledHours(testDate) { - return options.enabledHours[testDate.format('H')] === true; - }; - var isValid = function isValid(targetMoment, granularity) { - if (!targetMoment.isValid()) { - return false; - } - if (options.disabledDates && granularity === 'd' && isInDisabledDates(targetMoment)) { - return false; - } - if (options.enabledDates && granularity === 'd' && !isInEnabledDates(targetMoment)) { - return false; - } - if (options.minDate && targetMoment.isBefore(options.minDate, granularity)) { - return false; - } - if (options.maxDate && targetMoment.isAfter(options.maxDate, granularity)) { - return false; - } - if (options.daysOfWeekDisabled && granularity === 'd' && options.daysOfWeekDisabled.indexOf(targetMoment.day()) !== -1) { - return false; - } - if (options.disabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && isInDisabledHours(targetMoment)) { - return false; - } - if (options.enabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && !isInEnabledHours(targetMoment)) { - return false; - } - if (options.disabledTimeIntervals && (granularity === 'h' || granularity === 'm' || granularity === 's')) { - var found = false; - $.each(options.disabledTimeIntervals, function () { - if (targetMoment.isBetween(this[0], this[1])) { - found = true; - return false; - } - }); - if (found) { - return false; - } - } - return true; - }; - var fillMonths = function fillMonths() { - var spans = []; - var monthsShort = viewDate.clone().startOf('y').startOf('d'); - while (monthsShort.isSame(viewDate, 'y')) { - spans.push($('').attr('data-action', 'selectMonth').addClass('month').text(monthsShort.format('MMM'))); - monthsShort.add(1, 'M'); - } - widget.find('.datepicker-months td').empty().append(spans); - }; - var updateMonths = function updateMonths() { - var monthsView = widget.find('.datepicker-months'); - var monthsViewHeader = monthsView.find('th'); - var months = monthsView.find('tbody').find('span'); - monthsViewHeader.eq(0).find('span').attr('title', options.tooltips.prevYear); - monthsViewHeader.eq(1).attr('title', options.tooltips.selectYear); - monthsViewHeader.eq(2).find('span').attr('title', options.tooltips.nextYear); - monthsView.find('.disabled').removeClass('disabled'); - if (!isValid(viewDate.clone().subtract(1, 'y'), 'y')) { - monthsViewHeader.eq(0).addClass('disabled'); - } - monthsViewHeader.eq(1).text(viewDate.year()); - if (!isValid(viewDate.clone().add(1, 'y'), 'y')) { - monthsViewHeader.eq(2).addClass('disabled'); - } - months.removeClass('active'); - if (date.isSame(viewDate, 'y') && !unset) { - months.eq(date.month()).addClass('active'); - } - months.each(function (index) { - if (!isValid(viewDate.clone().month(index), 'M')) { - $(this).addClass('disabled'); - } - }); - }; - var updateYears = function updateYears() { - var yearsView = widget.find('.datepicker-years'); - var yearsViewHeader = yearsView.find('th'); - var startYear = viewDate.clone().subtract(5, 'y'); - var endYear = viewDate.clone().add(6, 'y'); - var html = ''; - yearsViewHeader.eq(0).find('span').attr('title', options.tooltips.prevDecade); - yearsViewHeader.eq(1).attr('title', options.tooltips.selectDecade); - yearsViewHeader.eq(2).find('span').attr('title', options.tooltips.nextDecade); - yearsView.find('.disabled').removeClass('disabled'); - if (options.minDate && options.minDate.isAfter(startYear, 'y')) { - yearsViewHeader.eq(0).addClass('disabled'); - } - yearsViewHeader.eq(1).text(startYear.year() + '-' + endYear.year()); - if (options.maxDate && options.maxDate.isBefore(endYear, 'y')) { - yearsViewHeader.eq(2).addClass('disabled'); - } - while (!startYear.isAfter(endYear, 'y')) { - html += '' + startYear.year() + ''; - startYear.add(1, 'y'); - } - yearsView.find('td').html(html); - }; - var updateDecades = function updateDecades() { - var decadesView = widget.find('.datepicker-decades'); - var decadesViewHeader = decadesView.find('th'); - var startDecade = moment({ - y: viewDate.year() - viewDate.year() % 100 - 1 - }); - var endDecade = startDecade.clone().add(100, 'y'); - var startedAt = startDecade.clone(); - var minDateDecade = false; - var maxDateDecade = false; - var endDecadeYear; - var html = ''; - decadesViewHeader.eq(0).find('span').attr('title', options.tooltips.prevCentury); - decadesViewHeader.eq(2).find('span').attr('title', options.tooltips.nextCentury); - decadesView.find('.disabled').removeClass('disabled'); - if (startDecade.isSame(moment({ - y: 1900 - })) || options.minDate && options.minDate.isAfter(startDecade, 'y')) { - decadesViewHeader.eq(0).addClass('disabled'); - } - decadesViewHeader.eq(1).text(startDecade.year() + '-' + endDecade.year()); - if (startDecade.isSame(moment({ - y: 2000 - })) || options.maxDate && options.maxDate.isBefore(endDecade, 'y')) { - decadesViewHeader.eq(2).addClass('disabled'); - } - while (!startDecade.isAfter(endDecade, 'y')) { - endDecadeYear = startDecade.year() + 12; - minDateDecade = options.minDate && options.minDate.isAfter(startDecade, 'y') && options.minDate.year() <= endDecadeYear; - maxDateDecade = options.maxDate && options.maxDate.isAfter(startDecade, 'y') && options.maxDate.year() <= endDecadeYear; - html += '' + (startDecade.year() + 1) + ' - ' + (startDecade.year() + 12) + ''; - startDecade.add(12, 'y'); - } - html += ''; // push the dangling block over, at least this way it's even - - decadesView.find('td').html(html); - decadesViewHeader.eq(1).text(startedAt.year() + 1 + '-' + startDecade.year()); - }; - var fillDate = function fillDate() { - var daysView = widget.find('.datepicker-days'); - var daysViewHeader = daysView.find('th'); - var currentDate; - var html = []; - var row; - var clsNames = []; - var i; - if (!hasDate()) { - return; - } - daysViewHeader.eq(0).find('span').attr('title', options.tooltips.prevMonth); - daysViewHeader.eq(1).attr('title', options.tooltips.selectMonth); - daysViewHeader.eq(2).find('span').attr('title', options.tooltips.nextMonth); - daysView.find('.disabled').removeClass('disabled'); - daysViewHeader.eq(1).text(viewDate.format(options.dayViewHeaderFormat)); - if (!isValid(viewDate.clone().subtract(1, 'M'), 'M')) { - daysViewHeader.eq(0).addClass('disabled'); - } - if (!isValid(viewDate.clone().add(1, 'M'), 'M')) { - daysViewHeader.eq(2).addClass('disabled'); - } - currentDate = viewDate.clone().startOf('M').startOf('w').startOf('d'); - for (i = 0; i < 42; i++) { - // always display 42 days (should show 6 weeks) - if (currentDate.weekday() === 0) { - row = $('
' + currentDate.week() + '' + currentDate.date() + '
' + currentHour.format(use24Hours ? 'HH' : 'hh') + '
' + currentMinute.format('mm') + '
' + currentSecond.format('ss') + '
').addClass('prev').attr('data-action', 'previous').append($('').addClass(options.icons.previous))).append($('').addClass('picker-switch').attr('data-action', 'pickerSwitch').attr('colspan', options.calendarWeeks ? '6' : '5')).append($('').addClass('next').attr('data-action', 'next').append($('').addClass(options.icons.next)))); + var contTemplate = $('
').attr('colspan', options.calendarWeeks ? '8' : '7'))); + return [$('
').addClass('datepicker-days').append($('').addClass('table-condensed').append(headTemplate).append($(''))), $('
').addClass('datepicker-months').append($('
').addClass('table-condensed').append(headTemplate.clone()).append(contTemplate.clone())), $('
').addClass('datepicker-years').append($('
').addClass('table-condensed').append(headTemplate.clone()).append(contTemplate.clone())), $('
').addClass('datepicker-decades').append($('
').addClass('table-condensed').append(headTemplate.clone()).append(contTemplate.clone()))]; + }; + var getTimePickerMainTemplate = function getTimePickerMainTemplate() { + var topRow = $(''); + var middleRow = $(''); + var bottomRow = $(''); + if (isEnabled('h')) { + topRow.append($('
').append($('').attr({ + href: '#', + tabindex: '-1', + 'title': options.tooltips.incrementHour + }).addClass('btn').attr('data-action', 'incrementHours').append($('').addClass(options.icons.up)))); + middleRow.append($('').append($('').addClass('timepicker-hour').attr({ + 'data-time-component': 'hours', + 'title': options.tooltips.pickHour + }).attr('data-action', 'showHours'))); + bottomRow.append($('').append($('').attr({ + href: '#', + tabindex: '-1', + 'title': options.tooltips.decrementHour + }).addClass('btn').attr('data-action', 'decrementHours').append($('').addClass(options.icons.down)))); + } + if (isEnabled('m')) { + if (isEnabled('h')) { + topRow.append($('').addClass('separator')); + middleRow.append($('').addClass('separator').html(':')); + bottomRow.append($('').addClass('separator')); + } + topRow.append($('').append($('').attr({ + href: '#', + tabindex: '-1', + 'title': options.tooltips.incrementMinute + }).addClass('btn').attr('data-action', 'incrementMinutes').append($('').addClass(options.icons.up)))); + middleRow.append($('').append($('').addClass('timepicker-minute').attr({ + 'data-time-component': 'minutes', + 'title': options.tooltips.pickMinute + }).attr('data-action', 'showMinutes'))); + bottomRow.append($('').append($('').attr({ + href: '#', + tabindex: '-1', + 'title': options.tooltips.decrementMinute + }).addClass('btn').attr('data-action', 'decrementMinutes').append($('').addClass(options.icons.down)))); + } + if (isEnabled('s')) { + if (isEnabled('m')) { + topRow.append($('').addClass('separator')); + middleRow.append($('').addClass('separator').html(':')); + bottomRow.append($('').addClass('separator')); + } + topRow.append($('').append($('').attr({ + href: '#', + tabindex: '-1', + 'title': options.tooltips.incrementSecond + }).addClass('btn').attr('data-action', 'incrementSeconds').append($('').addClass(options.icons.up)))); + middleRow.append($('').append($('').addClass('timepicker-second').attr({ + 'data-time-component': 'seconds', + 'title': options.tooltips.pickSecond + }).attr('data-action', 'showSeconds'))); + bottomRow.append($('').append($('').attr({ + href: '#', + tabindex: '-1', + 'title': options.tooltips.decrementSecond + }).addClass('btn').attr('data-action', 'decrementSeconds').append($('').addClass(options.icons.down)))); + } + if (!use24Hours) { + topRow.append($('').addClass('separator')); + middleRow.append($('').append($('').addClass('separator')); + } + return $('
').addClass('timepicker-picker').append($('').addClass('table-condensed').append([topRow, middleRow, bottomRow])); + }; + var getTimePickerTemplate = function getTimePickerTemplate() { + var hoursView = $('
').addClass('timepicker-hours').append($('
').addClass('table-condensed')); + var minutesView = $('
').addClass('timepicker-minutes').append($('
').addClass('table-condensed')); + var secondsView = $('
').addClass('timepicker-seconds').append($('
').addClass('table-condensed')); + var ret = [getTimePickerMainTemplate()]; + if (isEnabled('h')) { + ret.push(hoursView); + } + if (isEnabled('m')) { + ret.push(minutesView); + } + if (isEnabled('s')) { + ret.push(secondsView); + } + return ret; + }; + var getToolbar = function getToolbar() { + var row = []; + if (options.showTodayButton) { + row.push($('
').append($('').attr({ + 'data-action': 'today', + 'title': options.tooltips.today + }).append($('').addClass(options.icons.today)))); + } + if (!options.sideBySide && hasDate() && hasTime()) { + row.push($('').append($('').attr({ + 'data-action': 'togglePicker', + 'title': options.tooltips.selectTime + }).append($('').addClass(options.icons.time)))); + } + if (options.showClear) { + row.push($('').append($('').attr({ + 'data-action': 'clear', + 'title': options.tooltips.clear + }).append($('').addClass(options.icons.clear)))); + } + if (options.showClose) { + row.push($('').append($('').attr({ + 'data-action': 'close', + 'title': options.tooltips.close + }).append($('').addClass(options.icons.close)))); + } + return $('').addClass('table-condensed').append($('').append($('').append(row))); + }; + var getTemplate = function getTemplate() { + var template = $('
').addClass('bootstrap-datetimepicker-widget dropdown-menu'); + var dateView = $('
').addClass('datepicker').append(getDatePickerTemplate()); + var timeView = $('
').addClass('timepicker').append(getTimePickerTemplate()); + var content = $('
    ').addClass('list-unstyled'); + var toolbar = $('
  • ').addClass('picker-switch' + (options.collapse ? ' accordion-toggle' : '')).append(getToolbar()); + if (options.inline) { + template.removeClass('dropdown-menu'); + } + if (use24Hours) { + template.addClass('usetwentyfour'); + } + if (isEnabled('s') && !use24Hours) { + template.addClass('wider'); + } + if (options.sideBySide && hasDate() && hasTime()) { + template.addClass('timepicker-sbs'); + if (options.toolbarPlacement === 'top') { + template.append(toolbar); + } + template.append($('
    ').addClass('row').append(dateView.addClass('col-md-6')).append(timeView.addClass('col-md-6'))); + if (options.toolbarPlacement === 'bottom') { + template.append(toolbar); + } + return template; + } + if (options.toolbarPlacement === 'top') { + content.append(toolbar); + } + if (hasDate()) { + content.append($('
  • ').addClass(options.collapse && hasTime() ? 'collapse in' : '').append(dateView)); + } + if (options.toolbarPlacement === 'default') { + content.append(toolbar); + } + if (hasTime()) { + content.append($('
  • ').addClass(options.collapse && hasDate() ? 'collapse' : '').append(timeView)); + } + if (options.toolbarPlacement === 'bottom') { + content.append(toolbar); + } + return template.append(content); + }; + var dataToOptions = function dataToOptions() { + var eData; + var dataOptions = {}; + if (element.is('input') || options.inline) { + eData = element.data(); + } else { + eData = element.find('input').data(); + } + if (eData.dateOptions && eData.dateOptions instanceof Object) { + dataOptions = $.extend(true, dataOptions, eData.dateOptions); + } + $.each(options, function (key) { + var attributeName = 'date' + key.charAt(0).toUpperCase() + key.slice(1); + if (eData[attributeName] !== undefined) { + dataOptions[key] = eData[attributeName]; + } + }); + return dataOptions; + }; + var place = function place() { + var position = (component || element).position(); + var offset = (component || element).offset(); + var vertical = options.widgetPositioning.vertical; + var horizontal = options.widgetPositioning.horizontal; + var parent; + if (options.widgetParent) { + parent = options.widgetParent.append(widget); + } else if (element.is('input')) { + parent = element.after(widget).parent(); + } else if (options.inline) { + parent = element.append(widget); + return; + } else { + parent = element; + element.children().first().after(widget); + } + + // Top and bottom logic + if (vertical === 'auto') { + if (offset.top + widget.height() * 1.5 >= $(window).height() + $(window).scrollTop() && widget.height() + element.outerHeight() < offset.top) { + vertical = 'top'; + } else { + vertical = 'bottom'; + } + } + + // Left and right logic + if (horizontal === 'auto') { + if (parent.width() < offset.left + widget.outerWidth() / 2 && offset.left + widget.outerWidth() > $(window).width()) { + horizontal = 'right'; + } else { + horizontal = 'left'; + } + } + if (vertical === 'top') { + widget.addClass('top').removeClass('bottom'); + } else { + widget.addClass('bottom').removeClass('top'); + } + if (horizontal === 'right') { + widget.addClass('pull-right'); + } else { + widget.removeClass('pull-right'); + } + + // find the first parent element that has a non-static css positioning + if (parent.css('position') === 'static') { + parent = parent.parents().filter(function () { + return $(this).css('position') !== 'static'; + }).first(); + } + if (parent.length === 0) { + throw new Error('datetimepicker component should be placed within a non-static positioned container'); + } + widget.css({ + top: vertical === 'top' ? 'auto' : position.top + element.outerHeight(), + bottom: vertical === 'top' ? parent.outerHeight() - (parent === element ? 0 : position.top) : 'auto', + left: horizontal === 'left' ? parent === element ? 0 : position.left : 'auto', + right: horizontal === 'left' ? 'auto' : parent.outerWidth() - element.outerWidth() - (parent === element ? 0 : position.left) + }); + }; + var notifyEvent = function notifyEvent(e) { + if (e.type === 'dp.change' && (e.date && e.date.isSame(e.oldDate) || !e.date && !e.oldDate)) { + return; + } + element.trigger(e); + }; + var viewUpdate = function viewUpdate(e) { + if (e === 'y') { + e = 'YYYY'; + } + notifyEvent({ + type: 'dp.update', + change: e, + viewDate: viewDate.clone() + }); + }; + var showMode = function showMode(dir) { + if (!widget) { + return; + } + if (dir) { + currentViewMode = Math.max(minViewModeNumber, Math.min(3, currentViewMode + dir)); + } + widget.find('.datepicker > div').hide().filter('.datepicker-' + datePickerModes[currentViewMode].clsName).show(); + }; + var fillDow = function fillDow() { + var row = $('
'); + var currentDate = viewDate.clone().startOf('w').startOf('d'); + if (options.calendarWeeks === true) { + row.append($(''); + if (options.calendarWeeks) { + row.append(''); + } + html.push(row); + } + clsNames = ['day']; + if (currentDate.isBefore(viewDate, 'M')) { + clsNames.push('old'); + } + if (currentDate.isAfter(viewDate, 'M')) { + clsNames.push('new'); + } + if (currentDate.isSame(date, 'd') && !unset) { + clsNames.push('active'); + } + if (!isValid(currentDate, 'd')) { + clsNames.push('disabled'); + } + if (currentDate.isSame(getMoment(), 'd')) { + clsNames.push('today'); + } + if (currentDate.day() === 0 || currentDate.day() === 6) { + clsNames.push('weekend'); + } + notifyEvent({ + type: 'dp.classify', + date: currentDate, + classNames: clsNames + }); + row.append(''); + currentDate.add(1, 'd'); + } + daysView.find('tbody').empty().append(html); + updateMonths(); + updateYears(); + updateDecades(); + }; + var fillHours = function fillHours() { + var table = widget.find('.timepicker-hours table'); + var currentHour = viewDate.clone().startOf('d'); + var html = []; + var row = $(''); + if (viewDate.hour() > 11 && !use24Hours) { + currentHour.hour(12); + } + while (currentHour.isSame(viewDate, 'd') && (use24Hours || viewDate.hour() < 12 && currentHour.hour() < 12 || viewDate.hour() > 11)) { + if (currentHour.hour() % 4 === 0) { + row = $(''); + html.push(row); + } + row.append(''); + currentHour.add(1, 'h'); + } + table.empty().append(html); + }; + var fillMinutes = function fillMinutes() { + var table = widget.find('.timepicker-minutes table'); + var currentMinute = viewDate.clone().startOf('h'); + var html = []; + var row = $(''); + var step = options.stepping === 1 ? 5 : options.stepping; + while (viewDate.isSame(currentMinute, 'h')) { + if (currentMinute.minute() % (step * 4) === 0) { + row = $(''); + html.push(row); + } + row.append(''); + currentMinute.add(step, 'm'); + } + table.empty().append(html); + }; + var fillSeconds = function fillSeconds() { + var table = widget.find('.timepicker-seconds table'); + var currentSecond = viewDate.clone().startOf('m'); + var html = []; + var row = $(''); + while (viewDate.isSame(currentSecond, 'm')) { + if (currentSecond.second() % 20 === 0) { + row = $(''); + html.push(row); + } + row.append(''); + currentSecond.add(5, 's'); + } + table.empty().append(html); + }; + var fillTime = function fillTime() { + var toggle; + var newDate; + var timeComponents = widget.find('.timepicker span[data-time-component]'); + if (!use24Hours) { + toggle = widget.find('.timepicker [data-action=togglePeriod]'); + newDate = date.clone().add(date.hours() >= 12 ? -12 : 12, 'h'); + toggle.text(date.format('A')); + if (isValid(newDate, 'h')) { + toggle.removeClass('disabled'); + } else { + toggle.addClass('disabled'); + } + } + timeComponents.filter('[data-time-component=hours]').text(date.format(use24Hours ? 'HH' : 'hh')); + timeComponents.filter('[data-time-component=minutes]').text(date.format('mm')); + timeComponents.filter('[data-time-component=seconds]').text(date.format('ss')); + fillHours(); + fillMinutes(); + fillSeconds(); + }; + var update = function update() { + if (!widget) { + return; + } + fillDate(); + fillTime(); + }; + var setValue = function setValue(targetMoment) { + var oldDate = unset ? null : date; + + // case of calling setValue(null or false) + if (!targetMoment) { + unset = true; + input.val(''); + element.data('date', ''); + notifyEvent({ + type: 'dp.change', + date: false, + oldDate: oldDate + }); + update(); + return; + } + targetMoment = targetMoment.clone().locale(options.locale); + if (hasTimeZone()) { + targetMoment.tz(options.timeZone); + } + if (options.stepping !== 1) { + targetMoment.minutes(Math.round(targetMoment.minutes() / options.stepping) * options.stepping).seconds(0); + while (options.minDate && targetMoment.isBefore(options.minDate)) { + targetMoment.add(options.stepping, 'minutes'); + } + } + if (isValid(targetMoment)) { + date = targetMoment; + viewDate = date.clone(); + input.val(date.format(actualFormat)); + element.data('date', date.format(actualFormat)); + unset = false; + update(); + notifyEvent({ + type: 'dp.change', + date: date.clone(), + oldDate: oldDate + }); + } else { + if (!options.keepInvalid) { + input.val(unset ? '' : date.format(actualFormat)); + } else { + notifyEvent({ + type: 'dp.change', + date: targetMoment, + oldDate: oldDate + }); + } + notifyEvent({ + type: 'dp.error', + date: targetMoment, + oldDate: oldDate + }); + } + }; + + /** + * Hides the widget. Possibly will emit dp.hide + */ + var hide = function hide() { + var transitioning = false; + if (!widget) { + return picker; + } + // Ignore event if in the middle of a picker transition + widget.find('.collapse').each(function () { + var collapseData = $(this).data('collapse'); + if (collapseData && collapseData.transitioning) { + transitioning = true; + return false; + } + return true; + }); + if (transitioning) { + return picker; + } + if (component && component.hasClass('btn')) { + component.toggleClass('active'); + } + widget.hide(); + $(window).off('resize', place); + widget.off('click', '[data-action]'); + widget.off('mousedown', false); + widget.remove(); + widget = false; + notifyEvent({ + type: 'dp.hide', + date: date.clone() + }); + input.blur(); + viewDate = date.clone(); + return picker; + }; + var clear = function clear() { + setValue(null); + }; + var parseInputDate = function parseInputDate(inputDate) { + if (options.parseInputDate === undefined) { + if (!moment.isMoment(inputDate) || inputDate instanceof Date) { + inputDate = getMoment(inputDate); + } + } else { + inputDate = options.parseInputDate(inputDate); + } + // inputDate.locale(options.locale); + return inputDate; + }; + + /** ****************************************************************************** + * + * Widget UI interaction functions + * + ********************************************************************************/ + var actions = { + next: function next() { + var navFnc = datePickerModes[currentViewMode].navFnc; + viewDate.add(datePickerModes[currentViewMode].navStep, navFnc); + fillDate(); + viewUpdate(navFnc); + }, + previous: function previous() { + var navFnc = datePickerModes[currentViewMode].navFnc; + viewDate.subtract(datePickerModes[currentViewMode].navStep, navFnc); + fillDate(); + viewUpdate(navFnc); + }, + pickerSwitch: function pickerSwitch() { + showMode(1); + }, + selectMonth: function selectMonth(e) { + var month = $(e.target).closest('tbody').find('span').index($(e.target)); + viewDate.month(month); + if (currentViewMode === minViewModeNumber) { + setValue(date.clone().year(viewDate.year()).month(viewDate.month())); + if (!options.inline) { + hide(); + } + } else { + showMode(-1); + fillDate(); + } + viewUpdate('M'); + }, + selectYear: function selectYear(e) { + var year = parseInt($(e.target).text(), 10) || 0; + viewDate.year(year); + if (currentViewMode === minViewModeNumber) { + setValue(date.clone().year(viewDate.year())); + if (!options.inline) { + hide(); + } + } else { + showMode(-1); + fillDate(); + } + viewUpdate('YYYY'); + }, + selectDecade: function selectDecade(e) { + var year = parseInt($(e.target).data('selection'), 10) || 0; + viewDate.year(year); + if (currentViewMode === minViewModeNumber) { + setValue(date.clone().year(viewDate.year())); + if (!options.inline) { + hide(); + } + } else { + showMode(-1); + fillDate(); + } + viewUpdate('YYYY'); + }, + selectDay: function selectDay(e) { + var day = viewDate.clone(); + if ($(e.target).is('.old')) { + day.subtract(1, 'M'); + } + if ($(e.target).is('.new')) { + day.add(1, 'M'); + } + setValue(day.date(parseInt($(e.target).text(), 10))); + if (!hasTime() && !options.keepOpen && !options.inline) { + hide(); + } + }, + incrementHours: function incrementHours() { + var newDate = date.clone().add(1, 'h'); + if (isValid(newDate, 'h')) { + setValue(newDate); + } + }, + incrementMinutes: function incrementMinutes() { + var newDate = date.clone().add(options.stepping, 'm'); + if (isValid(newDate, 'm')) { + setValue(newDate); + } + }, + incrementSeconds: function incrementSeconds() { + var newDate = date.clone().add(1, 's'); + if (isValid(newDate, 's')) { + setValue(newDate); + } + }, + decrementHours: function decrementHours() { + var newDate = date.clone().subtract(1, 'h'); + if (isValid(newDate, 'h')) { + setValue(newDate); + } + }, + decrementMinutes: function decrementMinutes() { + var newDate = date.clone().subtract(options.stepping, 'm'); + if (isValid(newDate, 'm')) { + setValue(newDate); + } + }, + decrementSeconds: function decrementSeconds() { + var newDate = date.clone().subtract(1, 's'); + if (isValid(newDate, 's')) { + setValue(newDate); + } + }, + togglePeriod: function togglePeriod() { + setValue(date.clone().add(date.hours() >= 12 ? -12 : 12, 'h')); + }, + togglePicker: function togglePicker(e) { + var $this = $(e.target); + var $parent = $this.closest('ul'); + var expanded = $parent.find('.in'); + var closed = $parent.find('.collapse:not(.in)'); + var collapseData; + if (expanded && expanded.length) { + collapseData = expanded.data('collapse'); + if (collapseData && collapseData.transitioning) { + return; + } + if (expanded.collapse) { + // if collapse plugin is available through bootstrap.js then use it + expanded.collapse('hide'); + closed.collapse('show'); + } else { + // otherwise just toggle in class on the two views + expanded.removeClass('in'); + closed.addClass('in'); + } + if ($this.is('span')) { + $this.toggleClass(options.icons.time + ' ' + options.icons.date); + } else { + $this.find('span').toggleClass(options.icons.time + ' ' + options.icons.date); + } + + // NOTE: uncomment if toggled state will be restored in show() + // if (component) { + // component.find('span').toggleClass(options.icons.time + ' ' + options.icons.date); + // } + } + }, + showPicker: function showPicker() { + widget.find('.timepicker > div:not(.timepicker-picker)').hide(); + widget.find('.timepicker .timepicker-picker').show(); + }, + showHours: function showHours() { + widget.find('.timepicker .timepicker-picker').hide(); + widget.find('.timepicker .timepicker-hours').show(); + }, + showMinutes: function showMinutes() { + widget.find('.timepicker .timepicker-picker').hide(); + widget.find('.timepicker .timepicker-minutes').show(); + }, + showSeconds: function showSeconds() { + widget.find('.timepicker .timepicker-picker').hide(); + widget.find('.timepicker .timepicker-seconds').show(); + }, + selectHour: function selectHour(e) { + var hour = parseInt($(e.target).text(), 10); + if (!use24Hours) { + if (date.hours() >= 12) { + if (hour !== 12) { + hour += 12; + } + } else { + if (hour === 12) { + hour = 0; + } + } + } + setValue(date.clone().hours(hour)); + actions.showPicker.call(picker); + }, + selectMinute: function selectMinute(e) { + setValue(date.clone().minutes(parseInt($(e.target).text(), 10))); + actions.showPicker.call(picker); + }, + selectSecond: function selectSecond(e) { + setValue(date.clone().seconds(parseInt($(e.target).text(), 10))); + actions.showPicker.call(picker); + }, + clear: clear, + today: function today() { + var todaysDate = getMoment(); + if (isValid(todaysDate, 'd')) { + setValue(todaysDate); + } + }, + close: hide + }; + var doAction = function doAction(e) { + if ($(e.currentTarget).is('.disabled')) { + return false; + } + actions[$(e.currentTarget).data('action')].apply(picker, arguments); + return false; + }; + + /** + * Shows the widget. Possibly will emit dp.show and dp.change + */ + var show = function show() { + var currentMoment; + var useCurrentGranularity = { + 'year': function year(m) { + return m.month(0).date(1).hours(0).seconds(0).minutes(0); + }, + 'month': function month(m) { + return m.date(1).hours(0).seconds(0).minutes(0); + }, + 'day': function day(m) { + return m.hours(0).seconds(0).minutes(0); + }, + 'hour': function hour(m) { + return m.seconds(0).minutes(0); + }, + 'minute': function minute(m) { + return m.seconds(0); + } + }; + if (input.prop('disabled') || !options.ignoreReadonly && input.prop('readonly') || widget) { + return picker; + } + if (input.val() !== undefined && input.val().trim().length !== 0) { + setValue(parseInputDate(input.val().trim())); + } else if (unset && options.useCurrent && (options.inline || input.is('input') && input.val().trim().length === 0)) { + currentMoment = getMoment(); + if (typeof options.useCurrent === 'string') { + currentMoment = useCurrentGranularity[options.useCurrent](currentMoment); + } + setValue(currentMoment); + } + widget = getTemplate(); + fillDow(); + fillMonths(); + widget.find('.timepicker-hours').hide(); + widget.find('.timepicker-minutes').hide(); + widget.find('.timepicker-seconds').hide(); + update(); + showMode(); + $(window).on('resize', place); + widget.on('click', '[data-action]', doAction); // this handles clicks on the widget + widget.on('mousedown', false); + if (component && component.hasClass('btn')) { + component.toggleClass('active'); + } + place(); + widget.show(); + if (options.focusOnShow && !input.is(':focus')) { + input.focus(); + } + notifyEvent({ + type: 'dp.show' + }); + return picker; + }; + + /** + * Shows or hides the widget + */ + var toggle = function toggle() { + return widget ? hide() : show(); + }; + var keydown = function keydown(e) { + var handler = null; + var index; + var index2; + var pressedKeys = []; + var pressedModifiers = {}; + var currentKey = e.which; + var keyBindKeys; + var allModifiersPressed; + var pressed = 'p'; + keyState[currentKey] = pressed; + for (index in keyState) { + if (keyState.hasOwnProperty(index) && keyState[index] === pressed) { + pressedKeys.push(index); + if (parseInt(index, 10) !== currentKey) { + pressedModifiers[index] = true; + } + } + } + for (index in options.keyBinds) { + if (options.keyBinds.hasOwnProperty(index) && typeof options.keyBinds[index] === 'function') { + keyBindKeys = index.split(' '); + if (keyBindKeys.length === pressedKeys.length && keyMap[currentKey] === keyBindKeys[keyBindKeys.length - 1]) { + allModifiersPressed = true; + for (index2 = keyBindKeys.length - 2; index2 >= 0; index2--) { + if (!(keyMap[keyBindKeys[index2]] in pressedModifiers)) { + allModifiersPressed = false; + break; + } + } + if (allModifiersPressed) { + handler = options.keyBinds[index]; + break; + } + } + } + } + if (handler) { + handler.call(picker, widget); + e.stopPropagation(); + e.preventDefault(); + } + }; + var keyup = function keyup(e) { + keyState[e.which] = 'r'; + e.stopPropagation(); + e.preventDefault(); + }; + var change = function change(e) { + var val = $(e.target).val().trim(); + var parsedDate = val ? parseInputDate(val) : null; + setValue(parsedDate); + e.stopImmediatePropagation(); + return false; + }; + var attachDatePickerElementEvents = function attachDatePickerElementEvents() { + input.on({ + 'change': change, + 'blur': options.debug ? '' : hide, + 'keydown': keydown, + 'keyup': keyup, + 'focus': options.allowInputToggle ? show : '' + }); + if (element.is('input')) { + input.on({ + 'focus': show + }); + } else if (component) { + component.on('click', toggle); + component.on('mousedown', false); + } + }; + var detachDatePickerElementEvents = function detachDatePickerElementEvents() { + input.off({ + 'change': change, + 'blur': blur, + 'keydown': keydown, + 'keyup': keyup, + 'focus': options.allowInputToggle ? hide : '' + }); + if (element.is('input')) { + input.off({ + 'focus': show + }); + } else if (component) { + component.off('click', toggle); + component.off('mousedown', false); + } + }; + var indexGivenDates = function indexGivenDates(givenDatesArray) { + // Store given enabledDates and disabledDates as keys. + // This way we can check their existence in O(1) time instead of looping through whole array. + // (for example: options.enabledDates['2014-02-27'] === true) + var givenDatesIndexed = {}; + $.each(givenDatesArray, function () { + var dDate = parseInputDate(this); + if (dDate.isValid()) { + givenDatesIndexed[dDate.format('YYYY-MM-DD')] = true; + } + }); + return Object.keys(givenDatesIndexed).length ? givenDatesIndexed : false; + }; + var indexGivenHours = function indexGivenHours(givenHoursArray) { + // Store given enabledHours and disabledHours as keys. + // This way we can check their existence in O(1) time instead of looping through whole array. + // (for example: options.enabledHours['2014-02-27'] === true) + var givenHoursIndexed = {}; + $.each(givenHoursArray, function () { + givenHoursIndexed[this] = true; + }); + return Object.keys(givenHoursIndexed).length ? givenHoursIndexed : false; + }; + var initFormatting = function initFormatting() { + var format = options.format || 'L LT'; + actualFormat = format.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput) { + var newinput = date.localeData().longDateFormat(formatInput) || formatInput; + return newinput.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput2) { + // temp fix for #740 + return date.localeData().longDateFormat(formatInput2) || formatInput2; + }); + }); + parseFormats = options.extraFormats ? options.extraFormats.slice() : []; + if (parseFormats.indexOf(format) < 0 && parseFormats.indexOf(actualFormat) < 0) { + parseFormats.push(actualFormat); + } + use24Hours = actualFormat.toLowerCase().indexOf('a') < 1 && actualFormat.replace(/\[.*?\]/g, '').indexOf('h') < 1; + if (isEnabled('y')) { + minViewModeNumber = 2; + } + if (isEnabled('M')) { + minViewModeNumber = 1; + } + if (isEnabled('d')) { + minViewModeNumber = 0; + } + currentViewMode = Math.max(minViewModeNumber, currentViewMode); + if (!unset) { + setValue(date); + } + }; + + /** ****************************************************************************** + * + * Public API functions + * ===================== + * + * Important: Do not expose direct references to private objects or the options + * object to the outer world. Always return a clone when returning values or make + * a clone when setting a private variable. + * + ********************************************************************************/ + picker.destroy = function () { + // /Destroys the widget and removes all attached event listeners + hide(); + detachDatePickerElementEvents(); + element.removeData('DateTimePicker'); + element.removeData('date'); + }; + picker.toggle = toggle; + picker.show = show; + picker.hide = hide; + picker.disable = function () { + // /Disables the input element, the component is attached to, by adding a disabled="true" attribute to it. + // /If the widget was visible before that call it is hidden. Possibly emits dp.hide + hide(); + if (component && component.hasClass('btn')) { + component.addClass('disabled'); + } + input.prop('disabled', true); + return picker; + }; + picker.enable = function () { + // /Enables the input element, the component is attached to, by removing disabled attribute from it. + if (component && component.hasClass('btn')) { + component.removeClass('disabled'); + } + input.prop('disabled', false); + return picker; + }; + picker.ignoreReadonly = function (ignoreReadonly) { + if (arguments.length === 0) { + return options.ignoreReadonly; + } + if (typeof ignoreReadonly !== 'boolean') { + throw new TypeError('ignoreReadonly () expects a boolean parameter'); + } + options.ignoreReadonly = ignoreReadonly; + return picker; + }; + picker.options = function (newOptions) { + if (arguments.length === 0) { + return $.extend(true, {}, options); + } + if (!(newOptions instanceof Object)) { + throw new TypeError('options() options parameter should be an object'); + } + $.extend(true, options, newOptions); + $.each(options, function (key, value) { + if (picker[key] !== undefined) { + picker[key](value); + } else { + throw new TypeError('option ' + key + ' is not recognized!'); + } + }); + return picker; + }; + picker.date = function (newDate) { + // / + // /Returns the component's model current date, a moment object or null if not set. + // /date.clone() + // / + // / + // /Sets the components model current moment to it. Passing a null value unsets the components model current moment. Parsing of the newDate parameter is made using moment library with the options.format and options.useStrict components configuration. + // /Takes string, Date, moment, null parameter. + // / + if (arguments.length === 0) { + if (unset) { + return null; + } + return date.clone(); + } + if (newDate !== null && typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) { + throw new TypeError('date() parameter must be one of [null, string, moment or Date]'); + } + setValue(newDate === null ? null : parseInputDate(newDate)); + return picker; + }; + picker.format = function (newFormat) { + // /test su + // /info about para + // /returns foo + if (arguments.length === 0) { + return options.format; + } + if (typeof newFormat !== 'string' && (typeof newFormat !== 'boolean' || newFormat !== false)) { + throw new TypeError('format() expects a string or boolean:false parameter ' + newFormat); + } + options.format = newFormat; + if (actualFormat) { + initFormatting(); // reinit formatting + } + return picker; + }; + picker.timeZone = function (newZone) { + if (arguments.length === 0) { + return options.timeZone; + } + if (typeof newZone !== 'string') { + throw new TypeError('newZone() expects a string parameter'); + } + options.timeZone = newZone; + return picker; + }; + picker.dayViewHeaderFormat = function (newFormat) { + if (arguments.length === 0) { + return options.dayViewHeaderFormat; + } + if (typeof newFormat !== 'string') { + throw new TypeError('dayViewHeaderFormat() expects a string parameter'); + } + options.dayViewHeaderFormat = newFormat; + return picker; + }; + picker.extraFormats = function (formats) { + if (arguments.length === 0) { + return options.extraFormats; + } + if (formats !== false && !(formats instanceof Array)) { + throw new TypeError('extraFormats() expects an array or false parameter'); + } + options.extraFormats = formats; + if (parseFormats) { + initFormatting(); // reinit formatting + } + return picker; + }; + picker.disabledDates = function (dates) { + // / + // /Returns an array with the currently set disabled dates on the component. + // /options.disabledDates + // / + // / + // /Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of + // /options.enabledDates if such exist. + // /Takes an [ string or Date or moment ] of values and allows the user to select only from those days. + // / + if (arguments.length === 0) { + return options.disabledDates ? $.extend({}, options.disabledDates) : options.disabledDates; + } + if (!dates) { + options.disabledDates = false; + update(); + return picker; + } + if (!(dates instanceof Array)) { + throw new TypeError('disabledDates() expects an array parameter'); + } + options.disabledDates = indexGivenDates(dates); + options.enabledDates = false; + update(); + return picker; + }; + picker.enabledDates = function (dates) { + // / + // /Returns an array with the currently set enabled dates on the component. + // /options.enabledDates + // / + // / + // /Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of options.disabledDates if such exist. + // /Takes an [ string or Date or moment ] of values and allows the user to select only from those days. + // / + if (arguments.length === 0) { + return options.enabledDates ? $.extend({}, options.enabledDates) : options.enabledDates; + } + if (!dates) { + options.enabledDates = false; + update(); + return picker; + } + if (!(dates instanceof Array)) { + throw new TypeError('enabledDates() expects an array parameter'); + } + options.enabledDates = indexGivenDates(dates); + options.disabledDates = false; + update(); + return picker; + }; + picker.daysOfWeekDisabled = function (daysOfWeekDisabled) { + if (arguments.length === 0) { + return options.daysOfWeekDisabled.splice(0); + } + if (typeof daysOfWeekDisabled === 'boolean' && !daysOfWeekDisabled) { + options.daysOfWeekDisabled = false; + update(); + return picker; + } + if (!(daysOfWeekDisabled instanceof Array)) { + throw new TypeError('daysOfWeekDisabled() expects an array parameter'); + } + options.daysOfWeekDisabled = daysOfWeekDisabled.reduce(function (previousValue, currentValue) { + currentValue = parseInt(currentValue, 10); + if (currentValue > 6 || currentValue < 0 || isNaN(currentValue)) { + return previousValue; + } + if (previousValue.indexOf(currentValue) === -1) { + previousValue.push(currentValue); + } + return previousValue; + }, []).sort(); + if (options.useCurrent && !options.keepInvalid) { + var tries = 0; + while (!isValid(date, 'd')) { + date.add(1, 'd'); + if (tries === 31) { + throw new Error('Tried 31 times to find a valid date'); + } + tries++; + } + setValue(date); + } + update(); + return picker; + }; + picker.maxDate = function (maxDate) { + if (arguments.length === 0) { + return options.maxDate ? options.maxDate.clone() : options.maxDate; + } + if (typeof maxDate === 'boolean' && maxDate === false) { + options.maxDate = false; + update(); + return picker; + } + if (typeof maxDate === 'string') { + if (maxDate === 'now' || maxDate === 'moment') { + maxDate = getMoment(); + } + } + var parsedDate = parseInputDate(maxDate); + if (!parsedDate.isValid()) { + throw new TypeError('maxDate() Could not parse date parameter: ' + maxDate); + } + if (options.minDate && parsedDate.isBefore(options.minDate)) { + throw new TypeError('maxDate() date parameter is before options.minDate: ' + parsedDate.format(actualFormat)); + } + options.maxDate = parsedDate; + if (options.useCurrent && !options.keepInvalid && date.isAfter(maxDate)) { + setValue(options.maxDate); + } + if (viewDate.isAfter(parsedDate)) { + viewDate = parsedDate.clone().subtract(options.stepping, 'm'); + } + update(); + return picker; + }; + picker.minDate = function (minDate) { + if (arguments.length === 0) { + return options.minDate ? options.minDate.clone() : options.minDate; + } + if (typeof minDate === 'boolean' && minDate === false) { + options.minDate = false; + update(); + return picker; + } + if (typeof minDate === 'string') { + if (minDate === 'now' || minDate === 'moment') { + minDate = getMoment(); + } + } + var parsedDate = parseInputDate(minDate); + if (!parsedDate.isValid()) { + throw new TypeError('minDate() Could not parse date parameter: ' + minDate); + } + if (options.maxDate && parsedDate.isAfter(options.maxDate)) { + throw new TypeError('minDate() date parameter is after options.maxDate: ' + parsedDate.format(actualFormat)); + } + options.minDate = parsedDate; + if (options.useCurrent && !options.keepInvalid && date.isBefore(minDate)) { + setValue(options.minDate); + } + if (viewDate.isBefore(parsedDate)) { + viewDate = parsedDate.clone().add(options.stepping, 'm'); + } + update(); + return picker; + }; + picker.defaultDate = function (defaultDate) { + // / + // /Returns a moment with the options.defaultDate option configuration or false if not set + // /date.clone() + // / + // / + // /Will set the picker's inital date. If a boolean:false value is passed the options.defaultDate parameter is cleared. + // /Takes a string, Date, moment, boolean:false + // / + if (arguments.length === 0) { + return options.defaultDate ? options.defaultDate.clone() : options.defaultDate; + } + if (!defaultDate) { + options.defaultDate = false; + return picker; + } + if (typeof defaultDate === 'string') { + if (defaultDate === 'now' || defaultDate === 'moment') { + defaultDate = getMoment(); + } else { + defaultDate = getMoment(defaultDate); + } + } + var parsedDate = parseInputDate(defaultDate); + if (!parsedDate.isValid()) { + throw new TypeError('defaultDate() Could not parse date parameter: ' + defaultDate); + } + if (!isValid(parsedDate)) { + throw new TypeError('defaultDate() date passed is invalid according to component setup validations'); + } + options.defaultDate = parsedDate; + if (options.defaultDate && options.inline || input.val().trim() === '') { + setValue(options.defaultDate); + } + return picker; + }; + picker.locale = function (locale) { + if (arguments.length === 0) { + return options.locale; + } + if (!moment.localeData(locale)) { + throw new TypeError('locale() locale ' + locale + ' is not loaded from moment locales!'); + } + options.locale = locale; + date.locale(options.locale); + viewDate.locale(options.locale); + if (actualFormat) { + initFormatting(); // reinit formatting + } + if (widget) { + hide(); + show(); + } + return picker; + }; + picker.stepping = function (stepping) { + if (arguments.length === 0) { + return options.stepping; + } + stepping = parseInt(stepping, 10); + if (isNaN(stepping) || stepping < 1) { + stepping = 1; + } + options.stepping = stepping; + return picker; + }; + picker.useCurrent = function (useCurrent) { + var useCurrentOptions = ['year', 'month', 'day', 'hour', 'minute']; + if (arguments.length === 0) { + return options.useCurrent; + } + if (typeof useCurrent !== 'boolean' && typeof useCurrent !== 'string') { + throw new TypeError('useCurrent() expects a boolean or string parameter'); + } + if (typeof useCurrent === 'string' && useCurrentOptions.indexOf(useCurrent.toLowerCase()) === -1) { + throw new TypeError('useCurrent() expects a string parameter of ' + useCurrentOptions.join(', ')); + } + options.useCurrent = useCurrent; + return picker; + }; + picker.collapse = function (collapse) { + if (arguments.length === 0) { + return options.collapse; + } + if (typeof collapse !== 'boolean') { + throw new TypeError('collapse() expects a boolean parameter'); + } + if (options.collapse === collapse) { + return picker; + } + options.collapse = collapse; + if (widget) { + hide(); + show(); + } + return picker; + }; + picker.icons = function (icons) { + if (arguments.length === 0) { + return $.extend({}, options.icons); + } + if (!(icons instanceof Object)) { + throw new TypeError('icons() expects parameter to be an Object'); + } + $.extend(options.icons, icons); + if (widget) { + hide(); + show(); + } + return picker; + }; + picker.tooltips = function (tooltips) { + if (arguments.length === 0) { + return $.extend({}, options.tooltips); + } + if (!(tooltips instanceof Object)) { + throw new TypeError('tooltips() expects parameter to be an Object'); + } + $.extend(options.tooltips, tooltips); + if (widget) { + hide(); + show(); + } + return picker; + }; + picker.useStrict = function (useStrict) { + if (arguments.length === 0) { + return options.useStrict; + } + if (typeof useStrict !== 'boolean') { + throw new TypeError('useStrict() expects a boolean parameter'); + } + options.useStrict = useStrict; + return picker; + }; + picker.sideBySide = function (sideBySide) { + if (arguments.length === 0) { + return options.sideBySide; + } + if (typeof sideBySide !== 'boolean') { + throw new TypeError('sideBySide() expects a boolean parameter'); + } + options.sideBySide = sideBySide; + if (widget) { + hide(); + show(); + } + return picker; + }; + picker.viewMode = function (viewMode) { + if (arguments.length === 0) { + return options.viewMode; + } + if (typeof viewMode !== 'string') { + throw new TypeError('viewMode() expects a string parameter'); + } + if (viewModes.indexOf(viewMode) === -1) { + throw new TypeError('viewMode() parameter must be one of (' + viewModes.join(', ') + ') value'); + } + options.viewMode = viewMode; + currentViewMode = Math.max(viewModes.indexOf(viewMode), minViewModeNumber); + showMode(); + return picker; + }; + picker.toolbarPlacement = function (toolbarPlacement) { + if (arguments.length === 0) { + return options.toolbarPlacement; + } + if (typeof toolbarPlacement !== 'string') { + throw new TypeError('toolbarPlacement() expects a string parameter'); + } + if (toolbarPlacements.indexOf(toolbarPlacement) === -1) { + throw new TypeError('toolbarPlacement() parameter must be one of (' + toolbarPlacements.join(', ') + ') value'); + } + options.toolbarPlacement = toolbarPlacement; + if (widget) { + hide(); + show(); + } + return picker; + }; + picker.widgetPositioning = function (widgetPositioning) { + if (arguments.length === 0) { + return $.extend({}, options.widgetPositioning); + } + if ({}.toString.call(widgetPositioning) !== '[object Object]') { + throw new TypeError('widgetPositioning() expects an object variable'); + } + if (widgetPositioning.horizontal) { + if (typeof widgetPositioning.horizontal !== 'string') { + throw new TypeError('widgetPositioning() horizontal variable must be a string'); + } + widgetPositioning.horizontal = widgetPositioning.horizontal.toLowerCase(); + if (horizontalModes.indexOf(widgetPositioning.horizontal) === -1) { + throw new TypeError('widgetPositioning() expects horizontal parameter to be one of (' + horizontalModes.join(', ') + ')'); + } + options.widgetPositioning.horizontal = widgetPositioning.horizontal; + } + if (widgetPositioning.vertical) { + if (typeof widgetPositioning.vertical !== 'string') { + throw new TypeError('widgetPositioning() vertical variable must be a string'); + } + widgetPositioning.vertical = widgetPositioning.vertical.toLowerCase(); + if (verticalModes.indexOf(widgetPositioning.vertical) === -1) { + throw new TypeError('widgetPositioning() expects vertical parameter to be one of (' + verticalModes.join(', ') + ')'); + } + options.widgetPositioning.vertical = widgetPositioning.vertical; + } + update(); + return picker; + }; + picker.calendarWeeks = function (calendarWeeks) { + if (arguments.length === 0) { + return options.calendarWeeks; + } + if (typeof calendarWeeks !== 'boolean') { + throw new TypeError('calendarWeeks() expects parameter to be a boolean value'); + } + options.calendarWeeks = calendarWeeks; + update(); + return picker; + }; + picker.showTodayButton = function (showTodayButton) { + if (arguments.length === 0) { + return options.showTodayButton; + } + if (typeof showTodayButton !== 'boolean') { + throw new TypeError('showTodayButton() expects a boolean parameter'); + } + options.showTodayButton = showTodayButton; + if (widget) { + hide(); + show(); + } + return picker; + }; + picker.showClear = function (showClear) { + if (arguments.length === 0) { + return options.showClear; + } + if (typeof showClear !== 'boolean') { + throw new TypeError('showClear() expects a boolean parameter'); + } + options.showClear = showClear; + if (widget) { + hide(); + show(); + } + return picker; + }; + picker.widgetParent = function (widgetParent) { + if (arguments.length === 0) { + return options.widgetParent; + } + if (typeof widgetParent === 'string') { + widgetParent = $(widgetParent); + } + if (widgetParent !== null && typeof widgetParent !== 'string' && !(widgetParent instanceof $)) { + throw new TypeError('widgetParent() expects a string or a jQuery object parameter'); + } + options.widgetParent = widgetParent; + if (widget) { + hide(); + show(); + } + return picker; + }; + picker.keepOpen = function (keepOpen) { + if (arguments.length === 0) { + return options.keepOpen; + } + if (typeof keepOpen !== 'boolean') { + throw new TypeError('keepOpen() expects a boolean parameter'); + } + options.keepOpen = keepOpen; + return picker; + }; + picker.focusOnShow = function (focusOnShow) { + if (arguments.length === 0) { + return options.focusOnShow; + } + if (typeof focusOnShow !== 'boolean') { + throw new TypeError('focusOnShow() expects a boolean parameter'); + } + options.focusOnShow = focusOnShow; + return picker; + }; + picker.inline = function (inline) { + if (arguments.length === 0) { + return options.inline; + } + if (typeof inline !== 'boolean') { + throw new TypeError('inline() expects a boolean parameter'); + } + options.inline = inline; + return picker; + }; + picker.clear = function () { + clear(); + return picker; + }; + picker.keyBinds = function (keyBinds) { + if (arguments.length === 0) { + return options.keyBinds; + } + options.keyBinds = keyBinds; + return picker; + }; + picker.getMoment = function (d) { + return getMoment(d); + }; + picker.debug = function (debug) { + if (typeof debug !== 'boolean') { + throw new TypeError('debug() expects a boolean parameter'); + } + options.debug = debug; + return picker; + }; + picker.allowInputToggle = function (allowInputToggle) { + if (arguments.length === 0) { + return options.allowInputToggle; + } + if (typeof allowInputToggle !== 'boolean') { + throw new TypeError('allowInputToggle() expects a boolean parameter'); + } + options.allowInputToggle = allowInputToggle; + return picker; + }; + picker.showClose = function (showClose) { + if (arguments.length === 0) { + return options.showClose; + } + if (typeof showClose !== 'boolean') { + throw new TypeError('showClose() expects a boolean parameter'); + } + options.showClose = showClose; + return picker; + }; + picker.keepInvalid = function (keepInvalid) { + if (arguments.length === 0) { + return options.keepInvalid; + } + if (typeof keepInvalid !== 'boolean') { + throw new TypeError('keepInvalid() expects a boolean parameter'); + } + options.keepInvalid = keepInvalid; + return picker; + }; + picker.datepickerInput = function (datepickerInput) { + if (arguments.length === 0) { + return options.datepickerInput; + } + if (typeof datepickerInput !== 'string') { + throw new TypeError('datepickerInput() expects a string parameter'); + } + options.datepickerInput = datepickerInput; + return picker; + }; + picker.parseInputDate = function (parseInputDate) { + if (arguments.length === 0) { + return options.parseInputDate; + } + if (typeof parseInputDate !== 'function') { + throw new TypeError('parseInputDate() sholud be as function'); + } + options.parseInputDate = parseInputDate; + return picker; + }; + picker.disabledTimeIntervals = function (disabledTimeIntervals) { + // / + // /Returns an array with the currently set disabled dates on the component. + // /options.disabledTimeIntervals + // / + // / + // /Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of + // /options.enabledDates if such exist. + // /Takes an [ string or Date or moment ] of values and allows the user to select only from those days. + // / + if (arguments.length === 0) { + return options.disabledTimeIntervals ? $.extend({}, options.disabledTimeIntervals) : options.disabledTimeIntervals; + } + if (!disabledTimeIntervals) { + options.disabledTimeIntervals = false; + update(); + return picker; + } + if (!(disabledTimeIntervals instanceof Array)) { + throw new TypeError('disabledTimeIntervals() expects an array parameter'); + } + options.disabledTimeIntervals = disabledTimeIntervals; + update(); + return picker; + }; + picker.disabledHours = function (hours) { + // / + // /Returns an array with the currently set disabled hours on the component. + // /options.disabledHours + // / + // / + // /Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of + // /options.enabledHours if such exist. + // /Takes an [ int ] of values and disallows the user to select only from those hours. + // / + if (arguments.length === 0) { + return options.disabledHours ? $.extend({}, options.disabledHours) : options.disabledHours; + } + if (!hours) { + options.disabledHours = false; + update(); + return picker; + } + if (!(hours instanceof Array)) { + throw new TypeError('disabledHours() expects an array parameter'); + } + options.disabledHours = indexGivenHours(hours); + options.enabledHours = false; + if (options.useCurrent && !options.keepInvalid) { + var tries = 0; + while (!isValid(date, 'h')) { + date.add(1, 'h'); + if (tries === 24) { + throw new Error('Tried 24 times to find a valid date'); + } + tries++; + } + setValue(date); + } + update(); + return picker; + }; + picker.enabledHours = function (hours) { + // / + // /Returns an array with the currently set enabled hours on the component. + // /options.enabledHours + // / + // / + // /Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of options.disabledHours if such exist. + // /Takes an [ int ] of values and allows the user to select only from those hours. + // / + if (arguments.length === 0) { + return options.enabledHours ? $.extend({}, options.enabledHours) : options.enabledHours; + } + if (!hours) { + options.enabledHours = false; + update(); + return picker; + } + if (!(hours instanceof Array)) { + throw new TypeError('enabledHours() expects an array parameter'); + } + options.enabledHours = indexGivenHours(hours); + options.disabledHours = false; + if (options.useCurrent && !options.keepInvalid) { + var tries = 0; + while (!isValid(date, 'h')) { + date.add(1, 'h'); + if (tries === 24) { + throw new Error('Tried 24 times to find a valid date'); + } + tries++; + } + setValue(date); + } + update(); + return picker; + }; + /** + * Returns the component's model current viewDate, a moment object or null if not set. Passing a null value unsets the components model current moment. Parsing of the newDate parameter is made using moment library with the options.format and options.useStrict components configuration. + * @param {Takes string, viewDate, moment, null parameter.} newDate + * @returns {viewDate.clone()} + */ + picker.viewDate = function (newDate) { + if (arguments.length === 0) { + return viewDate.clone(); + } + if (!newDate) { + viewDate = date.clone(); + return picker; + } + if (typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) { + throw new TypeError('viewDate() parameter must be one of [string, moment or Date]'); + } + viewDate = parseInputDate(newDate); + viewUpdate(); + return picker; + }; + + // initializing element and component attributes + if (element.is('input')) { + input = element; + } else { + input = element.find(options.datepickerInput); + if (input.length === 0) { + input = element.find('input'); + } else if (!input.is('input')) { + throw new Error('CSS class "' + options.datepickerInput + '" cannot be applied to non input element'); + } + } + if (element.hasClass('input-group')) { + // in case there is more then one 'input-group-addon' Issue #48 + if (element.find('.datepickerbutton').length === 0) { + component = element.find('.input-group-addon'); + } else { + component = element.find('.datepickerbutton'); + } + } + if (!options.inline && !input.is('input')) { + throw new Error('Could not initialize DateTimePicker without an input element'); + } + + // Set defaults for date here now instead of in var declaration + date = getMoment(); + viewDate = date.clone(); + $.extend(true, options, dataToOptions()); + picker.options(options); + initFormatting(); + attachDatePickerElementEvents(); + if (input.prop('disabled')) { + picker.disable(); + } + if (input.is('input') && input.val().trim().length !== 0) { + setValue(parseInputDate(input.val().trim())); + } else if (options.defaultDate && input.attr('placeholder') === undefined) { + setValue(options.defaultDate); + } + if (options.inline) { + show(); + } + return picker; + }; + + /** ****************************************************************************** + * + * jQuery plugin constructor and defaults object + * + ********************************************************************************/ + + /** + * See (http://jquery.com/). + * @name jQuery + * @class + * See the jQuery Library (http://jquery.com/) for full details. This just + * documents the function and classes that are added to jQuery by this plug-in. + */ + /** + * See (http://jquery.com/) + * @name fn + * @class + * See the jQuery Library (http://jquery.com/) for full details. This just + * documents the function and classes that are added to jQuery by this plug-in. + * @memberOf jQuery + */ + /** + * Show comments + * @class datetimepicker + * @memberOf jQuery.fn + */ + $.fn.datetimepicker = function (options) { + options = options || {}; + var args = Array.prototype.slice.call(arguments, 1); + var isInstance = true; + var thisMethods = ['destroy', 'hide', 'show', 'toggle']; + var returnValue; + if (_typeof(options) === 'object') { + return this.each(function () { + var $this = $(this); + var _options; + if (!$this.data('DateTimePicker')) { + // create a private copy of the defaults object + _options = $.extend(true, {}, $.fn.datetimepicker.defaults, options); + $this.data('DateTimePicker', dateTimePicker($this, _options)); + } + }); + } else if (typeof options === 'string') { + this.each(function () { + var $this = $(this); + var instance = $this.data('DateTimePicker'); + if (!instance) { + throw new Error('bootstrap-datetimepicker("' + options + '") method was called on an element that is not using DateTimePicker'); + } + returnValue = instance[options].apply(instance, args); + isInstance = returnValue === instance; + }); + if (isInstance || $.inArray(options, thisMethods) > -1) { + return this; + } + return returnValue; + } + throw new TypeError('Invalid arguments for DateTimePicker: ' + options); + }; + $.fn.datetimepicker.defaults = { + timeZone: '', + format: false, + dayViewHeaderFormat: 'MMMM YYYY', + extraFormats: false, + stepping: 1, + minDate: false, + maxDate: false, + useCurrent: true, + collapse: true, + locale: moment.locale(), + defaultDate: false, + disabledDates: false, + enabledDates: false, + icons: { + time: 'glyphicon glyphicon-time', + date: 'glyphicon glyphicon-calendar', + up: 'glyphicon glyphicon-chevron-up', + down: 'glyphicon glyphicon-chevron-down', + previous: 'glyphicon glyphicon-chevron-left', + next: 'glyphicon glyphicon-chevron-right', + today: 'glyphicon glyphicon-screenshot', + clear: 'glyphicon glyphicon-trash', + close: 'glyphicon glyphicon-remove' + }, + tooltips: { + today: 'Go to today', + clear: 'Clear selection', + close: 'Close the picker', + selectMonth: 'Select Month', + prevMonth: 'Previous Month', + nextMonth: 'Next Month', + selectYear: 'Select Year', + prevYear: 'Previous Year', + nextYear: 'Next Year', + selectDecade: 'Select Decade', + prevDecade: 'Previous Decade', + nextDecade: 'Next Decade', + prevCentury: 'Previous Century', + nextCentury: 'Next Century', + pickHour: 'Pick Hour', + incrementHour: 'Increment Hour', + decrementHour: 'Decrement Hour', + pickMinute: 'Pick Minute', + incrementMinute: 'Increment Minute', + decrementMinute: 'Decrement Minute', + pickSecond: 'Pick Second', + incrementSecond: 'Increment Second', + decrementSecond: 'Decrement Second', + togglePeriod: 'Toggle Period', + selectTime: 'Select Time' + }, + useStrict: false, + sideBySide: false, + daysOfWeekDisabled: false, + calendarWeeks: false, + viewMode: 'days', + toolbarPlacement: 'default', + showTodayButton: false, + showClear: false, + showClose: false, + widgetPositioning: { + horizontal: 'auto', + vertical: 'auto' + }, + widgetParent: null, + ignoreReadonly: false, + keepOpen: false, + focusOnShow: true, + inline: false, + keepInvalid: false, + datepickerInput: '.datepickerinput', + keyBinds: { + up: function up(widget) { + if (!widget) { + return; + } + var d = this.date() || this.getMoment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().subtract(7, 'd')); + } else { + this.date(d.clone().add(this.stepping(), 'm')); + } + }, + down: function down(widget) { + if (!widget) { + this.show(); + return; + } + var d = this.date() || this.getMoment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().add(7, 'd')); + } else { + this.date(d.clone().subtract(this.stepping(), 'm')); + } + }, + 'control up': function control_up(widget) { + if (!widget) { + return; + } + var d = this.date() || this.getMoment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().subtract(1, 'y')); + } else { + this.date(d.clone().add(1, 'h')); + } + }, + 'control down': function control_down(widget) { + if (!widget) { + return; + } + var d = this.date() || this.getMoment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().add(1, 'y')); + } else { + this.date(d.clone().subtract(1, 'h')); + } + }, + left: function left(widget) { + if (!widget) { + return; + } + var d = this.date() || this.getMoment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().subtract(1, 'd')); + } + }, + right: function right(widget) { + if (!widget) { + return; + } + var d = this.date() || this.getMoment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().add(1, 'd')); + } + }, + pageUp: function pageUp(widget) { + if (!widget) { + return; + } + var d = this.date() || this.getMoment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().subtract(1, 'M')); + } + }, + pageDown: function pageDown(widget) { + if (!widget) { + return; + } + var d = this.date() || this.getMoment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().add(1, 'M')); + } + }, + enter: function enter() { + this.hide(); + }, + escape: function escape() { + this.hide(); + }, + // tab: function (widget) { //this break the flow of the form. disabling for now + // var toggle = widget.find('.picker-switch a[data-action="togglePicker"]'); + // if(toggle.length > 0) toggle.click(); + // }, + 'control space': function control_space(widget) { + if (!widget) { + return; + } + if (widget.find('.timepicker').is(':visible')) { + widget.find('.btn[data-action="togglePeriod"]').click(); + } + }, + t: function t() { + this.date(this.getMoment()); + }, + 'delete': function _delete() { + this.clear(); + } + }, + debug: false, + allowInputToggle: false, + disabledTimeIntervals: false, + disabledHours: false, + enabledHours: false, + viewDate: false + }; + return $.fn.datetimepicker; +}); + +/***/ }), + +/***/ 75139: +/***/ ((module) => { + +"use strict"; +module.exports = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC"; + +/***/ }), + /***/ 95851: /***/ (() => { @@ -14490,7 +14644,7 @@ external_jQuery_default()(__webpack_require__.g).on('sidebar_state._grav', funct /******/ // startup /******/ // Load entry module and return exports /******/ // This entry module depends on other loaded chunks and execution need to be delayed -/******/ var __webpack_exports__ = __webpack_require__.O(undefined, [121], () => (__webpack_require__(75322))) +/******/ var __webpack_exports__ = __webpack_require__.O(undefined, [121], () => (__webpack_require__(36736))) /******/ __webpack_exports__ = __webpack_require__.O(__webpack_exports__); /******/ Grav = __webpack_exports__; /******/ diff --git a/themes/grav/templates/partials/tools-restore-grav.html.twig b/themes/grav/templates/partials/tools-restore-grav.html.twig index 79d1b88a..d3cf7380 100644 --- a/themes/grav/templates/partials/tools-restore-grav.html.twig +++ b/themes/grav/templates/partials/tools-restore-grav.html.twig @@ -1,6 +1,9 @@ {% do assets.addInlineCss(' \ .restore-grav-content {\n padding-bottom: 2rem;\n}\n\n.restore-grav-intro {\n margin-left: 1.5rem;\n margin-bottom: 1.5rem;\n}\n\n.restore-grav-table {\n margin-bottom: 1.5rem;\n}\n\n.restore-grav-table code {\n white-space: nowrap;\n}\n\n.restore-grav-content .button-bar {\n margin-bottom: 1rem;\n}\n') %} +{% do assets.addInlineCss(' \ +.restore-grav-content {\n padding-bottom: 2rem;\n}\n\n.restore-grav-intro {\n margin-left: 1.5rem;\n margin-bottom: 1.5rem;\n}\n\n.restore-grav-table {\n margin-bottom: 1.5rem;\n}\n\n.restore-grav-table code {\n white-space: nowrap;\n}\n\n.restore-grav-content .button-bar {\n margin-bottom: 1rem;\n}\n') %} +

{{ "PLUGIN_ADMIN.RESTORE_GRAV"|t }}

@@ -44,12 +47,7 @@ {% endif %}
{% endfor %}
').addClass('cw').text('#')); + } + while (currentDate.isBefore(viewDate.clone().endOf('w'))) { + row.append($('').addClass('dow').text(currentDate.format('dd'))); + currentDate.add(1, 'd'); + } + widget.find('.datepicker-days thead').append(row); + }; + var isInDisabledDates = function isInDisabledDates(testDate) { + return options.disabledDates[testDate.format('YYYY-MM-DD')] === true; + }; + var isInEnabledDates = function isInEnabledDates(testDate) { + return options.enabledDates[testDate.format('YYYY-MM-DD')] === true; + }; + var isInDisabledHours = function isInDisabledHours(testDate) { + return options.disabledHours[testDate.format('H')] === true; + }; + var isInEnabledHours = function isInEnabledHours(testDate) { + return options.enabledHours[testDate.format('H')] === true; + }; + var isValid = function isValid(targetMoment, granularity) { + if (!targetMoment.isValid()) { + return false; + } + if (options.disabledDates && granularity === 'd' && isInDisabledDates(targetMoment)) { + return false; + } + if (options.enabledDates && granularity === 'd' && !isInEnabledDates(targetMoment)) { + return false; + } + if (options.minDate && targetMoment.isBefore(options.minDate, granularity)) { + return false; + } + if (options.maxDate && targetMoment.isAfter(options.maxDate, granularity)) { + return false; + } + if (options.daysOfWeekDisabled && granularity === 'd' && options.daysOfWeekDisabled.indexOf(targetMoment.day()) !== -1) { + return false; + } + if (options.disabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && isInDisabledHours(targetMoment)) { + return false; + } + if (options.enabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && !isInEnabledHours(targetMoment)) { + return false; + } + if (options.disabledTimeIntervals && (granularity === 'h' || granularity === 'm' || granularity === 's')) { + var found = false; + $.each(options.disabledTimeIntervals, function () { + if (targetMoment.isBetween(this[0], this[1])) { + found = true; + return false; + } + }); + if (found) { + return false; + } + } + return true; + }; + var fillMonths = function fillMonths() { + var spans = []; + var monthsShort = viewDate.clone().startOf('y').startOf('d'); + while (monthsShort.isSame(viewDate, 'y')) { + spans.push($('').attr('data-action', 'selectMonth').addClass('month').text(monthsShort.format('MMM'))); + monthsShort.add(1, 'M'); + } + widget.find('.datepicker-months td').empty().append(spans); + }; + var updateMonths = function updateMonths() { + var monthsView = widget.find('.datepicker-months'); + var monthsViewHeader = monthsView.find('th'); + var months = monthsView.find('tbody').find('span'); + monthsViewHeader.eq(0).find('span').attr('title', options.tooltips.prevYear); + monthsViewHeader.eq(1).attr('title', options.tooltips.selectYear); + monthsViewHeader.eq(2).find('span').attr('title', options.tooltips.nextYear); + monthsView.find('.disabled').removeClass('disabled'); + if (!isValid(viewDate.clone().subtract(1, 'y'), 'y')) { + monthsViewHeader.eq(0).addClass('disabled'); + } + monthsViewHeader.eq(1).text(viewDate.year()); + if (!isValid(viewDate.clone().add(1, 'y'), 'y')) { + monthsViewHeader.eq(2).addClass('disabled'); + } + months.removeClass('active'); + if (date.isSame(viewDate, 'y') && !unset) { + months.eq(date.month()).addClass('active'); + } + months.each(function (index) { + if (!isValid(viewDate.clone().month(index), 'M')) { + $(this).addClass('disabled'); + } + }); + }; + var updateYears = function updateYears() { + var yearsView = widget.find('.datepicker-years'); + var yearsViewHeader = yearsView.find('th'); + var startYear = viewDate.clone().subtract(5, 'y'); + var endYear = viewDate.clone().add(6, 'y'); + var html = ''; + yearsViewHeader.eq(0).find('span').attr('title', options.tooltips.prevDecade); + yearsViewHeader.eq(1).attr('title', options.tooltips.selectDecade); + yearsViewHeader.eq(2).find('span').attr('title', options.tooltips.nextDecade); + yearsView.find('.disabled').removeClass('disabled'); + if (options.minDate && options.minDate.isAfter(startYear, 'y')) { + yearsViewHeader.eq(0).addClass('disabled'); + } + yearsViewHeader.eq(1).text(startYear.year() + '-' + endYear.year()); + if (options.maxDate && options.maxDate.isBefore(endYear, 'y')) { + yearsViewHeader.eq(2).addClass('disabled'); + } + while (!startYear.isAfter(endYear, 'y')) { + html += '' + startYear.year() + ''; + startYear.add(1, 'y'); + } + yearsView.find('td').html(html); + }; + var updateDecades = function updateDecades() { + var decadesView = widget.find('.datepicker-decades'); + var decadesViewHeader = decadesView.find('th'); + var startDecade = moment({ + y: viewDate.year() - viewDate.year() % 100 - 1 + }); + var endDecade = startDecade.clone().add(100, 'y'); + var startedAt = startDecade.clone(); + var minDateDecade = false; + var maxDateDecade = false; + var endDecadeYear; + var html = ''; + decadesViewHeader.eq(0).find('span').attr('title', options.tooltips.prevCentury); + decadesViewHeader.eq(2).find('span').attr('title', options.tooltips.nextCentury); + decadesView.find('.disabled').removeClass('disabled'); + if (startDecade.isSame(moment({ + y: 1900 + })) || options.minDate && options.minDate.isAfter(startDecade, 'y')) { + decadesViewHeader.eq(0).addClass('disabled'); + } + decadesViewHeader.eq(1).text(startDecade.year() + '-' + endDecade.year()); + if (startDecade.isSame(moment({ + y: 2000 + })) || options.maxDate && options.maxDate.isBefore(endDecade, 'y')) { + decadesViewHeader.eq(2).addClass('disabled'); + } + while (!startDecade.isAfter(endDecade, 'y')) { + endDecadeYear = startDecade.year() + 12; + minDateDecade = options.minDate && options.minDate.isAfter(startDecade, 'y') && options.minDate.year() <= endDecadeYear; + maxDateDecade = options.maxDate && options.maxDate.isAfter(startDecade, 'y') && options.maxDate.year() <= endDecadeYear; + html += '' + (startDecade.year() + 1) + ' - ' + (startDecade.year() + 12) + ''; + startDecade.add(12, 'y'); + } + html += ''; // push the dangling block over, at least this way it's even + + decadesView.find('td').html(html); + decadesViewHeader.eq(1).text(startedAt.year() + 1 + '-' + startDecade.year()); + }; + var fillDate = function fillDate() { + var daysView = widget.find('.datepicker-days'); + var daysViewHeader = daysView.find('th'); + var currentDate; + var html = []; + var row; + var clsNames = []; + var i; + if (!hasDate()) { + return; + } + daysViewHeader.eq(0).find('span').attr('title', options.tooltips.prevMonth); + daysViewHeader.eq(1).attr('title', options.tooltips.selectMonth); + daysViewHeader.eq(2).find('span').attr('title', options.tooltips.nextMonth); + daysView.find('.disabled').removeClass('disabled'); + daysViewHeader.eq(1).text(viewDate.format(options.dayViewHeaderFormat)); + if (!isValid(viewDate.clone().subtract(1, 'M'), 'M')) { + daysViewHeader.eq(0).addClass('disabled'); + } + if (!isValid(viewDate.clone().add(1, 'M'), 'M')) { + daysViewHeader.eq(2).addClass('disabled'); + } + currentDate = viewDate.clone().startOf('M').startOf('w').startOf('d'); + for (i = 0; i < 42; i++) { + // always display 42 days (should show 6 weeks) + if (currentDate.weekday() === 0) { + row = $('
' + currentDate.week() + '' + currentDate.date() + '
' + currentHour.format(use24Hours ? 'HH' : 'hh') + '
' + currentMinute.format('mm') + '
' + currentSecond.format('ss') + ' -
- - - {{ nonce_field('admin-form', 'admin-nonce')|raw }} - -
+