MediaWiki:Gadget-ImageAnnotator.js: Difference between revisions

Content deleted Content added
Stricten a few comparisions
More code cleanup; remove unused variables, use mw.config.get
Line 3:
/*
ImageAnnotator v3.0.0
 
Requires an environment running MediaWiki 1.23 or later.
 
Image annotations. Draw rectangles onto image thumbnail displayed on image description
Line 21 ⟶ 23:
See http://commons.wikimedia.org/wiki/Help:Gadget-ImageAnnotator for documentation.
*/
/*jshint undef:true, unused:true, browser:true, eqnull:true, laxbreak:true, laxcomma:true */
/*global prompt */ // discouraged browser globals
/*global wgServer, wgScript, wgScriptPath, wgArticlePath, wgContentLanguage */ // legacy config site globals
/*global wgUserGroups, wgNamespaceNumber, wgRestrictionEdit */ // legacy config page globals
/*global mw, $ */
// Global:*global importScript, importScriptURI (*/ // wikibits.js)
/*global LAPI, Buttons, Tooltip, Tooltips, TextCleaner, UIElements */ // imported scripts
/*global ImageAnnotator:true, ImageAnnotator_disable:false */ // this script
 
if (typeof ImageAnnotator === 'undefined') { // Guard against multiple inclusions
// Global: importScript, importScriptURI (wikibits.js)
if (typeof ImageAnnotator === 'undefined') {
// Global: wgPageName, wgCurRevisionId, wgUserGroups, wgRestrictionEdit (inline script on the page)
// Global: wgNamespaceNumber, wgUserLanguage, wgContentLanguage (inline script)
// Global: wgNamespaceIds (inline script)
/*jshint eqnull:true, laxbreak:true, laxcomma:true */
 
if (typeof ImageAnnotator === 'undefined') { // Guard against multiple inclusions
 
importScript('MediaWiki:LAPI.js');
Line 35 ⟶ 40:
importScript('MediaWiki:UIElements.js');
 
(function () { // Local scope
, (function (request) {
 
var conf = mw.config.get([
'skin',
'stylepath',
'wgNamespaceIds',
'wgCurRevisionId',
'wgTitle',
'wgPageName',
'wgArticleId',
'wgUserLanguage'
]);
 
var ImageAnnotator_config = null;
 
var ImageAnnotation = function () {
this.initialize.apply(this, arguments);
};
 
ImageAnnotation.compare = function (a, b) {
{
var result = b.area() - a.area();
if (result !== 0) return result;
return a.model.id - b.model.id; // Just to make sure the order is complete
return a.model.id - b.model.id;
};
 
ImageAnnotation.prototype = {
view : null, // Rectangle to be displayed on image: a div with pos and size
{
view: null,
view : null, // Rectangle to be displayed on image: a div with pos and size
model : null, // Internal representation of the annotation
tooltip model: null,
// Tooltip to display the annotation
content tooltip: null,
// Content of the tooltip
viewer content: null,
// Reference to the viewer this note belongs to
viewer: null,
 
initialize : function (node, viewer, id) {
var is_new = false;
var view_w = 0, view_h = 0, view_x = 0, view_y = 0;
Line 73 ⟶ 91:
var html = IA.getRawItem('content_' + id, viewer.scope);
if (x === null || y === null || w === null || h === null || html === null)
throw new Error ('Invalid note');
if (x < 0 || x >= viewer.full_img.width || y < 0 || y >= viewer.full_img.height)
throw new Error ('Invalid note: origin invalid on note ' + id);
if ( x + w > viewer.full_img.width + 10
|| y + h > viewer.full_img.height + 10
Line 170 ⟶ 188:
},
 
setTooltip : function () {
if (this.tooltip || !this.view) return; // Already set, or corrupt
// Note: on IE, don't have tooltips appear automatically. IE doesn't do it right for transparent
// targets and we have to show and hide them ourselves through a mousemove listener in the viewer
// anyway. The occasional event that IE sends to the tooltip may then lead to ugly flickering.
this.tooltip = new Tooltip(
( this.view.firstChild,
, this.display.bind(this),
);{
, { activate : (LAPI.DOM.is_ie ? Tooltip.NONE : Tooltip.HOVER)
,deactivate activate: (LAPI.DOM.is_ie ? Tooltip.ESCAPENONE : Tooltip.LEAVEHOVER),
, { activate deactivate: (LAPI.DOM.is_ie ? Tooltip.NONEESCAPE : Tooltip.HOVERLEAVE),
,close_button : null,
,mode : Tooltip.MOUSE,
,mouse_offset : { x: -5, y: -5, dx: (IA.is_rtl ? -1 : 1), dy: 1 },
,open_delay : 0
,hide_delay open_delay: 0,
hide_delay: 0,onclose
onclose: (function (tooltip, evt) {
if (this.view) {
try {
this.view.style.border = '1px solid ' + this.viewer.outer_border;
} catch (ex) {
this.view.style.border = '1px solid ' + IA.outer_border;
}
}
if (this.viewer.tip == tooltip) this.viewer.tip = }null;
// Hide all boxes if we're outside the image. Relies on hide checking the
if (this.viewer.tip == tooltip) this.viewer.tip = null;
// coordinates! (Otherwise, we'd always hide...)
// Hide all boxes if we're outside the image. Relies on hide checking the
if (evt) this.viewer.hide(evt);
// coordinates! (Otherwise, we'd always hide...)
}).bind(this),
if (evt) this.viewer.hide(evt);
onopen: , (function (requesttooltip) {
}).bind(this)
,onopen : (functionif (tooltipthis.view) {
if (this.view)try {
this.view.style.border = '1px solid ' + try {this.viewer.active_border;
} catch (ex) {
this.view.style.border = '1px solid ' + this.viewerIA.active_border;
} catch (ex) {
}
this.view.style.border = '1px solid ' + IA.active_border;
this.viewer.tip = }tooltip;
}).bind(this)
);},
this.viewer.tip = tooltip;
, IA.tooltip_styles
}).bind(this)
});
, IA.tooltip_styles
);
},
 
display : function (evt) {
if (!this.content) {
this.content = LAPI.make('div');
Line 223 ⟶ 242:
this.content.appendChild(LAPI.make('div', null, { clear: 'both' }));
if (this.viewer.may_edit) {
this.content.button_section = LAPI.make(
LAPI.make('div',
'div'null,
,null{
,{ fontSize : 'smaller',
,textAlign: (IA.is_rtl ? 'left' : 'right'),
,borderTop: IA.tooltip_styles.border
}
);
this.content.appendChild(this.content.button_section);
this.content.button_section.appendChild(LAPI.DOM.makeLink(
'#',
, ImageAnnotator.UI.get('wpImageAnnotatorEdit', true),
, null,
, LAPI.Evt.makeListener(this, this.edit)
));
);
if (ImageAnnotator_config.mayDelete()) {
this.content.button_section.appendChild(document.createTextNode('\xa0'));
this.content.button_section.appendChild(LAPI.DOM.makeLink(
(LAPI.DOM.makeLink'#',
ImageAnnotator.UI.get( '#wpImageAnnotatorDelete', true),
)null,
, ImageAnnotator.UI.get('wpImageAnnotatorDelete', true)
LAPI.Evt.makeListener(this, nullthis.remove_event)
));
, LAPI.Evt.makeListener(this, this.remove_event)
)
);
}
}
Line 256 ⟶ 272:
},
 
edit : function (evt) {
if (IA.canEdit()) IA.editor.editNote(this);
if (evt) return LAPI.Evt.kill(evt);
Line 262 ⟶ 278:
},
 
remove_event : function (evt) {
if (IA.canEdit()) this.remove();
return LAPI.Evt.kill(evt);
},
 
remove : function () {
if (!this.content) { // New note: just destroy it.
this.destroy();
Line 296 ⟶ 312:
if (this.tooltip) this.tooltip.size_change();
LAPI.Ajax.editPage(
conf.wgPageName
, function (doc, editForm, failureFunc, revision_id) {
try {
if (revision_id && revision_id != conf.wgCurRevisionId)
throw new Error ('#Page version (revision ID) mismatch: edit conflict.');
 
var textbox = editForm.wpTextbox1;
if (!textbox) throw new Error ('#Server replied with invalid edit page.');
var pagetext = textbox.value.replace(/\r\n/g, '\n');
// Normalize different end-of-line handling. Opera and IE may use \r\n, whereas other
Line 338 ⟶ 354:
);
} catch (ex) {
failure (null, ex);
return;
}
Line 345 ⟶ 361:
editForm
, function (request) {
if (edit_page.isFake && (typeof edit_page.dispose === 'function'))
edit_page.dispose();
var revision_id = LAPI.WP.revisionFromHtml(request.responseText);
if (!revision_id) {
failureFunc (request, new Error ('Revision ID not found. Please reload the page.'));
return;
}
conf.wgCurRevisionId = revision_id; // Bump revision id!!
LAPI.Ajax.removeSpinner(spinnerId);
if (self.tooltip) self.tooltip.size_change();
Line 358 ⟶ 374:
}
, function (request, ex) {
if (edit_page.isFake && (typeof edit_page.dispose === 'function'))
edit_page.dispose();
failureFunc (request, ex);
}
);
}
, function (request, ex) {
// Failure. What now? TODO: Implement some kind of user feedback.
LAPI.Ajax.removeSpinner(spinnerId);
Line 488 ⟶ 504:
this.visible = false;
LAPI.Evt.listenTo(this, this.tooltip.popup, IA.mouse_in,
function (evt) {
Array.forEach(IA.viewers, (function (viewer) {
if (viewer != this.viewer && viewer.visible) viewer.hide();
Line 515 ⟶ 531:
// Existing note, and we don't have the wikitext yet: go get it
var self = this;
LAPI.Ajax.apiGet('query',
'query'{
, { prop : 'revisions',
,titles : conf.wgPageName,
,rvlimit : 1,
,rvstartid : conf.wgCurRevisionId,
,rvprop : 'ids|content'
},
, function (request, json_result) {
if (json_result && json_result.query && json_result.query.pages) {
// Should have only one page here
for (var page in json_result.query.pages) {
var p = json_result.query.pages[page];
if (p && p.revisions && p.revisions.length) {
var rev = p.revisions[0];
if (rev.revid == conf.wgCurRevisionId && rev["*"] && rev["*"].length)
IA.setWikitext(rev["*"]);
}
break;
}
,open_delay : 0break;
}
// TODO: What upon a failure?
self.open_editor(same_note, cover);
}
, function (request) {
// TODO: What upon a failure?
self.open_editor(same_note, cover);
}
// TODO: What upon a failure?
self.open_editor(same_note, cover);
},
function () {
// TODO: What upon a failure?
self.open_editor(same_note, cover);
}
);
} else {
Line 691 ⟶ 707:
this.editor.enable(0); // Disable all buttons
this.saving = true;
LAPI.Ajax.editPage(conf.wgPageName,
, function (doc, editForm, failureFunc, revision_id) {
wgPageName
, function (doc, editForm, failureFunc, revision_id) {
try {
if (revision_id && revision_id != conf.wgCurRevisionId)
// Page was edited since the user loaded it.
throw new Error('#Page version (revision ID) mismatch: edit conflict.');
Line 756 ⟶ 771:
}
} catch (ex) {
failureFunc (null, ex);
return;
}
var edit_page = doc;
LAPI.Ajax.submitEdit(editForm, function (request) {
editForm
, function (request) {
// After a successful submit.
if (edit_page.isFake && (typeof edit_page.dispose === 'function'))
Line 775 ⟶ 788:
if (doc.isFake && (typeof doc.dispose === 'function')) doc.dispose();
failureFunc
(request, new Error ('#Note not found after saving. Please reload the page.'));
return;
}
Line 782 ⟶ 795:
if (doc.isFake && (typeof doc.dispose === 'function')) doc.dispose();
failureFunc
(request, new Error ('#Version inconsistency after saving. Please reload the page.'));
return;
}
conf.wgCurRevisionId = revision_id; // Bump revision id!!
self.note.model.html = LAPI.DOM.importNode(document, html, true);
if (doc.isFake && (typeof doc.dispose === 'function')) doc.dispose();
Line 812 ⟶ 825:
IA.is_editing = false;
self.editor.setText(data); // In case the same note is re-opened: start new undo cycle
},
, function (request, ex) {
if (edit_page.isFake && (typeof edit_page.dispose === 'function'))
edit_page.dispose();
failureFunc (request, ex);
}
);
},
, function (request, ex) {
self.editor.busy(false);
self.saving = false;
Line 833 ⟶ 846:
if (lk && lk.length && lk[0].firstChild.nodeName.toLowerCase() === 'a') {
lk = lk[0].firstChild;
lk.href = wgServer + wgArticlePath.replace('$1', encodeURIComponent(conf.wgPageName)) + '?action=edit';
}
if (ex) {
Line 851 ⟶ 864:
self.editor.textarea.style.backgroundColor = '#EEEEEE';
self.editor.enable(LAPI.Edit.CANCEL); // Disable all other buttons
}
);
},
 
onpreview : function (editor) {
if (this.tooltip) this.tooltip.size_change();
},
Line 1,215 ⟶ 1,228:
},
 
show : function (evt) {
if (this.visible || this.icon) return;
this.toggle(IA.is_adding || IA.is_editing);
Line 1,303 ⟶ 1,316:
},
 
check_hide : function (evt) {
if (this.icon) return true;
if (this.visible)
Line 1,310 ⟶ 1,323:
},
 
register : function (new_note) {
this.annotations[this.annotations.length] = new_note;
if (new_note.model.id > 0) {
Line 1,319 ⟶ 1,332:
},
 
deregister : function (note) {
Array.remove(this.annotations, note);
if (note.model.id == this.max_id) this.max_id--;
Line 1,325 ⟶ 1,338:
},
 
setDefaultMsg : function () {
if (this.annotations && this.annotations.length && this.msg) {
LAPI.DOM.removeChildren(this.msg);
Line 1,451 ⟶ 1,464:
 
// We'll include self.haveAjax later on once more, to catch the !ajaxQueried case.
self.may_edit = wgNamespaceNumber >= 0 && conf.wgArticleId > 0 && self.haveAjax && config.editingEnabled();
 
function namespaceCheck (list) {
if (!list || !$.isArray(list)) return false;
for (var i = 0; i < list.length; i++) {
if (conf.wgNamespaceIds
&& typeof list[i] === 'string'
&& (list[i] === '*'
|| conf.wgNamespaceIds[list[i].toLowerCase().replace(/ /g, '_')] === wgNamespaceNumber
)
)
Line 1,715 ⟶ 1,728:
var img = null;
if (scope == document) {
img = LAPI.WP.getPreviewImage(conf.wgTitle);
// TIFFs may be multi-paged: allow only for single-page TIFFs
if ( document.pageselector ) img = null;
Line 1,736 ⟶ 1,749:
var name = null;
if (scope == document) {
name = conf.wgPageName;
} else {
name = LAPI.WP.pageFromLink(img.parentNode);
Line 1,865 ⟶ 1,878:
}
 
var start = 0, chunk = 0, params;
var zoom_src start = null0;
while (to_do > 0) {
params = build_titles (start, Math.min(50, to_do), url_limit);
Line 2,035 ⟶ 2,049:
result = self.repo[id];
} else {
result = UIElements.getEntry(id, self.repo, conf.wgUserLanguage, null);
add_plea = !result;
if (!result) result = UIElements.getEntry(id, self.repo);
Line 2,066 ⟶ 2,080:
wgServer + wgScript + '?title=MediaWiki_talk:ImageAnnotatorTexts'
+ '&action=edit&section=new&withJS=MediaWiki:ImageAnnotatorTranslator.js'
+ '&language=' + conf.wgUserLanguage
, translate
, (typeof translate === 'string' ? translate : LAPI.DOM.getInnerText(translate).trim())
Line 2,107 ⟶ 2,121:
 
var ui_page = '{{MediaWiki:ImageAnnotatorTexts'
+ (conf.wgUserLanguage != wgContentLanguage ? '|lang=' + conf.wgUserLanguage : '')
+ '|live=1}}';
 
Line 2,205 ⟶ 2,219:
else
img_page_name = '';
self.may_edit = (img_page_name.replace(/ /g, '_') == conf.wgTitle.replace(/ /g, '_'));
}
 
Line 2,270 ⟶ 2,284:
}
 
function pause (evt) {
LAPI.Evt.remove(document, 'mousemove', track, true);
if (!LAPI.Browser.is_ie && typeof document.captureEvents === 'function')
Line 2,277 ⟶ 2,291:
}
 
function resume (evt) {
// captureEvents is actually deprecated, but I haven't succeeded to make this work with
// addEventListener only.
Line 2,368 ⟶ 2,382:
}
 
function add_new (evt) {
if (!self.canEdit()) return;
 
Line 2,640 ⟶ 2,654:
function make_script_calls (list, api) {
var template = api + '?action=parse&pst&text=&prop=text&format=json'
+ '&maxage=1800&smaxage=1800&uselang=' + conf.wgUserLanguage //see bugzilla 22764
+ '&callback=ImageAnnotator.script_callbacks[].callback';
if (template.startsWith('//')) template = document.___location.protocol + template; // Avoid protocol-relative URIs (IE7 bug)
Line 2,738 ⟶ 2,752:
var zoom_width = Math.floor(self.viewers[0].thumb.width * self.zoom_factor);
var zoom_height = Math.floor(self.viewers[0].thumb.height * self.zoom_factor);
var zoom_src = null;
// For SVGs, always use a scaled PNG for the zoom.
if (zoom_width > 0.9 * self.viewers[0].full_img.width && src.search(/\.svg\.png$/i) < 0) {
Line 3,099 ⟶ 3,112:
}
 
}; // end IA (private)
 
// Public interface
window.ImageAnnotator = {
install: function (config) {
mw.loader.using(['mediawiki.util'], function () {
IA.install(config);
});
}
};