MarkTraceur has uploaded a new change for review.
https://gerrit.wikimedia.org/r/300296
Change subject: Refactor into a class, make editing easier
......................................................................
Refactor into a class, make editing easier
This was largely so we didn't have two separate code paths for editing
annotations (creation/editing existing), and now it's finally set. This
should make extending this code much easier.
Change-Id: If1386afb2a14882b9e8fd15e328fcaff953b3421
---
M resources/src/fileannotations.js
M resources/src/fileannotations.less
2 files changed, 268 insertions(+), 203 deletions(-)
git pull
ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/FileAnnotations
refs/changes/96/300296/1
diff --git a/resources/src/fileannotations.js b/resources/src/fileannotations.js
index c6d9c66..2c3cfd4 100644
--- a/resources/src/fileannotations.js
+++ b/resources/src/fileannotations.js
@@ -1,19 +1,137 @@
( function ( $, mw ) {
- var createButton, $annotationsContainer, $mainImage,
- annotationsCache, offset, $annotationInfo,
- api = new mw.Api(),
- pageTitle = mw.Title.newFromText( mw.config.get( 'wgPageName' )
),
- annotationsTitle = mw.Title.newFromText( 'File_Annotations:' +
pageTitle.getMain() ),
+ var pageTitle = mw.Title.newFromText( mw.config.get( 'wgPageName' ) ),
isFilePage = pageTitle.getNamespaceId() === 6,
$fileLink = $( '#file' );
- function getAnnotationsJSON( pageTitle ) {
- return api.get( {
+ /**
+ * Class for rendering, editing, creating and deleting annotations on a
file.
+ *
+ * @class mw.FileAnnotator
+ * @constructor
+ * @param {jQuery} $fileLink Look for '#file' on the file page.
+ * @param {mw.Title} fileTitle Title of the file.
+ */
+ function FileAnnotator( $fileLink, fileTitle ) {
+ var offset, $annotationInfo, createButton,
+ annotator = this;
+
+ this.api = new mw.Api();
+
+ this.$fileLink = $fileLink;
+ this.fileTitle = fileTitle;
+ this.$img = $fileLink.find( 'img' );
+
+ $annotationInfo = $( '<div>' )
+ .addClass( 'fileannotation-info' )
+ .append(
+ $( '<p>' ).text( mw.message(
'file-has-annotations' ).text() )
+ );
+
+ $fileLink.after( $annotationInfo );
+
+ this.$container = $( '<div>' )
+ .addClass( 'annotation-wrapper' );
+
+ offset = this.$img.offset();
+
+ this.$container.css( {
+ top: offset.top,
+ left: offset.left,
+ width: this.$img.width(),
+ height: this.$img.height()
+ } );
+
+ $( 'body' ).append( this.$container );
+
+ this.annotationsTitle = mw.Title.newFromText(
'File_Annotations:' + fileTitle.getMain() );
+
+ this.getAndRenderAnnotations();
+
+ this.getAnnotationsHTML().then( function ( data ) {
+ var pageId = data.query.pageids[ 0 ],
+ page = data.query.pages[ pageId ],
+ imageInfo = page.imageinfo[ 0 ],
+ fullw = imageInfo.width,
+ fullh = imageInfo.height,
+ imgw = annotator.$img.width(),
+ imgh = annotator.$img.height(),
+ adjustRatioX = imgw / fullw,
+ adjustRatioY = imgh / fullh;
+
+ // Make it possible to create new annotations
graphically.
+ createButton = new OO.ui.ButtonWidget( {
+ label: mw.message( 'fileannotation-create'
).text(),
+ icon: 'add',
+ flags: [ 'constructive' ]
+ } );
+
+ createButton.on( 'click', function () {
+ if ( annotator.$container.hasClass(
'click-to-create' ) ) {
+ // Don't turn it on twice!
+ return;
+ }
+
+ // Turn on click-to-initiate...
+ annotator.$container
+ .addClass( 'click-to-create' );
+
+ annotator.$container
+ .one( 'click', function ( e ) {
+ // Add outline and edit
interface
+ var x = e.offsetX,
+ y = e.offsetY,
+ // We want the
annotation to default to at least 40 pixels,
+ // or 1/20th of the
size of the image, unless the image is less than 40
+ // pixels in which case
we'll just select the whole thing.
+ defaultHeight =
Math.min( Math.max( 40, fullh / 20 ), fullh ),
+ defaultWidth =
Math.min( Math.max( 40, fullw / 20 ), fullw ),
+ adjustedDefaultDim =
Math.min(
+ defaultHeight *
adjustRatioY,
+ defaultWidth *
adjustRatioX
+ );
+
+
annotator.$container.removeClass( 'click-to-create' );
+
+
annotator.createAnnotationEditor( x, y, adjustedDefaultDim, adjustedDefaultDim )
+ .then( function ( newX,
newY, newWidth, newHeight, newText ) {
+
annotator.getAnnotationsJSON().then( function ( annotations ) {
+
annotations.annotations.push( {
+
content: newText,
+
x: newX / adjustRatioX,
+
y: newY / adjustRatioY,
+
width: newWidth / adjustRatioX,
+
height: newHeight / adjustRatioY
+ } );
+
+ return
annotator.saveAnnotations(
+
annotations,
+
'Added a file annotation from the file page, text: "' + newText + '"'
+ );
+ } ).then(
function () {
+ //
Close interface, make the annotation official.
+
annotator.annotationsCache = undefined;
+
annotator.getAndRenderAnnotations();
+ } );
+ } );
+ } );
+ } );
+
+ $annotationInfo.append( createButton.$element );
+ } );
+ }
+
+ /**
+ * Get JSON data for the annotations on the page, suitable for editing.
+ *
+ * @return {jQuery.Promise}
+ */
+ FileAnnotator.prototype.getAnnotationsJSON = function () {
+ return this.api.get( {
action: 'query',
prop: 'revisions',
rvprop: 'content',
indexpageids: true,
- titles: pageTitle.getPrefixedDb()
+ titles: this.annotationsTitle.getPrefixedDb()
} ).then( function ( data ) {
var rv, text, annotations,
pages = data.query.pages,
@@ -34,12 +152,19 @@
return annotations;
} );
- }
+ };
- function saveUpdatedAnnotations( annotations, pageTitle, summary ) {
- return api.postWithToken( 'csrf', {
+ /**
+ * Save the annotations to the server.
+ *
+ * @param {Object} annotations A valid JavaScript object adhering to
the annotations schema.
+ * @param {string} summary The edit summary.
+ * @return {jQuery.Promise}
+ */
+ FileAnnotator.prototype.saveAnnotations = function ( annotations,
summary ) {
+ return this.api.postWithToken( 'csrf', {
action: 'edit',
- title: pageTitle.getPrefixedDb(),
+ title: this.annotationsTitle.getPrefixedDb(),
text: JSON.stringify( annotations ),
summary: summary
} );
@@ -51,29 +176,25 @@
*
* @return {jQuery.Promise}
*/
- function getAnnotationsHTML( imageTitle ) {
- if ( annotationsCache === undefined ) {
- if ( !isFilePage ) {
- annotationsCache = $.Deferred().reject( 'Not a
file page, aborting request for annotations' );
- } else {
- annotationsCache = api.get( {
- action: 'query',
- indexpageids: true,
- prop: [ 'fileannotations', 'imageinfo'
],
- titles: imageTitle.getPrefixedDb(),
- faparse: true,
- iiprop: 'size'
- } ).then( function ( data ) {
- if ( data.error ) {
- return $.Deferred().reject(
data.error );
- }
+ FileAnnotator.prototype.getAnnotationsHTML = function () {
+ if ( this.annotationsCache === undefined ) {
+ this.annotationsCache = this.api.get( {
+ action: 'query',
+ indexpageids: true,
+ prop: [ 'fileannotations', 'imageinfo' ],
+ titles: this.fileTitle.getPrefixedDb(),
+ faparse: true,
+ iiprop: 'size'
+ } ).then( function ( data ) {
+ if ( data.error ) {
+ return $.Deferred().reject( data.error
);
+ }
- return data;
- } );
- }
+ return data;
+ } );
}
- return annotationsCache;
+ return this.annotationsCache;
}
/**
@@ -81,9 +202,9 @@
*
* @param {jQuery} $container Where to put the editor interface.
* @param {string} text The wikitext of the annotation.
- * @return {jQuery.promise} Resolved with the new text if annotation is
saved, rejected if annotation is discarded.
+ * @return {jQuery.Promise} Resolved with the new text if annotation is
saved, rejected if annotation is discarded.
*/
- function createAnnotationTextEditor( $container, text ) {
+ FileAnnotator.prototype.createAnnotationTextEditor = function (
$container, text ) {
var deferred = $.Deferred(),
$annotationEditor = $( '<div>' )
.addClass( 'annotation-editor' ),
@@ -135,6 +256,78 @@
}
/**
+ * Create an editing interface for an annotation, including text editor
+ * and graphical location/size editor.
+ *
+ * @param {number} x
+ * @param {number} y
+ * @param {number} width
+ * @param {number} height
+ * @param {string} [text] If the annotation already exists, this is the
wikitext.
+ * @param {jQuery} [$existing] If the annotation already exists, this
is the rendered box.
+ * @return {jQuery.Promise}
+ */
+ FileAnnotator.prototype.createAnnotationEditor = function ( x, y,
width, height, text, $existing ) {
+ var $box, $contain;
+
+ this.$container.addClass( 'editing-annotations' );
+
+ if ( $existing ) {
+ $box = $existing;
+ $box.addClass( 'editing-annotation' );
+ $contain = $box.find( '.annotation-container' );
+ } else {
+ $box = $( '<div>' )
+ .addClass( 'new-annotation' )
+ .css( {
+ top: y,
+ left: x,
+ width: width,
+ height: height
+ } );
+
+ this.$container.append( $box );
+
+ // For a new annotation, the box is the container.
+ $contain = $box;
+ }
+
+ $box.draggable( {
+ containment: 'parent'
+ } );
+
+ $box.resizable( {
+ containment: 'parent'
+ } );
+
+ return annotator.createAnnotationTextEditor( $contain, text
).then( function ( newText ) {
+ var newY = parseInt( $box.css( 'top' ), 10 ),
+ newX = parseInt( $box.css( 'left' ), 10 ),
+ newWidth = parseInt( $box.css( 'width' ), 10 ),
+ newHeight = parseInt( $box.css( 'height' ), 10
);
+
+ return $.Deferred().resolve( newX, newY, newWidth,
newHeight, newText );
+ }, function () {
+ annotator.$container.removeClass( 'editing-annotations'
);
+
+ if ( $existing ) {
+ $box.removeClass( 'editing-annotation' );
+ $box.resizable( 'destroy' );
+ $box.draggable( 'destroy' );
+
+ $box.css( {
+ top: y,
+ left: x,
+ height: height,
+ width: width
+ } );
+ } else {
+ $box.remove();
+ }
+ } );
+ };
+
+ /**
* Render an annotation, and the edit interface.
*
* @param {number} i Which number this annotation is in the list.
@@ -150,8 +343,9 @@
* @param {number} adjustRatioY Same as above, but for the Y axis.
* @return {jQuery} The annotation box to be added to the container.
*/
- function renderAnnotation( i, annotation, imageInfo, adjustRatioX,
adjustRatioY ) {
- var $annotation = $( '<div>' )
+ FileAnnotator.prototype.renderAnnotation = function ( i, annotation,
imageInfo, adjustRatioX, adjustRatioY ) {
+ var annotator = this,
+ $annotation = $( '<div>' )
.addClass( 'file-annotation' )
.append( annotation.parsed ),
adjustedX = annotation.x * adjustRatioX,
@@ -195,59 +389,44 @@
currentWidth = $annotationBox.css( 'width' ),
currentHeight = $annotationBox.css( 'height' );
- // Create edit interface and link things up
- $annotationBox.addClass( 'editing-annotation' );
- $annotationsContainer.addClass( 'editing-annotations' );
-
- $annotationBox.draggable( {
- containment: 'parent'
- } );
-
- $annotationBox.resizable( {
- containment: 'parent'
- } );
-
- createAnnotationTextEditor( $annotationContain,
annotation.text ).then( function ( newText ) {
- var newY = parseInt( $annotationBox.css( 'top'
), 10 ),
- newX = parseInt( $annotationBox.css(
'left' ), 10 ),
- newWidth = parseInt(
$annotationBox.css( 'width' ), 10 ),
- newHeight = parseInt(
$annotationBox.css( 'height' ), 10 );
-
- getAnnotationsJSON( annotationsTitle ).then(
function ( annotations ) {
+ annotator.createAnnotationEditor(
+ currentX,
+ currentY,
+ currentWidth,
+ currentHeight,
+ annotation.text,
+ $annotationBox
+ ).then( function ( newX, newY, newWidth, newHeight,
newText ) {
+ annotator.getAnnotationsJSON().then( function (
annotations ) {
annotations.annotations[ i ].content =
newText;
annotations.annotations[ i ].x = newX /
adjustRatioX;
annotations.annotations[ i ].y = newY /
adjustRatioY;
annotations.annotations[ i ].width =
newWidth / adjustRatioX;
annotations.annotations[ i ].height =
newHeight / adjustRatioY;
- saveUpdatedAnnotations( annotations,
annotationsTitle, 'Edited annotation on file page. New text: "' + newText + '"'
).then( function () {
+ annotator.saveAnnotations(
+ annotations,
+ 'Edited annotation on file
page. New text: "' + newText + '"'
+ ).then( function () {
// Close edit interface, make
the annotation official.
- annotationsCache = undefined;
- getAndRenderAnnotations(
pageTitle, $annotationsContainer, $mainImage );
+ annotator.annotationsCache =
undefined;
+
annotator.getAndRenderAnnotations();
} );
- } );
- }, function () {
- $annotationBox.removeClass(
'editing-annotation' );
- $annotationBox.resizable( 'destroy' );
- $annotationBox.draggable( 'destroy' );
-
- $annotationBox.css( {
- top: currentY,
- left: currentX,
- height: currentHeight,
- width: currentWidth
} );
} );
} );
deleteButton.on( 'click', function () {
// Delete the annotation and refresh.
- getAnnotationsJSON( annotationsTitle ).then( function (
annotations ) {
+ annotator.getAnnotationsJSON().then( function (
annotations ) {
annotations.annotations.splice( i, 1 );
- saveUpdatedAnnotations( annotations,
annotationsTitle, 'Deleted annotation on file page.' ).then( function () {
+ annotator.saveAnnotations(
+ annotations,
+ 'Deleted annotation on file page.'
+ ).then( function () {
// Close edit interface, make the
annotation official.
- annotationsCache = undefined;
- getAndRenderAnnotations( pageTitle,
$annotationsContainer, $mainImage );
+ annotator.annotationsCache = undefined;
+ annotator.getAndRenderAnnotations();
} );
} );
} );
@@ -266,8 +445,13 @@
return $annotationBox;
}
- function getAndRenderAnnotations( imageTitle, $container, $img ) {
- getAnnotationsHTML( imageTitle )
+ /**
+ * Get the annotations, and render them on the image.
+ */
+ FileAnnotator.prototype.getAndRenderAnnotations = function () {
+ var annotator = this;
+
+ this.getAnnotationsHTML( this.fileTitle )
.then( function ( data ) {
var i,
pageId = data.query.pageids[ 0 ],
@@ -276,145 +460,25 @@
annotations = page.fileannotations[ 0 ],
fullw = imageInfo.width,
fullh = imageInfo.height,
- imgw = $img.width(),
- imgh = $img.height(),
+ imgw = annotator.$img.width(),
+ imgh = annotator.$img.height(),
adjustRatioX = imgw / fullw,
adjustRatioY = imgh / fullh;
// Clear any existing annotations so we start
fresh.
- $container.empty();
+ annotator.$container.empty();
for ( i = 0; i < annotations.length; i++ ) {
- $container.append(
- renderAnnotation( i,
annotations[ i ], imageInfo, adjustRatioX, adjustRatioY )
+ annotator.$container.append(
+ annotator.renderAnnotation( i,
annotations[ i ], imageInfo, adjustRatioX, adjustRatioY )
);
}
} );
}
if ( isFilePage ) {
- $mainImage = $fileLink.find( 'img' );
-
- $annotationInfo = $( '<div>' )
- .addClass( 'fileannotation-info' )
- .append(
- $( '<p>' ).text( mw.message(
'file-has-annotations' ).text() )
- );
-
- $fileLink.after( $annotationInfo );
-
- $annotationsContainer = $( '<div>' )
- .addClass( 'annotation-wrapper' );
-
- offset = $mainImage.offset();
-
- $annotationsContainer.css( {
- top: offset.top,
- left: offset.left,
- width: $mainImage.width(),
- height: $mainImage.height()
- } );
-
- $( 'body' ).append( $annotationsContainer );
-
- getAndRenderAnnotations( pageTitle, $annotationsContainer,
$mainImage );
-
- getAnnotationsHTML( pageTitle )
- .then( function ( data ) {
- var pageId = data.query.pageids[ 0 ],
- page = data.query.pages[ pageId ],
- imageInfo = page.imageinfo[ 0 ],
- fullw = imageInfo.width,
- fullh = imageInfo.height,
- imgw = $mainImage.width(),
- imgh = $mainImage.height(),
- adjustRatioX = imgw / fullw,
- adjustRatioY = imgh / fullh;
-
- // Make it possible to create new annotations
graphically.
- createButton = new OO.ui.ButtonWidget( {
- label: mw.message(
'fileannotation-create' ).text(),
- icon: 'add',
- flags: [ 'constructive' ]
- } );
-
- createButton.on( 'click', function () {
- if ( $annotationsContainer.hasClass(
'click-to-create' ) ) {
- // Don't turn it on twice!
- return;
- }
-
- // Turn on click-to-initiate...
- $annotationsContainer
- .addClass( 'click-to-create' );
-
- $annotationsContainer
- .one( 'click', function ( e ) {
- // Add outline and edit
interface
- var x = e.offsetX,
- y = e.offsetY,
- // We want the
annotation to default to at least 40 pixels,
- // or 1/20th of
the size of the image, unless the image is less than 40
- // pixels in
which case we'll just select the whole thing.
- defaultHeight =
Math.min( Math.max( 40, fullh / 20 ), fullh ),
- defaultWidth =
Math.min( Math.max( 40, fullw / 20 ), fullw ),
-
adjustedDefaultDim = Math.min(
-
defaultHeight * adjustRatioY,
-
defaultWidth * adjustRatioX
- ),
-
- $newBox = $(
'<div>' )
-
.addClass( 'new-annotation' )
- .css( {
-
top: y,
-
left: x,
-
width: adjustedDefaultDim,
-
height: adjustedDefaultDim
- } );
-
- $newBox.draggable( {
- containment:
'parent'
- } );
- $newBox.resizable( {
- containment:
'parent'
- } );
-
-
$annotationsContainer.append( $newBox );
-
-
$annotationsContainer.removeClass( 'click-to-create' );
-
-
createAnnotationTextEditor( $newBox )
- .fail( function
() {
-
$newBox.remove();
- } ).done(
function ( newText ) {
- var
newY = parseInt( $newBox.css( 'top' ), 10 ),
-
newX = parseInt( $newBox.css( 'left' ), 10 ),
-
newWidth = parseInt( $newBox.css( 'width' ), 10 ),
-
newHeight = parseInt( $newBox.css( 'height' ), 10 );
-
-
getAnnotationsJSON( annotationsTitle ).then( function ( annotations ) {
-
annotations.annotations.push( {
-
content: newText,
-
x: newX / adjustRatioX,
-
y: newY / adjustRatioY,
-
width: newWidth / adjustRatioX,
-
height: newHeight / adjustRatioY
-
} );
-
-
return saveUpdatedAnnotations(
-
annotations, annotationsTitle,
-
'Added a file annotation from the file page, text: "' + newText + '"'
-
);
- }
).then( function () {
-
// Close interface, make the annotation official.
-
annotationsCache = undefined;
-
getAndRenderAnnotations( pageTitle, $annotationsContainer, $mainImage );
- } );
- } );
- } );
- } );
-
- $annotationInfo.append( createButton.$element );
- } );
+ annotator = new FileAnnotator( $fileLink, pageTitle );
}
+
+ mw.FileAnnotator = FileAnnotator;
}( jQuery, mediaWiki ) );
diff --git a/resources/src/fileannotations.less
b/resources/src/fileannotations.less
index 7521660..dba0884 100644
--- a/resources/src/fileannotations.less
+++ b/resources/src/fileannotations.less
@@ -19,6 +19,7 @@
border: 1px dotted green;
.annotation-container {
display: block;
+ .file-annotation,
.annotation-edit-buttons {
display: none;
}
--
To view, visit https://gerrit.wikimedia.org/r/300296
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: If1386afb2a14882b9e8fd15e328fcaff953b3421
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/FileAnnotations
Gerrit-Branch: master
Gerrit-Owner: MarkTraceur <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits