Localisation updates from https://translatewiki.net.
[mediawiki.git] / resources / src / mediawiki.special.block / components / ExpiryField.vue
bloba6ca561394439b002362128615ed3ec52166cbb3
1 <template>
2         <cdx-field
3                 class="mw-block-expiry-field"
4                 :is-fieldset="true"
5         >
6                 <template #label>
7                         {{ $i18n( 'block-expiry' ).text() }}
8                 </template>
10                 <cdx-radio
11                         v-model="expiryType"
12                         name="expiryType"
13                         input-value="preset-duration"
14                 >
15                         {{ $i18n( 'block-expiry-preset' ).text() }}
16                         <template v-if="expiryType === 'preset-duration'" #custom-input>
17                                 <cdx-field
18                                         class="mw-block-expiry-field__preset-duration"
19                                         :status="presetDurationStatus"
20                                         :messages="presetDurationMessages"
21                                 >
22                                         <cdx-select
23                                                 v-if="expiryType === 'preset-duration'"
24                                                 v-model:selected="presetDuration"
25                                                 :menu-items="presetDurationOptions"
26                                                 :default-label="$i18n( 'block-expiry-preset-placeholder' ).text()"
27                                                 @update:selected="() => {
28                                                         presetDurationStatus = 'default';
29                                                         presetDurationMessages = {};
30                                                 }"
31                                         ></cdx-select>
32                                 </cdx-field>
33                         </template>
34                 </cdx-radio>
36                 <cdx-radio
37                         v-model="expiryType"
38                         name="expiryType"
39                         input-value="custom-duration"
40                 >
41                         {{ $i18n( 'block-expiry-custom' ).text() }}
42                         <template v-if="expiryType === 'custom-duration'" #custom-input>
43                                 <cdx-field
44                                         class="mw-block-expiry-field__custom-duration"
45                                         :status="customDurationStatus"
46                                         :messages="customDurationMessages"
47                                 >
48                                         <validating-text-input
49                                                 v-model="customDurationNumber"
50                                                 input-type="number"
51                                                 min="1"
52                                                 required
53                                                 @update:status="( status, message ) => {
54                                                         customDurationMessages = message;
55                                                         customDurationStatus = status;
56                                                 }"
57                                         ></validating-text-input>
58                                         <cdx-select
59                                                 v-model:selected="customDurationUnit"
60                                                 :menu-items="customDurationOptions"
61                                         ></cdx-select>
62                                 </cdx-field>
63                         </template>
64                 </cdx-radio>
66                 <cdx-radio
67                         v-model="expiryType"
68                         name="expiryType"
69                         input-value="datetime"
70                 >
71                         {{ $i18n( 'block-expiry-datetime' ).text() }}
72                         <template v-if="expiryType === 'datetime'" #custom-input>
73                                 <cdx-field
74                                         class="mw-block-expiry-field__datetime"
75                                         :status="datetimeStatus"
76                                         :messages="datetimeMessages"
77                                 >
78                                         <validating-text-input
79                                                 v-if="expiryType === 'datetime'"
80                                                 v-model="datetime"
81                                                 input-type="datetime-local"
82                                                 name="wpExpiry-other"
83                                                 :min="new Date().toISOString().slice( 0, 16 )"
84                                                 required
85                                                 @update:status="( status, message ) => {
86                                                         datetimeMessages = message;
87                                                         datetimeStatus = status;
88                                                 }"
89                                         ></validating-text-input>
90                                 </cdx-field>
91                         </template>
92                 </cdx-radio>
93         </cdx-field>
94 </template>
96 <script>
97 const { defineComponent, ref, computed, watch } = require( 'vue' );
98 const { CdxField, CdxRadio, CdxSelect } = require( '@wikimedia/codex' );
99 const { storeToRefs } = require( 'pinia' );
100 const ValidatingTextInput = require( './ValidatingTextInput.js' );
101 const useBlockStore = require( '../stores/block.js' );
103 module.exports = exports = defineComponent( {
104         name: 'ExpiryField',
105         components: {
106                 CdxField,
107                 CdxRadio,
108                 CdxSelect,
109                 ValidatingTextInput
110         },
111         setup() {
112                 const store = useBlockStore();
113                 const blockExpiryOptions = mw.config.get( 'blockExpiryOptions' );
114                 const presetDurationOptions = Object.keys( blockExpiryOptions )
115                         .map( ( key ) => ( { label: key, value: blockExpiryOptions[ key ] } ) )
116                         // Don't include "other" in the preset options as it's handled separately in the new UI
117                         .filter( ( option ) => option.value !== 'other' );
119                 const customDurationOptions = [
120                         { value: 'minutes', label: mw.msg( 'block-expiry-custom-minutes' ) },
121                         { value: 'hours', label: mw.msg( 'block-expiry-custom-hours' ) },
122                         { value: 'days', label: mw.msg( 'block-expiry-custom-days' ) },
123                         { value: 'weeks', label: mw.msg( 'block-expiry-custom-weeks' ) },
124                         { value: 'months', label: mw.msg( 'block-expiry-custom-months' ) },
125                         { value: 'years', label: mw.msg( 'block-expiry-custom-years' ) }
126                 ];
128                 const presetDuration = ref( null );
129                 const presetDurationStatus = ref( 'default' );
130                 const presetDurationMessages = ref( {} );
131                 const customDurationNumber = ref( 1 );
132                 const customDurationUnit = ref( 'hours' );
133                 const customDurationStatus = ref( 'default' );
134                 const customDurationMessages = ref( {} );
135                 const datetime = ref( '' );
136                 const datetimeStatus = ref( 'default' );
137                 const datetimeMessages = ref( {} );
138                 const expiryType = ref( 'preset-duration' );
140                 const computedModelValue = computed( () => {
141                         if ( expiryType.value === 'preset-duration' ) {
142                                 return presetDuration.value;
143                         } else if ( expiryType.value === 'custom-duration' ) {
144                                 return `${ Number( customDurationNumber.value ) } ${ customDurationUnit.value }`;
145                         } else {
146                                 return datetime.value;
147                         }
148                 } );
150                 /**
151                  * Set the form fields according to the given expiry.
152                  *
153                  * @param {string} given
154                  */
155                 function setDurationFromGiven( given ) {
156                         const optionsContainsValue = ( opts, v ) => opts.some( ( option ) => option.value === v );
157                         if ( mw.util.isInfinity( given ) ) {
158                                 expiryType.value = 'preset-duration';
159                                 // FIXME: Assumes that the "infinite" option exists.
160                                 // (It has to be for this form as there's no other way to specify infinite)
161                                 presetDuration.value = 'infinite';
162                         } else if ( optionsContainsValue( presetDurationOptions, given ) ) {
163                                 expiryType.value = 'preset-duration';
164                                 presetDuration.value = given;
165                         } else if ( /^\d+ [a-z]+$/.test( given ) ) {
166                                 const [ number, unit ] = given.split( ' ' );
167                                 // Normalize the unit to plural form, as used by customDurationOptions.
168                                 const unitPlural = unit.replace( /s?$/, 's' );
169                                 if ( optionsContainsValue( customDurationOptions, unitPlural ) ) {
170                                         expiryType.value = 'custom-duration';
171                                         customDurationNumber.value = Number( number );
172                                         customDurationUnit.value = unitPlural;
173                                 }
174                         } else if ( /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test( given ) ) {
175                                 expiryType.value = 'datetime';
176                                 // Truncate longer datetime strings to be compatible with input type=datetime-local.
177                                 // This is also done in SpecialBlock.php
178                                 datetime.value = given.slice( 0, 16 );
179                         } else {
180                                 // Unsupported format; Reset to defaults.
181                                 expiryType.value = 'preset-duration';
182                                 customDurationNumber.value = 1;
183                                 customDurationUnit.value = 'hours';
184                                 datetime.value = '';
185                         }
186                 }
188                 const { expiry } = storeToRefs( store );
190                 // Update the store's expiry value when the computed value changes.
191                 watch( computedModelValue, ( newValue ) => {
192                         if ( newValue !== expiry.value ) {
193                                 // Remove browser-specific milliseconds from datetime for consistency.
194                                 if ( expiryType.value === 'datetime' ) {
195                                         newValue = newValue.replace( /\.000$/, '' );
196                                 }
197                                 expiry.value = newValue;
198                         }
199                 } );
201                 // Update the form fields when the store's expiry value changes.
202                 watch( expiry, ( newValue ) => {
203                         if ( newValue !== computedModelValue.value ) {
204                                 setDurationFromGiven( newValue );
205                         }
206                 }, { immediate: true } );
208                 /**
209                  * The preset duration field is a dropdown that requires custom validation.
210                  * We simply need to assert something is selected, but only do so after form submission.
211                  */
212                 watch( () => store.formSubmitted, ( submitted ) => {
213                         if ( submitted && expiryType.value === 'preset-duration' && !presetDuration.value ) {
214                                 presetDurationStatus.value = 'error';
215                                 presetDurationMessages.value = { error: mw.msg( 'ipb_expiry_invalid' ) };
216                         }
217                 } );
219                 return {
220                         presetDurationOptions,
221                         presetDurationStatus,
222                         presetDurationMessages,
223                         customDurationStatus,
224                         customDurationMessages,
225                         customDurationOptions,
226                         presetDuration,
227                         customDurationNumber,
228                         customDurationUnit,
229                         datetime,
230                         datetimeStatus,
231                         datetimeMessages,
232                         expiryType
233                 };
234         }
235 } );
236 </script>
238 <style lang="less">
239 /* stylelint-disable plugin/no-unsupported-browser-features */
240 @import 'mediawiki.skin.variables.less';
242 .mw-block-expiry-field {
243         .cdx-radio__custom-input .cdx-label {
244                 display: none;
245         }
247         .cdx-select-vue {
248                 margin-bottom: 0;
249         }
251         &__custom-duration .cdx-field__control {
252                 display: flex;
253                 gap: @spacing-50;
255                 &__number,
256                 &__unit {
257                         flex-grow: 1;
258                 }
259         }
261 </style>