2016-03-08 21:14:12 +00:00
|
|
|
/*!
|
|
|
|
* VisualEditor user interface MWGalleryDialog class.
|
|
|
|
*
|
2023-12-01 16:06:11 +00:00
|
|
|
* @copyright See AUTHORS.txt
|
2016-03-08 21:14:12 +00:00
|
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Dialog for editing MediaWiki galleries.
|
|
|
|
*
|
|
|
|
* @class
|
2016-11-19 23:11:06 +00:00
|
|
|
* @extends ve.ui.NodeDialog
|
2016-03-08 21:14:12 +00:00
|
|
|
*
|
|
|
|
* @constructor
|
|
|
|
* @param {Object} [config] Configuration options
|
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog = function VeUiMWGalleryDialog() {
|
|
|
|
// Parent constructor
|
|
|
|
ve.ui.MWGalleryDialog.super.apply( this, arguments );
|
|
|
|
|
|
|
|
this.$element.addClass( 've-ui-mwGalleryDialog' );
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Inheritance */
|
|
|
|
|
2016-11-19 23:11:06 +00:00
|
|
|
OO.inheritClass( ve.ui.MWGalleryDialog, ve.ui.NodeDialog );
|
2016-03-08 21:14:12 +00:00
|
|
|
|
|
|
|
/* Static properties */
|
|
|
|
|
|
|
|
ve.ui.MWGalleryDialog.static.name = 'gallery';
|
|
|
|
|
|
|
|
ve.ui.MWGalleryDialog.static.size = 'large';
|
|
|
|
|
|
|
|
ve.ui.MWGalleryDialog.static.title =
|
|
|
|
OO.ui.deferMsg( 'visualeditor-mwgallerydialog-title' );
|
|
|
|
|
|
|
|
ve.ui.MWGalleryDialog.static.modelClasses = [ ve.dm.MWGalleryNode ];
|
|
|
|
|
2016-11-19 23:11:06 +00:00
|
|
|
ve.ui.MWGalleryDialog.static.includeCommands = null;
|
|
|
|
|
|
|
|
ve.ui.MWGalleryDialog.static.excludeCommands = [
|
|
|
|
// No formatting
|
|
|
|
'paragraph',
|
|
|
|
'heading1',
|
|
|
|
'heading2',
|
|
|
|
'heading3',
|
|
|
|
'heading4',
|
|
|
|
'heading5',
|
|
|
|
'heading6',
|
|
|
|
'preformatted',
|
|
|
|
'blockquote',
|
|
|
|
// No block-level markup is allowed inside gallery caption (or gallery image captions)
|
|
|
|
// No tables
|
|
|
|
'insertTable',
|
|
|
|
'deleteTable',
|
|
|
|
'mergeCells',
|
|
|
|
'tableCaption',
|
|
|
|
'tableCellHeader',
|
|
|
|
'tableCellData',
|
|
|
|
// No structure
|
|
|
|
'bullet',
|
|
|
|
'bulletWrapOnce',
|
|
|
|
'number',
|
|
|
|
'numberWrapOnce',
|
|
|
|
'indent',
|
|
|
|
'outdent',
|
|
|
|
// Nested galleries don't work either
|
|
|
|
'gallery'
|
|
|
|
];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the import rules for the surface widget in the dialog
|
|
|
|
*
|
|
|
|
* @see ve.dm.ElementLinearData#sanitize
|
|
|
|
* @return {Object} Import rules
|
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.static.getImportRules = function () {
|
2024-05-21 14:22:56 +00:00
|
|
|
const rules = ve.copy( ve.init.target.constructor.static.importRules );
|
2016-11-19 23:11:06 +00:00
|
|
|
return ve.extendObject(
|
2019-04-09 17:46:19 +00:00
|
|
|
rules,
|
2016-11-19 23:11:06 +00:00
|
|
|
{
|
|
|
|
all: {
|
2019-04-09 17:46:19 +00:00
|
|
|
blacklist: ve.extendObject(
|
|
|
|
{
|
2016-11-19 23:11:06 +00:00
|
|
|
// No block-level markup is allowed inside gallery caption (or gallery image captions).
|
|
|
|
// No lists, no tables.
|
2019-04-09 17:46:19 +00:00
|
|
|
list: true,
|
|
|
|
listItem: true,
|
|
|
|
definitionList: true,
|
|
|
|
definitionListItem: true,
|
|
|
|
table: true,
|
|
|
|
tableCaption: true,
|
|
|
|
tableSection: true,
|
|
|
|
tableRow: true,
|
|
|
|
tableCell: true,
|
|
|
|
mwTable: true,
|
|
|
|
mwTransclusionTableCell: true,
|
2016-11-19 23:11:06 +00:00
|
|
|
// Nested galleries don't work either
|
2019-04-09 17:46:19 +00:00
|
|
|
mwGallery: true
|
|
|
|
},
|
|
|
|
ve.getProp( rules, 'all', 'blacklist' )
|
2016-11-19 23:11:06 +00:00
|
|
|
),
|
|
|
|
// Headings are also possible, but discouraged
|
2019-04-09 17:46:19 +00:00
|
|
|
conversions: ve.extendObject(
|
|
|
|
{
|
|
|
|
mwHeading: 'paragraph'
|
|
|
|
},
|
|
|
|
ve.getProp( rules, 'all', 'conversions' )
|
|
|
|
)
|
2016-11-19 23:11:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2016-03-08 21:14:12 +00:00
|
|
|
/* Methods */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.prototype.initialize = function () {
|
|
|
|
// Parent method
|
|
|
|
ve.ui.MWGalleryDialog.super.prototype.initialize.call( this );
|
|
|
|
|
|
|
|
// States
|
|
|
|
this.highlightedItem = null;
|
|
|
|
this.searchPanelVisible = false;
|
2016-11-19 15:51:21 +00:00
|
|
|
this.selectedFilenames = {};
|
|
|
|
this.initialImageData = [];
|
2018-10-10 10:59:06 +00:00
|
|
|
this.originalMwDataNormalized = null;
|
2019-01-03 15:13:15 +00:00
|
|
|
this.originalGalleryGroupItems = [];
|
2016-11-19 23:11:06 +00:00
|
|
|
this.imageData = {};
|
2018-10-10 13:09:39 +00:00
|
|
|
this.isMobile = OO.ui.isMobile();
|
2016-03-08 21:14:12 +00:00
|
|
|
|
2016-08-04 23:28:53 +00:00
|
|
|
// Default settings
|
|
|
|
this.defaults = mw.config.get( 'wgVisualEditorConfig' ).galleryOptions;
|
|
|
|
|
2017-05-10 18:46:49 +00:00
|
|
|
// Images and options tab panels
|
2018-10-04 12:56:18 +00:00
|
|
|
this.indexLayout = new OO.ui.IndexLayout();
|
2024-05-21 14:22:56 +00:00
|
|
|
const imagesTabPanel = new OO.ui.TabPanelLayout( 'images', {
|
2016-03-08 21:14:12 +00:00
|
|
|
label: ve.msg( 'visualeditor-mwgallerydialog-card-images' ),
|
2018-10-04 12:56:18 +00:00
|
|
|
// Contains a menu layout which handles its own scrolling
|
2016-03-08 21:14:12 +00:00
|
|
|
scrollable: false,
|
|
|
|
padded: true
|
|
|
|
} );
|
2024-05-21 14:22:56 +00:00
|
|
|
const optionsTabPanel = new OO.ui.TabPanelLayout( 'options', {
|
2016-03-08 21:14:12 +00:00
|
|
|
label: ve.msg( 'visualeditor-mwgallerydialog-card-options' ),
|
|
|
|
padded: true
|
|
|
|
} );
|
|
|
|
|
2017-05-10 18:46:49 +00:00
|
|
|
// Images tab panel
|
2016-03-08 21:14:12 +00:00
|
|
|
|
|
|
|
// General layout
|
2024-05-21 14:22:56 +00:00
|
|
|
const imageListContentPanel = new OO.ui.PanelLayout( {
|
2016-03-08 21:14:12 +00:00
|
|
|
padded: true,
|
|
|
|
expanded: true,
|
|
|
|
scrollable: true
|
|
|
|
} );
|
2024-05-21 14:22:56 +00:00
|
|
|
const imageListMenuPanel = new OO.ui.PanelLayout( {
|
2016-11-30 21:48:37 +00:00
|
|
|
padded: true,
|
|
|
|
expanded: true
|
|
|
|
} );
|
2021-10-04 12:53:25 +00:00
|
|
|
this.imageListMenuLayout = new OO.ui.MenuLayout( {
|
2018-10-05 17:11:23 +00:00
|
|
|
menuPosition: this.isMobile ? 'after' : 'bottom',
|
|
|
|
classes: [
|
|
|
|
've-ui-mwGalleryDialog-imageListMenuLayout',
|
2020-04-09 13:33:54 +00:00
|
|
|
this.isMobile ?
|
|
|
|
've-ui-mwGalleryDialog-imageListMenuLayout-mobile' :
|
|
|
|
've-ui-mwGalleryDialog-imageListMenuLayout-desktop'
|
2018-10-05 17:11:23 +00:00
|
|
|
],
|
|
|
|
contentPanel: imageListContentPanel,
|
|
|
|
menuPanel: imageListMenuPanel
|
|
|
|
} );
|
2016-03-08 21:14:12 +00:00
|
|
|
this.editPanel = new OO.ui.PanelLayout( {
|
|
|
|
padded: true,
|
|
|
|
expanded: true,
|
|
|
|
scrollable: true
|
|
|
|
} );
|
|
|
|
this.searchPanel = new OO.ui.PanelLayout( {
|
|
|
|
padded: true,
|
|
|
|
expanded: true,
|
|
|
|
scrollable: true
|
2023-05-28 23:27:51 +00:00
|
|
|
} );
|
2018-10-05 17:09:13 +00:00
|
|
|
this.editSearchStack = new OO.ui.StackLayout( {
|
|
|
|
items: [ this.editPanel, this.searchPanel ]
|
|
|
|
} );
|
2018-10-05 17:11:23 +00:00
|
|
|
this.imageTabMenuLayout = new OO.ui.MenuLayout( {
|
|
|
|
menuPosition: this.isMobile ? 'top' : 'before',
|
|
|
|
classes: [
|
|
|
|
've-ui-mwGalleryDialog-menuLayout',
|
2020-04-09 13:33:54 +00:00
|
|
|
this.isMobile ?
|
|
|
|
've-ui-mwGalleryDialog-menuLayout-mobile' :
|
|
|
|
've-ui-mwGalleryDialog-menuLayout-desktop'
|
2018-10-05 17:11:23 +00:00
|
|
|
],
|
2021-10-04 12:53:25 +00:00
|
|
|
menuPanel: this.imageListMenuLayout,
|
2018-10-05 17:11:23 +00:00
|
|
|
contentPanel: this.editSearchStack
|
|
|
|
} );
|
2016-03-08 21:14:12 +00:00
|
|
|
|
|
|
|
// Menu
|
|
|
|
this.$emptyGalleryMessage = $( '<div>' )
|
|
|
|
.addClass( 'oo-ui-element-hidden' )
|
|
|
|
.text( ve.msg( 'visualeditor-mwgallerydialog-empty-gallery-message' ) );
|
2018-10-10 13:09:39 +00:00
|
|
|
this.galleryGroup = new ve.ui.MWGalleryGroupWidget( {
|
|
|
|
orientation: this.isMobile ? 'horizontal' : 'vertical'
|
|
|
|
} );
|
2016-03-08 21:14:12 +00:00
|
|
|
this.showSearchPanelButton = new OO.ui.ButtonWidget( {
|
2020-03-06 14:09:07 +00:00
|
|
|
label: ve.msg( 'visualeditor-mwgallerydialog-search-button-label' ),
|
|
|
|
invisibleLabel: !!this.isMobile,
|
2018-10-10 13:09:39 +00:00
|
|
|
icon: 'add',
|
|
|
|
framed: false,
|
2016-03-08 21:14:12 +00:00
|
|
|
flags: [ 'progressive' ],
|
|
|
|
classes: [ 've-ui-mwGalleryDialog-show-search-panel-button' ]
|
|
|
|
} );
|
|
|
|
|
|
|
|
// Edit panel
|
2017-06-06 14:46:31 +00:00
|
|
|
this.filenameFieldset = new OO.ui.FieldsetLayout( {
|
|
|
|
label: ve.msg( 'visualeditor-dialog-media-content-filename' ),
|
|
|
|
icon: 'image'
|
|
|
|
} );
|
2016-03-08 21:14:12 +00:00
|
|
|
this.$highlightedImage = $( '<div>' )
|
2023-08-30 18:02:52 +00:00
|
|
|
.addClass( 've-ui-mwGalleryDialog-highlighted-image mw-no-invert' );
|
2017-06-23 19:12:45 +00:00
|
|
|
this.filenameFieldset.$element.append( this.$highlightedImage );
|
2016-11-19 23:11:06 +00:00
|
|
|
this.highlightedCaptionTarget = ve.init.target.createTargetWidget( {
|
|
|
|
includeCommands: this.constructor.static.includeCommands,
|
|
|
|
excludeCommands: this.constructor.static.excludeCommands,
|
|
|
|
importRules: this.constructor.static.getImportRules(),
|
|
|
|
multiline: false
|
2016-03-08 21:14:12 +00:00
|
|
|
} );
|
2016-11-19 23:11:06 +00:00
|
|
|
this.highlightedAltTextInput = new OO.ui.TextInputWidget( {
|
|
|
|
placeholder: ve.msg( 'visualeditor-dialog-media-alttext-section' )
|
2017-06-06 14:46:31 +00:00
|
|
|
} );
|
Add a checkbox to use the image caption as the alt text for galleries
The need for something like this was anticipated in
I2bf43c7e83283f43e047229eb53c244918fcbb0c.
As of version 2.5.0 of Parsoid's output, if alternate text is missing
for an image but a caption is present and image isn't displaying the
caption (ie. it isn't a thumb or frame), then the text content of the
caption will be set as the alt attribute. Parsoid will then drop the
alt attribute when serializing if it matches the caption text, since
it's unnecessary.
However, if the caption is modified and the alt text isn't, the alt will
be serialized. This is likely to be unexpected to editor. They may
have missed that the both the caption and alt are populated in VE and
only edited one place.
Since all of the above is happening only for images where the caption
isn't visible, it doesn't appear to be a much used feature since, at
least for inline images, the experience of caption editing was already
less than optimal.
However, because of a quirk in how galleries are rendered in Parsoid,
this affects gallery caption editing, which is visible and presumably
used more often. See T268250 for a discussion on an improved gallery
structure. But for now, gallery images are effectively inline and set
the alternate text, thus subject to the above.
Here we add a checkbox so that the default is to ignore the alt if it's
the same as the caption. And only make use of it if it differed
originally or was explicitly unchecked to modify.
Bug: T311677
Change-Id: Idf297d8a98995971c5835b0cea56c3317a3626e2
2022-07-04 21:01:40 +00:00
|
|
|
this.altTextSameAsCaption = new OO.ui.CheckboxInputWidget();
|
2016-03-08 21:14:12 +00:00
|
|
|
this.removeButton = new OO.ui.ButtonWidget( {
|
|
|
|
label: ve.msg( 'visualeditor-mwgallerydialog-remove-button-label' ),
|
2018-10-10 13:09:39 +00:00
|
|
|
icon: 'trash',
|
2016-03-08 21:14:12 +00:00
|
|
|
flags: [ 'destructive' ],
|
|
|
|
classes: [ 've-ui-mwGalleryDialog-remove-button' ]
|
|
|
|
} );
|
|
|
|
|
2024-05-21 14:22:56 +00:00
|
|
|
const highlightedCaptionField = new OO.ui.FieldLayout( this.highlightedCaptionTarget, {
|
2018-10-03 21:16:43 +00:00
|
|
|
align: 'top'
|
|
|
|
} );
|
2024-05-21 14:22:56 +00:00
|
|
|
const highlightedCaptionFieldset = new OO.ui.FieldsetLayout( {
|
2018-10-03 21:16:43 +00:00
|
|
|
label: ve.msg( 'visualeditor-dialog-media-content-section' )
|
2016-11-19 23:11:06 +00:00
|
|
|
} );
|
2022-01-22 02:27:22 +00:00
|
|
|
highlightedCaptionFieldset.addItems( [ highlightedCaptionField ] );
|
2016-11-19 23:11:06 +00:00
|
|
|
|
2024-05-21 14:22:56 +00:00
|
|
|
const highlightedAltTextField = new OO.ui.FieldLayout( this.highlightedAltTextInput, {
|
2018-10-03 21:16:43 +00:00
|
|
|
align: 'top'
|
|
|
|
} );
|
2024-05-21 14:22:56 +00:00
|
|
|
const altTextSameAsCaptionField = new OO.ui.FieldLayout( this.altTextSameAsCaption, {
|
Add a checkbox to use the image caption as the alt text for galleries
The need for something like this was anticipated in
I2bf43c7e83283f43e047229eb53c244918fcbb0c.
As of version 2.5.0 of Parsoid's output, if alternate text is missing
for an image but a caption is present and image isn't displaying the
caption (ie. it isn't a thumb or frame), then the text content of the
caption will be set as the alt attribute. Parsoid will then drop the
alt attribute when serializing if it matches the caption text, since
it's unnecessary.
However, if the caption is modified and the alt text isn't, the alt will
be serialized. This is likely to be unexpected to editor. They may
have missed that the both the caption and alt are populated in VE and
only edited one place.
Since all of the above is happening only for images where the caption
isn't visible, it doesn't appear to be a much used feature since, at
least for inline images, the experience of caption editing was already
less than optimal.
However, because of a quirk in how galleries are rendered in Parsoid,
this affects gallery caption editing, which is visible and presumably
used more often. See T268250 for a discussion on an improved gallery
structure. But for now, gallery images are effectively inline and set
the alternate text, thus subject to the above.
Here we add a checkbox so that the default is to ignore the alt if it's
the same as the caption. And only make use of it if it differed
originally or was explicitly unchecked to modify.
Bug: T311677
Change-Id: Idf297d8a98995971c5835b0cea56c3317a3626e2
2022-07-04 21:01:40 +00:00
|
|
|
align: 'inline',
|
|
|
|
label: ve.msg( 'visualeditor-dialog-media-alttext-checkbox' )
|
|
|
|
} );
|
2024-05-21 14:22:56 +00:00
|
|
|
const highlightedAltTextFieldset = new OO.ui.FieldsetLayout( {
|
2018-10-03 21:16:43 +00:00
|
|
|
label: ve.msg( 'visualeditor-dialog-media-alttext-section' )
|
2016-11-19 23:11:06 +00:00
|
|
|
} );
|
Add a checkbox to use the image caption as the alt text for galleries
The need for something like this was anticipated in
I2bf43c7e83283f43e047229eb53c244918fcbb0c.
As of version 2.5.0 of Parsoid's output, if alternate text is missing
for an image but a caption is present and image isn't displaying the
caption (ie. it isn't a thumb or frame), then the text content of the
caption will be set as the alt attribute. Parsoid will then drop the
alt attribute when serializing if it matches the caption text, since
it's unnecessary.
However, if the caption is modified and the alt text isn't, the alt will
be serialized. This is likely to be unexpected to editor. They may
have missed that the both the caption and alt are populated in VE and
only edited one place.
Since all of the above is happening only for images where the caption
isn't visible, it doesn't appear to be a much used feature since, at
least for inline images, the experience of caption editing was already
less than optimal.
However, because of a quirk in how galleries are rendered in Parsoid,
this affects gallery caption editing, which is visible and presumably
used more often. See T268250 for a discussion on an improved gallery
structure. But for now, gallery images are effectively inline and set
the alternate text, thus subject to the above.
Here we add a checkbox so that the default is to ignore the alt if it's
the same as the caption. And only make use of it if it differed
originally or was explicitly unchecked to modify.
Bug: T311677
Change-Id: Idf297d8a98995971c5835b0cea56c3317a3626e2
2022-07-04 21:01:40 +00:00
|
|
|
highlightedAltTextFieldset.addItems( [
|
|
|
|
highlightedAltTextField,
|
|
|
|
altTextSameAsCaptionField
|
|
|
|
] );
|
2016-11-19 23:11:06 +00:00
|
|
|
|
2016-03-08 21:14:12 +00:00
|
|
|
// Search panel
|
2016-12-03 16:31:23 +00:00
|
|
|
this.searchWidget = new mw.widgets.MediaSearchWidget( {
|
2018-10-10 13:09:39 +00:00
|
|
|
rowHeight: this.isMobile ? 100 : 150
|
2016-12-03 16:31:23 +00:00
|
|
|
} );
|
2016-03-08 21:14:12 +00:00
|
|
|
|
2017-05-10 18:46:49 +00:00
|
|
|
// Options tab panel
|
2016-03-08 21:14:12 +00:00
|
|
|
|
|
|
|
// Input widgets
|
2023-07-07 08:25:55 +00:00
|
|
|
this.modeDropdown = new OO.ui.DropdownWidget( { menu: { items: [
|
|
|
|
'traditional',
|
|
|
|
'nolines',
|
|
|
|
'packed',
|
|
|
|
'packed-overlay',
|
|
|
|
'packed-hover',
|
|
|
|
'slideshow'
|
2024-04-30 16:44:25 +00:00
|
|
|
].map( ( data ) => new OO.ui.MenuOptionWidget( {
|
|
|
|
data: data,
|
|
|
|
// Messages used here:
|
|
|
|
// * visualeditor-mwgallerydialog-mode-dropdown-label-traditional
|
|
|
|
// * visualeditor-mwgallerydialog-mode-dropdown-label-nolines
|
|
|
|
// * visualeditor-mwgallerydialog-mode-dropdown-label-packed
|
|
|
|
// * visualeditor-mwgallerydialog-mode-dropdown-label-packed-overlay
|
|
|
|
// * visualeditor-mwgallerydialog-mode-dropdown-label-packed-hover
|
|
|
|
// * visualeditor-mwgallerydialog-mode-dropdown-label-slideshow
|
|
|
|
label: ve.msg( 'visualeditor-mwgallerydialog-mode-dropdown-label-' + data )
|
|
|
|
} ) ) } } );
|
2016-11-19 23:11:06 +00:00
|
|
|
this.captionTarget = ve.init.target.createTargetWidget( {
|
2019-02-02 03:51:35 +00:00
|
|
|
includeCommands: this.constructor.static.includeCommands,
|
|
|
|
excludeCommands: this.constructor.static.excludeCommands,
|
2016-11-19 23:11:06 +00:00
|
|
|
importRules: this.constructor.static.getImportRules(),
|
|
|
|
multiline: false
|
2016-03-08 21:14:12 +00:00
|
|
|
} );
|
|
|
|
this.widthsInput = new OO.ui.NumberInputWidget( {
|
|
|
|
min: 0,
|
2016-08-04 23:28:53 +00:00
|
|
|
showButtons: false,
|
|
|
|
input: {
|
|
|
|
placeholder: ve.msg( 'visualeditor-mwgallerydialog-widths-input-placeholder', this.defaults.imageWidth )
|
|
|
|
}
|
2016-03-08 21:14:12 +00:00
|
|
|
} );
|
|
|
|
this.heightsInput = new OO.ui.NumberInputWidget( {
|
|
|
|
min: 0,
|
2016-08-04 23:28:53 +00:00
|
|
|
showButtons: false,
|
|
|
|
input: {
|
|
|
|
placeholder: ve.msg( 'visualeditor-mwgallerydialog-heights-input-placeholder', this.defaults.imageHeight )
|
|
|
|
}
|
2016-03-08 21:14:12 +00:00
|
|
|
} );
|
2022-02-21 18:07:07 +00:00
|
|
|
this.perRowInput = new OO.ui.NumberInputWidget( {
|
2016-03-08 21:14:12 +00:00
|
|
|
min: 0,
|
|
|
|
showButtons: false
|
|
|
|
} );
|
|
|
|
this.showFilenameCheckbox = new OO.ui.CheckboxInputWidget( {
|
|
|
|
value: 'yes'
|
|
|
|
} );
|
|
|
|
this.classesInput = new OO.ui.TextInputWidget( {
|
|
|
|
placeholder: ve.msg( 'visualeditor-mwgallerydialog-classes-input-placeholder' )
|
|
|
|
} );
|
|
|
|
this.stylesInput = new OO.ui.TextInputWidget( {
|
|
|
|
placeholder: ve.msg( 'visualeditor-mwgallerydialog-styles-input-placeholder' )
|
|
|
|
} );
|
|
|
|
|
|
|
|
// Field layouts
|
2024-05-21 14:22:56 +00:00
|
|
|
const modeField = new OO.ui.FieldLayout( this.modeDropdown, {
|
2016-03-08 21:14:12 +00:00
|
|
|
label: ve.msg( 'visualeditor-mwgallerydialog-mode-field-label' )
|
|
|
|
} );
|
2024-05-21 14:22:56 +00:00
|
|
|
const captionField = new OO.ui.FieldLayout( this.captionTarget, {
|
2018-10-10 13:09:39 +00:00
|
|
|
label: ve.msg( 'visualeditor-mwgallerydialog-caption-field-label' ),
|
|
|
|
align: this.isMobile ? 'top' : 'left'
|
2016-03-08 21:14:12 +00:00
|
|
|
} );
|
2024-05-21 14:22:56 +00:00
|
|
|
const widthsField = new OO.ui.FieldLayout( this.widthsInput, {
|
2016-03-08 21:14:12 +00:00
|
|
|
label: ve.msg( 'visualeditor-mwgallerydialog-widths-field-label' )
|
|
|
|
} );
|
2024-05-21 14:22:56 +00:00
|
|
|
const heightsField = new OO.ui.FieldLayout( this.heightsInput, {
|
2016-03-08 21:14:12 +00:00
|
|
|
label: ve.msg( 'visualeditor-mwgallerydialog-heights-field-label' )
|
|
|
|
} );
|
2024-05-21 14:22:56 +00:00
|
|
|
const perRowField = new OO.ui.FieldLayout( this.perRowInput, {
|
2016-03-08 21:14:12 +00:00
|
|
|
label: ve.msg( 'visualeditor-mwgallerydialog-perrow-field-label' )
|
|
|
|
} );
|
2024-05-21 14:22:56 +00:00
|
|
|
const showFilenameField = new OO.ui.FieldLayout( this.showFilenameCheckbox, {
|
2016-03-08 21:14:12 +00:00
|
|
|
label: ve.msg( 'visualeditor-mwgallerydialog-show-filename-field-label' )
|
|
|
|
} );
|
2024-05-21 14:22:56 +00:00
|
|
|
const classesField = new OO.ui.FieldLayout( this.classesInput, {
|
2016-03-08 21:14:12 +00:00
|
|
|
label: ve.msg( 'visualeditor-mwgallerydialog-classes-field-label' )
|
|
|
|
} );
|
2024-05-21 14:22:56 +00:00
|
|
|
const stylesField = new OO.ui.FieldLayout( this.stylesInput, {
|
2016-03-08 21:14:12 +00:00
|
|
|
label: ve.msg( 'visualeditor-mwgallerydialog-styles-field-label' )
|
|
|
|
} );
|
|
|
|
|
|
|
|
// Append everything
|
2018-10-05 17:11:23 +00:00
|
|
|
imageListMenuPanel.$element.append(
|
|
|
|
this.showSearchPanelButton.$element
|
2016-03-08 21:14:12 +00:00
|
|
|
);
|
2018-10-05 17:11:23 +00:00
|
|
|
imageListContentPanel.$element.append(
|
|
|
|
this.$emptyGalleryMessage,
|
|
|
|
this.galleryGroup.$element
|
2018-10-05 17:09:13 +00:00
|
|
|
);
|
|
|
|
this.editPanel.$element.append(
|
|
|
|
this.filenameFieldset.$element,
|
|
|
|
highlightedCaptionFieldset.$element,
|
|
|
|
highlightedAltTextFieldset.$element,
|
|
|
|
this.removeButton.$element
|
|
|
|
);
|
|
|
|
this.searchPanel.$element.append(
|
|
|
|
this.searchWidget.$element
|
2016-03-08 21:14:12 +00:00
|
|
|
);
|
2017-05-10 18:46:49 +00:00
|
|
|
imagesTabPanel.$element.append(
|
2018-10-05 17:11:23 +00:00
|
|
|
this.imageTabMenuLayout.$element
|
2016-03-08 21:14:12 +00:00
|
|
|
);
|
2017-05-10 18:46:49 +00:00
|
|
|
optionsTabPanel.$element.append(
|
2016-03-08 21:14:12 +00:00
|
|
|
modeField.$element,
|
|
|
|
captionField.$element,
|
|
|
|
widthsField.$element,
|
|
|
|
heightsField.$element,
|
2022-02-21 18:07:07 +00:00
|
|
|
perRowField.$element,
|
2016-03-08 21:14:12 +00:00
|
|
|
showFilenameField.$element,
|
|
|
|
classesField.$element,
|
|
|
|
stylesField.$element
|
|
|
|
);
|
2017-05-10 18:46:49 +00:00
|
|
|
this.indexLayout.addTabPanels( [
|
|
|
|
imagesTabPanel,
|
|
|
|
optionsTabPanel
|
2016-03-08 21:14:12 +00:00
|
|
|
] );
|
2016-10-12 05:07:37 +00:00
|
|
|
this.$body.append( this.indexLayout.$element );
|
2016-03-08 21:14:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.prototype.getSetupProcess = function ( data ) {
|
|
|
|
return ve.ui.MWGalleryDialog.super.prototype.getSetupProcess.call( this, data )
|
2024-04-30 21:25:39 +00:00
|
|
|
.next( () => {
|
2024-05-21 14:22:56 +00:00
|
|
|
const namespaceIds = mw.config.get( 'wgNamespaceIds' ),
|
2018-10-10 10:59:06 +00:00
|
|
|
mwData = this.selectedNode && this.selectedNode.getAttribute( 'mw' ),
|
|
|
|
attributes = mwData && mwData.attrs,
|
2016-11-19 23:11:06 +00:00
|
|
|
captionNode = this.selectedNode && this.selectedNode.getCaptionNode(),
|
2019-02-08 19:18:22 +00:00
|
|
|
imageNodes = this.selectedNode && this.selectedNode.getImageNodes(),
|
|
|
|
isReadOnly = this.isReadOnly();
|
2016-11-19 23:11:06 +00:00
|
|
|
|
2018-11-15 20:16:43 +00:00
|
|
|
this.anyItemModified = false;
|
2016-03-08 21:14:12 +00:00
|
|
|
|
2017-05-10 18:46:49 +00:00
|
|
|
// Images tab panel
|
2016-03-08 21:14:12 +00:00
|
|
|
// If editing an existing gallery, populate with the images...
|
|
|
|
if ( this.selectedNode ) {
|
2024-05-21 14:22:56 +00:00
|
|
|
const imageTitles = [];
|
2016-11-19 23:11:06 +00:00
|
|
|
|
2024-05-21 14:22:56 +00:00
|
|
|
for ( let i = 0, ilen = imageNodes.length; i < ilen; i++ ) {
|
|
|
|
const image = imageNodes[ i ];
|
|
|
|
const resourceTitle = mw.Title.newFromText( mw.libs.ve.normalizeParsoidResourceName( image.getAttribute( 'resource' ) ), namespaceIds.file );
|
2020-08-17 18:13:42 +00:00
|
|
|
if ( !resourceTitle ) {
|
|
|
|
continue;
|
|
|
|
}
|
2024-05-21 14:22:56 +00:00
|
|
|
const resource = resourceTitle.getPrefixedText();
|
|
|
|
const imageCaptionNode = image.getCaptionNode();
|
2016-11-19 23:11:06 +00:00
|
|
|
imageTitles.push( resource );
|
|
|
|
this.initialImageData.push( {
|
|
|
|
resource: resource,
|
|
|
|
altText: image.getAttribute( 'altText' ),
|
Add a checkbox to use the image caption as the alt text for galleries
The need for something like this was anticipated in
I2bf43c7e83283f43e047229eb53c244918fcbb0c.
As of version 2.5.0 of Parsoid's output, if alternate text is missing
for an image but a caption is present and image isn't displaying the
caption (ie. it isn't a thumb or frame), then the text content of the
caption will be set as the alt attribute. Parsoid will then drop the
alt attribute when serializing if it matches the caption text, since
it's unnecessary.
However, if the caption is modified and the alt text isn't, the alt will
be serialized. This is likely to be unexpected to editor. They may
have missed that the both the caption and alt are populated in VE and
only edited one place.
Since all of the above is happening only for images where the caption
isn't visible, it doesn't appear to be a much used feature since, at
least for inline images, the experience of caption editing was already
less than optimal.
However, because of a quirk in how galleries are rendered in Parsoid,
this affects gallery caption editing, which is visible and presumably
used more often. See T268250 for a discussion on an improved gallery
structure. But for now, gallery images are effectively inline and set
the alternate text, thus subject to the above.
Here we add a checkbox so that the default is to ignore the alt if it's
the same as the caption. And only make use of it if it differed
originally or was explicitly unchecked to modify.
Bug: T311677
Change-Id: Idf297d8a98995971c5835b0cea56c3317a3626e2
2022-07-04 21:01:40 +00:00
|
|
|
altTextSame: image.getAttribute( 'altTextSame' ),
|
2023-02-15 01:53:10 +00:00
|
|
|
href: image.getAttribute( 'href' ),
|
2016-11-19 23:11:06 +00:00
|
|
|
src: image.getAttribute( 'src' ),
|
|
|
|
height: image.getAttribute( 'height' ),
|
|
|
|
width: image.getAttribute( 'width' ),
|
2022-02-14 09:07:10 +00:00
|
|
|
captionDocument: this.createCaptionDocument( imageCaptionNode ),
|
2022-05-23 14:18:20 +00:00
|
|
|
tagName: image.getAttribute( 'tagName' ),
|
2023-02-16 02:28:35 +00:00
|
|
|
isError: image.getAttribute( 'isError' ),
|
2023-05-11 00:19:47 +00:00
|
|
|
errorText: image.getAttribute( 'errorText' ),
|
|
|
|
imageClassAttr: image.getAttribute( 'imageClassAttr' ),
|
2023-10-24 23:12:22 +00:00
|
|
|
imgWrapperClassAttr: image.getAttribute( 'imgWrapperClassAttr' ),
|
2023-10-25 17:23:22 +00:00
|
|
|
mw: image.getAttribute( 'mw' ),
|
|
|
|
mediaClass: image.getAttribute( 'mediaClass' ),
|
|
|
|
mediaTag: image.getAttribute( 'mediaTag' )
|
2017-07-18 11:55:33 +00:00
|
|
|
} );
|
2016-11-19 23:11:06 +00:00
|
|
|
}
|
2016-03-08 21:14:12 +00:00
|
|
|
|
|
|
|
// Populate menu and edit panels
|
|
|
|
this.imagesPromise = this.requestImages( {
|
2016-09-30 16:07:10 +00:00
|
|
|
titles: imageTitles
|
2024-04-30 16:44:25 +00:00
|
|
|
} ).done( () => {
|
2024-05-01 12:32:49 +00:00
|
|
|
this.onHighlightItem();
|
2016-03-08 21:14:12 +00:00
|
|
|
} );
|
|
|
|
|
|
|
|
// ...Otherwise show the search panel
|
|
|
|
} else {
|
|
|
|
this.toggleEmptyGalleryMessage( true );
|
|
|
|
this.toggleSearchPanel( true );
|
|
|
|
}
|
|
|
|
|
2017-05-10 18:46:49 +00:00
|
|
|
// Options tab panel
|
2016-03-08 21:14:12 +00:00
|
|
|
|
|
|
|
// Set options
|
2024-05-21 14:22:56 +00:00
|
|
|
const mode = attributes && attributes.mode || this.defaults.mode;
|
|
|
|
const widths = attributes && parseInt( attributes.widths ) || '';
|
|
|
|
const heights = attributes && parseInt( attributes.heights ) || '';
|
|
|
|
const perRow = attributes && attributes.perrow || '';
|
|
|
|
const showFilename = attributes && attributes.showfilename === 'yes';
|
|
|
|
const classes = attributes && attributes.class || '';
|
|
|
|
const styles = attributes && attributes.style || '';
|
2016-11-19 23:11:06 +00:00
|
|
|
// Caption
|
|
|
|
this.captionDocument = this.createCaptionDocument( captionNode );
|
2016-03-08 21:14:12 +00:00
|
|
|
|
|
|
|
// Populate options panel
|
|
|
|
this.modeDropdown.getMenu().selectItemByData( mode );
|
|
|
|
this.widthsInput.setValue( widths );
|
|
|
|
this.heightsInput.setValue( heights );
|
2022-02-21 18:07:07 +00:00
|
|
|
this.perRowInput.setValue( perRow );
|
2016-03-08 21:14:12 +00:00
|
|
|
this.showFilenameCheckbox.setSelected( showFilename );
|
|
|
|
this.classesInput.setValue( classes );
|
|
|
|
this.stylesInput.setValue( styles );
|
2016-11-19 23:11:06 +00:00
|
|
|
// Caption
|
|
|
|
this.captionTarget.setDocument( this.captionDocument );
|
2019-02-08 19:18:22 +00:00
|
|
|
this.captionTarget.setReadOnly( isReadOnly );
|
2016-03-08 21:14:12 +00:00
|
|
|
|
2018-10-10 10:59:06 +00:00
|
|
|
if ( mwData ) {
|
|
|
|
this.originalMwDataNormalized = ve.copy( mwData );
|
|
|
|
this.updateMwData( this.originalMwDataNormalized );
|
|
|
|
}
|
|
|
|
|
2022-07-26 19:50:19 +00:00
|
|
|
this.highlightedAltTextInput.setReadOnly( isReadOnly || this.altTextSameAsCaption.isSelected() );
|
Add a checkbox to use the image caption as the alt text for galleries
The need for something like this was anticipated in
I2bf43c7e83283f43e047229eb53c244918fcbb0c.
As of version 2.5.0 of Parsoid's output, if alternate text is missing
for an image but a caption is present and image isn't displaying the
caption (ie. it isn't a thumb or frame), then the text content of the
caption will be set as the alt attribute. Parsoid will then drop the
alt attribute when serializing if it matches the caption text, since
it's unnecessary.
However, if the caption is modified and the alt text isn't, the alt will
be serialized. This is likely to be unexpected to editor. They may
have missed that the both the caption and alt are populated in VE and
only edited one place.
Since all of the above is happening only for images where the caption
isn't visible, it doesn't appear to be a much used feature since, at
least for inline images, the experience of caption editing was already
less than optimal.
However, because of a quirk in how galleries are rendered in Parsoid,
this affects gallery caption editing, which is visible and presumably
used more often. See T268250 for a discussion on an improved gallery
structure. But for now, gallery images are effectively inline and set
the alternate text, thus subject to the above.
Here we add a checkbox so that the default is to ignore the alt if it's
the same as the caption. And only make use of it if it differed
originally or was explicitly unchecked to modify.
Bug: T311677
Change-Id: Idf297d8a98995971c5835b0cea56c3317a3626e2
2022-07-04 21:01:40 +00:00
|
|
|
this.altTextSameAsCaption.setDisabled( isReadOnly );
|
2019-02-08 19:18:22 +00:00
|
|
|
this.modeDropdown.setDisabled( isReadOnly );
|
|
|
|
this.widthsInput.setReadOnly( isReadOnly );
|
|
|
|
this.heightsInput.setReadOnly( isReadOnly );
|
2022-02-21 18:07:07 +00:00
|
|
|
this.perRowInput.setReadOnly( isReadOnly );
|
2019-02-08 19:18:22 +00:00
|
|
|
this.showFilenameCheckbox.setDisabled( isReadOnly );
|
|
|
|
this.classesInput.setReadOnly( isReadOnly );
|
|
|
|
this.stylesInput.setReadOnly( isReadOnly );
|
|
|
|
|
2019-02-24 12:38:03 +00:00
|
|
|
this.showSearchPanelButton.setDisabled( isReadOnly );
|
|
|
|
this.removeButton.setDisabled( isReadOnly );
|
|
|
|
|
|
|
|
this.galleryGroup.toggleDraggable( !isReadOnly );
|
|
|
|
|
2016-12-03 15:36:19 +00:00
|
|
|
// Disable fields depending on mode
|
|
|
|
this.onModeDropdownChange();
|
|
|
|
|
2016-03-08 21:14:12 +00:00
|
|
|
// Add event handlers
|
2016-10-12 05:07:37 +00:00
|
|
|
this.indexLayout.connect( this, { set: 'updateDialogSize' } );
|
2016-03-08 21:14:12 +00:00
|
|
|
this.searchWidget.getResults().connect( this, { choose: 'onSearchResultsChoose' } );
|
|
|
|
this.showSearchPanelButton.connect( this, { click: 'onShowSearchPanelButtonClick' } );
|
|
|
|
this.galleryGroup.connect( this, { editItem: 'onHighlightItem' } );
|
2018-11-15 20:16:43 +00:00
|
|
|
this.galleryGroup.connect( this, { change: 'updateActions' } );
|
2016-03-08 21:14:12 +00:00
|
|
|
this.removeButton.connect( this, { click: 'onRemoveItem' } );
|
|
|
|
this.modeDropdown.getMenu().connect( this, { choose: 'onModeDropdownChange' } );
|
2017-03-10 16:45:09 +00:00
|
|
|
this.widthsInput.connect( this, { change: 'updateActions' } );
|
|
|
|
this.heightsInput.connect( this, { change: 'updateActions' } );
|
2022-02-21 18:07:07 +00:00
|
|
|
this.perRowInput.connect( this, { change: 'updateActions' } );
|
2017-03-10 16:45:09 +00:00
|
|
|
this.showFilenameCheckbox.connect( this, { change: 'updateActions' } );
|
|
|
|
this.classesInput.connect( this, { change: 'updateActions' } );
|
|
|
|
this.stylesInput.connect( this, { change: 'updateActions' } );
|
2018-11-15 20:16:43 +00:00
|
|
|
this.captionTarget.connect( this, { change: 'updateActions' } );
|
|
|
|
this.highlightedAltTextInput.connect( this, { change: 'updateActions' } );
|
Add a checkbox to use the image caption as the alt text for galleries
The need for something like this was anticipated in
I2bf43c7e83283f43e047229eb53c244918fcbb0c.
As of version 2.5.0 of Parsoid's output, if alternate text is missing
for an image but a caption is present and image isn't displaying the
caption (ie. it isn't a thumb or frame), then the text content of the
caption will be set as the alt attribute. Parsoid will then drop the
alt attribute when serializing if it matches the caption text, since
it's unnecessary.
However, if the caption is modified and the alt text isn't, the alt will
be serialized. This is likely to be unexpected to editor. They may
have missed that the both the caption and alt are populated in VE and
only edited one place.
Since all of the above is happening only for images where the caption
isn't visible, it doesn't appear to be a much used feature since, at
least for inline images, the experience of caption editing was already
less than optimal.
However, because of a quirk in how galleries are rendered in Parsoid,
this affects gallery caption editing, which is visible and presumably
used more often. See T268250 for a discussion on an improved gallery
structure. But for now, gallery images are effectively inline and set
the alternate text, thus subject to the above.
Here we add a checkbox so that the default is to ignore the alt if it's
the same as the caption. And only make use of it if it differed
originally or was explicitly unchecked to modify.
Bug: T311677
Change-Id: Idf297d8a98995971c5835b0cea56c3317a3626e2
2022-07-04 21:01:40 +00:00
|
|
|
this.altTextSameAsCaption.connect( this, { change: 'onAltTextSameAsCaptionChange' } );
|
2023-03-09 19:40:19 +00:00
|
|
|
this.highlightedCaptionTarget.connect( this, { change: 'onHighlightedCaptionTargetChange' } );
|
2018-06-21 21:08:17 +00:00
|
|
|
|
|
|
|
return this.imagesPromise;
|
2024-04-30 21:25:39 +00:00
|
|
|
} );
|
2016-03-08 21:14:12 +00:00
|
|
|
};
|
|
|
|
|
2016-11-19 23:11:06 +00:00
|
|
|
/**
|
|
|
|
* Get a new caption document for the gallery caption or an image caption.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {ve.dm.MWGalleryCaptionNode|ve.dm.MWGalleryImageCaptionNode|null} captionNode
|
|
|
|
* @return {ve.dm.Document}
|
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.prototype.createCaptionDocument = function ( captionNode ) {
|
|
|
|
if ( captionNode && captionNode.getLength() > 0 ) {
|
|
|
|
return this.selectedNode.getDocument().cloneFromRange( captionNode.getRange() );
|
|
|
|
} else {
|
|
|
|
return this.getFragment().getDocument().cloneWithData( [
|
|
|
|
{ type: 'paragraph', internal: { generated: 'wrapper' } },
|
|
|
|
{ type: '/paragraph' },
|
|
|
|
{ type: 'internalList' },
|
|
|
|
{ type: '/internalList' }
|
|
|
|
] );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-03-08 21:14:12 +00:00
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.prototype.getReadyProcess = function ( data ) {
|
|
|
|
return ve.ui.MWGalleryDialog.super.prototype.getReadyProcess.call( this, data )
|
2024-04-30 21:25:39 +00:00
|
|
|
.next( () => {
|
2016-09-27 20:50:15 +00:00
|
|
|
this.searchWidget.getQuery().focus().select();
|
2024-04-30 21:25:39 +00:00
|
|
|
} );
|
2016-03-08 21:14:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.prototype.getTeardownProcess = function ( data ) {
|
|
|
|
return ve.ui.MWGalleryDialog.super.prototype.getTeardownProcess.call( this, data )
|
2024-04-30 21:25:39 +00:00
|
|
|
.first( () => {
|
2018-10-05 17:11:23 +00:00
|
|
|
// Layouts
|
|
|
|
this.indexLayout.setTabPanel( 'images' );
|
|
|
|
this.indexLayout.resetScroll();
|
|
|
|
this.imageTabMenuLayout.resetScroll();
|
|
|
|
|
2017-06-30 12:35:30 +00:00
|
|
|
// Widgets
|
2016-03-08 21:14:12 +00:00
|
|
|
this.galleryGroup.clearItems();
|
|
|
|
this.searchWidget.getQuery().setValue( '' );
|
|
|
|
this.searchWidget.teardown();
|
2017-06-30 12:35:30 +00:00
|
|
|
|
|
|
|
// States
|
|
|
|
this.highlightedItem = null;
|
2016-03-08 21:14:12 +00:00
|
|
|
this.searchPanelVisible = false;
|
2017-06-30 12:35:30 +00:00
|
|
|
this.selectedFilenames = {};
|
|
|
|
this.initialImageData = [];
|
2018-10-10 10:59:06 +00:00
|
|
|
this.originalMwDataNormalized = null;
|
2019-01-03 15:13:15 +00:00
|
|
|
this.originalGalleryGroupItems = [];
|
2016-03-08 21:14:12 +00:00
|
|
|
|
|
|
|
// Disconnect events
|
2016-10-12 05:07:37 +00:00
|
|
|
this.indexLayout.disconnect( this );
|
2016-03-08 21:14:12 +00:00
|
|
|
this.searchWidget.getResults().disconnect( this );
|
|
|
|
this.showSearchPanelButton.disconnect( this );
|
|
|
|
this.galleryGroup.disconnect( this );
|
|
|
|
this.removeButton.disconnect( this );
|
|
|
|
this.modeDropdown.disconnect( this );
|
2017-03-10 16:45:09 +00:00
|
|
|
this.widthsInput.disconnect( this );
|
|
|
|
this.heightsInput.disconnect( this );
|
2022-02-21 18:07:07 +00:00
|
|
|
this.perRowInput.disconnect( this );
|
2017-03-10 16:45:09 +00:00
|
|
|
this.showFilenameCheckbox.disconnect( this );
|
|
|
|
this.classesInput.disconnect( this );
|
|
|
|
this.stylesInput.disconnect( this );
|
2018-11-15 20:16:43 +00:00
|
|
|
this.highlightedAltTextInput.disconnect( this );
|
Add a checkbox to use the image caption as the alt text for galleries
The need for something like this was anticipated in
I2bf43c7e83283f43e047229eb53c244918fcbb0c.
As of version 2.5.0 of Parsoid's output, if alternate text is missing
for an image but a caption is present and image isn't displaying the
caption (ie. it isn't a thumb or frame), then the text content of the
caption will be set as the alt attribute. Parsoid will then drop the
alt attribute when serializing if it matches the caption text, since
it's unnecessary.
However, if the caption is modified and the alt text isn't, the alt will
be serialized. This is likely to be unexpected to editor. They may
have missed that the both the caption and alt are populated in VE and
only edited one place.
Since all of the above is happening only for images where the caption
isn't visible, it doesn't appear to be a much used feature since, at
least for inline images, the experience of caption editing was already
less than optimal.
However, because of a quirk in how galleries are rendered in Parsoid,
this affects gallery caption editing, which is visible and presumably
used more often. See T268250 for a discussion on an improved gallery
structure. But for now, gallery images are effectively inline and set
the alternate text, thus subject to the above.
Here we add a checkbox so that the default is to ignore the alt if it's
the same as the caption. And only make use of it if it differed
originally or was explicitly unchecked to modify.
Bug: T311677
Change-Id: Idf297d8a98995971c5835b0cea56c3317a3626e2
2022-07-04 21:01:40 +00:00
|
|
|
this.altTextSameAsCaption.disconnect( this );
|
2018-11-15 20:16:43 +00:00
|
|
|
this.captionTarget.disconnect( this );
|
|
|
|
this.highlightedCaptionTarget.disconnect( this );
|
2018-10-10 10:59:06 +00:00
|
|
|
|
2024-04-30 21:25:39 +00:00
|
|
|
} );
|
2016-03-08 21:14:12 +00:00
|
|
|
};
|
|
|
|
|
2016-11-19 23:11:06 +00:00
|
|
|
ve.ui.MWGalleryDialog.prototype.getActionProcess = function ( action ) {
|
|
|
|
return ve.ui.MWGalleryDialog.super.prototype.getActionProcess.call( this, action )
|
2024-04-30 21:25:39 +00:00
|
|
|
.next( () => {
|
2016-11-19 23:11:06 +00:00
|
|
|
if ( action === 'done' ) {
|
|
|
|
// Save the input values for the highlighted item
|
|
|
|
this.updateHighlightedItem();
|
|
|
|
|
|
|
|
this.insertOrUpdateNode();
|
|
|
|
this.close( { action: 'done' } );
|
|
|
|
}
|
2024-04-30 21:25:39 +00:00
|
|
|
} );
|
2016-11-19 23:11:06 +00:00
|
|
|
};
|
|
|
|
|
2016-03-08 21:14:12 +00:00
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.prototype.getBodyHeight = function () {
|
|
|
|
return 600;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2017-05-10 18:46:49 +00:00
|
|
|
* Request the images for the images tab panel menu
|
2016-03-08 21:14:12 +00:00
|
|
|
*
|
|
|
|
* @param {Object} options Options for the request
|
2017-06-30 12:51:24 +00:00
|
|
|
* @return {jQuery.Promise} Promise which resolves when image data has been fetched
|
2016-03-08 21:14:12 +00:00
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.prototype.requestImages = function ( options ) {
|
2024-05-21 14:22:56 +00:00
|
|
|
const promises = options.titles.map( ( title ) => ve.init.platform.galleryImageInfoCache.get( title ) );
|
2022-02-21 18:07:07 +00:00
|
|
|
|
2019-11-02 16:29:11 +00:00
|
|
|
return ve.promiseAll( promises )
|
2024-05-01 12:32:49 +00:00
|
|
|
.done( ( ...args ) => {
|
2024-05-21 14:22:56 +00:00
|
|
|
const resp = {};
|
2024-05-01 12:32:49 +00:00
|
|
|
options.titles.forEach( ( title, i ) => {
|
|
|
|
resp[ title ] = args[ i ];
|
|
|
|
} );
|
|
|
|
this.onRequestImagesSuccess( resp );
|
2016-09-30 16:07:10 +00:00
|
|
|
} );
|
2016-03-08 21:14:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create items for the returned images and add them to the gallery group
|
|
|
|
*
|
|
|
|
* @param {Object} response jQuery response object
|
|
|
|
*/
|
2016-09-30 16:07:10 +00:00
|
|
|
ve.ui.MWGalleryDialog.prototype.onRequestImagesSuccess = function ( response ) {
|
2024-05-21 14:22:56 +00:00
|
|
|
const thumbUrls = {},
|
2018-10-10 13:09:39 +00:00
|
|
|
items = [],
|
2019-02-24 12:38:03 +00:00
|
|
|
config = { isMobile: this.isMobile, draggable: !this.isReadOnly() };
|
2016-03-08 21:14:12 +00:00
|
|
|
|
2024-05-21 14:22:56 +00:00
|
|
|
let title;
|
2016-09-30 16:07:10 +00:00
|
|
|
for ( title in response ) {
|
2016-11-19 23:11:06 +00:00
|
|
|
thumbUrls[ title ] = {
|
|
|
|
thumbUrl: response[ title ].thumburl,
|
|
|
|
width: response[ title ].thumbwidth,
|
|
|
|
height: response[ title ].thumbheight
|
|
|
|
};
|
2016-03-08 21:14:12 +00:00
|
|
|
}
|
|
|
|
|
2016-11-19 15:51:21 +00:00
|
|
|
if ( this.initialImageData.length > 0 ) {
|
2024-04-30 16:44:25 +00:00
|
|
|
this.initialImageData.forEach( ( image ) => {
|
2016-11-19 23:11:06 +00:00
|
|
|
image.thumbUrl = thumbUrls[ image.resource ].thumbUrl;
|
2018-10-10 13:09:39 +00:00
|
|
|
items.push( new ve.ui.MWGalleryItemWidget( image, config ) );
|
2016-11-19 15:51:21 +00:00
|
|
|
} );
|
|
|
|
this.initialImageData = [];
|
2018-11-15 20:16:43 +00:00
|
|
|
this.originalGalleryGroupItems = ve.copy( items );
|
2016-11-19 15:51:21 +00:00
|
|
|
} else {
|
|
|
|
for ( title in this.selectedFilenames ) {
|
|
|
|
if ( Object.prototype.hasOwnProperty.call( thumbUrls, title ) ) {
|
|
|
|
items.push( new ve.ui.MWGalleryItemWidget( {
|
2016-11-19 23:11:06 +00:00
|
|
|
resource: title,
|
2022-07-22 19:47:07 +00:00
|
|
|
altText: null,
|
|
|
|
altTextSame: true,
|
2023-11-28 17:37:42 +00:00
|
|
|
// TODO: support changing the link in the UI somewhere;
|
|
|
|
// for now, always link to the resource. Do it here when
|
|
|
|
// generating new results, so existing links from source
|
|
|
|
// will be preserved.
|
|
|
|
href: title,
|
2016-11-19 23:11:06 +00:00
|
|
|
src: '',
|
|
|
|
height: thumbUrls[ title ].height,
|
|
|
|
width: thumbUrls[ title ].width,
|
|
|
|
thumbUrl: thumbUrls[ title ].thumbUrl,
|
2022-05-23 14:18:20 +00:00
|
|
|
captionDocument: this.createCaptionDocument( null ),
|
2023-02-16 02:28:35 +00:00
|
|
|
isError: false,
|
2023-05-11 00:19:47 +00:00
|
|
|
errorText: null,
|
2023-10-24 23:12:22 +00:00
|
|
|
imageClassAttr: 'mw-file-element',
|
2023-10-25 17:23:22 +00:00
|
|
|
mw: {},
|
|
|
|
mediaClass: 'File',
|
|
|
|
mediaTag: 'img'
|
2018-10-10 13:09:39 +00:00
|
|
|
}, config ) );
|
2016-11-19 15:51:21 +00:00
|
|
|
delete this.selectedFilenames[ title ];
|
|
|
|
}
|
2016-10-20 01:06:54 +00:00
|
|
|
}
|
|
|
|
}
|
2018-11-15 20:16:43 +00:00
|
|
|
|
2016-03-08 21:14:12 +00:00
|
|
|
this.galleryGroup.addItems( items );
|
|
|
|
|
|
|
|
// Gallery is no longer empty
|
|
|
|
this.updateActions();
|
|
|
|
this.toggleEmptyGalleryMessage( false );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Request a new image and highlight it
|
|
|
|
*
|
2018-07-07 21:58:25 +00:00
|
|
|
* @param {string} title Normalized title of the new image
|
2016-03-08 21:14:12 +00:00
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.prototype.addNewImage = function ( title ) {
|
2016-11-19 15:51:21 +00:00
|
|
|
// Make list of unique pending images, for onRequestImagesSuccess
|
|
|
|
this.selectedFilenames[ title ] = true;
|
2016-03-08 21:14:12 +00:00
|
|
|
|
|
|
|
// Request image
|
|
|
|
this.requestImages( {
|
2016-09-30 16:07:10 +00:00
|
|
|
titles: [ title ]
|
2024-04-30 16:44:25 +00:00
|
|
|
} ).done( () => {
|
2016-03-08 21:14:12 +00:00
|
|
|
// populate edit panel with the new image
|
2024-05-21 14:22:56 +00:00
|
|
|
const items = this.galleryGroup.items;
|
2024-05-01 12:32:49 +00:00
|
|
|
this.onHighlightItem( items[ items.length - 1 ] );
|
|
|
|
this.highlightedCaptionTarget.focus();
|
2016-03-08 21:14:12 +00:00
|
|
|
} );
|
|
|
|
};
|
|
|
|
|
2018-03-01 23:39:54 +00:00
|
|
|
/**
|
|
|
|
* Update the image currently being edited (ve.ui.MWGalleryItemWidget) with the values from inputs
|
|
|
|
* in this dialog (currently only the image caption).
|
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.prototype.updateHighlightedItem = function () {
|
2018-11-15 20:16:43 +00:00
|
|
|
this.anyItemModified = this.anyItemModified || this.isHighlightedItemModified();
|
|
|
|
|
2016-11-19 23:11:06 +00:00
|
|
|
// TODO: Support link, page and lang
|
2018-03-01 23:39:54 +00:00
|
|
|
if ( this.highlightedItem ) {
|
2016-11-19 23:11:06 +00:00
|
|
|
// No need to call setCaptionDocument(), the document object is updated on every change
|
|
|
|
this.highlightedItem.setAltText( this.highlightedAltTextInput.getValue() );
|
Add a checkbox to use the image caption as the alt text for galleries
The need for something like this was anticipated in
I2bf43c7e83283f43e047229eb53c244918fcbb0c.
As of version 2.5.0 of Parsoid's output, if alternate text is missing
for an image but a caption is present and image isn't displaying the
caption (ie. it isn't a thumb or frame), then the text content of the
caption will be set as the alt attribute. Parsoid will then drop the
alt attribute when serializing if it matches the caption text, since
it's unnecessary.
However, if the caption is modified and the alt text isn't, the alt will
be serialized. This is likely to be unexpected to editor. They may
have missed that the both the caption and alt are populated in VE and
only edited one place.
Since all of the above is happening only for images where the caption
isn't visible, it doesn't appear to be a much used feature since, at
least for inline images, the experience of caption editing was already
less than optimal.
However, because of a quirk in how galleries are rendered in Parsoid,
this affects gallery caption editing, which is visible and presumably
used more often. See T268250 for a discussion on an improved gallery
structure. But for now, gallery images are effectively inline and set
the alternate text, thus subject to the above.
Here we add a checkbox so that the default is to ignore the alt if it's
the same as the caption. And only make use of it if it differed
originally or was explicitly unchecked to modify.
Bug: T311677
Change-Id: Idf297d8a98995971c5835b0cea56c3317a3626e2
2022-07-04 21:01:40 +00:00
|
|
|
this.highlightedItem.setAltTextSame( this.altTextSameAsCaption.isSelected() );
|
2018-03-01 23:39:54 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-03-08 21:14:12 +00:00
|
|
|
/**
|
|
|
|
* Handle search results choose event.
|
|
|
|
*
|
2016-11-16 18:21:22 +00:00
|
|
|
* @param {mw.widgets.MediaResultWidget} item Chosen item
|
2016-03-08 21:14:12 +00:00
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.prototype.onSearchResultsChoose = function ( item ) {
|
2024-05-21 14:22:56 +00:00
|
|
|
const title = mw.Title.newFromText( item.getData().title ).getPrefixedText();
|
2016-11-14 22:04:25 +00:00
|
|
|
|
2018-07-07 21:58:25 +00:00
|
|
|
// Check title against pending insertions
|
|
|
|
// TODO: Prevent two 'choose' events firing from the UI
|
2018-07-06 00:10:20 +00:00
|
|
|
if ( !Object.prototype.hasOwnProperty.call( this.selectedFilenames, title ) ) {
|
2016-10-20 01:06:54 +00:00
|
|
|
this.addNewImage( title );
|
|
|
|
}
|
2017-03-10 16:45:09 +00:00
|
|
|
|
|
|
|
this.updateActions();
|
2016-03-08 21:14:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle click event for the remove button
|
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.prototype.onRemoveItem = function () {
|
2024-09-04 10:01:59 +00:00
|
|
|
const removedItemIndex = this.galleryGroup.items.indexOf( this.highlightedItem );
|
2016-03-08 21:14:12 +00:00
|
|
|
// Remove the highlighted item
|
|
|
|
this.galleryGroup.removeItems( [ this.highlightedItem ] );
|
|
|
|
|
2018-02-21 21:09:44 +00:00
|
|
|
// Highlight another item, or show the search panel if the gallery is now empty
|
2024-09-04 10:01:59 +00:00
|
|
|
this.onHighlightItem( undefined, removedItemIndex !== -1 ? removedItemIndex : undefined );
|
2016-03-08 21:14:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle clicking on an image in the menu
|
|
|
|
*
|
2018-02-21 21:09:44 +00:00
|
|
|
* @param {ve.ui.MWGalleryItemWidget} [item] The item that was clicked on
|
2024-09-04 10:01:59 +00:00
|
|
|
* @param {number} [removedItemIndex] Index of just-removed item
|
2016-03-08 21:14:12 +00:00
|
|
|
*/
|
2024-09-04 10:01:59 +00:00
|
|
|
ve.ui.MWGalleryDialog.prototype.onHighlightItem = function ( item, removedItemIndex ) {
|
2016-03-08 21:14:12 +00:00
|
|
|
// Unhighlight previous item
|
|
|
|
if ( this.highlightedItem ) {
|
|
|
|
this.highlightedItem.toggleHighlighted( false );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Show edit panel
|
2018-03-01 23:39:54 +00:00
|
|
|
// (This also calls updateHighlightedItem() to save the input values.)
|
2016-03-08 21:14:12 +00:00
|
|
|
this.toggleSearchPanel( false );
|
|
|
|
|
2018-02-21 21:09:44 +00:00
|
|
|
// Highlight new item.
|
2024-09-04 10:01:59 +00:00
|
|
|
if ( removedItemIndex !== undefined ) {
|
|
|
|
// The removed item might have been the last item in the list, in which
|
|
|
|
// case highlight the new last item.
|
|
|
|
const index = Math.min( removedItemIndex, this.galleryGroup.items.length - 1 );
|
|
|
|
item = this.galleryGroup.items[ index ];
|
|
|
|
} else if ( !item ) {
|
|
|
|
// If no item was given, highlight the first item in the gallery.
|
|
|
|
item = this.galleryGroup.items[ 0 ];
|
|
|
|
}
|
2018-02-21 21:09:44 +00:00
|
|
|
|
|
|
|
if ( !item ) {
|
|
|
|
// Show the search panel if the gallery is empty
|
|
|
|
this.toggleEmptyGalleryMessage( true );
|
|
|
|
this.toggleSearchPanel( true );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-03-08 21:14:12 +00:00
|
|
|
item.toggleHighlighted( true );
|
|
|
|
this.highlightedItem = item;
|
|
|
|
|
2016-12-03 15:46:58 +00:00
|
|
|
// Scroll item into view in menu
|
|
|
|
OO.ui.Element.static.scrollIntoView( item.$element[ 0 ] );
|
|
|
|
|
2016-03-08 21:14:12 +00:00
|
|
|
// Populate edit panel
|
2024-05-21 14:22:56 +00:00
|
|
|
const title = mw.Title.newFromText( mw.libs.ve.normalizeParsoidResourceName( item.resource ) );
|
|
|
|
const $link = $( '<a>' )
|
2022-11-24 13:39:23 +00:00
|
|
|
.addClass( 've-ui-mwMediaDialog-description-link' )
|
|
|
|
.attr( 'target', '_blank' )
|
|
|
|
.attr( 'rel', 'noopener' )
|
|
|
|
.text( ve.msg( 'visualeditor-dialog-media-content-description-link' ) );
|
|
|
|
|
|
|
|
// T322704
|
|
|
|
ve.setAttributeSafe( $link[ 0 ], 'href', title.getUrl(), '#' );
|
|
|
|
|
2017-06-06 14:46:31 +00:00
|
|
|
this.filenameFieldset.setLabel(
|
|
|
|
$( '<span>' ).append(
|
2021-10-25 15:51:29 +00:00
|
|
|
$( document.createTextNode( title.getMainText() + ' ' ) ),
|
2022-11-24 13:39:23 +00:00
|
|
|
$link
|
2017-06-06 14:46:31 +00:00
|
|
|
)
|
|
|
|
);
|
2016-03-08 21:14:12 +00:00
|
|
|
this.$highlightedImage
|
|
|
|
.css( 'background-image', 'url(' + item.thumbUrl + ')' );
|
2016-11-19 23:11:06 +00:00
|
|
|
this.highlightedCaptionTarget.setDocument( item.captionDocument );
|
2019-02-08 19:18:22 +00:00
|
|
|
this.highlightedCaptionTarget.setReadOnly( this.isReadOnly() );
|
2016-11-19 23:11:06 +00:00
|
|
|
this.highlightedAltTextInput.setValue( item.altText );
|
2022-07-26 19:50:19 +00:00
|
|
|
this.highlightedAltTextInput.setReadOnly( this.isReadOnly() || item.altTextSame );
|
Add a checkbox to use the image caption as the alt text for galleries
The need for something like this was anticipated in
I2bf43c7e83283f43e047229eb53c244918fcbb0c.
As of version 2.5.0 of Parsoid's output, if alternate text is missing
for an image but a caption is present and image isn't displaying the
caption (ie. it isn't a thumb or frame), then the text content of the
caption will be set as the alt attribute. Parsoid will then drop the
alt attribute when serializing if it matches the caption text, since
it's unnecessary.
However, if the caption is modified and the alt text isn't, the alt will
be serialized. This is likely to be unexpected to editor. They may
have missed that the both the caption and alt are populated in VE and
only edited one place.
Since all of the above is happening only for images where the caption
isn't visible, it doesn't appear to be a much used feature since, at
least for inline images, the experience of caption editing was already
less than optimal.
However, because of a quirk in how galleries are rendered in Parsoid,
this affects gallery caption editing, which is visible and presumably
used more often. See T268250 for a discussion on an improved gallery
structure. But for now, gallery images are effectively inline and set
the alternate text, thus subject to the above.
Here we add a checkbox so that the default is to ignore the alt if it's
the same as the caption. And only make use of it if it differed
originally or was explicitly unchecked to modify.
Bug: T311677
Change-Id: Idf297d8a98995971c5835b0cea56c3317a3626e2
2022-07-04 21:01:40 +00:00
|
|
|
this.altTextSameAsCaption.setSelected( item.altTextSame );
|
2016-03-08 21:14:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle change event for this.modeDropdown
|
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.prototype.onModeDropdownChange = function () {
|
2024-05-21 14:22:56 +00:00
|
|
|
const mode = this.modeDropdown.getMenu().findSelectedItem().getData(),
|
2016-07-07 17:45:44 +00:00
|
|
|
disabled = (
|
|
|
|
mode === 'packed' ||
|
|
|
|
mode === 'packed-overlay' ||
|
|
|
|
mode === 'packed-hover' ||
|
|
|
|
mode === 'slideshow'
|
|
|
|
);
|
2016-03-08 21:14:12 +00:00
|
|
|
|
|
|
|
this.widthsInput.setDisabled( disabled );
|
2022-02-21 18:07:07 +00:00
|
|
|
this.perRowInput.setDisabled( disabled );
|
2016-12-03 15:36:19 +00:00
|
|
|
|
|
|
|
// heights is only ignored in slideshow mode
|
|
|
|
this.heightsInput.setDisabled( mode === 'slideshow' );
|
2017-03-10 16:45:09 +00:00
|
|
|
|
|
|
|
this.updateActions();
|
2016-03-08 21:14:12 +00:00
|
|
|
};
|
|
|
|
|
2023-03-09 19:40:19 +00:00
|
|
|
/**
|
|
|
|
* Handle change event for this.highlightedCaptionTarget
|
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.prototype.onHighlightedCaptionTargetChange = function () {
|
|
|
|
if ( this.altTextSameAsCaption.isSelected() ) {
|
2024-05-21 14:22:56 +00:00
|
|
|
const surfaceModel = this.highlightedCaptionTarget.getSurface().getModel();
|
|
|
|
const caption = surfaceModel.getLinearFragment(
|
2023-03-09 19:40:19 +00:00
|
|
|
surfaceModel.getDocument().getDocumentRange()
|
|
|
|
).getText();
|
|
|
|
this.highlightedAltTextInput.setValue( caption );
|
|
|
|
}
|
|
|
|
this.updateActions();
|
|
|
|
};
|
|
|
|
|
Add a checkbox to use the image caption as the alt text for galleries
The need for something like this was anticipated in
I2bf43c7e83283f43e047229eb53c244918fcbb0c.
As of version 2.5.0 of Parsoid's output, if alternate text is missing
for an image but a caption is present and image isn't displaying the
caption (ie. it isn't a thumb or frame), then the text content of the
caption will be set as the alt attribute. Parsoid will then drop the
alt attribute when serializing if it matches the caption text, since
it's unnecessary.
However, if the caption is modified and the alt text isn't, the alt will
be serialized. This is likely to be unexpected to editor. They may
have missed that the both the caption and alt are populated in VE and
only edited one place.
Since all of the above is happening only for images where the caption
isn't visible, it doesn't appear to be a much used feature since, at
least for inline images, the experience of caption editing was already
less than optimal.
However, because of a quirk in how galleries are rendered in Parsoid,
this affects gallery caption editing, which is visible and presumably
used more often. See T268250 for a discussion on an improved gallery
structure. But for now, gallery images are effectively inline and set
the alternate text, thus subject to the above.
Here we add a checkbox so that the default is to ignore the alt if it's
the same as the caption. And only make use of it if it differed
originally or was explicitly unchecked to modify.
Bug: T311677
Change-Id: Idf297d8a98995971c5835b0cea56c3317a3626e2
2022-07-04 21:01:40 +00:00
|
|
|
/**
|
|
|
|
* Handle change event for this.altTextSameAsCaption
|
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.prototype.onAltTextSameAsCaptionChange = function () {
|
2022-07-26 19:50:19 +00:00
|
|
|
this.highlightedAltTextInput.setReadOnly( this.isReadOnly() || this.altTextSameAsCaption.isSelected() );
|
2023-03-09 19:40:19 +00:00
|
|
|
this.onHighlightedCaptionTargetChange();
|
Add a checkbox to use the image caption as the alt text for galleries
The need for something like this was anticipated in
I2bf43c7e83283f43e047229eb53c244918fcbb0c.
As of version 2.5.0 of Parsoid's output, if alternate text is missing
for an image but a caption is present and image isn't displaying the
caption (ie. it isn't a thumb or frame), then the text content of the
caption will be set as the alt attribute. Parsoid will then drop the
alt attribute when serializing if it matches the caption text, since
it's unnecessary.
However, if the caption is modified and the alt text isn't, the alt will
be serialized. This is likely to be unexpected to editor. They may
have missed that the both the caption and alt are populated in VE and
only edited one place.
Since all of the above is happening only for images where the caption
isn't visible, it doesn't appear to be a much used feature since, at
least for inline images, the experience of caption editing was already
less than optimal.
However, because of a quirk in how galleries are rendered in Parsoid,
this affects gallery caption editing, which is visible and presumably
used more often. See T268250 for a discussion on an improved gallery
structure. But for now, gallery images are effectively inline and set
the alternate text, thus subject to the above.
Here we add a checkbox so that the default is to ignore the alt if it's
the same as the caption. And only make use of it if it differed
originally or was explicitly unchecked to modify.
Bug: T311677
Change-Id: Idf297d8a98995971c5835b0cea56c3317a3626e2
2022-07-04 21:01:40 +00:00
|
|
|
};
|
|
|
|
|
2016-03-08 21:14:12 +00:00
|
|
|
/**
|
|
|
|
* Handle click event for showSearchPanelButton
|
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.prototype.onShowSearchPanelButtonClick = function () {
|
|
|
|
this.toggleSearchPanel( true );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Toggle the search panel (and the edit panel, the opposite way)
|
|
|
|
*
|
2022-02-14 15:18:57 +00:00
|
|
|
* @param {boolean} [visible] The search panel is visible
|
2016-03-08 21:14:12 +00:00
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.prototype.toggleSearchPanel = function ( visible ) {
|
|
|
|
visible = visible !== undefined ? visible : !this.searchPanelVisible;
|
|
|
|
|
2018-03-01 23:39:54 +00:00
|
|
|
// If currently visible panel is an edit panel, save the input values for the highlighted item
|
|
|
|
if ( !this.searchPanelVisible ) {
|
|
|
|
this.updateHighlightedItem();
|
2016-03-08 21:14:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Record the state of the search panel
|
|
|
|
this.searchPanelVisible = visible;
|
|
|
|
|
|
|
|
// Toggle the search panel, and do the opposite for the edit panel
|
2018-10-05 17:09:13 +00:00
|
|
|
this.editSearchStack.setItem( visible ? this.searchPanel : this.editPanel );
|
2016-03-08 21:14:12 +00:00
|
|
|
|
2021-10-04 12:53:25 +00:00
|
|
|
this.imageListMenuLayout.toggleMenu( !visible );
|
|
|
|
if ( this.highlightedItem && visible ) {
|
|
|
|
this.highlightedItem.toggleHighlighted( false );
|
|
|
|
this.highlightedItem = null;
|
|
|
|
}
|
|
|
|
|
2016-11-19 23:11:06 +00:00
|
|
|
// If the edit panel is visible, focus the caption target
|
2016-03-08 21:14:12 +00:00
|
|
|
if ( !visible ) {
|
2016-11-19 23:11:06 +00:00
|
|
|
this.highlightedCaptionTarget.focus();
|
2016-03-08 21:14:12 +00:00
|
|
|
} else {
|
2020-07-30 15:45:17 +00:00
|
|
|
// Try to populate with user uploads
|
|
|
|
this.searchWidget.queryMediaQueue();
|
2016-09-27 20:50:15 +00:00
|
|
|
this.searchWidget.getQuery().focus().select();
|
2016-03-08 21:14:12 +00:00
|
|
|
}
|
2016-10-12 05:07:37 +00:00
|
|
|
this.updateDialogSize();
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resize the dialog according to which panel is focused
|
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.prototype.updateDialogSize = function () {
|
2017-05-10 18:46:49 +00:00
|
|
|
if ( this.searchPanelVisible && this.indexLayout.currentTabPanelName === 'images' ) {
|
2016-10-12 05:07:37 +00:00
|
|
|
this.setSize( 'larger' );
|
|
|
|
} else {
|
|
|
|
this.setSize( 'large' );
|
|
|
|
}
|
2016-03-08 21:14:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Toggle the empty gallery message
|
|
|
|
*
|
|
|
|
* @param {boolean} empty The gallery is empty
|
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.prototype.toggleEmptyGalleryMessage = function ( empty ) {
|
2023-07-07 08:24:13 +00:00
|
|
|
this.$emptyGalleryMessage.toggleClass( 'oo-ui-element-hidden', !empty );
|
2016-03-08 21:14:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Disable the "Done" button if the gallery is empty, otherwise enable it
|
2016-11-19 23:11:06 +00:00
|
|
|
*
|
|
|
|
* TODO Disable the button until the user makes any changes
|
2016-03-08 21:14:12 +00:00
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.prototype.updateActions = function () {
|
2024-04-29 15:16:33 +00:00
|
|
|
this.actions.setAbilities( { done: this.isSaveable() } );
|
2018-10-10 10:59:06 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2018-11-15 20:16:43 +00:00
|
|
|
* Check if gallery attributes or contents would be modified if changes were applied.
|
2018-10-10 10:59:06 +00:00
|
|
|
*
|
2018-11-15 20:16:43 +00:00
|
|
|
* @return {boolean}
|
2018-10-10 10:59:06 +00:00
|
|
|
*/
|
2024-04-29 15:16:33 +00:00
|
|
|
ve.ui.MWGalleryDialog.prototype.isSaveable = function () {
|
2018-11-15 20:16:43 +00:00
|
|
|
// Check attributes
|
2018-10-10 10:59:06 +00:00
|
|
|
if ( this.originalMwDataNormalized ) {
|
2024-05-21 14:22:56 +00:00
|
|
|
const mwDataCopy = ve.copy( this.selectedNode.getAttribute( 'mw' ) );
|
2018-10-10 10:59:06 +00:00
|
|
|
this.updateMwData( mwDataCopy );
|
2018-11-15 20:16:43 +00:00
|
|
|
if ( !ve.compare( mwDataCopy, this.originalMwDataNormalized ) ) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( this.captionTarget.hasBeenModified() ) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check contents: each image's attributes and contents (caption)
|
|
|
|
if ( this.anyItemModified || this.isHighlightedItemModified() ) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check contents: added/removed/reordered images
|
|
|
|
if ( this.originalGalleryGroupItems ) {
|
|
|
|
if ( this.galleryGroup.items.length !== this.originalGalleryGroupItems.length ) {
|
|
|
|
return true;
|
|
|
|
}
|
2024-05-21 14:22:56 +00:00
|
|
|
for ( let i = 0; i < this.galleryGroup.items.length; i++ ) {
|
2018-11-15 20:16:43 +00:00
|
|
|
if ( this.galleryGroup.items[ i ] !== this.originalGalleryGroupItems[ i ] ) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if currently highlighted item's attributes or contents would be modified if changes were
|
|
|
|
* applied.
|
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.prototype.isHighlightedItemModified = function () {
|
|
|
|
if ( this.highlightedItem ) {
|
|
|
|
if ( this.highlightedAltTextInput.getValue() !== this.highlightedItem.altText ) {
|
|
|
|
return true;
|
|
|
|
}
|
Add a checkbox to use the image caption as the alt text for galleries
The need for something like this was anticipated in
I2bf43c7e83283f43e047229eb53c244918fcbb0c.
As of version 2.5.0 of Parsoid's output, if alternate text is missing
for an image but a caption is present and image isn't displaying the
caption (ie. it isn't a thumb or frame), then the text content of the
caption will be set as the alt attribute. Parsoid will then drop the
alt attribute when serializing if it matches the caption text, since
it's unnecessary.
However, if the caption is modified and the alt text isn't, the alt will
be serialized. This is likely to be unexpected to editor. They may
have missed that the both the caption and alt are populated in VE and
only edited one place.
Since all of the above is happening only for images where the caption
isn't visible, it doesn't appear to be a much used feature since, at
least for inline images, the experience of caption editing was already
less than optimal.
However, because of a quirk in how galleries are rendered in Parsoid,
this affects gallery caption editing, which is visible and presumably
used more often. See T268250 for a discussion on an improved gallery
structure. But for now, gallery images are effectively inline and set
the alternate text, thus subject to the above.
Here we add a checkbox so that the default is to ignore the alt if it's
the same as the caption. And only make use of it if it differed
originally or was explicitly unchecked to modify.
Bug: T311677
Change-Id: Idf297d8a98995971c5835b0cea56c3317a3626e2
2022-07-04 21:01:40 +00:00
|
|
|
if ( this.altTextSameAsCaption.isSelected() !== this.highlightedItem.altTextSame ) {
|
|
|
|
return true;
|
|
|
|
}
|
2018-11-15 20:16:43 +00:00
|
|
|
if ( this.highlightedCaptionTarget.hasBeenModified() ) {
|
|
|
|
return true;
|
|
|
|
}
|
2018-10-10 10:59:06 +00:00
|
|
|
}
|
2018-11-15 20:16:43 +00:00
|
|
|
return false;
|
2016-03-08 21:14:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2019-04-16 15:17:29 +00:00
|
|
|
* Insert or update the node in the document model from the new values
|
2016-03-08 21:14:12 +00:00
|
|
|
*/
|
2016-11-19 23:11:06 +00:00
|
|
|
ve.ui.MWGalleryDialog.prototype.insertOrUpdateNode = function () {
|
2024-05-21 16:40:36 +00:00
|
|
|
const surfaceModel = this.getFragment().getSurface(),
|
2016-11-19 23:11:06 +00:00
|
|
|
surfaceModelDocument = surfaceModel.getDocument(),
|
2024-05-29 16:03:23 +00:00
|
|
|
items = this.galleryGroup.items,
|
|
|
|
data = [];
|
2016-11-19 23:11:06 +00:00
|
|
|
|
2024-05-21 14:22:56 +00:00
|
|
|
let mwData;
|
2016-11-19 23:11:06 +00:00
|
|
|
|
2022-02-21 18:07:07 +00:00
|
|
|
function scaleImage( height, width, maxHeight, maxWidth ) {
|
2024-05-21 14:22:56 +00:00
|
|
|
const heightScaleFactor = maxHeight / height;
|
|
|
|
const widthScaleFactor = maxWidth / width;
|
2016-11-19 23:11:06 +00:00
|
|
|
|
2024-05-21 14:22:56 +00:00
|
|
|
const scaleFactor = width * heightScaleFactor > maxWidth ? widthScaleFactor : heightScaleFactor;
|
2016-11-19 23:11:06 +00:00
|
|
|
|
|
|
|
return {
|
|
|
|
height: Math.round( height * scaleFactor ),
|
|
|
|
width: Math.round( width * scaleFactor )
|
|
|
|
};
|
2017-03-21 22:29:59 +00:00
|
|
|
}
|
2017-03-10 16:45:09 +00:00
|
|
|
|
2022-05-19 13:57:41 +00:00
|
|
|
/**
|
|
|
|
* Get linear data from a gallery item
|
|
|
|
*
|
|
|
|
* @param {ve.ui.MWGalleryItemWidget} galleryItem Gallery item
|
|
|
|
* @return {Array} Linear data
|
|
|
|
*/
|
|
|
|
function getImageLinearData( galleryItem ) {
|
2024-05-21 14:22:56 +00:00
|
|
|
const size = scaleImage(
|
2022-05-19 13:57:41 +00:00
|
|
|
parseInt( galleryItem.height ),
|
|
|
|
parseInt( galleryItem.width ),
|
2016-11-19 23:11:06 +00:00
|
|
|
parseInt( mwData.attrs.heights || this.defaults.imageHeight ),
|
|
|
|
parseInt( mwData.attrs.widths || this.defaults.imageWidth )
|
|
|
|
);
|
2024-05-21 14:22:56 +00:00
|
|
|
const imageAttributes = {
|
2022-05-19 13:57:41 +00:00
|
|
|
resource: './' + galleryItem.resource,
|
|
|
|
altText: ( !galleryItem.altText && !galleryItem.originalAltText ) ?
|
|
|
|
// Use original null/empty value
|
|
|
|
galleryItem.originalAltText :
|
|
|
|
galleryItem.altText,
|
Add a checkbox to use the image caption as the alt text for galleries
The need for something like this was anticipated in
I2bf43c7e83283f43e047229eb53c244918fcbb0c.
As of version 2.5.0 of Parsoid's output, if alternate text is missing
for an image but a caption is present and image isn't displaying the
caption (ie. it isn't a thumb or frame), then the text content of the
caption will be set as the alt attribute. Parsoid will then drop the
alt attribute when serializing if it matches the caption text, since
it's unnecessary.
However, if the caption is modified and the alt text isn't, the alt will
be serialized. This is likely to be unexpected to editor. They may
have missed that the both the caption and alt are populated in VE and
only edited one place.
Since all of the above is happening only for images where the caption
isn't visible, it doesn't appear to be a much used feature since, at
least for inline images, the experience of caption editing was already
less than optimal.
However, because of a quirk in how galleries are rendered in Parsoid,
this affects gallery caption editing, which is visible and presumably
used more often. See T268250 for a discussion on an improved gallery
structure. But for now, gallery images are effectively inline and set
the alternate text, thus subject to the above.
Here we add a checkbox so that the default is to ignore the alt if it's
the same as the caption. And only make use of it if it differed
originally or was explicitly unchecked to modify.
Bug: T311677
Change-Id: Idf297d8a98995971c5835b0cea56c3317a3626e2
2022-07-04 21:01:40 +00:00
|
|
|
altTextSame: galleryItem.altTextSame,
|
2023-02-15 01:53:10 +00:00
|
|
|
href: galleryItem.href,
|
2022-07-01 12:14:03 +00:00
|
|
|
// For existing images use `src` to avoid triggering a diff if the
|
|
|
|
// thumbnail size changes. For new images we have to use `thumbUrl` (T310623).
|
|
|
|
src: galleryItem.src || galleryItem.thumbUrl,
|
2016-11-19 23:11:06 +00:00
|
|
|
height: size.height,
|
2020-12-16 22:28:51 +00:00
|
|
|
width: size.width,
|
2022-05-23 14:18:20 +00:00
|
|
|
tagName: galleryItem.tagName,
|
2023-02-16 02:28:35 +00:00
|
|
|
isError: galleryItem.isError,
|
2023-05-11 00:19:47 +00:00
|
|
|
errorText: galleryItem.errorText,
|
|
|
|
imageClassAttr: galleryItem.imageClassAttr,
|
2023-10-24 23:12:22 +00:00
|
|
|
imgWrapperClassAttr: galleryItem.imgWrapperClassAttr,
|
2023-10-25 17:23:22 +00:00
|
|
|
mw: galleryItem.mw,
|
|
|
|
mediaClass: galleryItem.mediaClass,
|
|
|
|
mediaTag: galleryItem.mediaTag
|
2016-11-19 23:11:06 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return [
|
|
|
|
{ type: 'mwGalleryImage', attributes: imageAttributes },
|
|
|
|
{ type: 'mwGalleryImageCaption' },
|
|
|
|
// Actual caption contents are inserted later
|
|
|
|
{ type: '/mwGalleryImageCaption' },
|
|
|
|
{ type: '/mwGalleryImage' }
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2024-05-21 14:22:56 +00:00
|
|
|
let innerRange;
|
2016-11-19 23:11:06 +00:00
|
|
|
if ( this.selectedNode ) {
|
|
|
|
// Update mwData
|
|
|
|
mwData = ve.copy( this.selectedNode.getAttribute( 'mw' ) );
|
|
|
|
this.updateMwData( mwData );
|
|
|
|
surfaceModel.change(
|
|
|
|
ve.dm.TransactionBuilder.static.newFromAttributeChanges(
|
|
|
|
surfaceModelDocument,
|
|
|
|
this.selectedNode.getOuterRange().start,
|
|
|
|
{ mw: mwData }
|
|
|
|
)
|
|
|
|
);
|
2016-03-08 21:14:12 +00:00
|
|
|
|
2016-11-19 23:11:06 +00:00
|
|
|
innerRange = this.selectedNode.getRange();
|
|
|
|
} else {
|
|
|
|
// Make gallery node and mwData
|
2024-05-21 14:22:56 +00:00
|
|
|
const element = {
|
2016-11-19 23:11:06 +00:00
|
|
|
type: 'mwGallery',
|
|
|
|
attributes: {
|
|
|
|
mw: {
|
|
|
|
name: 'gallery',
|
|
|
|
attrs: {},
|
|
|
|
body: {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
mwData = element.attributes.mw;
|
|
|
|
this.updateMwData( mwData );
|
|
|
|
// Collapse returns a new fragment, so update this.fragment
|
|
|
|
this.fragment = this.getFragment().collapseToEnd();
|
|
|
|
this.getFragment().insertContent( [
|
|
|
|
element,
|
|
|
|
{ type: '/mwGallery' }
|
|
|
|
] );
|
|
|
|
|
|
|
|
innerRange = new ve.Range( this.fragment.getSelection().getRange().from + 1 );
|
|
|
|
}
|
2018-03-01 23:39:54 +00:00
|
|
|
|
2016-11-19 23:11:06 +00:00
|
|
|
// Update all child elements' data, but without the contents of the captions
|
|
|
|
if ( this.captionDocument.data.hasContent() ) {
|
2024-05-29 16:03:23 +00:00
|
|
|
data.push(
|
2016-11-19 23:11:06 +00:00
|
|
|
{ type: 'mwGalleryCaption' },
|
|
|
|
{ type: '/mwGalleryCaption' }
|
2024-05-29 16:03:23 +00:00
|
|
|
);
|
2016-11-19 23:11:06 +00:00
|
|
|
}
|
|
|
|
// Build node for each image
|
2024-05-21 16:40:36 +00:00
|
|
|
for ( let i = 0, ilen = items.length; i < ilen; i++ ) {
|
2024-05-29 16:03:23 +00:00
|
|
|
ve.batchPush( data, getImageLinearData.call( this, items[ i ] ) );
|
2016-11-19 23:11:06 +00:00
|
|
|
}
|
|
|
|
// Replace whole contents of this node with the new ones
|
|
|
|
surfaceModel.change(
|
|
|
|
ve.dm.TransactionBuilder.static.newFromReplacement(
|
|
|
|
surfaceModelDocument,
|
|
|
|
innerRange,
|
|
|
|
data
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Minus 2 to skip past </mwGalleryImageCaption></mwGalleryImage>
|
2024-05-21 14:22:56 +00:00
|
|
|
let captionInsertionOffset = innerRange.from + data.length - 2;
|
2016-11-19 23:11:06 +00:00
|
|
|
// Update image captions. In reverse order to avoid having to adjust offsets for each insertion.
|
2024-05-21 16:40:36 +00:00
|
|
|
for ( let i = items.length - 1; i >= 0; i-- ) {
|
2018-08-01 02:35:49 +00:00
|
|
|
surfaceModel.change(
|
|
|
|
ve.dm.TransactionBuilder.static.newFromDocumentInsertion(
|
|
|
|
surfaceModel.getDocument(),
|
|
|
|
captionInsertionOffset,
|
|
|
|
items[ i ].captionDocument
|
|
|
|
)
|
|
|
|
);
|
2016-11-19 23:11:06 +00:00
|
|
|
// Skip past </mwGalleryImageCaption></mwGalleryImage><mwGalleryImage><mwGalleryImageCaption>
|
|
|
|
captionInsertionOffset -= 4;
|
2016-03-08 21:14:12 +00:00
|
|
|
}
|
|
|
|
|
2016-11-19 23:11:06 +00:00
|
|
|
// Update gallery caption
|
|
|
|
if ( this.captionDocument.data.hasContent() ) {
|
|
|
|
surfaceModel.change(
|
|
|
|
ve.dm.TransactionBuilder.static.newFromDocumentInsertion(
|
|
|
|
surfaceModel.getDocument(),
|
|
|
|
// Plus 1 to skip past <mwGalleryCaption>
|
|
|
|
innerRange.from + 1,
|
|
|
|
this.captionDocument
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
2017-03-10 16:45:09 +00:00
|
|
|
};
|
2016-08-04 23:28:53 +00:00
|
|
|
|
2017-03-10 16:45:09 +00:00
|
|
|
/**
|
2016-11-19 23:11:06 +00:00
|
|
|
* Update the 'mw' attribute with data from inputs in the dialog.
|
|
|
|
*
|
|
|
|
* @param {Object} mwData Value of the 'mw' attribute, updated in-place
|
|
|
|
* @private
|
2017-03-10 16:45:09 +00:00
|
|
|
*/
|
|
|
|
ve.ui.MWGalleryDialog.prototype.updateMwData = function ( mwData ) {
|
2016-11-19 23:11:06 +00:00
|
|
|
// Need to do this, otherwise mwData.body.extsrc will override all attribute changes
|
|
|
|
mwData.body = {};
|
|
|
|
// Need to do this, otherwise it will override the caption from the gallery caption node
|
|
|
|
delete mwData.attrs.caption;
|
|
|
|
// Update attributes
|
2024-05-21 14:22:56 +00:00
|
|
|
let mode;
|
2016-11-19 23:11:06 +00:00
|
|
|
if ( this.modeDropdown.getMenu().findSelectedItem() ) {
|
|
|
|
mode = this.modeDropdown.getMenu().findSelectedItem().getData();
|
|
|
|
}
|
|
|
|
// Unset mode attribute if it is the same as the default
|
|
|
|
mwData.attrs.mode = mode === this.defaults.mode ? undefined : mode;
|
|
|
|
mwData.attrs.widths = this.widthsInput.getValue() || undefined;
|
|
|
|
mwData.attrs.heights = this.heightsInput.getValue() || undefined;
|
2022-02-21 18:07:07 +00:00
|
|
|
mwData.attrs.perrow = this.perRowInput.getValue() || undefined;
|
2016-11-19 23:11:06 +00:00
|
|
|
mwData.attrs.showfilename = this.showFilenameCheckbox.isSelected() ? 'yes' : undefined;
|
|
|
|
mwData.attrs.class = this.classesInput.getValue() || undefined;
|
|
|
|
mwData.attrs.style = this.stylesInput.getValue() || undefined;
|
2016-03-08 21:14:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/* Registration */
|
|
|
|
|
|
|
|
ve.ui.windowFactory.register( ve.ui.MWGalleryDialog );
|