jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/354618 )
Change subject: Assign column-agnostic UID to individual list entries ...................................................................... Assign column-agnostic UID to individual list entries Unlike the other ID assignment mechanism, this is based on the item's position in the JSON's native representation. This allows list items to be edited regardless of how they appear in the rendered HTML. Bug: T165672 Change-Id: I37da1639e2e70d1b51ba090444d2a5e73be94613 --- M includes/content/CollaborationListContent.php M modules/ext.CollaborationKit.list.edit.js M modules/ext.CollaborationKit.list.ui.js 3 files changed, 221 insertions(+), 150 deletions(-) Approvals: Brian Wolff: Looks good to me, approved Harej: Looks good to me, but someone else must approve jenkins-bot: Verified diff --git a/includes/content/CollaborationListContent.php b/includes/content/CollaborationListContent.php index 47c68ff..8027394 100644 --- a/includes/content/CollaborationListContent.php +++ b/includes/content/CollaborationListContent.php @@ -300,10 +300,19 @@ return $text; } + $columns = $this->columns; + + // Assign a UID to each list entry. + $uidCounter = 0; + foreach ( $columns as $colId => $column ) { + foreach ( $column->items as $rowId => $row ) { + $columns[$colId]->items[$rowId]->uid = $uidCounter; + $uidCounter++; + } + } + if ( $this->displaymode === 'members' && count( $this->columns ) === 1 ) { - $columns = $this->sortUsersIntoColumns( $this->columns[0] ); - } else { - $columns = $this->columns; + $columns = $this->sortUsersIntoColumns( $columns[0] ); } $columns = $this->filterColumns( $columns, $options['columns'] ); @@ -335,106 +344,111 @@ if ( count( $column->items ) === 0 ) { $text .= "\n<div class=\"mw-ck-list-item\">"; $text .= "{{mediawiki:collaborationkit-list-emptycolumn}}</div>\n"; - $text .= "</div>\n"; - continue; - } + } else { + $curItem = 0; - $curItem = 0; + $sortedItems = $column->items; + $this->sortList( $sortedItems, $options['defaultSort'] ); - $sortedItems = $column->items; - $this->sortList( $sortedItems, $options['defaultSort'] ); - - $itemCounter = 0; - foreach ( $sortedItems as $item ) { - if ( $offset !== 0 ) { - $offset--; - continue; - } - $curItem++; - if ( $maxItems !== false && $maxItems < $curItem ) { - break; - } - - $itemTags = isset( $item->tags ) ? $item->tags : []; - if ( !$this->matchesTag( $options['tags'], $itemTags ) ) { - continue; - } - - $titleForItem = null; - if ( !isset( $item->link ) ) { - $titleForItem = Title::newFromText( $item->title ); - } elseif ( $item->link !== false ) { - $titleForItem = Title::newFromText( $item->link ); - } - $text .= Html::openElement( 'div', [ - 'style' => "min-height:{$iconWidth}px", - 'class' => 'mw-ck-list-item', - 'data-collabkit-item-title' => $item->title, - 'data-collabkit-item-id' => $colId . '-' . $itemCounter - ] ); - $itemCounter++; - if ( $options['mode'] !== 'no-img' ) { - if ( isset( $item->image ) ) { - $text .= static::generateImage( - $item->image, - $this->displaymode, - $titleForItem, - $iconWidth - ); - } else { - // Use fallback mechanisms - $text .= static::generateImage( - null, - $this->displaymode, - $titleForItem, - $iconWidth - ); + $itemCounter = 0; + foreach ( $sortedItems as $item ) { + if ( $offset !== 0 ) { + $offset--; + continue; } - } + $curItem++; + if ( $maxItems !== false && $maxItems < $curItem ) { + break; + } - $text .= '<div class="mw-ck-list-container">'; - // Question: Arguably it would be more semantically correct to - // use an <Hn> element for this. Would that be better? Unclear. - $text .= '<div class="mw-ck-list-title">'; - if ( $titleForItem ) { - if ( $this->displaymode == 'members' - && !isset( $item->link ) - && $titleForItem->inNamespace( NS_USER ) + $itemTags = isset( $item->tags ) ? $item->tags : []; + if ( !$this->matchesTag( $options['tags'], $itemTags ) ) { + continue; + } + + $titleForItem = null; + if ( !isset( $item->link ) ) { + $titleForItem = Title::newFromText( $item->title ); + } elseif ( $item->link !== false ) { + $titleForItem = Title::newFromText( $item->link ); + } + $text .= Html::openElement( 'div', [ + 'style' => "min-height:{$iconWidth}px", + 'class' => 'mw-ck-list-item', + 'data-collabkit-item-title' => $item->title, + 'data-collabkit-item-id' => $colId . '-' . $itemCounter, + 'data-collabkit-item-uid' => $item->uid + ] ); + $itemCounter++; + if ( $options['mode'] !== 'no-img' ) { + if ( isset( $item->image ) ) { + $text .= static::generateImage( + $item->image, + $this->displaymode, + $titleForItem, + $iconWidth + ); + } else { + // Use fallback mechanisms + $text .= static::generateImage( + null, + $this->displaymode, + $titleForItem, + $iconWidth + ); + } + } + + $text .= '<div class="mw-ck-list-container">'; + // Question: Arguably it would be more semantically correct to + // use an <Hn> element for this. Would that be better? Unclear. + $text .= '<div class="mw-ck-list-title">'; + if ( $titleForItem ) { + if ( $this->displaymode == 'members' + && !isset( $item->link ) + && $titleForItem->inNamespace( NS_USER ) + ) { + $titleText = $titleForItem->getText(); + } else { + $titleText = $item->title; + } + $text .= '[[:' . $titleForItem->getPrefixedDBkey() . '|' + . wfEscapeWikiText( $titleText ) . ']]'; + } else { + $text .= wfEscapeWikiText( $item->title ); + } + $text .= "</div>\n"; + $text .= '<div class="mw-ck-list-notes">' . "\n"; + if ( isset( $item->notes ) && is_string( $item->notes ) ) { + $text .= $item->notes . "\n"; + } + + if ( isset( $item->tags ) && is_array( $item->tags ) + && count( $item->tags ) ) { - $titleText = $titleForItem->getText(); - } else { - $titleText = $item->title; + $text .= "\n<div class='toccolours mw-ck-list-tags'>" . + wfMessage( 'collaborationkit-list-taglist' ) + ->inLanguage( $lang ) + ->params( + $lang->commaList( + array_map( 'wfEscapeWikiText', $item->tags ) + ) + )->numParams( count( $item->tags ) ) + ->text() . + "</div>\n"; } - $text .= '[[:' . $titleForItem->getPrefixedDBkey() . '|' - . wfEscapeWikiText( $titleText ) . ']]'; - } else { - $text .= wfEscapeWikiText( $item->title ); + $text .= '</div></div></div>' . "\n"; } - $text .= "</div>\n"; - $text .= '<div class="mw-ck-list-notes">' . "\n"; - if ( isset( $item->notes ) && is_string( $item->notes ) ) { - $text .= $item->notes . "\n"; - } - - if ( isset( $item->tags ) && is_array( $item->tags ) - && count( $item->tags ) - ) { - $text .= "\n<div class='toccolours mw-ck-list-tags'>" . - wfMessage( 'collaborationkit-list-taglist' ) - ->inLanguage( $lang ) - ->params( - $lang->commaList( - array_map( 'wfEscapeWikiText', $item->tags ) - ) - )->numParams( count( $item->tags ) ) - ->text() . - "</div>\n"; - } - $text .= '</div></div></div>' . "\n"; + } + if ( $this->displaymode != 'members' ) { + $text .= "\n<div class=\"mw-ck-list-additem-container\"></div>"; } $text .= "\n</div>"; } $text .= "\n</div>"; + if ( $this->displaymode == 'members' ) { + $text .= "\n<div class=\"mw-ck-list-additem-container\"></div>"; + } return $text; } @@ -1117,7 +1131,7 @@ * Filter users into active and inactive. * * @note The results of this function get stored in parser cache. - * @param array $userList Array of user dbkeys => stdClass + * @param array $userList Array of usernames => stdClass * @return array [ 'active' => [..], 'inactive' => '[..]' ] */ private function filterActiveUsers( $userList ) { diff --git a/modules/ext.CollaborationKit.list.edit.js b/modules/ext.CollaborationKit.list.edit.js index 75400fe..85e8c0d 100644 --- a/modules/ext.CollaborationKit.list.edit.js +++ b/modules/ext.CollaborationKit.list.edit.js @@ -46,6 +46,7 @@ spinner, title = $item.data( 'collabkit-item-title' ), itemId = $item.data( 'collabkit-item-id' ), + uid = $item.data( 'collabkit-item-uid' ), colId = getColId( $item ); if ( mw.config.get( 'wgCollaborationKitIsMemberList' ) ) { @@ -65,7 +66,7 @@ var newItems = []; oldItems = res.content.columns[ colId ].items; for ( i = 0; i < oldItems.length; i++ ) { - if ( colId + '-' + i === itemId ) { + if ( oldItems[ i ].uid === uid ) { continue; } newItems[ newItems.length ] = oldItems[ i ]; @@ -97,14 +98,18 @@ * @return {Array} 2D array of all items in all columns. */ getListOfItems = function ( $elm ) { - var list = []; + var list = [], + relevantItem; $elm.children( '.mw-ck-list-column' ).each( function () { var $this, colId; $this = $( this ); colId = $this.data( 'collabkit-column-id' ); list[ colId ] = []; $this.children( '.mw-ck-list-item' ).each( function () { - list[ colId ][ list[ colId ].length ] = $( this ).data( 'collabkit-item-id' ); + relevantItem = $( this ).data( 'collabkit-item-id' ); + if ( relevantItem ) { + list[ colId ][ list[ colId ].length ] = relevantItem; + } } ); } ); return list; @@ -217,7 +222,10 @@ * @param {Object} callback */ getCurrentJson = function ( pageId, callback ) { - var api = new mw.Api(); + var api = new mw.Api(), + i, + j, + uidCounter = 0; api.get( { action: 'query', @@ -247,6 +255,15 @@ res.pageid = pageId; res.timestamp = rev.timestamp; res.content = JSON.parse( rev[ '*' ] ); + + // Assigning UID to each list entry + for ( i = 0; i < res.content.columns.length; i++ ) { + for ( j = 0; j < res.content.columns[ i ].items.length; j++ ) { + res.content.columns[ i ].items[ j ].uid = uidCounter; + uidCounter++; + } + } + callback( res ); } ).fail( function () { alert( mw.msg( 'collaborationkit-list-error-generic' ) ); } @@ -260,7 +277,16 @@ * @param {Object} callback */ saveJson = function ( params, callback ) { - var api = new mw.Api(); + var api = new mw.Api(), + i, + j; + + // Strip out UID; we don't want to save it. + for ( i = 0; i < params.content.columns.length; i++ ) { + for ( j = 0; j < params.content.columns[ i ].items.length; j++ ) { + delete params.content.columns[ i ].items[ j ].uid; + } + } // This will explode if we hit a captcha api.postWithEditToken( { diff --git a/modules/ext.CollaborationKit.list.ui.js b/modules/ext.CollaborationKit.list.ui.js index 0c3dbc3..7560ad4 100644 --- a/modules/ext.CollaborationKit.list.ui.js +++ b/modules/ext.CollaborationKit.list.ui.js @@ -4,9 +4,37 @@ ( function ( $, mw, OO ) { 'use strict'; - var addItem, modifyItem, modifyExistingItem, LE; + var getItemFromUid, addItem, modifyItem, modifyExistingItem, LE; LE = require( 'ext.CollaborationKit.list.edit' ); + + /** + * Finds item object based on a UID. + * + * @param {number} uid The relevant unique ID + * @param {Object} columns The columns object from the content object + * @return {Object} An object with keys relevantItem, relevantColumn, relevantRow + */ + getItemFromUid = function ( uid, columns ) { + var i, j, relevantItem, relevantColumn, relevantRow; + + outer: for ( i = 0; i < columns.length; i++ ) { + for ( j = 0; j < columns[ i ].items.length; j++ ) { + if ( columns[ i ].items[ j ].uid === uid ) { + relevantItem = columns[ i ].items[ j ]; + relevantColumn = i; + relevantRow = j; + break outer; + } + } + } + + return { + relevantItem: relevantItem, + relevantColumn: relevantColumn, + relevantRow: relevantRow + }; + }; /** * Adds a new item to a list @@ -20,7 +48,8 @@ /** * Opens a window to manage list item modification * - * @param {Object} itemToEdit The name of the title to modify, or false to add new. + * @param {Object} itemToEdit Data concerning the item to edit/add. At + * minimum you need an itemColId attribute with the column ID. */ modifyItem = function ( itemToEdit ) { var dialog, windowManager; @@ -36,29 +65,22 @@ * Edit an existing item. * * @param {string} itemName The title of the item in question - * @param {string} itemId ID string corresponding to the column/item index - * @param {number} colId Which column the item is in + * @param {string} uid Unique identifier based on order in native JSON representation */ - modifyExistingItem = function ( itemName, itemId, colId ) { + modifyExistingItem = function ( itemName, uid ) { LE.getCurrentJson( mw.config.get( 'wgArticleId' ), function ( res ) { var done, itemIdNumber, toEdit; done = false; - // Member lists pretend to have two columns (active and inactive), - // but in the source code, it's only one column. This forces one column - // for the purposes of the JS editor. - if ( mw.config.get( 'wgCollaborationKitIsMemberList' ) ) { - colId = 0; - } - itemIdNumber = parseInt( itemId.split( '-' )[ 1 ] ); - toEdit = res.content.columns[ colId ].items[ itemIdNumber ]; + toEdit = getItemFromUid( uid, res.content.columns ); + done = true; modifyItem( { - itemTitle: toEdit.title, - itemImage: toEdit.image, - itemDescription: toEdit.notes, - itemIndex: itemIdNumber, - itemColId: colId + itemTitle: toEdit.relevantItem.title, + itemImage: toEdit.relevantItem.image, + itemDescription: toEdit.relevantItem.notes, + itemColId: toEdit.relevantColumn, + itemUid: uid } ); if ( !done ) { @@ -82,7 +104,7 @@ this.itemTitle = config.itemTitle; this.itemDescription = config.itemDescription; this.itemImage = config.itemImage; - this.itemIndex = config.itemIndex; + this.itemUid = config.itemUid; } if ( config.itemColId ) { this.itemColId = config.itemColId; @@ -109,7 +131,7 @@ * @param {Object} itemInfo info from json */ NewItemDialog.prototype.initialize = function ( itemInfo ) { - var description, itemTitleObj; + var description, itemTitleObj, i, j; NewItemDialog.parent.prototype.initialize.apply( this, arguments ); this.panel1 = new OO.ui.PanelLayout( { padded: true, expanded: false } ); @@ -200,27 +222,38 @@ } LE.getCurrentJson( mw.config.get( 'wgArticleId' ), function ( res ) { - var index, itemToAdd = { - title: title, - notes: notes - }; + var index, + itemToAdd = { + title: title, + notes: notes + }, + relevantItem, + relevantRow; if ( file ) { itemToAdd.image = file; } - if ( dialog.itemIndex !== undefined ) { - if ( res.content.columns[ dialog.itemColId ].items <= dialog.itemIndex || - res.content.columns[ dialog.itemColId ].items[ dialog.itemIndex ].title !== dialog.itemTitle - ) { + if ( dialog.itemUid !== undefined ) { + // We are editing an existing item + + relevantItem = getItemFromUid( dialog.itemUid, res.content.columns ); + + relevantRow = relevantItem.relevantRow; + relevantItem = relevantItem.relevantItem; + + // Edit conflict detection + if ( relevantItem.title !== dialog.itemTitle ) { alert( mw.msg( 'collaborationkit-list-error-editconflict' ) ); location.reload(); // fixme proper handling. throw new Error( 'edit conflict' ); } - index = dialog.itemIndex; + + index = relevantRow; } else { index = res.content.columns[ dialog.itemColId ].items.length; } + res.content.columns[ dialog.itemColId ].items[ index ] = itemToAdd; res.summary = mw.msg( 'collaborationkit-list-add-summary', title ); LE.saveJson( res, function () { @@ -240,11 +273,7 @@ } $list = $( '.mw-ck-list' ); - if ( mw.config.get( 'wgCollaborationKitIsMemberList' ) ) { - column = $( '.mw-ck-list-column[data-collabkit-column-id=0] .mw-ck-list-item:last-child' ); - } else { - column = $( '.mw-ck-list-item:last-child' ); - } + $list.find( '.mw-ck-list-item' ).each( function () { var deleteButton, moveButton, @@ -278,8 +307,7 @@ } ).on( 'click', function () { modifyExistingItem( item.data( 'collabkit-item-title' ), - item.data( 'collabkit-item-id' ), - colId ); + item.data( 'collabkit-item-uid' ) ); } ); // FIXME, the <a> might make an extra target when tabbing @@ -376,22 +404,25 @@ buttonMsg = mw.config.get( 'wgCollaborationKitIsMemberList' ) ? 'collaborationkit-list-add-user' : 'collaborationkit-list-add'; - column.after( - $( '<div></div>' ) - // FIXME There is probably a way to add the class without - // extra div. - .addClass( 'mw-ck-list-additem' ) - .append( - new OO.ui.ButtonWidget( { - label: mw.msg( buttonMsg ), - icon: 'add', - flags: 'constructive' - } ).on( 'click', function () { - addItem( $( event.target ).closest( '.mw-ck-list-column' ).data( 'collabkit-column-id' ) ); - } ) - .$element - ) - ); + $( '.mw-ck-list-additem-container' ).each( function () { + var $addButton = $( this ); + $addButton.append( + $( '<div></div>' ) + // FIXME There is probably a way to add the class without + // extra div. + .addClass( 'mw-ck-list-additem' ) + .append( + new OO.ui.ButtonWidget( { + label: mw.msg( buttonMsg ), + icon: 'add', + flags: 'constructive' + } ).on( 'click', function ( event ) { + addItem( $addButton.closest( '.mw-ck-list-column' ).data( 'collabkit-column-id' ) ); + } ) + .$element + ) + ); + } ); } ); } )( jQuery, mediaWiki, OO ); -- To view, visit https://gerrit.wikimedia.org/r/354618 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I37da1639e2e70d1b51ba090444d2a5e73be94613 Gerrit-PatchSet: 5 Gerrit-Project: mediawiki/extensions/CollaborationKit Gerrit-Branch: master Gerrit-Owner: Harej <jamesmh...@gmail.com> Gerrit-Reviewer: Brian Wolff <bawolff...@gmail.com> Gerrit-Reviewer: Harej <jamesmh...@gmail.com> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits