Файловый менеджер - Редактировать - /var/www/html/mediawiki.widgets.datetime.zip
Ðазад
PK ! w�3��H �H DateTimeFormatter.jsnu �Iw�� ( function () { /** * @class * @classdesc Provides various methods needed for formatting dates and times. * @abstract * @mixes OO.EventEmitter * * @constructor * @description Create an instance of `mw.widgets.datetime.DateTimeFormatter`. * @param {Object} [config] Configuration options * @param {string} [config.format='@default'] May be a key from the * {@link mw.widgets.datetime.DateTimeFormatter.formats}, or a format * specification as defined by {@link mw.widgets.datetime.DateTimeFormatter#parseFieldSpec} * and {@link mw.widgets.datetime.DateTimeFormatter#getFieldForTag}. * @param {boolean} [config.local=false] Whether dates are local time or UTC * @param {string[]} [config.fullZones] Time zone indicators. Array of 2 strings, for * UTC and local time. * @param {string[]} [config.shortZones] Abbreviated time zone indicators. Array of 2 * strings, for UTC and local time. * @param {Date} [config.defaultDate] Default date, for filling unspecified components. * Defaults to the current date and time (with 0 milliseconds). */ mw.widgets.datetime.DateTimeFormatter = function MwWidgetsDatetimeDateTimeFormatter( config ) { this.constructor.static.setupDefaults(); config = Object.assign( { format: '@default', local: false, fullZones: this.constructor.static.fullZones, shortZones: this.constructor.static.shortZones }, config ); // Mixin constructors OO.EventEmitter.call( this ); // Properties if ( this.constructor.static.formats[ config.format ] ) { this.format = this.constructor.static.formats[ config.format ]; } else { this.format = config.format; } this.local = !!config.local; this.fullZones = config.fullZones; this.shortZones = config.shortZones; if ( config.defaultDate instanceof Date ) { this.defaultDate = config.defaultDate; } else { this.defaultDate = new Date(); if ( this.local ) { this.defaultDate.setMilliseconds( 0 ); } else { this.defaultDate.setUTCMilliseconds( 0 ); } } }; /* Setup */ OO.initClass( mw.widgets.datetime.DateTimeFormatter ); OO.mixinClass( mw.widgets.datetime.DateTimeFormatter, OO.EventEmitter ); /* Static */ /** * Default format specifications. See the {@link #format format} parameter. * * @static * @inheritable * @type {Object} * @name mw.widgets.datetime.DateTimeFormatter.formats */ mw.widgets.datetime.DateTimeFormatter.static.formats = {}; /** * Default time zone indicators. * * @static * @inheritable * @type {string[]} * @name mw.widgets.datetime.DateTimeFormatter.fullZones */ mw.widgets.datetime.DateTimeFormatter.static.fullZones = null; /** * Default abbreviated time zone indicators. * * @static * @inheritable * @type {string[]} * @name mw.widgets.datetime.DateTimeFormatter.shortZones */ mw.widgets.datetime.DateTimeFormatter.static.shortZones = null; mw.widgets.datetime.DateTimeFormatter.static.setupDefaults = function () { if ( !this.fullZones ) { this.fullZones = [ mw.msg( 'timezone-utc' ), mw.msg( 'timezone-local' ) ]; } if ( !this.shortZones ) { this.shortZones = [ 'Z', this.fullZones[ 1 ].slice( 0, 1 ).toUpperCase() ]; if ( this.shortZones[ 1 ] === 'Z' ) { this.shortZones[ 1 ] = 'L'; } } }; /* Events */ /** * A `local` event is emitted when the 'local' flag is changed. * * @event mw.widgets.datetime.DateTimeFormatter.local * @param {boolean} local Whether dates are local time */ /* Methods */ /** * Whether dates are in local time or UTC. * * @return {boolean} True if local time */ mw.widgets.datetime.DateTimeFormatter.prototype.getLocal = function () { return this.local; }; /** * Toggle whether dates are in local time or UTC. * * @param {boolean} [flag] Set the flag instead of toggling it * @fires mw.widgets.datetime.DateTimeFormatter.local * @chainable * @return {mw.widgets.datetime.DateTimeFormatter} */ mw.widgets.datetime.DateTimeFormatter.prototype.toggleLocal = function ( flag ) { if ( flag === undefined ) { flag = !this.local; } else { flag = !!flag; } if ( this.local !== flag ) { this.local = flag; this.emit( 'local', this.local ); } return this; }; /** * Get the default date. * * @return {Date} */ mw.widgets.datetime.DateTimeFormatter.prototype.getDefaultDate = function () { return new Date( this.defaultDate.getTime() ); }; /** * Fetch the field specification array for this object. * * See {@link #parseFieldSpec parseFieldSpec} for details on the return value structure. * * @return {Array} */ mw.widgets.datetime.DateTimeFormatter.prototype.getFieldSpec = function () { return this.parseFieldSpec( this.format ); }; /** * Parse a format string into a field specification. * * The input is a string containing tags formatted as ${tag|param|param...} * (for editable fields) and $!{tag|param|param...} (for non-editable fields). * Most tags are defined by {@link #getFieldForTag getFieldForTag}, but a few * are defined here: * - ${intercalary|X|text}: Text that is only displayed when the 'intercalary' * component is X. * - ${not-intercalary|X|text}: Text that is displayed unless the 'intercalary' * component is X. * * Elements of the returned array are strings or objects. Strings are meant to * be displayed as-is. Objects are as returned by {@link #getFieldForTag getFieldForTag}. * * @protected * @param {string} format * @return {Array} */ mw.widgets.datetime.DateTimeFormatter.prototype.parseFieldSpec = function ( format ) { let m, last, tag, params, spec; const ret = [], re = /(.*?)(\$(!?)\{([^}]+)\})/g; last = 0; while ( ( m = re.exec( format ) ) !== null ) { last = re.lastIndex; if ( m[ 1 ] !== '' ) { ret.push( m[ 1 ] ); } params = m[ 4 ].split( '|' ); tag = params.shift(); spec = this.getFieldForTag( tag, params ); if ( spec ) { if ( m[ 3 ] === '!' ) { spec.editable = false; } ret.push( spec ); } else { ret.push( m[ 2 ] ); } } if ( last < format.length ) { ret.push( format.slice( last ) ); } return ret; }; /** * @typedef {Object} mw.widgets.datetime.DateTimeFormatter~FieldSpecificationObject * @property {string|null} component Date component corresponding to this field, if any. * @property {boolean} editable Whether this field is editable. * @property {string} type What kind of field this is: * - 'static': The field is a static string; component will be null. * - 'number': The field is generally numeric. * - 'string': The field is generally textual. * - 'boolean': The field is a boolean. * - 'toggleLocal': The field represents {@link #getLocal this.getLocal()}. * Editing should directly call {@link #toggleLocal this.toggleLocal()}. * @property {boolean} calendarComponent Whether this field is part of a calendar, e.g. * part of the date instead of the time. * @property {number} size Maximum number of characters in the field (when * the 'intercalary' component is falsey). If 0, the field should be hidden entirely. * @property {Object.<string,number>} intercalarySize Map from * 'intercalary' component values to overridden sizes. * @property {string} value For type='static', the string to display. * @property {function(Mixed): string} formatValue A function to format a * component value as a display string. * @property {function(string): Mixed} parseValue A function to parse a * display string into a component value. If parsing fails, returns undefined. */ /** * Turn a tag into a field specification object. * * Fields implemented here are: * - ${intercalary|X|text}: Text that is only displayed when the 'intercalary' * component is X. * - ${not-intercalary|X|text}: Text that is displayed unless the 'intercalary' * component is X. * - ${zone|#}: Timezone offset, "+0000" format. * - ${zone|:}: Timezone offset, "+00:00" format. * - ${zone|short}: Timezone from 'shortZones' configuration setting. * - ${zone|full}: Timezone from 'fullZones' configuration setting. * * @protected * @abstract * @param {string} tag * @param {string[]} params * @return {FieldSpecificationObject} Field specification object, or null if the tag+params are unrecognized. */ mw.widgets.datetime.DateTimeFormatter.prototype.getFieldForTag = function ( tag, params ) { let c, spec = null; switch ( tag ) { case 'intercalary': case 'not-intercalary': if ( params.length < 2 || !params[ 0 ] ) { return null; } spec = { component: null, calendarComponent: false, editable: false, type: 'static', value: params.slice( 1 ).join( '|' ), size: 0, intercalarySize: {} }; if ( tag === 'intercalary' ) { spec.intercalarySize[ params[ 0 ] ] = spec.value.length; } else { spec.size = spec.value.length; spec.intercalarySize[ params[ 0 ] ] = 0; } return spec; case 'zone': switch ( params[ 0 ] ) { case '#': case ':': c = params[ 0 ] === '#' ? '' : ':'; return { component: 'zone', calendarComponent: false, editable: true, type: 'toggleLocal', size: 5 + c.length, formatValue: function ( v ) { let o, r; if ( v ) { o = new Date().getTimezoneOffset(); r = String( Math.abs( o ) % 60 ); while ( r.length < 2 ) { r = '0' + r; } r = String( Math.floor( Math.abs( o ) / 60 ) ) + c + r; while ( r.length < 4 + c.length ) { r = '0' + r; } return ( o <= 0 ? '+' : '−' ) + r; } else { return '+00' + c + '00'; } }, parseValue: function ( v ) { let m; v = String( v ).trim(); if ( ( m = /^([+-−])([0-9]{1,2}):?([0-9]{2})$/.test( v ) ) ) { return ( m[ 2 ] * 60 + m[ 3 ] ) * ( m[ 1 ] === '+' ? -1 : 1 ); } else { return undefined; } } }; case 'short': case 'full': spec = { component: 'zone', calendarComponent: false, editable: true, type: 'toggleLocal', values: params[ 0 ] === 'short' ? this.shortZones : this.fullZones, formatValue: this.formatSpecValue, parseValue: this.parseSpecValue }; spec.size = Math.max.apply( // eslint-disable-next-line no-jquery/no-map-util null, $.map( spec.values, ( v ) => v.length ) ); return spec; } return null; default: return null; } }; /** * Format a value for a field specification. * * 'this' must be the field specification object. The intention is that you * could just assign this function as the 'formatValue' for each field spec. * * Besides the publicly-documented fields, uses the following: * - values: Enumerated values for the field * - zeropad: Whether to pad the number with zeros. * * @protected * @param {any} v * @return {string} */ mw.widgets.datetime.DateTimeFormatter.prototype.formatSpecValue = function ( v ) { if ( v === undefined || v === null ) { return ''; } if ( typeof v === 'boolean' || this.type === 'toggleLocal' ) { v = v ? 1 : 0; } if ( this.values ) { return this.values[ v ]; } v = String( v ); if ( this.zeropad ) { while ( v.length < this.size ) { v = '0' + v; } } return v; }; /** * Parse a value for a field specification. * * 'this' must be the field specification object. The intention is that you * could just assign this function as the 'parseValue' for each field spec. * * Besides the publicly-documented fields, uses the following: * - values: Enumerated values for the field * * @protected * @param {string} v * @return {number|string|null} */ mw.widgets.datetime.DateTimeFormatter.prototype.parseSpecValue = function ( v ) { let k; if ( v === '' ) { return null; } if ( !this.values ) { v = +v; if ( this.type === 'boolean' || this.type === 'toggleLocal' ) { return isNaN( v ) ? undefined : !!v; } else { return isNaN( v ) ? undefined : v; } } if ( v.normalize ) { v = v.normalize(); } const re = new RegExp( '^\\s*' + mw.util.escapeRegExp( v ), 'i' ); for ( k in this.values ) { k = +k; if ( !isNaN( k ) && re.test( this.values[ k ] ) ) { if ( this.type === 'boolean' || this.type === 'toggleLocal' ) { return !!k; } else { return k; } } } return undefined; }; /** * Get components from a Date object. * * Most specific components are defined by the subclass. "Global" components * are: * - intercalary: {string} Non-falsey values are used to indicate intercalary days. * - zone: {number} Timezone offset in minutes. * * @abstract * @param {Date|null} date * @return {Object} Components */ mw.widgets.datetime.DateTimeFormatter.prototype.getComponentsFromDate = function ( date ) { // Should be overridden by subclass return { zone: this.local ? date.getTimezoneOffset() : 0 }; }; /** * Get a Date object from components. * * @param {Object} components Date components * @return {Date} */ mw.widgets.datetime.DateTimeFormatter.prototype.getDateFromComponents = function ( /* components */ ) { // Should be overridden by subclass return new Date(); }; /** * Adjust a date. * * @param {Date|null} date To be adjusted * @param {string} component To adjust * @param {number} delta Adjustment amount * @param {string} mode Adjustment mode: * - 'overflow': "Jan 32" => "Feb 1", "Jan 33" => "Feb 2", "Feb 0" => "Jan 31", etc. * - 'wrap': "Jan 32" => "Jan 1", "Jan 33" => "Jan 2", "Jan 0" => "Jan 31", etc. * - 'clip': "Jan 32" => "Jan 31", "Feb 32" => "Feb 28" (or 29), "Feb 0" => "Feb 1", etc. * @return {Date} Adjusted date */ mw.widgets.datetime.DateTimeFormatter.prototype.adjustComponent = function ( date /* , component, delta, mode */ ) { // Should be overridden by subclass return date; }; /** * Get the column headings (weekday abbreviations) for a calendar grid. * * Null-valued columns are hidden if getCalendarData() returns no "day" object * for all days in that column. * * @abstract * @return {Array} string or null */ mw.widgets.datetime.DateTimeFormatter.prototype.getCalendarHeadings = function () { // Should be overridden by subclass return []; }; /** * Test whether two dates are in the same calendar grid. * * @abstract * @param {Date} date1 * @param {Date} date2 * @return {boolean} */ mw.widgets.datetime.DateTimeFormatter.prototype.sameCalendarGrid = function ( date1, date2 ) { // Should be overridden by subclass return date1.getTime() === date2.getTime(); }; /** * Test whether the date parts of two Dates are equal. * * @param {Date} date1 * @param {Date} date2 * @return {boolean} */ mw.widgets.datetime.DateTimeFormatter.prototype.datePartIsEqual = function ( date1, date2 ) { if ( this.local ) { return ( date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate() ); } else { return ( date1.getUTCFullYear() === date2.getUTCFullYear() && date1.getUTCMonth() === date2.getUTCMonth() && date1.getUTCDate() === date2.getUTCDate() ); } }; /** * Test whether the time parts of two Dates are equal. * * @param {Date} date1 * @param {Date} date2 * @return {boolean} */ mw.widgets.datetime.DateTimeFormatter.prototype.timePartIsEqual = function ( date1, date2 ) { if ( this.local ) { return ( date1.getHours() === date2.getHours() && date1.getMinutes() === date2.getMinutes() && date1.getSeconds() === date2.getSeconds() && date1.getMilliseconds() === date2.getMilliseconds() ); } else { return ( date1.getUTCHours() === date2.getUTCHours() && date1.getUTCMinutes() === date2.getUTCMinutes() && date1.getUTCSeconds() === date2.getUTCSeconds() && date1.getUTCMilliseconds() === date2.getUTCMilliseconds() ); } }; /** * Test whether toggleLocal() changes the date part. * * @param {Date} date * @return {boolean} */ mw.widgets.datetime.DateTimeFormatter.prototype.localChangesDatePart = function ( date ) { return ( date.getUTCFullYear() !== date.getFullYear() || date.getUTCMonth() !== date.getMonth() || date.getUTCDate() !== date.getDate() ); }; /** * Create a new Date by merging the date part from one with the time part from * another. * * @param {Date} datepart * @param {Date} timepart * @return {Date} */ mw.widgets.datetime.DateTimeFormatter.prototype.mergeDateAndTime = function ( datepart, timepart ) { const ret = new Date( datepart.getTime() ); if ( this.local ) { ret.setHours( timepart.getHours(), timepart.getMinutes(), timepart.getSeconds(), timepart.getMilliseconds() ); } else { ret.setUTCHours( timepart.getUTCHours(), timepart.getUTCMinutes(), timepart.getUTCSeconds(), timepart.getUTCMilliseconds() ); } return ret; }; /** * @typedef {Object} mw.widgets.datetime.DateTimeFormatter~CalendarGridData * @property {string} header String to display as the calendar header * @property {string} monthComponent Component to adjust by ±1 to change months. * @property {string} dayComponent Component to adjust by ±1 to change days. * @property {string} [weekComponent] Component to adjust by ±1 to change * weeks. If omitted, the dayComponent should be adjusted by ±the number of * non-nullable columns returned by this.getCalendarHeadings() to change weeks. * @property {Array} rows Array of arrays of "day" objects or null/undefined. */ /** * Get data for a calendar grid. * * A "day" object is: * - display: {string} Display text for the day. * - date: {Date} Date to use when the day is selected. * - extra: {string|null} 'prev' or 'next' on days used to fill out the weeks * at the start and end of the month. * * In any one result object, 'extra' + 'display' will always be unique. * * @abstract * @param {Date|null} current Current date * @return {CalendarGridData} Data */ mw.widgets.datetime.DateTimeFormatter.prototype.getCalendarData = function ( /* components */ ) { // Should be overridden by subclass return { header: '', monthComponent: 'month', dayComponent: 'day', rows: [] }; }; }() ); PK ! �*�L�; �; DiscordianDateTimeFormatter.jsnu �Iw�� ( function () { /** * @classdesc DateTimeFormatter for the Discordian calendar. * * Provides various methods needed for formatting dates and times. This * implementation implements the [Discordian calendar](https://en.wikipedia.org/wiki/Discordian_calendar), * mainly for testing with something very different from the usual Gregorian * calendar. * * Being intended mainly for testing, niceties like i18n and better * configurability have been omitted. * * @class * @extends mw.widgets.datetime.DateTimeFormatter * * @constructor * @description Create an instance of `mw.widgets.datetime.DiscordianDateTimeFormatter`. * @param {Object} [config] Configuration options */ mw.widgets.datetime.DiscordianDateTimeFormatter = function MwWidgetsDatetimeDiscordianDateTimeFormatter( config ) { config = Object.assign( {}, config ); // Parent constructor mw.widgets.datetime.DiscordianDateTimeFormatter.super.call( this, config ); }; /* Setup */ OO.inheritClass( mw.widgets.datetime.DiscordianDateTimeFormatter, mw.widgets.datetime.DateTimeFormatter ); /* Static */ /** * Default format specifications. * * See the `format` parameter in {@link mw.widgets.datetime.DateTimeFormatter}. * * @memberof mw.widgets.datetime.DiscordianDateTimeFormatter * @type {Object.<string,string>} * @name formats */ mw.widgets.datetime.DiscordianDateTimeFormatter.static.formats = { '@time': '${hour|0}:${minute|0}:${second|0}', '@date': '$!{dow|full}${not-intercalary|1|, }${season|full}${not-intercalary|1| }${day|#}, ${year|#}', '@datetime': '$!{dow|full}${not-intercalary|1|, }${season|full}${not-intercalary|1| }${day|#}, ${year|#} ${hour|0}:${minute|0}:${second|0} $!{zone|short}', '@default': '$!{dow|full}${not-intercalary|1|, }${season|full}${not-intercalary|1| }${day|#}, ${year|#} ${hour|0}:${minute|0}:${second|0} $!{zone|short}' }; /* Methods */ /** * Turn a tag into a field specification object. * * Additional fields implemented here are: * - ${year|#}: Year as a number * - ${season|#}: Season as a number * - ${season|full}: Season as a string * - ${day|#}: Day of the month as a number * - ${day|0}: Day of the month as a number with leading 0 * - ${dow|full}: Day of the week as a string * - ${hour|#}: Hour as a number * - ${hour|0}: Hour as a number with leading 0 * - ${minute|#}: Minute as a number * - ${minute|0}: Minute as a number with leading 0 * - ${second|#}: Second as a number * - ${second|0}: Second as a number with leading 0 * - ${millisecond|#}: Millisecond as a number * - ${millisecond|0}: Millisecond as a number, zero-padded to 3 digits * * @protected * @param {string} tag * @param {string[]} params * @return {FieldSpecificationObject} Field specification object, or null if the tag+params are unrecognized. */ mw.widgets.datetime.DiscordianDateTimeFormatter.prototype.getFieldForTag = function ( tag, params ) { let spec = null; switch ( tag + '|' + params[ 0 ] ) { case 'year|#': spec = { component: 'Year', calendarComponent: true, type: 'number', size: 4, zeropad: false }; break; case 'season|#': spec = { component: 'Season', calendarComponent: true, type: 'number', size: 1, intercalarySize: { 1: 0 }, zeropad: false }; break; case 'season|full': spec = { component: 'Season', calendarComponent: true, type: 'string', intercalarySize: { 1: 0 }, values: { 1: 'Chaos', 2: 'Discord', 3: 'Confusion', 4: 'Bureaucracy', 5: 'The Aftermath' } }; break; case 'dow|full': spec = { component: 'DOW', calendarComponent: true, editable: false, type: 'string', intercalarySize: { 1: 0 }, values: { '-1': 'N/A', 0: 'Sweetmorn', 1: 'Boomtime', 2: 'Pungenday', 3: 'Prickle-Prickle', 4: 'Setting Orange' } }; break; case 'day|#': case 'day|0': spec = { component: 'Day', calendarComponent: true, type: 'string', size: 2, intercalarySize: { 1: 13 }, zeropad: params[ 0 ] === '0', formatValue: function ( v ) { if ( v === 'tib' ) { return 'St. Tib\'s Day'; } return mw.widgets.datetime.DateTimeFormatter.prototype.formatSpecValue.call( this, v ); }, parseValue: function ( v ) { if ( /^\s*(st.?\s*)?tib('?s)?(\s*day)?\s*$/i.test( v ) ) { return 'tib'; } return mw.widgets.datetime.DateTimeFormatter.prototype.parseSpecValue.call( this, v ); } }; break; case 'hour|#': case 'hour|0': case 'minute|#': case 'minute|0': case 'second|#': case 'second|0': spec = { component: tag.charAt( 0 ).toUpperCase() + tag.slice( 1 ), calendarComponent: false, type: 'number', size: 2, zeropad: params[ 0 ] === '0' }; break; case 'millisecond|#': case 'millisecond|0': spec = { component: 'Millisecond', calendarComponent: false, type: 'number', size: 3, zeropad: params[ 0 ] === '0' }; break; default: return mw.widgets.datetime.DiscordianDateTimeFormatter.super.prototype.getFieldForTag.call( this, tag, params ); } if ( spec ) { if ( spec.editable === undefined ) { spec.editable = true; } if ( spec.component !== 'Day' ) { spec.formatValue = this.formatSpecValue; spec.parseValue = this.parseSpecValue; } if ( spec.values ) { spec.size = Math.max.apply( // eslint-disable-next-line no-jquery/no-map-util null, $.map( spec.values, ( v ) => v.length ) ); } } return spec; }; /** * Get components from a Date object. * * Components are: * - Year {number} * - Season {number} 1-5 * - Day {number|string} 1-73 or 'tib' * - DOW {number} 0-4, or -1 on St. Tib's Day * - Hour {number} 0-23 * - Minute {number} 0-59 * - Second {number} 0-59 * - Millisecond {number} 0-999 * - intercalary {string} '1' on St. Tib's Day * * @param {Date|null} date * @return {Object} Components */ mw.widgets.datetime.DiscordianDateTimeFormatter.prototype.getComponentsFromDate = function ( date ) { let ret, day, month; const monthDays = [ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 ]; if ( !( date instanceof Date ) ) { date = this.defaultDate; } if ( this.local ) { day = date.getDate(); month = date.getMonth(); ret = { Year: date.getFullYear() + 1166, Hour: date.getHours(), Minute: date.getMinutes(), Second: date.getSeconds(), Millisecond: date.getMilliseconds(), zone: date.getTimezoneOffset() }; } else { day = date.getUTCDate(); month = date.getUTCMonth(); ret = { Year: date.getUTCFullYear() + 1166, Hour: date.getUTCHours(), Minute: date.getUTCMinutes(), Second: date.getUTCSeconds(), Millisecond: date.getUTCMilliseconds(), zone: 0 }; } if ( month === 1 && day === 29 ) { ret.Season = 1; ret.Day = 'tib'; ret.DOW = -1; ret.intercalary = '1'; } else { day = monthDays[ month ] + day - 1; ret.Season = Math.floor( day / 73 ) + 1; ret.Day = ( day % 73 ) + 1; ret.DOW = day % 5; ret.intercalary = ''; } return ret; }; /** * @inheritdoc */ mw.widgets.datetime.DiscordianDateTimeFormatter.prototype.adjustComponent = function ( date, component, delta, mode ) { return this.getDateFromComponents( this.adjustComponentInternal( this.getComponentsFromDate( date ), component, delta, mode ) ); }; /** * Adjust the components directly. * * @private * @param {Object} components Modified in place * @param {string} component * @param {number} delta * @param {string} mode * @return {Object} components */ mw.widgets.datetime.DiscordianDateTimeFormatter.prototype.adjustComponentInternal = function ( components, component, delta, mode ) { let i, min, max, range, next, preTib, postTib, wasTib; if ( delta === 0 ) { return components; } switch ( component ) { case 'Year': min = 1166; max = 11165; next = null; break; case 'Season': min = 1; max = 5; next = 'Year'; break; case 'Week': if ( components.Day === 'tib' ) { components.Day = 59; // Could choose either one... components.Season = 1; } min = 1; max = 73; next = 'Season'; break; case 'Day': min = 1; max = 73; next = 'Season'; break; case 'Hour': min = 0; max = 23; next = 'Day'; break; case 'Minute': min = 0; max = 59; next = 'Hour'; break; case 'Second': min = 0; max = 59; next = 'Minute'; break; case 'Millisecond': min = 0; max = 999; next = 'Second'; break; default: return components; } switch ( mode ) { case 'overflow': case 'clip': case 'wrap': } if ( component === 'Day' ) { i = Math.abs( delta ); delta = delta < 0 ? -1 : 1; preTib = delta > 0 ? 59 : 60; postTib = delta > 0 ? 60 : 59; while ( i-- > 0 ) { if ( components.Day === preTib && components.Season === 1 && this.isLeapYear( components.Year ) ) { components.Day = 'tib'; } else if ( components.Day === 'tib' ) { components.Day = postTib; components.Season = 1; } else { components.Day += delta; if ( components.Day < min ) { switch ( mode ) { case 'overflow': components.Day = max; this.adjustComponentInternal( components, 'Season', -1, mode ); break; case 'wrap': components.Day = max; break; case 'clip': components.Day = min; i = 0; break; } } if ( components.Day > max ) { switch ( mode ) { case 'overflow': components.Day = min; this.adjustComponentInternal( components, 'Season', 1, mode ); break; case 'wrap': components.Day = min; break; case 'clip': components.Day = max; i = 0; break; } } } } } else { if ( component === 'Week' ) { component = 'Day'; delta *= 5; } if ( components.Day === 'tib' ) { components.Season = 1; } switch ( mode ) { case 'overflow': if ( components.Day === 'tib' && ( component === 'Season' || component === 'Year' ) ) { components.Day = 59; // Could choose either one... wasTib = true; } else { wasTib = false; } i = Math.abs( delta ); delta = delta < 0 ? -1 : 1; while ( i-- > 0 ) { components[ component ] += delta; if ( components[ component ] < min ) { components[ component ] = max; components = this.adjustComponentInternal( components, next, -1, mode ); } if ( components[ component ] > max ) { components[ component ] = min; components = this.adjustComponentInternal( components, next, 1, mode ); } } if ( wasTib && components.Season === 1 && this.isLeapYear( components.Year ) ) { components.Day = 'tib'; } break; case 'wrap': range = max - min + 1; components[ component ] += delta; while ( components[ component ] < min ) { components[ component ] += range; } while ( components[ component ] > max ) { components[ component ] -= range; } break; case 'clip': components[ component ] += delta; if ( components[ component ] < min ) { components[ component ] = min; } if ( components[ component ] > max ) { components[ component ] = max; } break; } if ( components.Day === 'tib' && ( components.Season !== 1 || !this.isLeapYear( components.Year ) ) ) { components.Day = 59; // Could choose either one... } } return components; }; /** * @inheritdoc */ mw.widgets.datetime.DiscordianDateTimeFormatter.prototype.getDateFromComponents = function ( components ) { let month, day; const date = new Date(), monthDays = [ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 ]; components = Object.assign( {}, this.getComponentsFromDate( null ), components ); if ( components.Day === 'tib' ) { month = 1; day = 29; } else { const days = components.Season * 73 + components.Day - 74; month = 0; while ( days >= monthDays[ month + 1 ] ) { month++; } day = days - monthDays[ month ] + 1; } if ( components.zone ) { // Can't just use the constructor because that's stupid about ancient years. date.setFullYear( components.Year - 1166, month, day ); date.setHours( components.Hour, components.Minute, components.Second, components.Millisecond ); } else { // Date.UTC() is stupid about ancient years too. date.setUTCFullYear( components.Year - 1166, month, day ); date.setUTCHours( components.Hour, components.Minute, components.Second, components.Millisecond ); } return date; }; /** * Get whether the year is a leap year. * * @private * @param {number} year * @return {boolean} */ mw.widgets.datetime.DiscordianDateTimeFormatter.prototype.isLeapYear = function ( year ) { year -= 1166; if ( year % 4 ) { return false; } else if ( year % 100 ) { return true; } return ( year % 400 ) === 0; }; /** * @inheritdoc */ mw.widgets.datetime.DiscordianDateTimeFormatter.prototype.getCalendarHeadings = function () { return [ 'SM', 'BT', 'PD', 'PP', null, 'SO' ]; }; /** * @inheritdoc */ mw.widgets.datetime.DiscordianDateTimeFormatter.prototype.sameCalendarGrid = function ( date1, date2 ) { const components1 = this.getComponentsFromDate( date1 ), components2 = this.getComponentsFromDate( date2 ); return components1.Year === components2.Year && components1.Season === components2.Season; }; /** * @inheritdoc */ mw.widgets.datetime.DiscordianDateTimeFormatter.prototype.getCalendarData = function ( date ) { const ret = { dayComponent: 'Day', weekComponent: 'Week', monthComponent: 'Season' }, seasons = [ 'Chaos', 'Discord', 'Confusion', 'Bureaucracy', 'The Aftermath' ], seasonStart = [ 0, -3, -1, -4, -2 ]; if ( !( date instanceof Date ) ) { date = this.defaultDate; } const components = this.getComponentsFromDate( date ); components.Day = 1; const season = components.Season; ret.header = seasons[ season - 1 ] + ' ' + components.Year; if ( seasonStart[ season - 1 ] ) { this.adjustComponentInternal( components, 'Day', seasonStart[ season - 1 ], 'overflow' ); } ret.rows = []; do { const row = []; for ( let i = 0; i < 6; i++ ) { const dt = this.getDateFromComponents( components ); row[ i ] = { display: components.Day === 'tib' ? 'Tib' : String( components.Day ), date: dt, extra: components.Season < season ? 'prev' : components.Season > season ? 'next' : null }; this.adjustComponentInternal( components, 'Day', 1, 'overflow' ); if ( components.Day !== 'tib' && i === 3 ) { row[ ++i ] = null; } } ret.rows.push( row ); } while ( components.Season === season ); return ret; }; }() ); PK ! �ic� � mediawiki.widgets.datetime.jsnu �Iw�� /** * OOUI widgets specific to MediaWiki for inputting date and time. * * @namespace mw.widgets.datetime */ mw.widgets.datetime = {}; PK ! e�'| | DateTimeInputWidget.lessnu �Iw�� @import 'mediawiki.widgets.datetime.definitions'; .mw-widgets-datetime-dateTimeInputWidget { display: inline-block; position: relative; width: 100%; // Hack: Set maximum width equivalent to DateInputWidgets calendar popup max-width: 24.275em; .oo-ui-inline-spacing( 0.5em ); vertical-align: middle; &-fields { position: relative; display: table; z-index: 2; .oo-ui-unselectable(); > .mw-widgets-datetime-dateTimeInputWidget-field { display: table-cell; white-space: pre; } } &-handle { background-color: @background-color-base; color: @color-base; display: inline-block; box-sizing: border-box; width: 100%; height: @size-base; border: @border-base; border-radius: @border-radius-base; padding: 0 @padding-horizontal-input-text; box-shadow: inset 0 0 0 0 @box-shadow-color-progressive--focus; // Needed for proper behavior with `overflow: hidden`. vertical-align: bottom; overflow: hidden; .oo-ui-unselectable(); transition: box-shadow @transition-duration-base; > .oo-ui-iconElement-icon, > .oo-ui-indicatorElement-indicator { background-position: center center; background-repeat: no-repeat; position: absolute; top: 0; z-index: 1; } > .oo-ui-iconElement-icon { left: @padding-start-input-text-icon; width: @size-icon; height: @size-icon; } > .oo-ui-indicatorElement-indicator { right: @padding-horizontal-base; } } &.oo-ui-iconElement .mw-widgets-datetime-dateTimeInputWidget-handle { padding-left: @padding-start-input-text-icon-label; } &.oo-ui-indicatorElement .mw-widgets-datetime-dateTimeInputWidget-handle { padding-right: @min-size-indicator + 2 * @padding-horizontal-input-text; } &-field { background-color: transparent; color: inherit; box-sizing: content-box; border: 0; border-radius: @border-radius-base; padding-top: @padding-vertical-base; padding-bottom: @padding-vertical-base; box-shadow: none; font-size: inherit; font-family: inherit; line-height: @line-height-widget-base; text-align: center; vertical-align: top; // Day field, f.e. “Sat” &:first-child { cursor: pointer; } // Day field & time zome field, f.e. “Sat” & “Z” &[ tabindex='-1' ] { // Support: Firefox, Chrome outline: 0; } } &-editField { .mw-widgets-datetime-dateTimeInputWidget-invalid { border: @border-width-base @border-style-base @border-color-error; box-shadow: @box-shadow-inset-small @box-shadow-color-transparent; &:focus { border: @border-width-base @border-style-base @border-color-error; box-shadow: @box-shadow-inset-small @color-error; } } } &-clearButton { // Override `&-field` from above padding-top: 0; padding-bottom: 0; padding-left: @padding-start-button-clear; // `&.oo-ui-iconElement` needed for specificity &.oo-ui-iconElement > .oo-ui-buttonElement-button { padding-top: @padding-top-button-clear; } .oo-ui-iconElement-icon { background-size: @size-indicator @size-indicator; } } &-empty { .mw-widgets-datetime-dateTimeInputWidget-handle { color: @color-subtle; } .mw-widgets-datetime-dateTimeInputWidget-clearButton { display: none; } } &.oo-ui-widget-enabled { .mw-widgets-datetime-dateTimeInputWidget-handle { transition-property: border-color; transition-duration: @transition-duration-medium; &:hover { border-color: @border-color-input--hover; } } // Set on widget parent to also enable `:hover` on child elements &:hover { input, textarea { border-color: @border-color-input--hover; } } .mw-widgets-datetime-dateTimeInputWidget-editField:hover { background-color: @background-color-interactive; } .mw-widgets-datetime-dateTimeInputWidget-editField:focus { outline: @outline-base--focus; box-shadow: @box-shadow-inset-small @box-shadow-color-progressive--focus, @box-shadow-outset-small @box-shadow-color-progressive--focus; } &.oo-ui-flaggedElement-invalid { .mw-widgets-datetime-dateTimeInputWidget-handle { border-color: @border-color-error; box-shadow: @box-shadow-inset-small @box-shadow-color-transparent; &:focus { border-color: @border-color-error; box-shadow: @box-shadow-inset-small @color-error; } } } } &.oo-ui-widget-disabled { .mw-widgets-datetime-dateTimeInputWidget-handle { background-color: @background-color-disabled-subtle; // Support: Safari -webkit-text-fill-color: @color-disabled; color: @color-disabled; border-color: @border-color-disabled; text-shadow: @text-shadow-base--disabled; } > .oo-ui-iconElement-icon, > .oo-ui-indicatorElement-indicator { opacity: @opacity-icon-base--disabled; } } } PK ! ���� � + mediawiki.widgets.datetime.definitions.lessnu �Iw�� @import 'mediawiki.skin.variables.less'; @import 'mediawiki.mixins.less'; /*! * OOUI definitions used by the existing CSS (will make it easier to put this * widget in OOUI once OOUI is capable of handling it) */ .oo-ui-unselectable() { -webkit-touch-callout: none; .user-select( none ); } .oo-ui-inline-spacing( @spacing, @cancelled-spacing: 0 ) { margin-right: @spacing; &:last-child { margin-right: @cancelled-spacing; } } // Variables taken from OOUI's WikimediaUI theme, see its common.less for further explanations @ooui-font-size-browser: 16; // assumed browser default of `16px` @ooui-font-size-base: 0.875em; // equals `14px` at browser default of `16px` @ooui-unit: em; @min-size-indicator: 12px; @size-base: (32 / @ooui-font-size-browser / @ooui-font-size-base); @size-icon: (24 / @ooui-font-size-browser / @ooui-font-size-base); @size-indicator: (12 / @ooui-font-size-browser / @ooui-font-size-base); @max-width-base: 50em; @max-width-input: @max-width-base; @padding-input-text: @padding-vertical-base @padding-horizontal-input-text; @padding-horizontal-base: 12px; @padding-horizontal-input-text: 8px; @padding-vertical-base: 6px; // All paddings holding icons need `em`s due to font-size derived icon scaling. @padding-top-button-clear: (28 / @ooui-font-size-browser / @ooui-font-size-base); // As it's inside an input, we need to reduce from borders surrounding. @padding-start-input-text-icon: (6 / @ooui-font-size-browser / @ooui-font-size-base); @padding-start-input-text-icon-label: (32 / @ooui-font-size-browser / @ooui-font-size-base); @padding-start-button-clear: (4 / @ooui-font-size-browser / @ooui-font-size-base); // `line-height` has to be relative/in `em` to enable user override in browser settings. @line-height-widget-base: unit( 18 / @ooui-font-size-browser / @ooui-font-size-base, @ooui-unit ); // equals `18px` at base `font-size: 14px; @text-shadow-base: 0 1px 1px @color-inverted; // 'coined' effect @text-shadow-base--disabled: @text-shadow-base; PK ! �T\� � CalendarWidget.lessnu �Iw�� @import 'mediawiki.skin.variables.less'; @import 'mediawiki.widgets.datetime.definitions'; /* stylelint-disable no-duplicate-selectors */ .mw-widgets-datetime-calendarWidget { background-color: @background-color-base; display: inline-block; position: relative; border: @border-base; border-radius: @border-radius-base; padding: 0.5em; box-shadow: @box-shadow-drop-medium; vertical-align: middle; &:focus { border-color: @border-color-progressive--focus; outline: @outline-base--focus; box-shadow: @box-shadow-inset-small @box-shadow-color-progressive--focus, @box-shadow-drop-medium; } &.mw-widgets-datetime-calendarWidget-dependent { display: block; position: absolute; z-index: 4; } &-grid { table-layout: fixed; .mw-widgets-datetime-calendarWidget-cell { display: table-cell; white-space: nowrap; } } &.mw-widgets-datetime-calendarWidget-dependent { margin-top: -1px; } &-heading { font-weight: bold; text-align: center; vertical-align: middle; white-space: nowrap; .mw-widgets-datetime-calendarWidget-previous { float: left; // Overwrite OOUI's `.oo-ui-buttonElement-frameless.oo-ui-iconElement:first-child` &:first-child { margin-left: 0; } } .mw-widgets-datetime-calendarWidget-next { float: right; } } &-grid { margin: 0 auto; .mw-widgets-datetime-calendarWidget-cell { text-align: center; .oo-ui-buttonElement-button { width: 100%; border: 1px solid rgba( 255, 255, 255, 0 ); transition-property: @transition-property-base; transition-duration: @transition-duration-base; } &.mw-widgets-datetime-calendarWidget-extra .oo-ui-buttonElement-button { .oo-ui-labelElement-label { color: @color-subtle; } &:hover .oo-ui-labelElement-label { color: @color-inverted; } } &.mw-widgets-datetime-calendarWidget-selected .oo-ui-buttonElement-button { background-color: @background-color-progressive--active; .oo-ui-labelElement-label { color: @color-inverted; } } &.oo-ui-widget-enabled .oo-ui-buttonElement-button:hover { background-color: #36c; color: @color-inverted; border-color: #36c; } } } &:focus &-grid &-cell&-focused .oo-ui-buttonElement-button { border-color: rgba( 0, 0, 0, 0.3 ); } } PK ! fb!gB gB CalendarWidget.jsnu �Iw�� ( function () { /** * @classdesc CalendarWidget displays a calendar that can be used to select a date. It * uses {@link mw.widgets.datetime.DateTimeFormatter DateTimeFormatter} to get the details of * the calendar. * * This widget is mainly intended to be used as a popup from a * {@link mw.widgets.datetime.DateTimeInputWidget DateTimeInputWidget}, but may also be used * standalone. * * @class * @extends OO.ui.Widget * @mixes OO.ui.mixin.TabIndexedElement * * @constructor * @description Create an instance of `mw.widgets.CalendarWidget`. * @param {Object} [config] Configuration options * @param {Object|mw.widgets.datetime.DateTimeFormatter} [config.formatter={}] Configuration options for * {@link mw.widgets.datetime.ProlepticGregorianDateTimeFormatter}, or an * {@link mw.widgets.datetime.DateTimeFormatter} instance to use. * @param {OO.ui.Widget|null} [config.widget=null] Widget associated with the calendar. * Specifying this configures the calendar to be used as a popup from the * specified widget (e.g. absolute positioning, automatic hiding when clicked * outside). * @param {Date|null} [config.min=null] Minimum allowed date * @param {Date|null} [config.max=null] Maximum allowed date * @param {Date} [config.focusedDate] Initially focused date. * @param {Date|Date[]|null} [config.selected=null] Selected date(s). */ mw.widgets.datetime.CalendarWidget = function MwWidgetsDatetimeCalendarWidget( config ) { // Configuration initialization config = Object.assign( { min: null, max: null, focusedDate: new Date(), selected: null, formatter: {} }, config ); // Parent constructor mw.widgets.datetime.CalendarWidget.super.call( this, config ); // Mixin constructors OO.ui.mixin.TabIndexedElement.call( this, Object.assign( {}, config, { $tabIndexed: this.$element } ) ); // Properties if ( config.min instanceof Date && config.min.getTime() >= -62167219200000 ) { this.min = config.min; } else { this.min = new Date( -62167219200000 ); // 0000-01-01T00:00:00.000Z } if ( config.max instanceof Date && config.max.getTime() <= 253402300799999 ) { this.max = config.max; } else { this.max = new Date( 253402300799999 ); // 9999-12-31T12:59:59.999Z } if ( config.focusedDate instanceof Date ) { this.focusedDate = config.focusedDate; } else { this.focusedDate = new Date(); } this.selected = []; if ( config.formatter instanceof mw.widgets.datetime.DateTimeFormatter ) { this.formatter = config.formatter; } else if ( $.isPlainObject( config.formatter ) ) { this.formatter = new mw.widgets.datetime.ProlepticGregorianDateTimeFormatter( config.formatter ); } else { throw new Error( '"formatter" must be an mw.widgets.datetime.DateTimeFormatter or a plain object' ); } this.calendarData = null; this.widget = config.widget; this.$widget = config.widget ? config.widget.$element : null; this.onDocumentMouseDownHandler = this.onDocumentMouseDown.bind( this ); this.$head = $( '<div>' ); this.$header = $( '<span>' ); this.$table = $( '<table>' ); this.cols = []; this.colNullable = []; this.headings = []; this.$tableBody = $( '<tbody>' ); this.rows = []; this.buttons = {}; this.minWidth = 1; this.daysPerWeek = 0; // Events this.$element.on( { keydown: this.onKeyDown.bind( this ) } ); this.formatter.connect( this, { local: 'onLocalChange' } ); if ( this.$widget ) { this.checkFocusHandler = this.checkFocus.bind( this ); this.$element.on( { focusout: this.onFocusOut.bind( this ) } ); this.$widget.on( { focusout: this.onFocusOut.bind( this ) } ); } // Initialization this.$head .addClass( 'mw-widgets-datetime-calendarWidget-heading' ) .append( new OO.ui.ButtonWidget( { icon: 'previous', framed: false, classes: [ 'mw-widgets-datetime-calendarWidget-previous' ], tabIndex: -1 } ).connect( this, { click: 'onPrevClick' } ).$element, new OO.ui.ButtonWidget( { icon: 'next', framed: false, classes: [ 'mw-widgets-datetime-calendarWidget-next' ], tabIndex: -1 } ).connect( this, { click: 'onNextClick' } ).$element, this.$header ); const $colgroup = $( '<colgroup>' ); const $headTR = $( '<tr>' ); this.$table .addClass( 'mw-widgets-datetime-calendarWidget-grid' ) .append( $colgroup ) .append( $( '<thead>' ).append( $headTR ) ) .append( this.$tableBody ); const headings = this.formatter.getCalendarHeadings(); for ( let i = 0; i < headings.length; i++ ) { this.cols[ i ] = $( '<col>' ); this.headings[ i ] = $( '<th>' ); this.colNullable[ i ] = headings[ i ] === null; if ( headings[ i ] !== null ) { this.headings[ i ].text( headings[ i ] ); this.minWidth = Math.max( this.minWidth, headings[ i ].length ); this.daysPerWeek++; } $colgroup.append( this.cols[ i ] ); $headTR.append( this.headings[ i ] ); } this.setSelected( config.selected ); this.$element .addClass( 'mw-widgets-datetime-calendarWidget' ) .append( this.$head, this.$table ); if ( this.widget ) { this.$element.addClass( 'mw-widgets-datetime-calendarWidget-dependent' ); // Initially hidden - using #toggle may cause errors if subclasses override toggle with methods // that reference properties not initialized at that time of parent class construction // TODO: Find a better way to handle post-constructor setup this.visible = false; this.$element.addClass( 'oo-ui-element-hidden' ); } else { this.updateUI(); } }; /* Setup */ OO.inheritClass( mw.widgets.datetime.CalendarWidget, OO.ui.Widget ); OO.mixinClass( mw.widgets.datetime.CalendarWidget, OO.ui.mixin.TabIndexedElement ); /* Events */ /** * A `change` event is emitted when the selected dates change. * * @event mw.widgets.datetime.CalendarWidget.change * @param {Date|Date[]|null} dates The new date(s) or null */ /** * A `focusChanged` event is emitted when the focused date changes. * * @event mw.widgets.datetime.CalendarWidget.focusChanged * @param {Date} date The newly focused date */ /** * A `page` event is emitted when the current "month" changes. * * @event mw.widgets.datetime.CalendarWidget.page * @param {Date} date The new date */ /* Methods */ /** * Return the current selected dates. * * @return {Date[]} */ mw.widgets.datetime.CalendarWidget.prototype.getSelected = function () { return this.selected; }; /** * Set the selected dates. * * @param {Date|Date[]|null} dates * @fires mw.widgets.datetime.CalendarWidget.change * @chainable * @return {mw.widgets.datetime.CalendarWidget} */ mw.widgets.datetime.CalendarWidget.prototype.setSelected = function ( dates ) { let i, changed = false; if ( dates instanceof Date ) { dates = [ dates ]; } else if ( Array.isArray( dates ) ) { dates = dates.filter( ( dt ) => dt instanceof Date ); dates.sort(); } else { dates = []; } if ( this.selected.length !== dates.length ) { changed = true; } else { for ( i = 0; i < dates.length; i++ ) { if ( dates[ i ].getTime() !== this.selected[ i ].getTime() ) { changed = true; break; } } } if ( changed ) { this.selected = dates; this.emit( 'change', dates ); this.updateUI(); } return this; }; /** * Return the currently-focused date. * * @return {Date} */ mw.widgets.datetime.CalendarWidget.prototype.getFocusedDate = function () { return this.focusedDate; }; /** * Set the currently-focused date. * * @param {Date} date * @fires mw.widgets.datetime.CalendarWidget.focusChanged * @fires mw.widgets.datetime.CalendarWidget.page * @chainable * @return {mw.widgets.datetime.CalendarWidget} */ mw.widgets.datetime.CalendarWidget.prototype.setFocusedDate = function ( date ) { let changePage = false, updateUI = false; if ( this.focusedDate.getTime() === date.getTime() ) { return this; } if ( !this.formatter.sameCalendarGrid( this.focusedDate, date ) ) { changePage = true; updateUI = true; } else if ( !this.formatter.timePartIsEqual( this.focusedDate, date ) || !this.formatter.datePartIsEqual( this.focusedDate, date ) ) { updateUI = true; } this.focusedDate = date; this.emit( 'focusChanged', this.focusedDate ); if ( changePage ) { this.emit( 'page', date ); } if ( updateUI ) { this.updateUI(); } return this; }; /** * Adjust a date. * * @protected * @param {Date} date Date to adjust * @param {string} component Component: 'month', 'week', or 'day' * @param {number} delta Integer, usually -1 or 1 * @param {boolean} [enforceRange=true] Whether to enforce this.min and this.max * @return {Date} */ mw.widgets.datetime.CalendarWidget.prototype.adjustDate = function ( date, component, delta ) { let newDate; const data = this.calendarData; if ( !data ) { return date; } switch ( component ) { case 'month': newDate = this.formatter.adjustComponent( date, data.monthComponent, delta, 'overflow' ); break; case 'week': if ( data.weekComponent === undefined ) { newDate = this.formatter.adjustComponent( date, data.dayComponent, delta * this.daysPerWeek, 'overflow' ); } else { newDate = this.formatter.adjustComponent( date, data.weekComponent, delta, 'overflow' ); } break; case 'day': newDate = this.formatter.adjustComponent( date, data.dayComponent, delta, 'overflow' ); break; default: throw new Error( 'Unknown component' ); } while ( newDate < this.min ) { newDate = this.formatter.adjustComponent( newDate, data.dayComponent, 1, 'overflow' ); } while ( newDate > this.max ) { newDate = this.formatter.adjustComponent( newDate, data.dayComponent, -1, 'overflow' ); } return newDate; }; /** * Update the user interface. * * @protected */ mw.widgets.datetime.CalendarWidget.prototype.updateUI = function () { let row, day, k, $cell, width = this.minWidth; const nullCols = [], focusedDate = this.getFocusedDate(), selected = this.getSelected(), datePartIsEqual = this.formatter.datePartIsEqual.bind( this.formatter ), isSelected = function ( dt ) { return datePartIsEqual( this, dt ); }; this.calendarData = this.formatter.getCalendarData( focusedDate ); this.$header.text( this.calendarData.header ); for ( let c = 0; c < this.colNullable.length; c++ ) { nullCols[ c ] = this.colNullable[ c ]; if ( nullCols[ c ] ) { for ( let r = 0; r < this.calendarData.rows.length; r++ ) { if ( this.calendarData.rows[ r ][ c ] ) { nullCols[ c ] = false; break; } } } } this.$tableBody.children().detach(); for ( let r = 0; r < this.calendarData.rows.length; r++ ) { if ( !this.rows[ r ] ) { this.rows[ r ] = $( '<tr>' ); } else { this.rows[ r ].children().detach(); } this.$tableBody.append( this.rows[ r ] ); row = this.calendarData.rows[ r ]; for ( let c = 0; c < row.length; c++ ) { day = row[ c ]; if ( day === null ) { k = 'empty-' + r + '-' + c; if ( !this.buttons[ k ] ) { this.buttons[ k ] = $( '<td>' ); } $cell = this.buttons[ k ]; $cell.toggleClass( 'oo-ui-element-hidden', nullCols[ c ] ); } else { k = ( day.extra ? day.extra : '' ) + day.display; width = Math.max( width, day.display.length ); if ( !this.buttons[ k ] ) { this.buttons[ k ] = new OO.ui.ButtonWidget( { $element: $( '<td>' ), classes: [ 'mw-widgets-datetime-calendarWidget-cell', day.extra ? 'mw-widgets-datetime-calendarWidget-extra' : '' ], framed: true, label: day.display, tabIndex: -1 } ); this.buttons[ k ].connect( this, { click: [ 'onDayClick', this.buttons[ k ] ] } ); } this.buttons[ k ] .setData( day.date ) .setDisabled( day.date < this.min || day.date > this.max ); $cell = this.buttons[ k ].$element; $cell .toggleClass( 'mw-widgets-datetime-calendarWidget-focused', this.formatter.datePartIsEqual( focusedDate, day.date ) ) .toggleClass( 'mw-widgets-datetime-calendarWidget-selected', selected.some( isSelected, day.date ) ); } this.rows[ r ].append( $cell ); } } for ( let c = 0; c < this.cols.length; c++ ) { if ( nullCols[ c ] ) { this.cols[ c ].width( 0 ); } else { this.cols[ c ].width( width + 'em' ); } this.cols[ c ].toggleClass( 'oo-ui-element-hidden', nullCols[ c ] ); this.headings[ c ].toggleClass( 'oo-ui-element-hidden', nullCols[ c ] ); } }; /** * Handles formatter 'local' flag changing. * * @protected */ mw.widgets.datetime.CalendarWidget.prototype.onLocalChange = function () { if ( this.formatter.localChangesDatePart( this.getFocusedDate() ) ) { this.emit( 'page', this.getFocusedDate() ); } this.updateUI(); }; /** * Handles previous button click. * * @protected */ mw.widgets.datetime.CalendarWidget.prototype.onPrevClick = function () { this.setFocusedDate( this.adjustDate( this.getFocusedDate(), 'month', -1 ) ); if ( !this.$widget || OO.ui.contains( this.$element[ 0 ], document.activeElement, true ) ) { this.$element.trigger( 'focus' ); } }; /** * Handles next button click. * * @protected */ mw.widgets.datetime.CalendarWidget.prototype.onNextClick = function () { this.setFocusedDate( this.adjustDate( this.getFocusedDate(), 'month', 1 ) ); if ( !this.$widget || OO.ui.contains( this.$element[ 0 ], document.activeElement, true ) ) { this.$element.trigger( 'focus' ); } }; /** * Handles day button click. * * @protected * @param {OO.ui.ButtonWidget} button */ mw.widgets.datetime.CalendarWidget.prototype.onDayClick = function ( button ) { const data = button.getData(); this.setFocusedDate( data ); this.setSelected( [ data ] ); if ( !this.$widget || OO.ui.contains( this.$element[ 0 ], document.activeElement, true ) ) { this.$element.trigger( 'focus' ); } }; /** * Handles document mouse down events. * * @protected * @param {jQuery.Event} e Mouse down event */ mw.widgets.datetime.CalendarWidget.prototype.onDocumentMouseDown = function ( e ) { if ( this.$widget && !OO.ui.contains( this.$element[ 0 ], e.target, true ) && !OO.ui.contains( this.$widget[ 0 ], e.target, true ) ) { this.toggle( false ); } }; /** * Handles key presses. * * @protected * @param {jQuery.Event} e Key down event * @return {boolean|undefined} False to cancel the default event */ mw.widgets.datetime.CalendarWidget.prototype.onKeyDown = function ( e ) { const focusedDate = this.getFocusedDate(); if ( !this.isDisabled() ) { switch ( e.which ) { case OO.ui.Keys.ENTER: case OO.ui.Keys.SPACE: this.setSelected( [ focusedDate ] ); return false; case OO.ui.Keys.LEFT: this.setFocusedDate( this.adjustDate( focusedDate, 'day', -1 ) ); return false; case OO.ui.Keys.RIGHT: this.setFocusedDate( this.adjustDate( focusedDate, 'day', 1 ) ); return false; case OO.ui.Keys.UP: this.setFocusedDate( this.adjustDate( focusedDate, 'week', -1 ) ); return false; case OO.ui.Keys.DOWN: this.setFocusedDate( this.adjustDate( focusedDate, 'week', 1 ) ); return false; case OO.ui.Keys.PAGEUP: this.setFocusedDate( this.adjustDate( focusedDate, 'month', -1 ) ); return false; case OO.ui.Keys.PAGEDOWN: this.setFocusedDate( this.adjustDate( focusedDate, 'month', 1 ) ); return false; } } }; /** * Handles focusout events in dependent mode. * * @private */ mw.widgets.datetime.CalendarWidget.prototype.onFocusOut = function () { setTimeout( this.checkFocusHandler ); }; /** * When we or our widget lost focus, check if the calendar should be hidden. * * @private */ mw.widgets.datetime.CalendarWidget.prototype.checkFocus = function () { const containers = [ this.$element[ 0 ], this.$widget[ 0 ] ], activeElement = document.activeElement; if ( !activeElement || !OO.ui.contains( containers, activeElement, true ) ) { this.toggle( false ); } }; /** * @inheritdoc */ mw.widgets.datetime.CalendarWidget.prototype.toggle = function ( visible ) { visible = ( visible === undefined ? !this.visible : !!visible ); const change = visible !== this.isVisible(); // Parent method mw.widgets.datetime.CalendarWidget.super.prototype.toggle.call( this, visible ); if ( change ) { if ( visible ) { // Auto-hide if ( this.$widget ) { this.getElementDocument().addEventListener( 'mousedown', this.onDocumentMouseDownHandler, true ); } this.updateUI(); } else { this.getElementDocument().removeEventListener( 'mousedown', this.onDocumentMouseDownHandler, true ); } } return this; }; }() ); PK ! ��Ù&N &N &