MediaWiki:Gadget-ImageAnnotator.js: Difference between revisions

Content deleted Content added
More code cleanup; remove unused variables, use mw.config.get
Migrate from legacy globals and hardcoded script paths to using getUrl() and wikiScript(); Also hoist functions that were nested in if() blocks, Javascript doesn't support this and just hoists them unconditonally to the nearest top of a function scope.
Line 25:
/*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, $ */
Line 390:
},
 
destroy : function () {
if (this.view) LAPI.DOM.removeNode(this.view);
if (this.dummy) LAPI.DOM.removeNode(this.dummy);
Line 402:
},
 
area : function () {
if (!this.model || !this.model.dimension) return 0;
return (this.model.dimension.w * this.model.dimension.h);
},
 
cannotEdit : function () {
if (this.content && this.content.button_section) {
LAPI.DOM.removeNode(this.content.button_section);
Line 421:
ImageAnnotationEditor.prototype =
{
initialize : function () {
var editor_width = 50;
// Respect potential user-defined width setting
Line 447:
,onpreview : this.onpreview.bind(this)
,oncancel : this.cancel.bind(this)
,ongettext : function (text) {
if (text == null) return '';
text = text.trim()
Line 512:
},
 
get_editor : function () {
return this.box;
},
 
editNote : function (note) {
var same_note = (note == this.note);
this.note = note;
Line 565:
},
 
open_editor : function (same_note, cover) {
this.editor.hidePreview();
if (!same_note || this.editor.textarea.readOnly)
Line 603:
},
 
hide_editor : function (evt) {
if (!this.visible) return;
this.visible = false;
Line 623:
},
 
save : function (editor) {
var data = editor.getText();
if (!data || !data.length) {
Line 846:
if (lk && lk.length && lk[0].firstChild.nodeName.toLowerCase() === 'a') {
lk = lk[0].firstChild;
lk.href = wgServer + wgArticlePathmw.util.replace('$1', encodeURIComponentgetUrl(conf.wgPageName)), +{ action: '?action=edit' });
}
if (ex) {
Line 868:
},
 
onpreview : function () {
if (this.tooltip) this.tooltip.size_change();
},
 
cancel : function (editor) {
if (!this.note) return;
if (!this.note.content) {
Line 882:
},
 
close_tooltip : function (tooltip, evt) {
this.hide_editor(evt);
this.cancel();
Line 893:
ImageNotesViewer.prototype =
{
initialize : function (descriptor, may_edit) {
Object.merge(descriptor, this);
this.annotations = [];
Line 921:
},
 
setup : function (onlyIcon) {
this.setup_done = true;
var name = this.realName;
Line 1,144:
},
 
cannotEdit : function () {
if (!this.may_edit) return;
this.may_edit = false;
Line 1,150:
},
 
setShowHideEvents : function (set) {
if (this.icon) return;
if (set) {
Line 1,164:
},
 
removeMoveListener : function () {
if (this.icon) return;
this.move_listening = false;
Line 1,174:
},
 
adjustRectangleSize : function (node) {
if (this.icon) return;
// Make sure the note boxes don't overlap the image boundary; we might get an event
Line 1,205:
},
 
toggle : function (dummies) {
var i;
if (!this.annotations || this.annotations.length === 0 || this.icon) return;
Line 1,228:
},
 
show : function () {
if (this.visible || this.icon) return;
this.toggle(IA.is_adding || IA.is_editing);
Line 1,239:
},
 
hide : function (evt) {
function intersect_rectangles (a, b) {
if (b.x > a.r || b.r < a.x || b.y > a.b || b.b < a.y)
return { x:0, y:0, r:0, b:0 };
 
return {
x: Math.max(a.x, b.x),
y: Math.max(a.y, b.y),
r: Math.min(a.r, b.r),
b: Math.min(a.b, b.b)
};
}
if (this.icon) return true;
if (!this.visible) {
Line 1,266 ⟶ 1,277:
// Compute the actually visible region by intersecting the rectangle given by img_pos and
// this.img.offsetWidth, this.img.offsetTop with the rectangles of all overflow parents.
 
function intersect_rectangles (a, b) {
if (b.x > a.r || b.r < a.x || b.y > a.b || b.b < a.y)
return { x:0, y:0, r:0, b:0 };
 
return {
x: Math.max(a.x, b.x),
y: Math.max(a.y, b.y),
r: Math.min(a.r, b.r),
b: Math.min(a.b, b.b)
};
}
 
for (i = 0; i < this.overflowParents.length && rect.x < rect.r && rect.y < rect.b; i++) {
Line 1,351 ⟶ 1,350:
lk.parentNode.replaceChild(
LAPI.DOM.makeLink(
wgArticlePathmw.replace('$1', encodeURIComponentutil.getUrl(this.realName))
, this.realName
, this.realName
Line 1,375 ⟶ 1,374:
// for drawing rectangles onto the image if there is only one image and editing is allowed.
 
haveAjax : false,
 
button_div : null,
Line 1,629 ⟶ 1,628:
},
 
wait_for_required_libraries : function () {
if (typeof Tooltip === 'undefined' || typeof LAPI === 'undefined') {
if (IA.install_attempts++ < IA.max_install_attempts) {
Line 1,783 ⟶ 1,782:
}
return { scope : scope
,file_div : file_div
,img : img
,realName : name
Line 1,889 ⟶ 1,888:
 
function set_info (json) {
function get_size (info) {
if (!info.imageinfo || info.imageinfo.length === 0) return;
var title = info.title.replace(/ /g, '_');
var indices = cache[title];
if (!indices) return;
Array.forEach(
indices
, function (i) {
self.imgs[i].full_img = { width : info.imageinfo[0].width
,height: info.imageinfo[0].height};
self.imgs[i].has_page = (typeof info.missing === 'undefined');
self.imgs[i].isLocal = !info.imagerepository || info.imagerepository === 'local';
if (i != 0 || !self.may_edit || !info.protection || wgNamespaceNumber !== 6) return;
// Care about the protection settings
var protection = Array.any(info.protection, function (e) {
return (e.type === 'edit' ? e : null);
});
self.may_edit =
!protection
|| (wgUserGroups && wgUserGroups.join(' ').contains(protection.level))
;
}
);
}
try {
if (json && json.query && json.query.pages) {
function get_size (info) {
if (!info.imageinfo || info.imageinfo.length === 0) return;
var title = info.title.replace(/ /g, '_');
var indices = cache[title];
if (!indices) return;
Array.forEach(
indices
, function (i) {
self.imgs[i].full_img = { width : info.imageinfo[0].width
,height: info.imageinfo[0].height};
self.imgs[i].has_page = (typeof info.missing === 'undefined');
self.imgs[i].isLocal = !info.imagerepository || info.imagerepository === 'local';
if (i != 0 || !self.may_edit || !info.protection || wgNamespaceNumber !== 6) return;
// Care about the protection settings
var protection = Array.any(info.protection, function (e) {
return (e.type === 'edit' ? e : null);
});
self.may_edit =
!protection
|| (wgUserGroups && wgUserGroups.join(' ').contains(protection.level))
;
}
);
}
for (var page in json.query.pages) {
get_size (json.query.pages[page]);
Line 1,927 ⟶ 1,926:
// prompt by using getScript instead of parseWikitext in this case.
ImageAnnotator.info_callbacks = [];
var template = wgServer + wgScriptPathmw.util.wikiScript('api') + '/api.php?action=query&format=json&action=query'
+ '&prop=info|imageinfo&inprop=protection&iiprop=size'
+ '&titles=&callback=ImageAnnotator.info_callbacks[].callback';
Line 1,986 ⟶ 1,985:
},
 
setup_ui : function () {
// Complete the UI object we've gotten from config.
 
Line 2,078 ⟶ 2,077:
span.appendChild(
LAPI.DOM.makeLink(
wgServer + wgScript + mw.util.getUrl('?title=MediaWiki_talk:ImageAnnotatorTexts', {
+ '& action=edit&section=new&withJS=MediaWiki:ImageAnnotatorTranslator.js 'edit',
+ '&language=' + conf.wgUserLanguage section: 'new',
withJS: 'MediaWiki:ImageAnnotatorTranslator.js',
, translate
language: conf.wgUserLanguage
, (typeof translate === 'string' ? translate : LAPI.DOM.getInnerText(translate).trim())
}),
translate,
(typeof translate === 'string' ? translate : LAPI.DOM.getInnerText(translate).trim())
)
);
Line 2,126 ⟶ 2,128:
function get_ui_no_ajax () {
var url =
wgServer + wgScriptPathmw.util.wikiScript('api') + '/api.php?format=json&action=parse&pst&text='
+ encodeURIComponent(ui_page) + '&title=API&prop=text'
+ '&callback=ImageAnnotator.UI.init&maxage=14400&smaxage=14400'
Line 2,170 ⟶ 2,172:
},
 
setup_step_two : function () {
var self = IA;
 
Line 2,192 ⟶ 2,194:
},
 
complete_setup : function () {
function track(evt) {
// We can be sure to have the UI here because this is called only when the ready event of the
// UI object is fired.
var self = IA;
 
// Check edit permissions
if (self.may_edit && typeof wgRestrictionEdit !== 'undefined' ) {
self.may_edit =
( (wgRestrictionEdit.length === 0 || wgUserGroups && wgUserGroups.join(' ').contains('sysop'))
|| ( wgRestrictionEdit.length === 1 && wgRestrictionEdit[0] === 'autoconfirmed'
&& wgUserGroups && wgUserGroups.join(' ').contains('confirmed') // confirmed & autoconfirmed
)
);
}
 
if (self.may_edit) {
// Check whether the image is local. Don't allow editing if the file is remote.
var sharedUpload = document.getElementsByClassName('sharedUploadNotice');
self.may_edit = (!sharedUpload || sharedUpload.length === 0);
}
if (self.may_edit && wgNamespaceNumber !== 6) {
// Only allow edits if the stored page name matches the current one.
var img_page_name = self.imgs[0].scope.getElementsByClassName('wpImageAnnotatorPageName');
if (img_page_name && img_page_name.length)
img_page_name = LAPI.DOM.getInnerText(img_page_name[0]);
else
img_page_name = '';
self.may_edit = (img_page_name.replace(/ /g, '_') == conf.wgTitle.replace(/ /g, '_'));
}
 
if (self.may_edit && self.ajaxQueried) self.may_edit = self.haveAjax;
 
// Now create viewers for all images
self.viewers = new Array (self.imgs.length);
for (var i = 0; i < self.imgs.length; i++) {
self.viewers[i] = new ImageNotesViewer (self.imgs[i], i === 0 && self.may_edit);
}
 
if (self.may_edit) {
 
// Respect user override for zoom, if any
self.zoom_threshold = ImageAnnotator_config.zoom_threshold;
if ( typeof window.ImageAnnotator_zoom_threshold !== 'undefined'
&& !isNaN (window.ImageAnnotator_zoom_threshold)
&& window.ImageAnnotator_zoom_threshold >= 0.0
) {
// If somebody sets it to a nonsensical high value, that's his or her problem: there won't be any
// zooming.
self.zoom_threshold = window.ImageAnnotator_zoom_threshold;
}
// Adapt zoom threshold for small thumbnails or images with a very lopsided width/height ratio,
// but only if we *can* zoom at least twice
if ( self.viewers[0].full_img.width > 300
&& Math.min(self.viewers[0].factors.dx, self.viewers[0].factors.dy) >= 2.0
) {
if ( self.viewers[0].thumb.width < 400
|| self.viewers[0].thumb.width / self.viewers[0].thumb.height > 2.0
|| self.viewers[0].thumb.height / self.viewers[0].thumb.width > 2.0
) {
self.zoom_threshold = 0; // Force zooming
}
}
 
self.editor = new ImageAnnotationEditor ();
 
function track (evt) {
evt = evt || window.event;
if (self.is_adding) self.update_zoom(evt);
Line 2,291 ⟶ 2,229:
}
 
function resume () {
// captureEvents is actually deprecated, but I haven't succeeded to make this work with
// addEventListener only.
Line 2,302 ⟶ 2,240:
}
 
function stop_tracking (evt) {
evt = evt || window.event;
// Check that we're within the image. Note: this check can fail only on IE >= 7, on other
Line 2,349 ⟶ 2,287:
}
 
function start_tracking (evt) {
if (!self.is_tracking) {
self.is_tracking = true;
Line 2,382 ⟶ 2,320:
}
 
function add_new () {
if (!self.canEdit()) return;
 
Line 2,429 ⟶ 2,367:
self.viewers[0].msg.style.display = '';
}
 
// We can be sure to have the UI here because this is called only when the ready event of the
// UI object is fired.
var self = IA;
 
// Check edit permissions
if (self.may_edit && typeof wgRestrictionEdit !== 'undefined' ) {
self.may_edit =
( (wgRestrictionEdit.length === 0 || wgUserGroups && wgUserGroups.join(' ').contains('sysop'))
|| ( wgRestrictionEdit.length === 1 && wgRestrictionEdit[0] === 'autoconfirmed'
&& wgUserGroups && wgUserGroups.join(' ').contains('confirmed') // confirmed & autoconfirmed
)
);
}
 
if (self.may_edit) {
// Check whether the image is local. Don't allow editing if the file is remote.
var sharedUpload = document.getElementsByClassName('sharedUploadNotice');
self.may_edit = (!sharedUpload || sharedUpload.length === 0);
}
if (self.may_edit && wgNamespaceNumber !== 6) {
// Only allow edits if the stored page name matches the current one.
var img_page_name = self.imgs[0].scope.getElementsByClassName('wpImageAnnotatorPageName');
if (img_page_name && img_page_name.length)
img_page_name = LAPI.DOM.getInnerText(img_page_name[0]);
else
img_page_name = '';
self.may_edit = (img_page_name.replace(/ /g, '_') == conf.wgTitle.replace(/ /g, '_'));
}
 
if (self.may_edit && self.ajaxQueried) self.may_edit = self.haveAjax;
 
// Now create viewers for all images
self.viewers = new Array (self.imgs.length);
for (var i = 0; i < self.imgs.length; i++) {
self.viewers[i] = new ImageNotesViewer (self.imgs[i], i === 0 && self.may_edit);
}
 
if (self.may_edit) {
 
// Respect user override for zoom, if any
self.zoom_threshold = ImageAnnotator_config.zoom_threshold;
if ( typeof window.ImageAnnotator_zoom_threshold !== 'undefined'
&& !isNaN (window.ImageAnnotator_zoom_threshold)
&& window.ImageAnnotator_zoom_threshold >= 0.0
) {
// If somebody sets it to a nonsensical high value, that's his or her problem: there won't be any
// zooming.
self.zoom_threshold = window.ImageAnnotator_zoom_threshold;
}
// Adapt zoom threshold for small thumbnails or images with a very lopsided width/height ratio,
// but only if we *can* zoom at least twice
if ( self.viewers[0].full_img.width > 300
&& Math.min(self.viewers[0].factors.dx, self.viewers[0].factors.dy) >= 2.0
) {
if ( self.viewers[0].thumb.width < 400
|| self.viewers[0].thumb.width / self.viewers[0].thumb.height > 2.0
|| self.viewers[0].thumb.height / self.viewers[0].thumb.width > 2.0
) {
self.zoom_threshold = 0; // Force zooming
}
}
 
self.editor = new ImageAnnotationEditor();
 
self.button_div = LAPI.make('div');
Line 2,692 ⟶ 2,694:
 
if ((!window.XMLHttpRequest && !!window.ActiveXObject) || !self.haveAjax) {
make_script_calls(get_local, wgServer + wgScriptPath + mw.util.wikiScript('/api.php'));
} else {
make_calls(
Line 2,717 ⟶ 2,719:
},
 
show_zoom : function () {
var self = IA;
if ( ( self.viewers[0].factors.dx < self.zoom_threshold
Line 2,812 ⟶ 2,814:
},
 
update_zoom : function (evt) {
if (!evt) return; // We need an event to calculate positions!
var self = IA;
Line 2,848 ⟶ 2,850:
},
 
hide_zoom : function (evt) {
if (!IA.zoom) return;
if (evt) {
Line 2,857 ⟶ 2,859:
},
 
createHelpLink : function () {
var msg = ImageAnnotator.UI.get('wpImageAnnotatorHelp', false, true);
if (!msg || !msg.lastChild) return null;
Line 2,881 ⟶ 2,883:
tgt = msg.lastChild;
if (tgt.nodeName.toLowerCase() !== 'a')
tgt = wgServer + wgArticlePathmw.replaceutil.getUrl('$1', 'Help:Gadget-ImageAnnotator');
else
tgt = tgt.href;
Line 2,914 ⟶ 2,916:
},
 
get_cover : function () {
var self = IA;
var shim;
Line 2,972 ⟶ 2,974:
},
 
show_cover : function () {
var self = IA;
if (self.cover && !self.cover_visible) {
Line 2,985 ⟶ 2,987:
},
 
hide_cover : function () {
var self = IA;
if (self.cover && self.cover_visible) {
Line 2,998 ⟶ 3,000:
},
 
getRawItem : function (what, scope) {
var node = null;
if (!scope || scope == document) {
Line 3,009 ⟶ 3,011:
},
 
getItem : function (what, scope) {
var node = IA.getRawItem(what, scope);
if (!node) return null;
Line 3,015 ⟶ 3,017:
},
 
getIntItem : function (what, scope) {
var x = IA.getItem(what, scope);
if (x !== null) x = parseInt (x, 10);
Line 3,021 ⟶ 3,023:
},
 
findNote : function (text, id) {
function find (text, id, delim) {
var start = delim.start.replace('$1', id);
Line 3,039 ⟶ 3,041:
},
 
setWikitext : function (pagetext) {
var self = IA;
if (self.wiki_read) return;
Line 3,063 ⟶ 3,065:
},
 
setSummary : function (summary, initial_text, note_text) {
if (initial_text.contains('$1')) {
var max = (summary.maxlength || 200) - initial_text.length;
Line 3,075 ⟶ 3,077:
},
 
getScript : function (url, bypass_local_cache, bypass_caches) {
// Don't use LAPI here, it may not yet be available
if (bypass_caches) {
Line 3,093 ⟶ 3,095:
},
 
canEdit : function () {
var self = IA;
if (self.may_edit) {
Line 3,125 ⟶ 3,127:
// Start it. Bypass caches; but allow for 4 hours client-side caching. Small file.
IA.getScript(
wgScriptmw.util.wikiScript() + '?title=MediaWiki:ImageAnnotatorConfig.js&action=raw&ctype=text/javascript'
// Cache 4 hours
+ '&dummy=' + Math.floor((new Date()).getTime() / (14400 * 1000)),