Merge "Add truncatable text field, use for some fields"

This commit is contained in:
jenkins-bot 2014-04-10 12:13:46 +00:00 committed by Gerrit Code Review
commit 911e87adde
8 changed files with 428 additions and 60 deletions

View file

@ -379,6 +379,22 @@ $wgResourceModules += array(
),
),
'mmv.ui.truncatableTextField' => $wgMediaViewerResourceTemplate + array(
'scripts' => array(
'mmv/ui/mmv.ui.truncatableTextField.js',
),
'styles' => array(
'mmv/ui/mmv.ui.truncatableTextField.less',
),
'dependencies' => array(
'mmv.HtmlUtils',
'mmv.ui',
'oojs',
),
),
'mmv.ui.metadataPanel' => $wgMediaViewerResourceTemplate + array(
'scripts' => array(
'mmv/ui/mmv.ui.metadataPanel.js',
@ -389,6 +405,7 @@ $wgResourceModules += array(
),
'dependencies' => array(
'mmv.HtmlUtils',
'mmv.ui',
'mmv.ui.stripeButtons',
'mmv.ui.categories',
@ -396,7 +413,7 @@ $wgResourceModules += array(
'mmv.ui.fileUsage',
'mmv.ui.permission',
'mmv.ui.reuse.dialog',
'mmv.HtmlUtils',
'mmv.ui.truncatableTextField',
'moment',
'oojs',
),

View file

@ -216,6 +216,7 @@ class MultimediaViewerHooks {
'tests/qunit/mmv/ui/mmv.ui.reuse.share.test.js',
'tests/qunit/mmv/ui/mmv.ui.reuse.tab.test.js',
'tests/qunit/mmv/ui/mmv.ui.reuse.utils.test.js',
'tests/qunit/mmv/ui/mmv.ui.truncatableTextField.test.js',
'tests/qunit/mmv/mmv.testhelpers.js',
),
'dependencies' => array(

View file

@ -167,30 +167,23 @@
.appendTo( this.$titleAndCredit );
this.$title = $( '<span>' )
.addClass( 'mw-mmv-title' )
.appendTo( this.$titlePara );
.addClass( 'mw-mmv-title' );
this.title = new mw.mmv.ui.TruncatableTextField( this.$titlePara, this.$title );
};
/**
* Initializes the credit elements.
*/
MPP.initializeCredit = function () {
this.$source = $( '<span>' )
.addClass( 'mw-mmv-source' );
this.$author = $( '<span>' )
.addClass( 'mw-mmv-author' );
this.$credit = $( '<p>' )
.addClass( 'mw-mmv-credit empty' )
.html(
mw.message(
'multimediaviewer-credit',
this.$author.get( 0 ).outerHTML,
this.$source.get( 0 ).outerHTML
).plain()
)
.appendTo( this.$titleAndCredit );
.addClass( 'mw-mmv-credit empty' );
this.creditField = new mw.mmv.ui.TruncatableTextField(
this.$titleAndCredit,
this.$credit,
{ max: 200, small: 160 }
);
};
/**
@ -464,7 +457,7 @@
* @param {string} title
*/
MPP.setFileTitle = function ( title ) {
this.$title.text( title );
this.title.set( title );
};
/**
@ -493,13 +486,54 @@
this.$datetimeLi.removeClass( 'empty' );
};
/**
* Bignasty function for setting source and author. Both #setAuthor and
* #setSource use this with some shortcuts.
* @param {string} source With unsafe HTML
* @param {string} author With unsafe HTML
*/
MPP.setCredit = function ( source, author ) {
if ( source ) {
this.source = $( '<span>' )
.addClass( 'mw-mlb-source' )
.append( $.parseHTML( source ) )
.html();
} else {
this.source = null;
}
if ( author ) {
this.author = $( '<span>' )
.addClass( 'mw-mlb-author' )
.append( $.parseHTML( author ) )
.html();
} else {
this.author = null;
}
if ( author && source ) {
this.creditField.set(
mw.message(
'multimediaviewer-credit',
this.author,
this.source
).plain()
);
} else if ( author ) {
this.creditField.set( author );
} else if ( source ) {
this.creditField.set( source );
}
this.$credit.toggleClass( 'empty', !( author || source ) );
};
/**
* Sets the source in the panel
* @param {string} source Warning - unsafe HTML sometimes goes here
*/
MPP.setSource = function ( source ) {
this.$source.html( this.htmlUtils.htmlToTextWithLinks( source ) );
this.$credit.removeClass( 'empty' );
this.setCredit( source, this.author );
};
/**
@ -507,32 +541,7 @@
* @param {string} author Warning - unsafe HTML sometimes goes here
*/
MPP.setAuthor = function ( author ) {
this.$author.html( this.htmlUtils.htmlToTextWithLinks( author ) );
this.$credit.removeClass( 'empty' );
};
/**
* Consolidate the source and author fields into a credit field
* @param {boolean} source Do we have the source field?
* @param {boolean} author Do we have the author field?
*/
MPP.consolidateCredit = function ( source, author ) {
if ( source && author ) {
this.$credit.html(
mw.message(
'multimediaviewer-credit',
this.$author.get( 0 ).outerHTML,
this.$source.get( 0 ).outerHTML
).plain()
);
} else {
// Clobber the contents and only have one of the fields
if ( source ) {
this.$credit.empty().append( this.$source );
} else if ( author ) {
this.$credit.empty().append( this.$author );
}
}
this.setCredit( this.source, author );
};
/**
@ -668,10 +677,7 @@
this.setAuthor( imageData.author );
}
this.consolidateCredit( !!imageData.source, !!imageData.author );
this.buttons.set( imageData, repoData );
this.description.set( imageData.description, image.caption );
this.categories.set( repoData.getArticlePath(), imageData.categories );

View file

@ -0,0 +1,172 @@
/*
* This file is part of the MediaWiki extension MultimediaViewer.
*
* MultimediaViewer is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* MultimediaViewer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MultimediaViewer. If not, see <http://www.gnu.org/licenses/>.
*/
( function ( mw, $, oo ) {
var TTFP;
/**
* Represents any text field that needs to be truncated to be readable.
* @class mw.mmv.ui.TruncatableTextField
* @extends mw.mmv.ui.Element
* @constructor
* @param {jQuery} $container The container for the element.
* @param {jQuery} $element The element where we should put the text.
* @param {Object} [sizes] Overrides for the max and small properties.
* @param {number} [sizes.max=100] Override the max property.
* @param {number} [sizes.small=80] Override the small property.
*/
function TruncatableTextField( $container, $element, sizes ) {
mw.mmv.ui.Element.call( this, $container );
/** @property {jQuery} $element The DOM element that holds text for this element. */
this.$element = $element;
/** @property {mw.mmv.HtmlUtils} htmlUtils Our HTML utility instance. */
this.htmlUtils = new mw.mmv.HtmlUtils();
this.$container.append( this.$element );
if ( sizes ) {
if ( sizes.max ) {
this.max = sizes.max;
}
if ( sizes.small ) {
this.small = sizes.small;
}
}
}
oo.inheritClass( TruncatableTextField, mw.mmv.ui.Element );
TTFP = TruncatableTextField.prototype;
/**
* Maximum length of the field - we'll cut out the rest of the text.
* @property {number} max
*/
TTFP.max = 100;
/**
* Maximum ideal length of the field - we'll make the font smaller after this.
* @property {number} small
*/
TTFP.small = 80;
/**
* Sets the string for the element.
* @param {string} value Warning - unsafe HTML is allowed here.
* @override
*/
TTFP.set = function ( value ) {
this.whitelistHtmlAndSet( value );
this.shrink();
};
/**
* Whitelists HTML in the DOM element. Just a shortcut because
* this class has only one element member. Then sets the text.
* @param {string} value Has unsafe HTML.
*/
TTFP.whitelistHtmlAndSet = function ( value ) {
var $newEle = $.parseHTML( this.htmlUtils.htmlToTextWithLinks( value ) );
this.$element.empty().append( $newEle );
};
/**
* Makes the text smaller via a few different methods.
*/
TTFP.shrink = function () {
this.changeStyle();
this.$element = this.truncate( this.$element.get( 0 ), this.max, true );
};
/**
* Changes the element style if a certain length is reached.
*/
TTFP.changeStyle = function () {
this.$element.toggleClass( 'mw-mmv-truncate-toolong', this.$element.text().length > this.small );
};
/**
* Truncate the text in the DOM element according to a few different rules.
* @param {HTMLElement} element
* @param {number} maxlen Maximum text length for the element.
* @param {number} [appendEllipsis=true] Whether to stick an ellipsis at the end.
* @returns {jQuery}
*/
TTFP.truncate = function ( element, maxlen, appendEllipsis ) {
var $result, curEle,
curlen = ( element.textContent || { length: 0 } ).length;
if ( appendEllipsis === undefined ) {
appendEllipsis = true;
}
if ( curlen <= maxlen ) {
// Easy case
return $( element );
}
// Make room for the ellipsis
maxlen -= appendEllipsis ? 1 : 0;
// We're going to build up rather than remove until ready
curlen = 0;
// Create an empty element to dump things into
$result = $( element ).clone().empty();
// Fetch the first child.
curEle = element.firstChild;
while ( curEle !== null && curlen < maxlen ) {
if ( curEle.nodeType === curEle.TEXT_NODE ) {
if ( curEle.textContent.length < ( maxlen - curlen ) ) {
$result.append( curEle.cloneNode( true ) );
} else {
$result.append( this.truncateText( curEle.textContent, maxlen - curlen ) );
break;
}
} else {
$result.append( this.truncate( curEle.cloneNode( true ), maxlen - curlen, false ) );
}
curlen = $result.text().length;
curEle = curEle.nextSibling;
}
if ( appendEllipsis ) {
$result.append( '…' );
}
$( element ).replaceWith( $result );
return $result;
};
/**
* Truncate text to a maximum width.
* @param {string} text
* @param {number} maxlen
*/
TTFP.truncateText = function ( text, maxlen ) {
// Just return the substr for now.
return text.substr( 0, maxlen );
};
mw.mmv.ui.TruncatableTextField = TruncatableTextField;
}( mediaWiki, jQuery, OO ) );

View file

@ -0,0 +1,7 @@
.mw-mmv-truncate-toolong {
font-size: 0.8em;
&.mw-mmv-title {
font-size: 1em;
}
}

View file

@ -18,13 +18,13 @@
stubScrollTo();
function checkIfUIAreasAttachedToDocument( inDocument ) {
var msg = inDocument === 1 ? ' ' : ' not ';
assert.strictEqual( $( '.mw-mmv-wrapper' ).length, inDocument, 'Wrapper area' + msg + 'attached.' );
assert.strictEqual( $( '.mw-mmv-main' ).length, inDocument, 'Main area' + msg + 'attached.' );
assert.strictEqual( $( '.mw-mmv-title' ).length, inDocument, 'Title area' + msg + 'attached.' );
assert.strictEqual( $( '.mw-mmv-author' ).length, inDocument, 'Author area' + msg + 'attached.' );
assert.strictEqual( $( '.mw-mmv-image-desc' ).length, inDocument, 'Description area' + msg + 'attached.' );
assert.strictEqual( $( '.mw-mmv-image-links' ).length, inDocument, 'Links area' + msg + 'attached.' );
var msg = ( inDocument === 1 ? ' ' : ' not ' ) + 'attached.';
assert.strictEqual( $( '.mw-mmv-wrapper' ).length, inDocument, 'Wrapper area' + msg );
assert.strictEqual( $( '.mw-mmv-main' ).length, inDocument, 'Main area' + msg );
assert.strictEqual( $( '.mw-mmv-title' ).length, inDocument, 'Title area' + msg );
assert.strictEqual( $( '.mw-mmv-credit' ).length, inDocument, 'Author/source area' + msg );
assert.strictEqual( $( '.mw-mmv-image-desc' ).length, inDocument, 'Description area' + msg );
assert.strictEqual( $( '.mw-mmv-image-links' ).length, inDocument, 'Links area' + msg );
}
// UI areas not attached to the document yet.

View file

@ -120,7 +120,7 @@
);
} );
QUnit.test( 'Setting image information works as expected', 15, function ( assert ) {
QUnit.test( 'Setting image information works as expected', 14, function ( assert ) {
var gender,
$qf = $( '#qunit-fixture' ),
panel = new mw.mmv.ui.MetadataPanel( $qf, $( '<div>' ).appendTo( $qf ) ),
@ -171,9 +171,8 @@
assert.strictEqual( panel.$title.text(), title, 'Title is correctly set' );
assert.ok( !panel.$credit.hasClass( 'empty' ), 'Credit is not empty' );
assert.ok( !panel.$datetimeLi.hasClass( 'empty' ), 'Date/Time is not empty' );
assert.strictEqual( panel.$source.html(), 'Lost<a href=\"foo\">Bar</a>', 'Source is correctly set' );
assert.strictEqual( panel.creditField.$element.html(), imageData.author + ' - Lost<a href=\"foo\">Bar</a>', 'Source and author are correctly set' );
assert.ok( panel.$datetime.text().indexOf( 'August 26 2013' ) > 0, 'Correct date is displayed' );
assert.strictEqual( panel.$author.text(), imageData.author, 'Author is correctly set' );
assert.strictEqual( panel.$license.text(), 'CC BY 2.0', 'License is correctly set' );
assert.ok( panel.$username.text().indexOf( imageData.lastUploader ) > 0, 'Correct username is displayed' );

View file

@ -0,0 +1,166 @@
/*
* This file is part of the MediaWiki extension MediaViewer.
*
* MediaViewer is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* MediaViewer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MediaViewer. If not, see <http://www.gnu.org/licenses/>.
*/
( function( mw, $ ) {
QUnit.module( 'mmv.ui.TruncatableTextField', QUnit.newMwEnvironment() );
QUnit.test( 'Normal constructor', 2, function ( assert ) {
var $container = $( '#qunit-fixture' ),
$element = $( '<div>' ).appendTo( $container ).text( 'This is a unique string.' ),
ttf = new mw.mmv.ui.TruncatableTextField( $container, $element );
assert.strictEqual( ttf.$element.text(), 'This is a unique string.', 'The constructor set the element to the right thing.' );
assert.strictEqual( ttf.$element.closest( '#qunit-fixture' ).length, 1, 'The constructor put the element into the container.' );
} );
QUnit.test( 'Set method', 1, function ( assert ) {
var $container = $( '#qunit-fixture' ).empty(),
$element = $( '<div>' ).appendTo( $container ),
text = ( new Array( 500 ) ).join( 'a' ),
ttf = new mw.mmv.ui.TruncatableTextField( $container, $element );
ttf.htmlUtils.htmlToTextWithLinks = function ( value ) { return value; };
ttf.shrink = function () {};
ttf.set( text );
assert.strictEqual( $container.text(), text, 'Text is set accurately.' );
} );
QUnit.test( 'Truncate method', 1, function ( assert ) {
var $container = $( '#qunit-fixture' ).empty(),
$element = $( '<div>' ).appendTo( $container ),
textOne = ( new Array( 50 ) ).join( 'a' ),
textTwo = ( new Array( 100 ) ).join( 'b' ),
ttf = new mw.mmv.ui.TruncatableTextField( $container, $element );
$element.append(
$( '<span>' ).text( textOne ),
$( '<span>' ).text( textTwo )
);
// We only want to test the element exclusion here
ttf.truncateText = function () { return ''; };
ttf.truncate( $element.get( 0 ), ttf.max, false );
assert.strictEqual( $container.text(), textOne, 'The too-long element is excluded.' );
} );
QUnit.test( 'Truncate method', 2, function ( assert ) {
var $container = $( '#qunit-fixture' ).empty(),
$element = $( '<div>' ).appendTo( $container ),
textOne = ( new Array( 5 ) ).join( 'a' ),
textTwo = ( new Array( 5 ) ).join( 'b' ),
textThree = ( new Array( 5 ) ).join( 'c' ),
textFour = ( new Array( 5 ) ).join( 'd' ),
textFive = ( new Array( 100 ) ).join( 'e' ),
textFiveTruncated = ( new Array( 85 ) ).join( 'e' ),
textSix = ( new Array( 100 ) ).join( 'f' ),
ttf = new mw.mmv.ui.TruncatableTextField( $container, $element );
$element.append(
$( '<span>' )
.append(
textOne,
$( '<span>' ).text( textTwo )
.append(
$( '<span>' ).text( textThree ),
$( '<span>' ).text( textFour )
),
$( '<span>' ).text( textFive ),
textSix
)
);
ttf.truncate( $element.get( 0 ), ttf.max, false );
assert.strictEqual( $container.text().length, ttf.max, 'Correctly truncated to max length' );
assert.strictEqual(
$container.text(),
textOne + textTwo + textThree + textFour + textFiveTruncated,
'Markup truncated correctly.' );
} );
QUnit.test( 'Truncate method for text', 2, function ( assert ) {
var $container = $( '#qunit-fixture' ).empty(),
$element = $( '<div>' ).appendTo( $container ),
text = ( new Array( 500 ) ).join( 'a' ),
ttf = new mw.mmv.ui.TruncatableTextField( $container, $element ),
newText = ttf.truncateText( text, ttf.max );
assert.strictEqual( newText.length, 100, 'Text is the right length.' );
assert.strictEqual( newText, ( new Array( 101 ) ).join( 'a' ), 'Text has the right content.' );
} );
QUnit.test( 'Shrink method', 2, function ( assert ) {
var $container = $( '#qunit-fixture' ).empty(),
$element = $( '<div>' ).appendTo( $container ),
ttf = new mw.mmv.ui.TruncatableTextField( $container, $element );
ttf.truncate = function ( ele, max, ell ) {
assert.strictEqual( max, ttf.max, 'Max length is passed in right' );
assert.strictEqual( ell, true, 'Ellipses are enabled on the first call' );
};
ttf.shrink();
} );
QUnit.test( 'Different max length - text truncation', 2, function ( assert ) {
var $container = $( '#qunit-fixture' ).empty(),
$element = $( '<div>' ).appendTo( $container ),
text = ( new Array( 500 ) ).join( 'a' ),
ttf = new mw.mmv.ui.TruncatableTextField( $container, $element, { max: 200 } ),
newText = ttf.truncateText( text, ttf.max );
assert.strictEqual( newText.length, 200, 'Text is the right length.' );
assert.strictEqual( newText, ( new Array( 201 ) ).join( 'a' ), 'Text has the right content.' );
} );
QUnit.test( 'Different max length - DOM truncation', 1, function ( assert ) {
var $container = $( '#qunit-fixture' ).empty(),
$element = $( '<div>' ).appendTo( $container ),
textOne = ( new Array( 150 ) ).join( 'a' ),
textTwo = ( new Array( 100 ) ).join( 'b' ),
ttf = new mw.mmv.ui.TruncatableTextField( $container, $element, { max: 200 } );
$element.append(
$( '<span>' ).text( textOne ),
$( '<span>' ).text( textTwo )
);
// We only want to test the element exclusion here
ttf.truncateText = function () { return ''; };
ttf.truncate( $element.get( 0 ), ttf.max, false );
assert.strictEqual( $container.text(), textOne, 'The too-long element is removed.' );
} );
QUnit.test( 'Changing style for slightly too-long elements', 3, function ( assert ) {
var $container = $( '#qunit-fixture' ).empty(),
$element = $( '<div>' ).appendTo( $container ).text( ( new Array( 500 ) ).join( 'a' ) ),
ttf = new mw.mmv.ui.TruncatableTextField( $container, $element );
ttf.changeStyle();
assert.ok( ttf.$element.hasClass( 'mw-mmv-truncate-toolong' ), 'Class set on too-long text.' );
ttf.$element.text( 'a' );
ttf.changeStyle();
assert.ok( !ttf.$element.hasClass( 'mw-mmv-truncate-toolong' ), 'Class unset on short text.' );
ttf.$element.text( ( new Array( 300 ) ).join( 'a' ) );
ttf.changeStyle();
assert.ok( ttf.$element.hasClass( 'mw-mmv-truncate-toolong' ), 'Class re-set on too-long text.' );
} );
}( mediaWiki, jQuery ) );