Upload Wizard Describe step: improve title, caption, and description

Bug: T361049
Change-Id: I365dd5e4124160d112ff2f51d5d0f3fbe4c0a5b3
这个提交包含在:
Marco Fossati 2024-04-17 19:17:01 +02:00
父节点 f6c0e5a81e
当前提交 f19e7f5ae0
共有 9 个文件被更改,包括 205 次插入41 次删除

查看文件

@ -323,10 +323,11 @@
"mwe-upwiz-upload-error-duplicate-archive",
"mwe-upwiz-upload-error-stashed-anyway",
"mwe-upwiz-ok",
"mwe-upwiz-fileexists-replace-on-page",
"mwe-upwiz-fileexists-replace-on-page-v2",
"mwe-upwiz-fileexists-replace-no-link",
"mwe-upwiz-blacklisted-details",
"mwe-upwiz-blacklisted-details-feedback",
"mwe-upwiz-blacklisted-errordialog-title",
"mwe-upwiz-override",
"mwe-upwiz-override-upload",
"mwe-upwiz-next",
@ -342,9 +343,9 @@
"mwe-upwiz-previous",
"mwe-upwiz-home",
"mwe-upwiz-upload-another",
"mwe-upwiz-tooltip-title",
"mwe-upwiz-tooltip-caption",
"mwe-upwiz-tooltip-description",
"mwe-upwiz-description-same-as-caption",
"mwe-upwiz-tooltip-date",
"mwe-upwiz-tooltip-categories",
"mwe-upwiz-tooltip-other",
@ -364,8 +365,11 @@
"mwe-upwiz-error-date-license-unlikely",
"mwe-upwiz-error-too-long",
"mwe-upwiz-error-too-short",
"mwe-upwiz-error-title-blank",
"mwe-upwiz-error-title-too-long",
"mwe-upwiz-error-title-too-short",
"mwe-upwiz-error-caption-blank",
"mwe-upwiz-error-description-blank",
"mwe-upwiz-error-bad-captions",
"mwe-upwiz-error-bad-descriptions",
"mwe-upwiz-error-title-blacklisted",

查看文件

@ -170,7 +170,7 @@
"mwe-upwiz-caption-add": "{{PLURAL:$1|0=Add a caption|Add a caption in another language}}",
"mwe-upwiz-desc": "Description",
"mwe-upwiz-desc-add": "{{PLURAL:$1|0=Add a description|Add a description in another language}}",
"mwe-upwiz-title": "Image title",
"mwe-upwiz-title": "Title",
"mwe-upwiz-date-created": "Date",
"mwe-upwiz-select-date": "Select a date",
"mwe-upwiz-location": "Location",
@ -206,9 +206,11 @@
"mwe-upwiz-deleted-duplicate-unknown-filename": "Unknown filename",
"mwe-upwiz-ok": "OK",
"mwe-upwiz-fileexists-replace-on-page": "A file with this name exists already. If you want to replace it, go to the page for [$2 $1] and replace it there.",
"mwe-upwiz-fileexists-replace-on-page-v2": "This title has been used. Please choose a unique title. Tip: add the date or month or year of the work to make it unique.",
"mwe-upwiz-fileexists-replace-no-link": "Please choose a different title, because this title is already in use by another file.",
"mwe-upwiz-blacklisted-details": "Please choose a different, descriptive title ([$2 more info]).",
"mwe-upwiz-blacklisted-details-feedback": "Please choose a different, descriptive title ([$2 more info]). • [$3 Send feedback]",
"mwe-upwiz-blacklisted-details": "Please write a more informative title ([$2 see examples]).",
"mwe-upwiz-blacklisted-details-feedback": "Please write a more informative title ([$2 see examples]). • [$3 Send feedback]",
"mwe-upwiz-blacklisted-errordialog-title": "Title examples",
"mwe-upwiz-next": "Next",
"mwe-upwiz-next-file": "Continue",
"mwe-upwiz-next-deeds": "Next",
@ -228,8 +230,9 @@
"mwe-upwiz-tooltip-source": "Where this digital file came from — could be a URL, or a book or publication.",
"mwe-upwiz-tooltip-sign": "You can use your wiki user name or your real name.\nIn both cases, this will be linked to your wiki user page.",
"mwe-upwiz-tooltip-title": "Create a unique descriptive title using plain language with spaces. Omit the file extension, if any.",
"mwe-upwiz-tooltip-caption": "Add a one-line explanation of what this file represents, including only the most relevant information.",
"mwe-upwiz-tooltip-description": "Provide all information that will help others understand what this file represents.",
"mwe-upwiz-tooltip-caption": "One-line explanation",
"mwe-upwiz-tooltip-description": "We recommend providing detailed information on this work if you can",
"mwe-upwiz-description-same-as-caption": "Same as caption",
"mwe-upwiz-tooltip-date": "Choose the date this work was created or first published.",
"mwe-upwiz-tooltip-categories": "Add your file to [$1 categories] so as to make it easier to find.",
"mwe-upwiz-tooltip-other": "Any other information you want to include about this work.",
@ -254,8 +257,11 @@
"mwe-upwiz-error-question-blank": "Answer to this question is required",
"mwe-upwiz-error-too-long": "This entry is too long.\nPlease make sure this entry is at most $1 {{PLURAL:$1|character|characters}}.",
"mwe-upwiz-error-too-short": "This entry is too short.\nPlease make sure this entry is at least $1 {{PLURAL:$1|character|characters}}.",
"mwe-upwiz-error-title-blank": "Title is required",
"mwe-upwiz-error-title-too-long": "This title is too long.\nPlease make sure this title is at most $1 {{PLURAL:$1|byte|bytes}}.",
"mwe-upwiz-error-title-too-short": "This title is too short.\nPlease make sure this title is at least $1 {{PLURAL:$1|byte|bytes}}.",
"mwe-upwiz-error-caption-blank": "Caption is required",
"mwe-upwiz-error-description-blank": "Description is required",
"mwe-upwiz-error-bad-captions": "There are problems with some of the captions.",
"mwe-upwiz-error-bad-descriptions": "There are problems with some of the descriptions.",
"mwe-upwiz-error-title-blacklisted": "This title contains some undesirable text. Please revise it.",

查看文件

@ -240,9 +240,11 @@
"mwe-upwiz-deleted-duplicate-unknown-filename": "Text displayed instead of a file name in a list when it is not available.",
"mwe-upwiz-ok": "{{Identical|OK}}",
"mwe-upwiz-fileexists-replace-on-page": "Parameters:\n* $1 is an image name.\n* $2 is an URL.",
"mwe-upwiz-fileexists-replace-on-page-v2": "Used as error message.",
"mwe-upwiz-fileexists-replace-no-link": "Used as error message.",
"mwe-upwiz-blacklisted-details": "Used as error message.\n\nParameters:\n* $1 - file name (unused)\n* $2 - link to more information about the error\n\nSee also {{msg-mw|mwe-upwiz-blacklisted-details-feedback}}",
"mwe-upwiz-blacklisted-details-feedback": "Used as error message.\n\nParameters:\n* $1 - file name (unused)\n* $2 - link to more information about the error\n* $3 - link to feedback page\n\nSee also {{msg-mw|mwe-upwiz-blacklisted-details}}",
"mwe-upwiz-blacklisted-errordialog-title": "Error dialog title text for denylisted file titles.",
"mwe-upwiz-next": "Button text for going to the next Upload Wizard stage.\n{{Identical|Next}}",
"mwe-upwiz-next-file": "{{Identical|Continue}}",
"mwe-upwiz-next-deeds": "{{Identical|Next}}",
@ -263,7 +265,8 @@
"mwe-upwiz-tooltip-sign": "The tooltip documenting the field for the permissions signature. Often a username or a real name of the uploader.",
"mwe-upwiz-tooltip-title": "The tooltip documenting the title field for the file - used as the filename on-wiki.\n\nIdentical to {{msg-mw|upload-form-label-infoform-name-tooltip}}.",
"mwe-upwiz-tooltip-caption": "The tooltip documenting the caption fields on the details page.",
"mwe-upwiz-tooltip-description": "The tooltip documenting the description fields on the details page.\n\nIdentical to {{msg-mw|upload-form-label-infoform-description-tooltip}}.",
"mwe-upwiz-tooltip-description": "The tooltip documenting the description fields on the details page.",
"mwe-upwiz-description-same-as-caption": "Label for the checkbox indicating whether descriptions should be identical to captions.",
"mwe-upwiz-tooltip-date": "The tooltip documenting the date field on the details page, generally representing when the image being uploaded was created.",
"mwe-upwiz-tooltip-categories": "Used as hint for Category input form. Parameters:\n* $1 - the URL https://commons.wikimedia.org/wiki/Commons:Categories (hard-coded)",
"mwe-upwiz-tooltip-other": "The tooltip that documents the 'other info' field on the details page. This is a very open-ended field.",
@ -288,8 +291,11 @@
"mwe-upwiz-error-question-blank": "Error message for when an answer to a question is required.\nSee also:\n* {{msg-mw|Mwe-upwiz-error-blank}}",
"mwe-upwiz-error-too-long": "Used as error message. Parameters:\n* $1 - maximum number of characters\nSee also:\n* {{msg-mw|Mwe-upwiz-error-too-short}}",
"mwe-upwiz-error-too-short": "Used as error message. Parameters:\n* $1 - minimum number of characters\nSee also:\n* {{msg-mw|Mwe-upwiz-error-too-long}}",
"mwe-upwiz-error-title-blank": "Error message when the user hasn't entered any title.",
"mwe-upwiz-error-title-too-long": "Used as error message. Parameters:\n* $1 - maximum number of bytes\nSee also:\n* {{msg-mw|Mwe-upwiz-error-too-short}}",
"mwe-upwiz-error-title-too-short": "Used as error message. Parameters:\n* $1 - minimum number of bytes\nSee also:\n* {{msg-mw|Mwe-upwiz-error-too-long}}",
"mwe-upwiz-error-caption-blank": "Error message when the user hasn't entered any caption.",
"mwe-upwiz-error-description-blank": "Error message when the user hasn't entered any description.",
"mwe-upwiz-error-bad-captions": "Used as error message when something is wrong with one (or more) of the captions.",
"mwe-upwiz-error-bad-descriptions": "Used as error message when something is wrong with one (or more) of the descriptions.",
"mwe-upwiz-error-title-blacklisted": "Error message shown to the user when they have entered a file name that matches the 'blacklist' of banned words.",

查看文件

@ -13,6 +13,7 @@
* @cfg {mw.Message} [placeholder] Placeholder text for input field
* @cfg {mw.Message} [remove] Title text for remove icon
* @cfg {mw.Message} [error] Error message
* @cfg {mw.Message} [errorBlank] Error message for blank input
* @cfg {number} [minLength=0] Minimum input length
* @cfg {number} [maxLength=99999] Maximum input length
* @cfg {Object} [languages] { langcode: text } map of languages
@ -21,6 +22,7 @@
this.config = $.extend( {
required: true,
label: mw.message( '' ),
errorBlank: mw.message( 'mwe-upwiz-error-blank' ),
languages: this.getLanguageOptions()
}, config );
uw.MultipleLanguageInputWidget.super.call( this );
@ -220,7 +222,7 @@
}
// And add some more:
if ( this.required && this.getWikiText() === '' ) {
errors.push( mw.message( 'mwe-upwiz-error-blank' ) );
errors.push( self.config.errorBlank );
}
// TODO Check for duplicate languages
return errors;
@ -228,7 +230,7 @@
};
/**
* @return {Object} Object where the properties are language codes & values are input
* @return {Object} an object of `{ language code: text }` pairs
*/
uw.MultipleLanguageInputWidget.prototype.getValues = function () {
var values = {},
@ -249,6 +251,39 @@
return values;
};
/**
* Initialize the widget with one blank input field.
*/
uw.MultipleLanguageInputWidget.prototype.init = function () {
var config = $.extend(
{}, this.config, { canBeRemoved: !this.config.required }
);
this.clearItems();
this.addItems( [ new uw.SingleLanguageInputWidget( config ) ] );
};
/**
* Populate the widget with a given set of values.
*
* @param {Object} values An object of `{ language code: text }` pairs
*/
uw.MultipleLanguageInputWidget.prototype.populate = function ( values ) {
var language,
currentSubWidget,
subWidgets = [];
for ( language in values ) {
currentSubWidget = new uw.SingleLanguageInputWidget( this.config ); // @todo config not necessarily valid; i.e. missing "canBeRemoved" (either first should not be removable, or it should be and immediately add a new irremovable blank one)
currentSubWidget.setLanguage( language );
currentSubWidget.setText( values[ language ] );
subWidgets.push( currentSubWidget );
}
this.clearItems();
this.addItems( subWidgets );
};
/**
* @inheritdoc
*/
@ -289,7 +324,11 @@
this.removeItems( this.getItems() );
for ( i = 0; i < serialized.inputs.length; i++ ) {
config = $.extend( {}, config, { defaultLanguage: serialized.inputs[ i ].language } );
config = $.extend( {}, config, {
defaultLanguage: serialized.inputs[ i ].language,
canBeRemoved: i > 0 || !this.required
} );
this.addLanguageInput( config, serialized.inputs[ i ].text );
}
};

查看文件

@ -17,9 +17,9 @@
this.config = $.extend( {
inputWidgetConstructor: OO.ui.MultilineTextInputWidget.bind( null, {
classes: [ 'mwe-upwiz-singleLanguageInputWidget-text' ],
autosize: true,
rows: 2
autosize: true
} ),
canBeRemoved: true,
remove: mw.message( '' ),
minLength: 0,
maxLength: 99999
@ -47,7 +47,6 @@
classes: [ 'mwe-upwiz-singleLanguageInputWidget-removeItem' ],
icon: 'trash',
framed: false,
flags: [ 'destructive' ],
title: this.config.remove.exists() ? this.config.remove.text() : ''
} );
@ -69,7 +68,6 @@
this.$body = this.removeButton.$element; // HACK
}
this.$element.append( this.textInput.$element );
};
OO.inheritClass( uw.SingleLanguageInputWidget, uw.DetailsWidget );
OO.mixinClass( uw.SingleLanguageInputWidget, uw.ValidationMessageElement );

查看文件

@ -3,13 +3,16 @@
padding: 10px;
border: 1px solid #eaecf0;
border-radius: 2px;
margin-bottom: 0.8em;
&:not( :first-of-type ) {
border-top: 0;
}
.mwe-upwiz-singleLanguageInputWidget-language {
width: 11.5em;
.mwe-upwiz-singleLanguageInputWidget-language:has( + .mwe-upwiz-singleLanguageInputWidget-removeItem ) {
// Lower the width to accommodate for the delete icon (if there is one)
// 32px is icon width, 8px is this element's margin-right
width: calc( 100% - 8px - 32px );
}
.mwe-upwiz-singleLanguageInputWidget-text {

查看文件

@ -73,13 +73,23 @@
};
/**
* Get a mw.Title object for current value.
* Get a mw.Title object for current input.
*
* @return {mw.Title|null}
*/
uw.TitleDetailsWidget.prototype.getTitle = function () {
var value, extRegex, cleaned, title;
value = this.titleInput.getValue().trim();
return this.buildTitleFromInput( this.titleInput.getValue() );
};
/**
* Get a mw.Title object for a given value.
*
* @param {string} value
* @return {mw.Title}
*/
uw.TitleDetailsWidget.prototype.buildTitleFromInput = function ( value ) {
var extRegex, cleaned, title;
value = value.trim();
if ( !value ) {
return null;
}
@ -92,17 +102,16 @@
/**
* @return {jQuery.Promise}
*/
uw.TitleDetailsWidget.prototype.getErrors = function () {
uw.TitleDetailsWidget.prototype.validateTitleInput = function ( value ) {
var
errors = [],
value = this.titleInput.getValue().trim(),
processDestinationCheck = this.processDestinationCheck,
title = this.getTitle(),
title = this.buildTitleFromInput( value ),
// title length is dependent on DB column size and is bytes rather than characters
length = byteLength( value );
if ( value === '' ) {
errors.push( mw.message( 'mwe-upwiz-error-blank' ) );
errors.push( mw.message( 'mwe-upwiz-error-title-blank' ) );
return $.Deferred().resolve( errors ).promise();
}
@ -144,6 +153,15 @@
} );
};
/**
* @return {jQuery.Promise}
*/
uw.TitleDetailsWidget.prototype.getErrors = function () {
var value = this.titleInput.getValue().trim();
return this.validateTitleInput( value );
};
/**
* Process the result of a destination filename check, return array of mw.Messages objects
* representing errors.
@ -174,11 +192,7 @@
if ( !result.unique.isUnique ) {
// result is NOT unique
if ( result.unique.href ) {
errors.push( mw.message(
'mwe-upwiz-fileexists-replace-on-page',
titleString,
$( '<a>' ).attr( { href: result.unique.href, target: '_blank' } )
) );
errors.push( mw.message( 'mwe-upwiz-fileexists-replace-on-page-v2' ) );
} else {
errors.push( mw.message( 'mwe-upwiz-fileexists-replace-no-link', titleString ) );
}
@ -190,7 +204,10 @@
'mwe-upwiz-blacklisted-details',
titleString,
function () {
mw.errorDialog( $( '<div>' ).msg( result.blacklist.blacklistMessage ) );
mw.errorDialog(
$( '<div>' ).msg( result.blacklist.blacklistMessage ),
mw.message( 'mwe-upwiz-blacklisted-errordialog-title' ).text()
);
}
];
@ -239,7 +256,16 @@
* @param {string} serialized.title Title text
*/
uw.TitleDetailsWidget.prototype.setSerialized = function ( serialized ) {
this.titleInput.setValue( serialized.title );
var titleInput = this.titleInput;
// only prefill the title if the input is valid;
// it makes little sense to confuse users by automatically
// adding input that we already know we'll reject...
this.validateTitleInput( serialized.title ).then( function ( errors ) {
if ( errors.length === 0 ) {
titleInput.setValue( serialized.title );
}
} );
};
}( mw.uploadWizard ) );

查看文件

@ -26,9 +26,7 @@
// Has this details object been attached to the DOM already?
isAttached: false,
/**
* Build the interface and attach all elements - do this on demand.
*/
// Build the interface and attach all elements - do this on demand
buildInterface: function () {
var descriptionRequired, uri,
$moreDetailsWrapperDiv, $moreDetailsDiv,
@ -39,6 +37,9 @@
this.$dataDiv = $( '<div>' ).addClass( 'mwe-upwiz-data' );
//
// Title
//
this.titleDetails = new uw.TitleDetailsWidget( {
// Normalize file extension, e.g. 'JPEG' to 'jpg'
extension: mw.Title.normalizeExtension( this.upload.title.getExtension() ),
@ -47,25 +48,28 @@
} );
this.titleDetailsField = new uw.FieldLayout( this.titleDetails, {
label: mw.message( 'mwe-upwiz-title' ).text(),
help: mw.message( 'mwe-upwiz-tooltip-title' ).text(),
required: true
} );
this.mainFields.push( this.titleDetailsField );
//
// Captions
//
this.captionsDetails = new uw.MultipleLanguageInputWidget( {
inputWidgetConstructor: OO.ui.TextInputWidget.bind( null, {
classes: [ 'mwe-upwiz-singleLanguageInputWidget-text' ]
} ),
required: false,
// Messages: mwe-upwiz-caption-add-0, mwe-upwiz-caption-add-n
required: true,
label: mw.message( 'mwe-upwiz-caption-add' ),
error: mw.message( 'mwe-upwiz-error-bad-captions' ),
errorBlank: mw.message( 'mwe-upwiz-error-caption-blank' ),
remove: mw.message( 'mwe-upwiz-remove-caption' ),
minLength: config.minCaptionLength,
maxLength: config.maxCaptionLength
} );
this.captionsDetailsField = new uw.FieldLayout( this.captionsDetails, {
required: false,
required: true,
classes: [ 'mwe-upwiz-caption' ],
label: mw.message( 'mwe-upwiz-caption' ).text(),
help: mw.message( 'mwe-upwiz-tooltip-caption' ).text()
} );
@ -73,7 +77,9 @@
this.mainFields.push( this.captionsDetailsField );
}
// descriptions
//
// Descriptions
//
// Description is not required if a campaign provides alternative wikitext fields,
// which are assumed to function like a description
descriptionRequired = !(
@ -81,16 +87,82 @@
config.fields.length &&
config.fields[ 0 ].wikitext
);
// Main widget
this.descriptionsDetails = new uw.MultipleLanguageInputWidget( {
required: descriptionRequired,
// Messages: mwe-upwiz-desc-add-0, mwe-upwiz-desc-add-n
classes: [ 'mwe-upwiz-caption' ],
label: mw.message( 'mwe-upwiz-desc-add' ),
error: mw.message( 'mwe-upwiz-error-bad-descriptions' ),
errorBlank: mw.message( 'mwe-upwiz-error-description-blank' ),
remove: mw.message( 'mwe-upwiz-remove-description' ),
minLength: config.minDescriptionLength,
maxLength: config.maxDescriptionLength
} );
this.descriptionsDetailsField = new uw.FieldLayout( this.descriptionsDetails, {
// Checkbox telling whether descriptions must be identical to captions.
// If selected, copy captions to descriptions. This is the default behavior.
this.descriptionSameAsCaptionCheckbox = new OO.ui.CheckboxMultioptionWidget( {
label: mw.message( 'mwe-upwiz-description-same-as-caption' ).text(),
selected: true
} );
this.descriptionSameAsCaption = new OO.ui.CheckboxMultiselectWidget( {
classes: [ 'mwe-upwiz-description-same-as-caption-checkbox' ],
items: [ this.descriptionSameAsCaptionCheckbox ]
} );
// automatically copy captions over to descriptions, but only
// if the "copy" checkbox is selected
this.captionsDetails.on( 'change', function () {
if ( details.descriptionSameAsCaptionCheckbox.isSelected() ) {
details.descriptionsDetails.populate(
details.captionsDetails.getValues()
);
}
} );
this.descriptionSameAsCaptionCheckbox.on( 'change', function () {
if ( details.descriptionSameAsCaptionCheckbox.isSelected() ) {
details.descriptionsDetails.$element.hide();
details.descriptionsDetails.populate(
details.captionsDetails.getValues()
);
} else {
details.descriptionsDetails.init();
details.descriptionsDetails.$element.show();
}
} );
// Description are fickle; they are required (unless, as described earlier,
// a campaign provides alternatives), but are not necessarily visible:
// they default to being hidden and automatically being copied over from
// captions. Unless captions aren't even available, in which case they
// need to be on display after all...
// uw.FieldLayout doesn't currently lend itself to having additional content
// between the title and the validated element (descriptionsDetails in this
// case), and I'd rather avoid reaching into descriptionsDetails to
// conditionally insert the "copy" nodes in the right place.
// We also can't stick the title to the "copy" field, because that's not
// even guaranteed to be something that is supported.
// Best I can think of would be to combine both of these in another,
// separate widget; there's a little complication in forwarding the error
// handling between uw.FieldLayout (or rather, uw.ValidationMessageElement)
// and the actual widget (descriptionsDetails), but I guess that's what
// this comment is for!
this.descriptionsWidget = new OO.ui.Widget();
this.descriptionsWidget.$element.append(
// only show checkbox to copy from captions if captions are enabled)
config.wikibase.enabled && config.wikibase.captions ? this.descriptionSameAsCaption.$element : null,
// toggle visibility of descriptions based on availability of captions
this.descriptionsDetails.$element.toggle( !( config.wikibase.enabled && config.wikibase.captions ) )
);
// if something changes within this widget, then let this widget
// itself propagate the change event, to trigger input validation
// that is managed by uw.FieldLayout (or rather, uw.ValidationMessageElement)
this.descriptionSameAsCaption.connect( this.descriptionsWidget, { change: [ 'emit', 'change' ] } );
this.descriptionsDetails.connect( this.descriptionsWidget, { change: [ 'emit', 'change' ] } );
// forward warnings & errors checks between the combined widget & descriptionsDetails
this.descriptionsWidget.getWarnings = this.descriptionsDetails.getWarnings.bind( this.descriptionsDetails );
this.descriptionsWidget.getErrors = this.descriptionsDetails.getErrors.bind( this.descriptionsDetails );
this.descriptionsDetailsField = new uw.FieldLayout( this.descriptionsWidget, {
required: descriptionRequired,
label: mw.message( 'mwe-upwiz-desc' ).text(),
help: mw.message( 'mwe-upwiz-tooltip-description' ).text()

查看文件

@ -19,6 +19,12 @@
.oo-ui-fieldLayout-body {
margin-bottom: 1em;
}
// override default OOUI warning message style
.oo-ui-fieldLayout-messages,
.oo-ui-fieldLayout-messages > .oo-ui-messageWidget:first-child {
margin: 0;
}
}
.mwe-title {
@ -62,3 +68,7 @@
/* Override mediawiki.icon styles */
background-position: left center;
}
.mwe-upwiz-description-same-as-caption-checkbox {
padding-bottom: 0.5em;
}