resources/deed/uw.deed.OwnWork.js
/*
* This file is part of the MediaWiki extension UploadWizard.
*
* UploadWizard is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* UploadWizard is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with UploadWizard. If not, see <http://www.gnu.org/licenses/>.
*/
( function ( uw ) {
/**
* Set up the form and deed object for the deed option that says these uploads are all the user's own work.
*
* @class
* @param {Object} config The UW config
* @param {mw.UploadWizardUpload[]} uploads Array of uploads that this deed refers to
* @param {mw.Api} api API object - useful for doing previews
*/
uw.deed.OwnWork = function UWDeedOwnWork( config, uploads, api ) {
var self = this,
prefAuthName = mw.user.options.get( 'upwiz_licensename' ),
revealOptionContent = function ( $parent, $child ) {
// hide sub-content for all options
$parent
.find( '.mwe-upwiz-deed-radio-reveal' )
.filter( function () {
// .mwe-upwiz-deed-radio-reveal reveals content when an option is
// selected, but we need it to ignore nested instances in order not
// to reveal content from sub-options until they've been selected
return $( this ).parentsUntil( $parent, '.mwe-upwiz-deed-radio-reveal' ).length === 0;
} )
.hide();
// and reveal only in the selected option
$child
.find( '.mwe-upwiz-deed-radio-reveal' )
.filter( function () {
return $( this ).parentsUntil( $child, '.mwe-upwiz-deed-radio-reveal' ).length === 0;
} )
.show();
};
uw.deed.Abstract.call( this, 'ownwork', config, uploads );
this.uploadCount = uploads.length;
if ( !prefAuthName ) {
prefAuthName = mw.config.get( 'wgUserName' );
}
// Author, hidden
this.authorInput = new OO.ui.HiddenInputWidget( {
name: 'author',
value: prefAuthName
} );
// Main origin radio
this.originRadio = new OO.ui.RadioSelectWidget( {
items: [
new OO.ui.RadioOptionWidget( {
label: mw.message(
'mwe-upwiz-source-ownwork-origin-option-own',
this.uploadCount,
mw.user
).parse(),
data: 'own'
} ),
new OO.ui.RadioOptionWidget( {
label: $( '<div>' )
.msg(
'mwe-upwiz-source-ownwork-origin-option-others',
this.uploadCount,
mw.user
)
.append(
$( '<span>' )
.addClass( 'mwe-upwiz-label-extra' )
.msg(
'mwe-upwiz-source-ownwork-origin-option-others-explain',
this.uploadCount,
mw.user
),
$( '<div>' )
.addClass( 'mwe-upwiz-deed-origin-others-container' )
.addClass( 'mwe-upwiz-deed-radio-reveal' )
.addClass( 'mwe-upwiz-deed-subgroup' )
.append(
$( '<div>' )
.addClass( 'mwe-upwiz-deed-title' )
.msg(
'mwe-upwiz-source-ownwork-origin-option-others-subquestion',
this.uploadCount,
mw.user
)
// this.originOthersRadio.$element will be appended in here
// once it has been created (see below)
)
.hide()
)
.contents(),
data: 'others'
} ),
new OO.ui.RadioOptionWidget( {
label: $( '<div>' )
.append( self.templateOptions.aiGenerated.field.getLabel() )
.append(
$( '<div>' )
.addClass( 'mwe-upwiz-deed-radio-reveal' )
.addClass( 'mwe-upwiz-deed-subgroup' )
.append(
$( '<div>' )
.addClass( 'mwe-upwiz-deed-title' )
.msg(
'mwe-upwiz-source-ownwork-origin-option-ai-instruction',
this.uploadCount,
mw.user
),
$( '<div>' )
// this.aiTextInput.$element will be appended in here
// once it has been created (see below)
.addClass( 'mwe-upwiz-deed-origin-ai-container' ),
$( '<p>' )
.addClass( 'mwe-upwiz-label-extra' )
.msg(
'mwe-upwiz-source-ownwork-origin-option-ai-description',
this.uploadCount,
mw.user
)
).hide()
)
.contents(),
data: 'ai'
} )
],
classes: [ 'mwe-upwiz-deed-origin' ]
} );
this.originRadio.on( 'select', ( selectedOption ) => {
revealOptionContent( self.originRadio.$element, selectedOption.$element );
// this radio option implementation doesn't match the existing generic
// templateOptions implementation (as used for thirdparty works);
// instead of using it directly, we'll simply forward the selected state
// of this radio button to the templateOptions input
self.templateOptions.aiGenerated.input.setSelected( selectedOption.getData() === 'ai' );
// let's also emit a 'change' event to satisfy the listener that checks
// and shows/hides an error message
self.originRadio.emit( 'change' );
} );
// Origin sub-radio for "work of others" option
this.originOthersRadio = new OO.ui.RadioSelectWidget( {
items: [
new OO.ui.RadioOptionWidget( {
label: new OO.ui.HtmlSnippet(
mw.message(
'mwe-upwiz-source-ownwork-origin-option-others-freelicense',
this.uploadCount,
mw.user
).parse()
),
data: 'freelicense'
} ),
new OO.ui.RadioOptionWidget( {
label: mw.message(
'mwe-upwiz-source-ownwork-origin-option-others-nocopyright',
this.uploadCount,
mw.user
).parse(),
data: 'nocopyright'
} ),
new OO.ui.RadioOptionWidget( {
label: $( '<div>' )
.msg(
'mwe-upwiz-source-ownwork-origin-option-others-copyrighted',
this.uploadCount,
mw.user
)
.append(
new OO.ui.MessageWidget( {
type: 'warning',
label: new OO.ui.HtmlSnippet(
mw.message(
'mwe-upwiz-source-ownwork-origin-option-others-copyrighted-warning',
this.uploadCount,
mw.user
).parse()
),
classes: [ 'mwe-upwiz-deed-warning', 'mwe-upwiz-deed-radio-reveal' ]
} ).$element.hide()
)
.contents(),
data: 'copyrighted'
} ),
new OO.ui.RadioOptionWidget( {
label: $( '<div>' )
.msg(
'mwe-upwiz-source-ownwork-origin-option-others-unknown',
this.uploadCount,
mw.user
)
.append(
new OO.ui.MessageWidget( {
type: 'warning',
label: new OO.ui.HtmlSnippet(
mw.message(
'mwe-upwiz-source-ownwork-origin-option-others-unknown-warning',
this.uploadCount,
mw.user
).parse()
),
classes: [ 'mwe-upwiz-deed-warning', 'mwe-upwiz-deed-radio-reveal' ]
} ).$element.hide()
)
.contents(),
data: 'unknown'
} )
]
} );
this.originRadio.$element.find( '.mwe-upwiz-deed-origin-others-container' ).append( this.originOthersRadio.$element );
this.originOthersRadio.on( 'select', ( selectedOption ) => {
revealOptionContent( self.originOthersRadio.$element, selectedOption.$element );
// let's also emit a 'change' event on the parent radio to satisfy the listener
// that checks and shows/hides an error message
self.originRadio.emit( 'change' );
} );
// Origin text input for "work generated by an AI" option
this.aiTextInput = new OO.ui.MultilineTextInputWidget( {
autosize: true,
minLength: this.config.minAiInputLength,
maxLength: this.config.maxAiInputLength
} );
this.aiTextInput.$element.find( 'textarea' ).on( 'click', function () {
// Note: I have not fully figured out exactly why or what is the culprit,
// but it appears that some node is preventing clicks from propagating,
// and it's making it impossible to access this input by mouse;
// this is just a workaround to resolve that
$( this ).trigger( 'focus' );
} );
this.originRadio.$element.find( '.mwe-upwiz-deed-origin-ai-container' ).append( this.aiTextInput.$element );
this.aiTextInput.on( 'change', ( value ) => {
self.setAuthorInputValue( value );
// let's also emit a 'change' event on the parent radio to satisfy the listener
// that checks and shows/hides an error message
self.originRadio.emit( 'change' );
} );
this.originRadio.getErrors = this.getOwnWorkErrors.bind(
this,
this.originRadio,
this.originOthersRadio,
this.aiTextInput
);
this.originRadio.getWarnings = this.getOwnWorkWarnings.bind(
this,
this.originRadio,
this.originOthersRadio,
this.aiTextInput
);
this.originRadioField = new uw.FieldLayout( this.originRadio, {
label: $( '<div>' ).append(
$( '<li>' )
.addClass( 'mwe-upwiz-label-title' )
.append(
mw.message(
'mwe-upwiz-source-ownwork-origin-label',
this.uploadCount,
mw.user
).parseDom()
)
),
required: true
} );
this.licenseInput = new mw.UploadWizardLicenseInput(
this.config.licensing.ownWork,
this.uploadCount,
api
);
this.licenseInput.$element.addClass( 'mwe-upwiz-deed-forms' );
this.licenseInputField = new uw.FieldLayout( this.licenseInput, {
label: $( '<div>' ).append(
$( '<li>' )
.addClass( 'mwe-upwiz-label-title' )
.append( mw.message( 'mwe-upwiz-source-ownwork-question', this.uploadCount, mw.user ).parseDom() )
),
required: true
} );
this.purposeRadio = new OO.ui.RadioSelectWidget( {
items: [
new OO.ui.RadioOptionWidget( {
label: mw.message( 'mwe-upwiz-source-ownwork-purpose-option-knowledge', this.uploadCount, mw.user ).text(),
data: 'knowledge'
} ),
new OO.ui.RadioOptionWidget( {
label: $( '<div>' ).msg( 'mwe-upwiz-source-ownwork-purpose-option-personal-use', this.uploadCount, mw.user )
.append(
new OO.ui.MessageWidget( {
type: 'warning',
label: new OO.ui.HtmlSnippet(
mw.message(
'mwe-upwiz-source-ownwork-purpose-option-personal-warning',
this.uploadCount
).parse()
),
classes: [ 'mwe-upwiz-deed-warning', 'mwe-upwiz-deed-radio-reveal' ]
} ).$element.hide()
)
.contents(),
data: 'personal'
} )
],
classes: [ 'mwe-upwiz-deed-purpose' ]
} );
this.purposeRadio.on( 'select', ( selectedOption ) => {
revealOptionContent( self.purposeRadio.$element, selectedOption.$element );
// let's also emit a 'change' event to satisfy the listener that checks
// and shows/hides an error message
self.purposeRadio.emit( 'change' );
} );
this.purposeRadio.getErrors = function ( thorough ) {
if ( thorough !== true ) {
// `thorough` is the strict checks executed on submit, but we don't want errors
// to change/display every change event
return [];
}
if ( !self.purposeRadio.findSelectedItems() ) {
return [ mw.message( 'mwe-upwiz-deeds-require-selection' ) ];
}
return [];
};
this.purposeRadio.getWarnings = function () {
// not actually adding a warning here; there already is one shown immediately
// on the screen when the "wrong" option is selected
return [];
};
this.purposeField = new uw.FieldLayout( this.purposeRadio, {
label: $( '<div>' ).append(
$( '<li>' )
.addClass( 'mwe-upwiz-label-title' )
.append( mw.message( 'mwe-upwiz-source-ownwork-purpose-label', this.uploadCount, mw.user ).parseDom() )
),
required: true
} );
// grant patent license
this.threeDCount = uploads.filter( this.needsPatentAgreement.bind( this ) ).length;
if ( this.threeDCount > 0 ) {
this.patentAgreementField = this.getPatentAgreementField( uploads );
}
};
OO.inheritClass( uw.deed.OwnWork, uw.deed.Abstract );
uw.deed.OwnWork.prototype.unload = function () {
this.licenseInput.unload();
};
/**
* @return {uw.FieldLayout[]} Fields that need validation
*/
uw.deed.OwnWork.prototype.getFields = function () {
var fields = [ this.originRadioField, this.licenseInputField, this.purposeField ];
if ( this.threeDCount > 0 ) {
fields.push( this.patentAgreementField );
}
return fields;
};
uw.deed.OwnWork.prototype.setFormFields = function ( $selector ) {
var $formFields;
this.$selector = $selector;
this.$form = $( '<form>' );
$formFields = $( '<div>' ).addClass( 'mwe-upwiz-deed-form-internal' ).append(
$( '<ol>' ).append(
$( '<div>' ).addClass( 'mwe-upwiz-ownwork-origin' )
.append( this.originRadioField.$element ),
$( '<div>' ).addClass( 'mwe-upwiz-ownwork-license' )
.append( this.licenseInputField.$element ),
$( '<div>' ).addClass( 'mwe-upwiz-ownwork-purpose' )
.append( this.purposeField.$element )
)
);
// hidden inputs
$formFields.append( this.authorInput.$element );
if ( this.threeDCount > 0 ) {
$formFields.append( this.patentAgreementField.$element );
}
this.$form.append( $formFields ).appendTo( $selector );
this.setDefaultLicense();
};
/**
* OwnWork's default value is different to the default LicenseInput defaults...
* LicenseInput supports multiple default values, but this one does not.
*/
uw.deed.OwnWork.prototype.setDefaultLicense = function () {
var defaultLicenseKey, defaultLicense = {};
defaultLicenseKey = this.getDefaultLicense();
if ( defaultLicenseKey ) {
defaultLicense[ defaultLicenseKey ] = true;
this.licenseInput.setValues( defaultLicense );
}
};
/**
* @inheritdoc
*/
uw.deed.OwnWork.prototype.getSourceWikiText = function () {
return '{{own}}';
};
/**
* @inheritdoc
*/
uw.deed.OwnWork.prototype.getAuthorWikiText = function () {
var author = this.getAuthorInputValue();
if ( author.indexOf( '[' ) >= 0 || author.indexOf( '{' ) >= 0 ) {
return author;
}
return '[[User:' + mw.config.get( 'wgUserName' ) + '|' + author + ']]';
};
/**
* @inheritdoc
*/
uw.deed.OwnWork.prototype.getLicenseWikiText = function ( upload ) {
var wikitext = '';
wikitext += this.licenseInput.getWikiText();
if ( this.needsPatentAgreement( upload ) ) {
wikitext += '\n{{' + this.config.patents.template + '|ownwork}}';
}
return wikitext;
};
/**
* There's no getValue() on a hidden input in OOUI.
* Also handle an AI-generated work.
*
* @return {string}
*/
uw.deed.OwnWork.prototype.getAuthorInputValue = function () {
return this.authorInput.$element.val().trim();
};
uw.deed.OwnWork.prototype.setAuthorInputValue = function ( value ) {
this.authorInput.$element.val( value );
};
/**
* @return {Object}
*/
uw.deed.OwnWork.prototype.getSerialized = function () {
var serialized = $.extend(
uw.deed.Abstract.prototype.getSerialized.call( this ),
{ author: this.getAuthorInputValue() }
);
serialized.origin = this.originRadio.findSelectedItem() && this.originRadio.findSelectedItem().getData();
serialized.originOthers = this.originOthersRadio.findSelectedItem() && this.originOthersRadio.findSelectedItem().getData();
serialized.ai = this.aiTextInput.getValue();
serialized.license = this.licenseInput.getSerialized();
serialized.purpose = this.purposeRadio.findSelectedItem() && this.purposeRadio.findSelectedItem().getData();
return serialized;
};
/**
* @param {Object} serialized
*/
uw.deed.OwnWork.prototype.setSerialized = function ( serialized ) {
uw.deed.Abstract.prototype.setSerialized.call( this, serialized );
if ( serialized.author ) {
this.setAuthorInputValue( serialized.author );
}
if ( serialized.origin ) {
this.originRadio.selectItemByData( serialized.origin );
}
if ( serialized.originOthers ) {
this.originOthersRadio.selectItemByData( serialized.originOthers );
}
if ( serialized.ai ) {
this.aiTextInput.setValue( serialized.ai );
}
this.licenseInput.setSerialized( serialized.license );
if ( serialized.purpose ) {
this.purposeRadio.selectItemByData( serialized.purpose );
}
};
uw.deed.OwnWork.prototype.getDefaultLicense = function () {
var license;
if (
this.config.licensing.defaultType === 'ownwork' ||
this.config.licensing.defaultType === 'choice'
) {
license = this.config.licensing.ownWork.defaults;
return license instanceof Array ? license[ 0 ] : license;
}
};
/**
* @param {OO.ui.RadioSelectWidget} originRadio
* @param {OO.ui.RadioSelectWidget} originOthersRadio
* @param {OO.ui.TextInputWidget} aiTextInput
* @param {boolean} thorough
* @return {Array<string>}
*/
uw.deed.OwnWork.prototype.getOwnWorkErrors = function (
originRadio, originOthersRadio, aiTextInput, thorough
) {
var aiInputValue,
errors = [];
if ( thorough !== true ) {
// `thorough` is the strict checks executed on submit, but we don't want errors
// to change/display every change event
return [];
}
if ( originRadio.findSelectedItem() === null ) {
errors.push( mw.message( 'mwe-upwiz-deeds-require-selection' ) );
} else if (
originRadio.findSelectedItem().getData() === 'others' &&
originOthersRadio.findSelectedItem() === null
) {
errors.push( mw.message( 'mwe-upwiz-deeds-require-selection' ) );
} else if ( originRadio.findSelectedItem().getData() === 'ai' ) {
aiInputValue = aiTextInput.getValue().trim();
if ( aiInputValue === '' ) {
errors.push( mw.message( 'mwe-upwiz-error-question-blank' ) );
} else if ( aiInputValue.length < this.config.minAiInputLength ) {
errors.push( mw.message( 'mwe-upwiz-error-too-short', this.config.minAiInputLength ) );
} else if ( aiInputValue.length > this.config.maxAiInputLength ) {
errors.push( mw.message( 'mwe-upwiz-error-too-long', this.config.maxAiInputLength ) );
}
}
return errors;
};
/**
* @return {jQuery.Promise}
*/
uw.deed.OwnWork.prototype.getOwnWorkWarnings = function () {
return $.Deferred().resolve( [] ).promise();
};
/**
* @param {OO.ui.InputWidget} input
* @return {jQuery.Promise}
*/
uw.deed.OwnWork.prototype.getAuthorErrors = function ( input ) {
var
errors = [],
minLength = this.config.minAuthorLength,
maxLength = this.config.maxAuthorLength,
text = input.getValue().trim();
if ( text === '' ) {
errors.push( mw.message( 'mwe-upwiz-error-signature-blank' ) );
} else if ( text.length < minLength ) {
errors.push( mw.message( 'mwe-upwiz-error-signature-too-short', minLength ) );
} else if ( text.length > maxLength ) {
errors.push( mw.message( 'mwe-upwiz-error-signature-too-long', maxLength ) );
}
return $.Deferred().resolve( errors ).promise();
};
/**
* @return {jQuery.Promise}
*/
uw.deed.OwnWork.prototype.getAuthorWarnings = function () {
return $.Deferred().resolve( [] ).promise();
};
/**
* @param {mw.UploadWizardUpload[]} uploads
* @return {uw.PatentDialog}
*/
uw.deed.OwnWork.prototype.getPatentDialog = function ( uploads ) {
var config = { panels: [ 'warranty', 'license-ownership', 'license-grant' ] };
// Only show filename list when in "details" step & we're showing the dialog for individual files
if ( uploads[ 0 ] && uploads[ 0 ].state === 'details' ) {
config.panels.unshift( 'filelist' );
}
return new uw.PatentDialog( config, this.config, uploads );
};
}( mw.uploadWizard ) );