MediaWiki:Gadget-ImageAnnotator.js: Difference between revisions
Content deleted Content added
rv, not sure why - but it is broken... |
|||
Line 2:
/*
Requires an environment running MediaWiki 1.23 or later.
Image annotations. Draw rectangles onto image thumbnail displayed on image description
Line 8 ⟶ 10:
moves over the rectangles. If an image has annotations, display the rectangles. Add a
button to create new annotations.
Note: if an image that has annotations is overwritten by a new version, only display the
annotations if the size of the top image matches the stored size exactly. To recover
annotations, one will need to edit the image description page manually, adjusting image
sizes and rectangle coordinates, or re-enter annotations.
Author: [[User:Lupo]], June 2009 - March 2010
License: Quadruple licensed GFDL, GPL, LGPL and Creative Commons Attribution 3.0 (CC-BY-3.0)
Choose whichever license of these you like best :-)
See http://commons.wikimedia.org/wiki/Help:Gadget-ImageAnnotator for documentation.
*/
/*jshint eqeqeq:true, undef:true, unused:true, browser:true, eqnull:true, laxbreak:true, laxcomma:true */
/*global prompt */ // discouraged browser globals
/*global wgServer, wgContentLanguage */ // legacy config site globals
/*global wgUserGroups, wgRestrictionEdit */ // legacy config page globals
/*global mw, $ */
/*global importScript, importScriptURI */ // wikibits.js
/*global LAPI, Buttons, Tooltip, Tooltips, TextCleaner, UIElements */ // imported scripts
/*global ImageAnnotator:true, ImageAnnotator_disable:false */ // this script
// Guard against multiple inclusions
if (typeof ImageAnnotator === 'undefined') {
importScript('MediaWiki:LAPI.js');
importScript('MediaWiki:Tooltips.js');
importScript('MediaWiki:TextCleaner.js');
importScript('MediaWiki:UIElements.js');
// Local scope
(function () {
var conf = mw.config.get([
'skin',
'stylepath',
'wgArticleId',
'wgCurRevisionId',
'wgNamespaceIds',
'wgNamespaceNumber',
'wgPageName',
'wgTitle',
'wgUserLanguage'
]);
var ImageAnnotator_config = null;
var ImageAnnotation = function () {
this.initialize.apply };
ImageAnnotation.compare = function (a, b) {
var result = b.area() - a.area();
// Just to make sure the order is complete
return a.model.id - b.model.id;
};
ImageAnnotation.prototype = {
// Rectangle to be displayed on image: a div with pos and size
view: null,
// Tooltip to display the annotation // Content of the tooltip // 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;
this.viewer = viewer;
if (LAPI.DOM.hasClass
// Extract the info we need
var x = IA.getIntItem
var y = IA.getIntItem
var w = IA.getIntItem
var h = IA.getIntItem
var html = IA.getRawItem
if (x === null || y === null || w === null || h === null || html === null)
throw new Error
if (x < 0 || x >= viewer.full_img.width || y < 0 || y >= viewer.full_img.height)
throw new Error
if (
|| y + h > viewer.full_img.height + 10
) {
throw new Error
}
// Notes written by early versions may be slightly too large, whence the + 10 above. Fix this.
if (x + w > viewer.full_img.width) w = viewer.full_img.width - x;
if (y + h > viewer.full_img.height) h = viewer.full_img.height - y;
view_w = Math.floor
view_h = Math.floor
view_x = Math.floor
view_y = Math.floor
this.view =
LAPI.make
//
fontSize: '0px',
// We'll add the view to the DOM once we've loaded all notes
this.model = {
} else {
is_new = true;
this.view = node;
this.model = {
view_w = this.view.offsetWidth - 2; // Subtract cumulated border widths
view_h = this.view.offsetHeight - 2;
Line 119 ⟶ 143:
// Enforce a minimum size of the view. Center the 6x6px square over the center of the old view.
// If we overlap the image boundary, adjustRectangleSize will take care of it later.
if (view_w < 6) {view_x = Math.floor
if (view_h < 6) {view_y = Math.floor
Object.merge
{
top: height: },
this.view.style );
this.view.style.zIndex
try {
this.view.style.border = '1px solid ' + this.viewer.outer_border;
} catch (ex) {
this.view.style.border = '1px solid ' + IA.outer_border;
}
this.view.appendChild
LAPI.make
'div', null
, { lineHeight : '0px' // IE
,fontSize
,width
,height
}
)
Line 148 ⟶ 176:
this.view.firstChild.style.border = '1px solid ' + IA.inner_border;
}
if (is_new) viewer.adjustRectangleSize
// IE somehow passes through event to the view even if covered by our cover, displaying the tooltips
// when drawing a new rectangle, which is confusing and produces a selection nightmare. Hence we just
// display raw rectangles without any tooltips attached while drawing. Yuck.
this.dummy = this.view.cloneNode
viewer.img_div.appendChild
if (!is_new) {
// New notes get their tooltip only once the editor has saved, otherwise IE may try to
// open them if the mouse moves onto the view even though there is the cover above them!
this.setTooltip
}
},
setTooltip
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(
{
deactivate: (LAPI.DOM.is_ie ? Tooltip.ESCAPE : Tooltip.LEAVE),
close_button: null,
mode: Tooltip.MOUSE,
mouse_offset: { x: -5, y: -5, dx: (IA.is_rtl ? -1 : 1), dy: 1 },
hide_delay:
onclose: (function (tooltip, evt) { } if (this.viewer.tip === tooltip) this.viewer.tip =
// Hide all boxes if we're outside the image. Relies on hide checking the
// coordinates! (Otherwise, we'd always hide...)
if (evt) this.viewer.hide(evt);
}).bind(this),
onopen: (function
this.view.style.border = '1px solid ' +
} catch (ex)
this.view.style.border = '1px solid ' + }
this.viewer.tip =
},
IA.tooltip_styles
},
display
if (!this.content) {
this.content = LAPI.make
var main = LAPI.make
this.content.appendChild
this.content.main = main;
if (this.model.html) main.appendChild
// Make sure that the popup encompasses all floats
this.content.appendChild
if (this.viewer.may_edit) {
this.content.button_section = LAPI.make(
this.content.appendChild
this.content.button_section.appendChild(LAPI.DOM.makeLink(
null,
));
if (ImageAnnotator_config.mayDelete()) {
this.content.button_section.appendChild(document.createTextNode('\xa0'));
this.content.button_section.appendChild(LAPI.DOM.makeLink(
'#',
ImageAnnotator.UI.get('wpImageAnnotatorDelete', true),
));
}
}
Line 249 ⟶ 272:
return this.content;
},
edit
if (IA.canEdit()) IA.editor.editNote(this);
if (
return false;
},
remove_event
if (IA.canEdit()) this.remove();
},
remove
if (!this.content) { // New note: just destroy it.
this.destroy
return true;
}
if (!ImageAnnotator_config.mayDelete()) return false;
// Close and remove tooltip only if edit succeeded! Where and how to display error messages?
var reason =
if (!ImageAnnotator_config.mayBypassDeletionPrompt() || !window.ImageAnnotator_noDeletionPrompt) {
// Prompt for a removal reson
reason =
if (reason === null) return false; // Cancelled
reason = reason.trim();
if (!reason.length
if (!ImageAnnotator_config.emptyDeletionReasonAllowed()) return false;
}
// Re-show tooltip (without re-positioning it, we have no mouse coordinates here) in case
// it was hidden because of the alert. If possible, we want the user to see the spinner.
this.tooltip.show_now
}
var self = this;
var spinnerId = 'image_annotation_delete_' + this.model.id;
LAPI.Ajax.injectSpinner
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
var textbox = editForm.wpTextbox1;
if (!textbox) throw new Error
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 308 ⟶ 327:
// breaks when we remove the note.
IA.setWikitext
var span = IA.findNote
if (!span) { // Hmmm? Doesn't seem to exist
LAPI.Ajax.removeSpinner
if (self.tooltip) self.tooltip.size_change
self.destroy
return;
}
var char_before = 0;
var char_after
if (span.start > 0) char_before = pagetext.charCodeAt
if (span.end < pagetext.length) char_after = pagetext.charCodeAt
if (
&& String.fromCharCode
span.start = span.start - 1;
pagetext = pagetext.substring
textbox.value = pagetext;
var summary = editForm.wpSummary;
if (!summary)
throw new Error ('#Summary field not found. Check that edit pages have valid XHTML.');
IA.setSummary
summary
, ImageAnnotator.UI.get
|| '[[MediaWiki talk:Gadget-ImageAnnotator.js|Removing image note]]$1'
, (reason.length
);
} catch (ex) {
return;
}
var edit_page = doc;
LAPI.Ajax.submitEdit
editForm
, function (request) {
if (edit_page.isFake &&
edit_page.dispose
var revision_id = LAPI.WP.revisionFromHtml
if (!revision_id) {
failureFunc
return;
}
conf.wgCurRevisionId = revision_id; // Bump revision id!!
LAPI.Ajax.removeSpinner
if (self.tooltip) self.tooltip.size_change
self.destroy
}
, function (request, ex) {
if (edit_page.isFake &&
edit_page.dispose
failureFunc
}
);
}
, function (
// Failure. What now? TODO: Implement some kind of user feedback.
LAPI.Ajax.removeSpinner
if (self.tooltip) self.tooltip.size_change
}
);
Line 372 ⟶ 391:
},
destroy
if (this.view) LAPI.DOM.removeNode(this.view);
if (this.
if (this.
if (this.
this.model = null;
this.
this.content = null;
this.tooltip = null;
this.viewer
},
area
if (!this.model || !this.model.dimension) return 0;
return (this.model.dimension.w * this.model.dimension.h);
},
cannotEdit
if (this.content && this.content.button_section) {
LAPI.DOM.removeNode
this.content.button_section = null;
if (this.tooltip) this.tooltip.size_change
}
}
Line 402 ⟶ 418:
}; // end ImageAnnotation
var ImageAnnotationEditor = function () {this.initialize.apply
ImageAnnotationEditor.prototype =
{
initialize
var editor_width = 50;
// Respect potential user-defined width setting
if (
&& !isNaN (window.ImageAnnotationEditor_columns)
&& window.ImageAnnotationEditor_columns >= 30
&& window.ImageAnnotationEditor_columns <= 100) {
editor_width = window.ImageAnnotationEditor_columns;
}
this.editor =
new LAPI.Edit
, { box
,preview
,save
,revert
,cancel
,nullsave : ImageAnnotator_config.mayDelete()
? ImageAnnotator.UI.get
: null
,post
}
, {
onsave
,onpreview : this.onpreview.bind
,oncancel
,ongettext
if (text == null) return
text = text.trim
.replace
;
// Guard against people trying to break notes on purpose
if (text.length
text = TextCleaner.sanitizeWikiText
return text;
}
}
);
this.box = LAPI.make
this.box.appendChild
// Limit the width of the bounding box to the size of the textarea, taking into account the
// tooltip styles. Do *not* simply append this.box or the editor view, Opera behaves strangely
// if textboxes were ever hidden through a visibility setting! Use a second throw-away textbox
// instead.
var temp = LAPI.make
temp.appendChild
Object.merge(
{
position: 'absolute',
top: '0px',
left: '-10000px',
visibility: 'hidden'
},
temp.style
);
document.body.appendChild(temp);
// Now we know how wide this textbox will be
var box_width = temp.offsetWidth;
LAPI.DOM.removeNode
// Note: we need to use a tooltip with a dynamic content creator function here because
// static content is cloned inside the Tooltip. Cloning on IE loses all attached handlers,
// and thus the editor's controls wouldn't work anymore. (This is not a problem on FF3,
// where cloning preserves the handlers.)
this.tooltip = new Tooltip
IA.get_cover
, this.get_editor.bind
, { activate
,deactivate
,close_button : null // We have a cancel button anyway
,mode
,anchor
,mouse_offset
,max_pixels
,z_index
,open_delay
,hide_delay
,onclose
}
, IA.tooltip_styles
Line 482 ⟶ 504:
this.note = null;
this.visible = false;
LAPI.Evt.listenTo
function (
Array.forEach(IA.viewers, (function (viewer) {
if (viewer !== this.viewer && viewer.visible) viewer.hide();
}
);
},
get_editor
return this.box;
},
editNote
var same_note = (note === this.note);
this.note = note;
this.viewer = this.note.viewer;
var cover
cover.style.cursor = 'auto';
IA.show_cover
if (note.tooltip) note.tooltip.hide_now
IA.is_editing = true;
Line 515 ⟶ 532:
// Existing note, and we don't have the wikitext yet: go get it
var self = this;
LAPI.Ajax.apiGet
}
break;
}
}
// TODO: What upon a failure?
self.open_editor(same_note, cover);
},
function () {
// TODO: What upon a failure?
self.open_editor(same_note, cover);
}
);
} else {
this.open_editor
}
},
open_editor
this.editor.hidePreview();
if (!same_note || this.editor.textarea.readOnly)
// Different note, or save error last time
this.editor.setText
this.editor.enable
(LAPI.Edit.SAVE + LAPI.Edit.PREVIEW + LAPI.Edit.REVERT + LAPI.Edit.CANCEL);
Line 560 ⟶ 576:
this.editor.textarea.style.backgroundColor = 'white';
// Set the position relative to the note's view.
var view_pos = LAPI.Pos.position
var origin
this.tooltip.options.fixed_offset.x =
view_pos.x - origin.x + this.tooltip.options.mouse_offset.x;
Line 570 ⟶ 586:
// Make sure mouse event listeners are removed, especially on IE.
this.dim = { x : this.note.view.offsetLeft, y : this.note.view.offsetTop
,w : this.note.view.offsetWidth, h : this.note.view.offsetHeight };
this.viewer.setShowHideEvents
this.viewer.hide
this.viewer.toggle
// Now show the editor
this.tooltip.show_tip
var tpos = LAPI.Pos.position
var ppos = LAPI.Pos.position
tpos = tpos.x - ppos.x;
if (tpos + this.editor.textarea.offsetWidth > this.tooltip.popup.offsetWidth)
this.editor.textarea.style.width = (this.tooltip.popup.offsetWidth - 2 * tpos) + 'px';
if (LAPI.Browser.is_ie) {
// Fixate textarea width to prevent ugly flicker on each keypress in IE6...
this.editor.textarea.style.width = this.editor.textarea.offsetWidth + 'px';
}
Line 588 ⟶ 604:
},
hide_editor
if (!this.visible) return;
this.visible = false;
IA.is_editing = false;
this.tooltip.hide_now
if (evt && evt.type === 'keydown' && !this.saving) {
// ESC pressed on new note before a save attempt
this.cancel
}
IA.hide_cover
this.viewer.setDefaultMsg
this.viewer.setShowHideEvents
this.viewer.hide
this.viewer.show
// FIXME in Version 2.1: Unfortunately, we don't have a mouse position here, so sometimes we
// may show the note rectangles even though the mouse is now outside the image. (It was
Line 609 ⟶ 624:
},
save
var data = editor.getText();
if (this.note.remove
this.hide_editor
this.cancel
this.note = null;
} else {
this.hide_editor();
this.cancel
}
return;
} else if (data === this.note.model.wiki) {
this.
this.cancel();
return;
}
// Construct what to insert
var dim = Object.clone
if (!dim) {
dim = {
// Make sure everything is within bounds
if (dim.x + dim.w > this.viewer.full_img.width) {
Line 684 ⟶ 701:
+ '|style=2'
+ '}}\n'
+ data + (data.endsWith
+ '{{ImageNoteEnd|id=' + this.note.model.id + '}}';
// Now edit the page
var self = this;
this.editor.busy
this.editor.enable
this.saving = true;
LAPI.Ajax.editPage
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
// Modify the page
var textbox = editForm.wpTextbox1;
if (!textbox) throw new Error
var pagetext = textbox.value;
IA.setWikitext
var span = null;
if (self.note.content) // Otherwise it's a new note!
span = IA.findNote
if (span) { // Replace
pagetext =
pagetext.substring
+ self.to_insert
+ pagetext.substring
;
} else { // If not found, append
// Try to append right after existing notes
var lastNote = pagetext.lastIndexOf
if (lastNote >= 0) {
var endLastNote = pagetext.substring
if (endLastNote < 0) {
endLastNote = pagetext.substring
if (endLastNote < 0) lastNote = -1; else lastNote += endLastNote;
} else
Line 729 ⟶ 744:
if (lastNote >= 0) {
pagetext =
pagetext.substring
+ '\n' + self.to_insert
+ pagetext.substring
;
} else
pagetext = pagetext.trimRight
}
textbox.value = pagetext;
Line 742 ⟶ 757:
// If [[MediaWiki:Copyrightwarning]] is invalid XHTML, we may not have wpSummary!
if (self.note.content != null) {
IA.setSummary
summary
, ImageAnnotator.UI.get
|| '[[MediaWiki talk:Gadget-ImageAnnotator.js|Changing image note]]$1'
, data
);
} else {
IA.setSummary
summary
, ImageAnnotator.UI.get
|| '[[MediaWiki talk:Gadget-ImageAnnotator.js|Adding image note]]$1'
, data
Line 757 ⟶ 772:
}
} catch (ex) {
failureFunc
return;
}
var edit_page = doc;
LAPI.Ajax.submitEdit(editForm, function (request) {
// After a successful submit.
if (edit_page.isFake && (typeof
edit_page.dispose
// TODO: Actually, the edit got through here, so calling failureFunc on
// inconsistencies isn't quite right. Should we reload the page?
var id
var doc
if (!doc) return;
var html = LAPI.$ (id, doc);
if (!html) {
if (doc.isFake && (typeof
failureFunc
(request, new Error
return;
}
var revision_id = LAPI.WP.revisionFromHtml
if (!revision_id) {
if (doc.isFake && (typeof
failureFunc
(request, new Error
return;
}
conf.wgCurRevisionId = revision_id; // Bump revision id!!
self.note.model.html = LAPI.DOM.importNode
if (doc.isFake && (typeof
self.note.model.dimension = dim; // record dimension
self.note.model.html.style.display =
self.note.model.wiki = data;
self.editor.busy
if (self.note.content) {
LAPI.DOM.removeChildren
self.note.content.main.appendChild
} else {
// New note.
self.note.display
if (self.viewer.annotations.length > 1) {
self.viewer.annotations.sort
var idxOfNote = Array.indexOf
if (idxOfNote+1 < self.viewer.annotations.length)
LAPI.DOM.insertNode
Line 809 ⟶ 822:
self.to_insert = null;
self.saving = false;
if (!self.note.tooltip) self.note.setTooltip
self.hide_editor
IA.is_editing = false;
self.editor.setText
edit_page.dispose
);
self.saving = false;
// TODO: How and where to display error if user closed editor through ESC (or through
Line 829 ⟶ 841:
if (!self.visible) return;
// Change the tooltip to show the error.
self.editor.setText
// Error message. Use preview field for this.
var error_msg = ImageAnnotator.UI.get
var lk = error_msg.getElementsByClassName
if (lk && lk.length
lk = lk[0].firstChild;
lk.href =
}
if (ex) {
var ex_msg = LAPI.formatException
if (ex_msg) {
ex_msg.style.borderBottom = '1px solid red';
var tmp = LAPI.make
tmp.appendChild
tmp.appendChild
error_msg = tmp;
}
}
self.editor.setPreview
self.editor.showPreview
self.editor.textarea.readOnly = true;
// Force a light gray background, since IE has no visual readonly indication.
self.editor.textarea.style.backgroundColor = '#EEEEEE';
self.editor.enable
);
},
onpreview
if (this.tooltip) this.tooltip.size_change();
},
cancel
if (!this.note) return;
if (!this.note.content) {
// No content: Cancel and remove this note!
this.note.destroy
this.note = null;
}
if (editor) this.hide_editor
},
close_tooltip
this.hide_editor(evt);
this.
}
};
var ImageNotesViewer = function () {this.initialize.apply
ImageNotesViewer.prototype =
{
initialize
Object.merge(descriptor, this);
this.annotations = [];
this.max_id
this.main_div
this.msg
this.may_edit
this.setup_done
this.tip
this.icon
this.factors = {
if (!this.isThumbnail && !this.isOther) {
this.setup
} else {
// Normalize the namespace of the realName to 'File' to account for images possibly stored at
Line 910 ⟶ 918:
// the localized namespace names of other wikis, but the canonical namespace name 'File' works
// also locally.
this.realName = 'File:' + this.realName.substring
}
},
setup
this.setup_done = true;
var name = this.realName;
var $fullname;
if
this.
this.realName = '';
} else {
this.realName =
this.imgName
}
var annotations = this.scope.getElementsByClassName(IA.annotation_class);
if (!this.may_edit && (!annotations || annotations.length === 0))
return; // Nothing to do
// A div inserted around the image. It ensures that everything we add is positioned properly
// over the image, even if the browser window size changes and re-layouts occur.
var isEnabledImage = LAPI.DOM.hasClass
if (!this.isThumbnail && !this.isOther && !isEnabledImage) {
this.img_div =
LAPI.make
var floater =
LAPI.make
cssFloat: (IA.is_rtl ? '
position:
});
floater.appendChild
this.img.parentNode.parentNode.insertBefore
this.img_div.appendChild
// And now a clear:left to make the rest appear below the image, as usual.
var breaker = LAPI.make
LAPI.DOM.insertAfter
// Remove spurious br tag.
if (breaker.nextSibling && breaker.nextSibling.nodeName.toLowerCase
LAPI.DOM.removeNode
} else if (this.isOther || isEnabledImage) {
this.img_div =
LAPI.make
this.img.parentNode.parentNode.insertBefore
this.img_div.appendChild
// Insert one more to have a file_div, so that we can align the message text correctly
this.file_div = LAPI.make
this.img_div.parentNode.insertBefore
this.file_div.appendChild
} else { // Thumbnail
this.img_div =
LAPI.make
{ className: '
width: }
);
this.img.parentNode.parentNode.insertBefore
this.img.style.border = 'none';
this.img_div.appendChild
}
if (
&& (
|| this.iconOnly
|| ImageAnnotator_config.inlineImageUsesIndicator
(name, this.isLocal, this.thumb, this.full_img, annotations.length, this.isThumbnail)
)
)
// Use an onclick handler instead of a link around the image. The link may have a default white
// background, but we want to be sure to have transparency. The image should be an 8-bit indexed
// PNG or a GIF and have a transparent background.
this.icon = ImageAnnotator.UI.get
if (this.icon) this.icon = this.icon.firstChild; // Skip the message container span or div
// Guard against misconfigurations
if (
&& this.icon.nodeName.toLowerCase
&& this.icon.firstChild.nodeName.toLowerCase
)
// Make sure we use the right protocol:
var srcFixed = this.icon.firstChild.getAttribute
this.icon.firstChild.src = srcFixed;
this.icon.firstChild.title = this.icon.title;
this.icon = this.icon.firstChild;
} else if (!this.icon || this.icon.nodeName.toLowerCase
this.icon =
LAPI.DOM.makeImage
IA.indication_icon
, 14, 14
, ImageAnnotator.UI.get
);
}
Object.merge
{
);
this.icon.onclick = (function () {
if (IA.is_rtl)
this.icon.style.right = '0px';
else
this.icon.style.left = '0px';
this.img_div.appendChild
// And done. We just show the icon, no fancy event handling needed.
return;
}
// Set colors
var colors = IA.getRawItem
this.outer_border
this.inner_border = colors && IA.getItem
this.active_border =
colors && IA.getItem
if (annotations) {
for (var i = 0; i < annotations.length; i++) {
var id = annotations[i].id;
if (id && /^image_annotation_note_(\d+)$/.test
id = parseInt
} else
id = null;
if (id) {
if (id > this.max_id) this.max_id = id;
var w = IA.getIntItem
var h = IA.getIntItem
if (
&& !Array.exists
)
try {
this.register
} catch (ex) {
// Swallow.
Line 1,053 ⟶ 1,058:
}
}
if (this.annotations.length > 1) this.annotations.sort
// Add the rectangles of existing notes to the DOM now that they are sorted.
Array.forEach
if (this.isThumbnail) {
this.main_div =
if (!this.main_div || this.main_div.length === 0)
this.main_div = null;
else
Line 1,064 ⟶ 1,069:
}
if (!this.main_div) {
this.main_div = LAPI.make
if (IA.is_rtl) {
this.main_div.style.direction = 'rtl';
Line 1,073 ⟶ 1,078:
}
if (!this.isThumbnail && !this.isOther && !isEnabledImage) {
LAPI.DOM.insertAfter
} else {
LAPI.DOM.insertAfter
}
}
if (
|| !this.noCaption
&& !IA.hideCaptions
&& ImageAnnotator_config.displayCaptionInArticles
(name, this.isLocal, this.thumb, this.full_img, annotations.length, this.isThumbnail)
)
this.msg = LAPI.make('div', null, { display: 'none' });
if (IA.is_rtl) {
this.msg.style.direction = 'rtl';
Line 1,091 ⟶ 1,095:
}
if (this.isThumbnail) this.msg.style.fontSize = '90%';
this.main_div.appendChild
}
// Set overflow parents, if any
var simple
var checks
: ['overflow', 'overflowX', 'overflowY']
);
var curStyle = null;
for (var up = this.img.parentNode.parentNode; up !== document.body; up = up.parentNode) {
curStyle = (simple ? window.getComputedStyle
// "up.style" is actually incorrect, but a best-effort fallback.
var overflow =
Array.any(checks, function (t) {
var o
});
if (overflow) {
if (!this.overflowParents)
Line 1,122 ⟶ 1,126:
}
this.show_evt = LAPI.Evt.makeListener
if (this.overflowParents || LAPI.Browser.is_ie) {
// If we have overflowParents, also use a mousemove listener to show/hide the whole
Line 1,132 ⟶ 1,136:
// mouse crosses the border, and if one moves the mouse a little fast across the border, we
// don't get any event at all. That's no good.
this.move_evt = LAPI.Evt.makeListener
} else
this.hide_evt = LAPI.Evt.makeListener
this.move_listening = false;
this.setShowHideEvents
this.visible = false;
this.setDefaultMsg
},
cannotEdit
if (!this.may_edit) return;
this.may_edit = false;
Array.forEach
},
setShowHideEvents
if (this.icon) return;
if (set) {
LAPI.Evt.attach
if (this.hide_evt) LAPI.Evt.attach
} else {
LAPI.Evt.remove(this.img, IA.mouse_in, this.show_evt);
if (this.hide_evt) {
LAPI.Evt.remove
} else if (this.move_listening)
this.removeMoveListener
}
},
removeMoveListener
if (this.icon) return;
this.move_listening = false;
if (this.move_evt) {
if (!LAPI.Browser.is_ie && typeof
document.captureEvents
LAPI.Evt.remove
}
},
adjustRectangleSize
if (this.icon) return;
// Make sure the note boxes don't overlap the image boundary; we might get an event
Line 1,183:
var view_w = node.offsetWidth;
var view_h = node.offsetHeight;
if (view_x === 0) view_x = 1;
if (view_y === 0) view_y = 1;
if (view_x + view_w >= this.thumb.width) {
view_w = this.thumb.width - view_x - 1;
Line 1,194:
}
// Now set position and width and height, subtracting cumulated border widths
if (
|| view_w !== node.offsetWidth || view_h !== node.offsetHeight
) {
node.style.top =
node.style.left =
node.style.width =
node.style.height =
node.firstChild.style.width =
node.firstChild.style.height =
}
},
toggle
var i;
if (!this.annotations || this.annotations.length === 0 || this.icon) return;
if (dummies) {
for (
this.annotations[i].view.style.display = 'none';
if (this.visible && this.annotations[i].tooltip)
this.annotations[i].tooltip.hide_now
this.annotations[i].dummy.style.display = (this.visible ? 'none' :
if (!this.visible) this.adjustRectangleSize
}
} else {
for (
this.annotations[i].dummy.style.display = 'none';
this.annotations[i].view.style.display = (this.visible ? 'none' :
if (!this.visible) this.adjustRectangleSize
if (this.visible && this.annotations[i].tooltip)
this.annotations[i].tooltip.hide_now
}
}
this.visible = !this.visible;
},
show
if (this.visible || this.icon) return;
this.toggle
if (this.move_evt && !this.move_listening) {
LAPI.Evt.attach
this.move_listening = true;
if (!LAPI.Browser.is_ie && typeof
document.captureEvents
}
},
hide
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) {
// Huh?
if (this.move_listening) this.removeMoveListener
return true;
}
if (evt) {
var mouse_pos = LAPI.Pos.mousePosition
if (mouse_pos) {
if (this.tip) {
// Check whether we're within the visible note.
if (LAPI.Pos.isWithin
}
var is_within = true;
var img_pos = LAPI.Pos.position
var rect = {
x: img_pos.x, y: img_pos.y, b: (img_pos.y + this.img.offsetHeight) };
var if (this.overflowParents) {
// We're within some elements having overflow:hidden or overflow:auto or overflow:scroll set.
// 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.
for (i = 0; i < this.overflowParents.length && rect.x < rect.r && rect.y < rect.b; i++) {
img_pos = LAPI.Pos.position(this.overflowParents[i]);
img_pos.r = img_pos.x + this.overflowParents[i].clientWidth;
img_pos.b = img_pos.y + this.overflowParents[i].clientHeight;
rect = intersect_rectangles
}
}
is_within =
|| rect.y >= mouse_pos.y || rect.b <=
);
if (is_within) {
if (LAPI.Browser.is_ie && evt.type === 'mousemove') {
var display;
// Loop in reverse order to properly display top rectangle's note!
for (
display = this.annotations[i].view.style.display;
if (
&& LAPI.Pos.isWithin
) {
if (!this.annotations[i].tooltip.visible) this.annotations[i].tooltip.show(evt);
return true;
}
}
if (this.tip) this.tip.hide_now
}
return true;
Line 1,307 ⟶ 1,311:
}
// Not within the image, or forced hiding (no event)
if (this.move_listening) this.removeMoveListener
this.toggle
return true;
},
check_hide
if (this.icon) return true;
if (this.visible)
this.hide
return true;
},
register
this.annotations[this.annotations.length] = new_note;
if (new_note.model.id > 0) {
Line 1,329 ⟶ 1,331:
}
},
deregister
Array.remove(this.annotations, note);
if (note.model.id === this.max_id) this.max_id--;
if (this.annotations.length === 0) this.setDefaultMsg(); //If we removed the last one, clear the msg
},
setDefaultMsg
if (this.annotations && this.annotations.length && this.msg) {
this.msg.appendChild
(ImageAnnotator.UI.get
if (this.realName && typeof
var otherPageMsg = ImageAnnotator.UI.get
if (otherPageMsg) {
var lk = otherPageMsg.getElementsByTagName
if (lk && lk.length
lk = lk[0];
lk.parentNode.replaceChild
LAPI.DOM.makeLink
, this.realName
, this.realName
Line 1,357:
, lk
);
this.msg.appendChild
}
}
}
this.msg.style.display =
} else {
if (this.msg) this.msg.style.display = 'none';
}
if (IA.button_div && this.may_edit) IA.button_div.style.display =
}
};
var IA = {
// This object is responsible for setting up annotations when a page is loaded. It loads all
// annotations in the page source, and adds an "Annotate this image" button plus the support
// for drawing rectangles onto the image if there is only one image and editing is allowed.
haveAjax: false,
button_div: null,
add_button: null,
cover: null,
border: null,
definer: null,
mouse_in: (window.ActiveXObject ? 'mouseenter' : 'mouseover'),
mouse_out: (window.ActiveXObject ? 'mouseleave' : 'mouseout'),
annotation_class: 'image_annotation',
// Format of notes in Wikitext. Note: there are two formats, an old one and a new one.
Line 1,394 ⟶ 1,393:
// important, because the old format also used the ImageNote template, but for a different
// purpose.
note_delim
[
{ start
,end
,content_start
,content_end
}
,{ start
,end
,content_start
,content_end
}
],
tooltip_styles: {
,
, padding: '0.3em'
, fontSize: (conf.skin === 'monobook' ? '127%' : '100%')
// Scale up to default text size
},
editor
wiki_read
is_rtl
move_listening
is_tracking
is_adding
is_editing
zoom_threshold
zoom_factor
install_attempts
max_install_attempts
imgs_with_notes
thumbs
other_images
// Fallback
indication_icon
install
if (typeof ImageAnnotator_disable !== 'undefined' && !!ImageAnnotator_disable) return;
if (!config || ImageAnnotator_config != null) return;
// Double check.
if (!config.viewingEnabled
var self = IA;
Line 1,452 ⟶ 1,451:
// Determine whether we have XmlHttp. We try to determine this here to be able to avoid
// doing too much work.
if (
&& typeof
&& typeof
&& typeof
)
self.haveAjax = (LAPI.Ajax.getRequest() != null);
self.ajaxQueried = true;
} else {
self.haveAjax
self.ajaxQueried = false;
}
// We'll include self.haveAjax later on once more, to catch the !ajaxQueried case.
self.may_edit = conf.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] === '*'
|| conf.wgNamespaceIds[list[i].toLowerCase().replace(/ /g, '_')] === conf.wgNamespaceNumber
)
)
Line 1,483 ⟶ 1,480:
}
self.rules = { inline: {}, thumbs: {}, shared : {} };
// Now set the default rules. Undefined means default setting (true for show, false for icon),
// but overrideable by per-image rules. If set, it's not overrideable by per-image rules.
//
if (
|| !config.generalImagesEnabled
|| namespaceCheck
)
self.rules.inline.show = false;
self.rules.thumbs.show = false;
self.rules.shared.show = false;
} else {
if (
|| !config.thumbsEnabled
|| namespaceCheck
)
self.rules.thumbs.show = false;
}
if (conf.wgNamespaceNumber === 6)
self.rules.shared.show = true;
else if (
|| namespaceCheck
)
self.rules.shared.show = false;
}
if (namespaceCheck
self.rules.inline.icon = true;
if (namespaceCheck
self.rules.thumbs.icon = true;
}
// User rule for displaying captions on images in articles
self.hideCaptions = namespaceCheck
var do_images = typeof self.rules.inline.show === 'undefined' || self.rules.inline.show;
if (do_images) {
// Per-article switching off of note display on inline images and thumbnails
var rules = document.getElementById
if (rules) {
if (rules.className.indexOf
self.rules.inline.show = false;
self.rules.thumbs.show = false;
self.rules.shared.show = false;
}
if (
&& rules.className.indexOf
)
self.rules.inline.show = true;
}
if (rules.className.indexOf
self.rules.thumbs.show = false;
}
if (
&& rules.className.indexOf
)
self.rules.thumbs.show = true;
}
if (rules.className.indexOf
self.rules.inline.icon = true;
}
if (rules.className.indexOf
self.rules.thumbs.icon = true;
}
if (rules.className.indexOf
self.rules.shared.show = false;
}
Line 1,562 ⟶ 1,553:
// Make sure the shared value is set
self.rules.shared.show =
typeof
do_images = typeof
var do_thumbs = typeof
if (do_images) {
var bodyContent = document.getElementById
|| document.getElementById
|| document.getElementById
;
if (bodyContent) {
var all_imgs = bodyContent.getElementsByTagName
for (var i = 0; i < all_imgs.length; i++) {
// Exclude all that are in img_with_notes or in thumbs. Also exclude all in galleries.
var up = all_imgs[i].parentNode;
if (up.nodeName.toLowerCase
up = up.parentNode;
if ((' ' + up.className + ' ').indexOf
if ((' ' + up.className + ' ').indexOf
if (do_thumbs) self.thumbs[self.thumbs.length] = up;
continue;
Line 1,586 ⟶ 1,577:
up = up.parentNode;
if (!up) continue;
if ((' ' + up.className + ' ').indexOf
if ((' ' + up.className + ' ').indexOf
self.imgs_with_notes[self.imgs_with_notes.length] = up;
continue;
Line 1,594 ⟶ 1,585:
if (!up) continue;
// Other images not in galleries
if ((' ' + up.className + ' ').indexOf
if ((' ' + up.className + ' ').indexOf
if ((' ' + up.className + ' ').indexOf
self.imgs_with_notes[self.imgs_with_notes.length] = up;
continue;
}
up = up.parentNode;
if (!up) continue;
if ((' ' + up.className + ' ').indexOf
if ((' ' + up.className + ' ').indexOf
if ((' ' + up.className + ' ').indexOf
self.imgs_with_notes[self.imgs_with_notes.length] = up;
} else {
// Guard against other scripts adding aribtrary numbers of divs (dshuf for instance!)
var is_other = true;
while (up && up.nodeName.toLowerCase
up = up.parentNode;
if (up) is_other = (' ' + up.className + ' ').indexOf
}
if (is_other) self.other_images[self.other_images.length] = all_imgs[i];
Line 1,618 ⟶ 1,609:
}
} else {
self.imgs_with_notes = document.getElementsByClassName
if (do_thumbs)
self.thumbs = document.getElementsByClassName('thumbinner');
}
if (
|| (self.imgs_with_notes.length
|| (self.thumbs.length
|| (self.other_images.length
)
// Publish parts of config.
ImageAnnotator.UI
self.outer_border
self.inner_border
self.active_border = config.active_border;
self.new_border
self.wait_for_required_libraries
}
},
wait_for_required_libraries
if (typeof Tooltip === 'undefined' || typeof LAPI === 'undefined') {
if (IA.install_attempts++ < IA.max_install_attempts) {
}
return;
Line 1,648 ⟶ 1,638:
if (LAPI.Browser.is_opera && !LAPI.Browser.is_opera_ge_9) return; // Opera 8 has severe problems
// Get the UI. We're likely to need it if we made it to here.
IA.setup_ui
IA.setup();
},
setup: function () {
var self = IA;
self.imgs = [];
// Catch both native RTL and "faked" RTL through [[MediaWiki:Rtl.js]]
self.is_rtl =
LAPI.DOM.hasClass
|| (
&& LAPI.DOM.currentStyle
)
;
// Use this to temporarily display an image off-screen to get its dimensions
var testImgDiv =
LAPI.make
document.body.insertBefore(testImgDiv, document.body.firstChild);
function img_check
var srcW = parseInt (img.getAttribute('width', 2), 10);
var
// Don't do anything on extremely small previews. We need some minimum extent to be able to place
// rectangles after all...
Line 1,689 ⟶ 1,674:
// If the image is currently hidden, its clientWidth and clientHeight are not set. Try
// harder to get the true width and height:
if ((!w || !h) && img.style.display !== 'none') {
var copied = img.cloneNode(true);
copied.style.display =
testImgDiv.appendChild
testImgDiv.style.display =
w = copied.clientWidth;
h = copied.clientHeight;
testImgDiv.style.display = 'none';
LAPI.DOM.removeNode
}
// Quit if the image wasn't loaded properly for some reason:
if (w !== srcW || h !== srcH) return null;
// Exclude system images
if (img.src.contains
// Only if within a link
if (img.parentNode.nodeName.toLowerCase
if (is_other) {
// Only if the img-within-link construction is within some element that may contain a div!
Line 1,712 ⟶ 1,697:
// in a div, but for now we assume that all browsers can handle a div within a paragraph or
// a span in a meaningful way, even if that is not really allowed.
} else if (!/^(object|applet|map|fieldset|noscript|iframe|body|div|li|dd|blockquote|center|ins|del|button|th|td|form)$/i.test
return null;
}
// Exclude any that are within an image note!
var up = img.parentNode.parentNode;
while (up !== document.body) {
if (LAPI.DOM.hasClass
up = up.parentNode;
}
return {width: w, height: h};
}
function setup_one
var file_div = scope;
var is_thumb =
scope !== document
&& scope.nodeName.toLowerCase() === 'div'
&& LAPI.DOM.hasClass
;
var is_other = scope.nodeName.toLowerCase() === 'img';
if (is_other && self.imgs.length
if (scope === document) {
file_div = LAPI.$
} else if (!is_thumb && !is_other) {
file_div = scope.getElementsByClassName
if (!file_div || file_div.length !== 1) return null;
file_div = file_div[0];
}
if (!file_div) return null;
var img = null;
if (scope === document) {
img = LAPI.WP.getPreviewImage
// TIFFs may be multi-paged: allow only for single-page TIFFs
if ( document.pageselector ) img = null;
Line 1,749 ⟶ 1,734:
img = scope;
} else {
img = file_div.getElementsByTagName
if (!img || img.length === 0) return null;
img = img[0];
}
if (!img) return null;
var dim = img_check
if (!dim) return null;
// Conditionally exclude shared images.
if (
&& !self.rules.shared.show
&& ImageAnnotator_config.imageIsFromSharedRepository
)
return null;
var name = null;
if (scope === document) {
name = conf.wgPageName;
} else {
name = LAPI.WP.pageFromLink
if (!name) return null;
name = name.replace
if (is_thumb || is_other) {
var img_src = decodeURIComponent
// img_src should have a component "/name" in there somewhere
var colon = name.indexOf
if (colon <= 0) return null;
var img_name = name.substring
if (img_src.search(new RegExp('/' + img_name.escapeRE() + '(/.*)?$')) < 0)
return null;
Line 1,781 ⟶ 1,766:
}
}
if (name.search
// Finally check for wpImageAnnotatorControl
var icon_only = false;
Line 1,789 ⟶ 1,774:
// Three levels is sufficient: thumbinner-thumb-control, or floatnone-center-control, or direct
for (var i = 0; ++i <= 3 && up; up = up.parentNode) {
if (LAPI.DOM.hasClass
if (LAPI.DOM.hasClass
if (LAPI.DOM.hasClass
if (LAPI.DOM.hasClass
break;
}
}
}
return { scope
,file_div
,img
,realName
,isThumbnail: is_thumb
,isOther
,thumb
,iconOnly
,noCaption
};
}
function setup_images
Array.forEach(list,
function (elem) {
var desc = setup_one
if (desc) self.imgs[self.imgs.length] = desc;
}
Line 1,819 ⟶ 1,803:
}
if (conf.wgNamespaceNumber === 6) {
setup_images
self.may_edit = self.may_edit && (self.imgs.length === 1);
setup_images
} else {
setup_images
self.may_edit = self.may_edit && (self.imgs.length === 1);
}
self.may_edit = self.may_edit &&
if (self.haveAjax) {
setup_images
setup_images
}
// Remove the test div
LAPI.DOM.removeNode
if (self.imgs.length === 0) return;
// We get the UI texts in parallel, but wait for them at the beginning of complete_setup, where we
// need them. This has in particular a benefit if we do have to query for the file sizes below.
if (self.imgs.length === 1 && self.imgs[0].scope === document && !self.haveAjax) {
// Try to get the full size without Ajax.
self.imgs[0].full_img = LAPI.WP.fullImageSizeFromPage
if (self.imgs[0].full_img.width > 0 && self.imgs[0].full_img.height > 0) {
self.setup_step_two
return;
}
Line 1,854 ⟶ 1,838:
// Get the full sizes of all the images. If more than 50, make several calls. (The API has limits.)
// Also avoid using Ajax on IE6...
var cache = {};
var names = [];
Array.forEach(self.imgs, function (img, idx) {
if
cache[img.realName][cache[img.realName].length] = idx;
}
names[names.length] =
}
});
var to_do = names.length;
var done
function check_done
done += length;
if (done >= names.length) {
if (typeof
self.setup_step_two
}
}
function make_calls
function build_titles(from, length, url_limit) {
var done = 0;
var text =
for (var i = from; i < from + length; i++) {
var new_text = names[i];
if (url_limit) {
new_text = encodeURIComponent
if (text.length
}
text += (text.length
done++;
}
Line 1,900 ⟶ 1,878:
}
var
var start = 0;
while (to_do > 0) {
params = build_titles
execute_call
to_do -= params.n;
start += params.n;
}
}
function set_info
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 || conf.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) {
for (var page in json.query.pages) {
get_size
}
} // end if
Line 1,948 ⟶ 1,927:
// prompt by using getScript instead of parseWikitext in this case.
ImageAnnotator.info_callbacks = [];
var template =
+ '&prop=info|imageinfo&inprop=protection&iiprop=size'
+ '&titles=&callback=ImageAnnotator.info_callbacks[].callback';
if (template.startsWith('//')) template = document.___location.protocol + template; // Avoid protocol-relative URIs (IE7 bug)
make_calls
function (length, titles) {
var idx = ImageAnnotator.info_callbacks.length;
ImageAnnotator.info_callbacks[idx] = {
},
ImageAnnotator.info_callbacks[idx].script =
IA.getScript
template.replace
.replace
, true // No local caching!
);
Line 1,977 ⟶ 1,956:
// script (and call the callback) synchronously before the assignment is done. Clean
// up in that case.
if (
&& ImageAnnotator.info_callbacks[idx].done && ImageAnnotator.info_callbacks[idx].script
) {
LAPI.DOM.removeNode
ImageAnnotator.info_callbacks[idx].script = null;
}
// Some
);
} else {
make_calls
function (length, titles) {
LAPI.Ajax.apiGet
titles: titles,
},
function (request, json_result) { check_done(length);
}
);
}
Line 2,007 ⟶ 1,990:
},
setup_ui
// Complete the UI object we've gotten from config.
ImageAnnotator.UI.ready
ImageAnnotator.UI.repo
ImageAnnotator.UI.needs_plea = false;
var readyEvent = [];
ImageAnnotator.UI.fireReadyEvent = function () {
if (ImageAnnotator.UI.ready) return; // Already fired, nothing to do.
ImageAnnotator.UI.ready = true;
// Call all registered handlers, and clear the array.
Array.forEach( readyEvent , function (f, idx) {
try {f
readyEvent = null;
};
ImageAnnotator.UI.addReadyEventHandler = function (f) {
if (ImageAnnotator.UI.ready) {
f (); // Already fired: call directly
Line 2,039 ⟶ 2,016:
readyEvent[readyEvent.length] = f;
}
};
ImageAnnotator.UI.setup = function () {
if (ImageAnnotator.UI.repo) return;
var self = ImageAnnotator.UI;
var node = LAPI.make
var item;
document.body.appendChild if (typeof
self.basic = true;
self.repo = {};
for (
node.innerHTML = self.defaults[item];
self.repo[item] = node.firstChild;
LAPI.DOM.removeChildren
}
} else {
self.basic = false;
self.repo = UIElements.emptyRepository
for (
node.innerHTML = self.defaults[item];
UIElements.setEntry
LAPI.DOM.removeChildren
}
UIElements.load
}
LAPI.DOM.removeNode
};
ImageAnnotator.UI.get = function (id, basic, no_plea) {
var self = ImageAnnotator.UI;
if (!self.repo) self.setup
var result
var add_plea = false;
if (self.basic) {
result = self.repo[id];
} else {
result = UIElements.getEntry
add_plea = !result;
if (!result) result = UIElements.getEntry
}
self.needs_plea = add_plea;
if (!result) return null; // Hmmm... what happened here? We normally have defaults...
if (basic) return LAPI.DOM.getInnerText
result = result.cloneNode
if (wgServer.contains
// Add a translation plea.
if (result.nodeName.toLowerCase
result.appendChild
} else {
var span = LAPI.make
span.appendChild
span.appendChild
result = span;
}
Line 2,099 ⟶ 2,075:
};
ImageAnnotator.UI.get_plea = function () {
var self = ImageAnnotator.UI;
var translate = self.get
var span = LAPI.make
span.appendChild
span.appendChild
LAPI.DOM.makeLink
withJS: 'MediaWiki:ImageAnnotatorTranslator.js',
language: conf.wgUserLanguage
}),
translate,
(typeof translate === 'string' ? translate : LAPI.DOM.getInnerText(translate).trim())
)
);
span.appendChild
return span;
};
ImageAnnotator.UI.init = function (html_text_or_json) {
var text;
if (typeof
text = html_text_or_json;
else if (
text = html_text_or_json.parse.text['*'];
else
Line 2,133 ⟶ 2,110:
if (!text) {
ImageAnnotator.UI.fireReadyEvent
return;
}
var node = LAPI.make
document.body.appendChild
try {
node.innerHTML = text;
} catch (ex) {
LAPI.DOM.removeNode
node = null;
// Swallow. We'll just work with the default UI
}
if (node && !ImageAnnotator.UI.repo) ImageAnnotator.UI.setup
ImageAnnotator.UI.fireReadyEvent
};
var ui_page = '{{MediaWiki:ImageAnnotatorTexts'
+ (conf.wgUserLanguage !== wgContentLanguage ? '|lang=' + conf.wgUserLanguage :
+ '|live=1}}';
function get_ui_no_ajax () {
var url =
+ encodeURIComponent
+ '&callback=ImageAnnotator.UI.init&maxage=14400&smaxage=14400'
;
Line 2,164 ⟶ 2,140:
// that on IE. (On FF, we could use an onerror handler on the script tag, but on FF, we use Ajax
// anyway.)
IA.getScript
}
function get_ui
IA.haveAjax = (LAPI.Ajax.getRequest() != null);
IA.ajaxQueried = true;
Line 2,181 ⟶ 2,156:
}
LAPI.Ajax.parseWikitext
ui_page
, ImageAnnotator.UI.init
Line 2,198 ⟶ 2,173:
get_ui_no_ajax ();
} else {
get_ui
}
},
setup_step_two
var self = IA;
// Throw out any images for which we miss either the thumbnail or the full image size.
// Also throws out thumbnails that are larger than the full image.
self.imgs = Array.select(self.imgs, function (elem, idx) {
elem.thumb.width > 0 && elem.thumb.height > 0
&& elem.full_img.width > 0 && elem.full_img.height > 0
;
if (self.may_edit && idx === 0 &&
return result;
if (self.imgs.length === 0) return;
ImageAnnotator.UI.addReadyEventHandler
},
complete_setup
function track(evt) {
evt = evt || window.event;
if (self.is_adding) self.update_zoom
if (!self.is_tracking) return LAPI.Evt.kill
var mouse_pos = LAPI.Pos.mousePosition
if (!LAPI.Pos.isWithin
var origin
// Make mouse pos relative to cover
mouse_pos.x = mouse_pos.x - origin.x;
mouse_pos.y = mouse_pos.y - origin.y;
if (mouse_pos.x >= self.base_x) {
self.definer.style.width =
self.definer.style.left
} else {
self.definer.style.width =
self.definer.style.left
}
if (mouse_pos.y >= self.base_y) {
self.definer.style.height =
self.definer.style.top
} else {
self.definer.style.height =
self.definer.style.top
}
return LAPI.Evt.kill
}
function pause (
LAPI.Evt.remove(document, 'mousemove', track, true);
if (!LAPI.
self.move_listening = false;
}
function resume
// captureEvents is actually deprecated, but I haven't succeeded to make this work with
// addEventListener only.
if ((self.is_tracking || self.is_adding) && !self.move_listening) {
if (!LAPI.Browser.is_ie && typeof
document.captureEvents
LAPI.Evt.attach
self.move_listening = true;
}
}
function stop_tracking
evt = evt || window.event;
// Check that we're within the image. Note: this check can fail only on IE >= 7, on other
// browsers, we attach the handler on self.cover and thus we don't even get events outside
// that range.
var mouse_pos = LAPI.Pos.mousePosition
if (!LAPI.Pos.isWithin
if (self.is_tracking) {
self.is_tracking = false;
Line 2,361 ⟶ 2,259:
if (LAPI.Browser.is_ie) {
//Trust Microsoft to get everything wrong!
LAPI.Evt.remove
} else {
LAPI.Evt.remove
}
LAPI.Evt.remove
LAPI.Evt.remove
self.cover.style.cursor = 'auto';
LAPI.DOM.removeNode
LAPI.Evt.remove
LAPI.Evt.remove
self.hide_zoom
self.viewers[0].hide
if (!self.definer || self.definer.offsetWidth <= 0 || self.definer.offsetHeight <= 0) {
// Nothing: just remove the definer:
if (self.definer) LAPI.DOM.removeNode
// Re-attach event handlers
self.viewers[0].setShowHideEvents
self.hide_cover
self.viewers[0].setDefaultMsg
// And make sure we get the real view again
self.viewers[0].show
} else {
// We have a div with some extent: remove event capturing and create a new annotation
var new_note = new ImageAnnotation
self.viewers[0].register
self.editor.editNote
}
self.definer = null;
}
if (evt) return LAPI.Evt.kill
return false;
}
function start_tracking
if (!self.is_tracking) {
self.is_tracking = true;
evt = evt || window.event;
// Set the position, size 1
var mouse_pos = LAPI.Pos.mousePosition
var origin
self.base_x = mouse_pos.x - origin.x;
self.base_y = mouse_pos.y - origin.y;
Object.merge
{ left
,top
,width
,height
,display:
}
, self.definer.style
);
// Set mouse handlers
LAPI.Evt.remove
if (LAPI.Browser.is_ie) {
LAPI.Evt.attach
} else {
LAPI.Evt.attach
}
resume ();
LAPI.Evt.attach
LAPI.Evt.attach
}
if (evt) return LAPI.Evt.kill
return false;
}
function add_new
if (!self.canEdit()) return;
self.editor.hide_editor
Tooltips.close
var cover = self.get_cover
cover.style.cursor = 'crosshair';
self.definer =
LAPI.make
border: '1px solid '
// IE needs this,
fontSize: '0px',
// Below
zIndex: cover.style.zIndex - 2
});
self.viewers[0].img_div.appendChild // Enter mouse-tracking mode to define extent of view. Mouse cursor is outside of image,
// hence none of our tooltips are visible.
self.viewers[0].img_div.appendChild
self.show_cover
self.is_tracking = false;
self.is_adding
LAPI.Evt.attach
resume ();
self.button_div.style.display = 'none';
// Remove the event listeners on the image: IE sometimes invokes them even when the image is covered
self.viewers[0].setShowHideEvents
self.viewers[0].hide
self.viewers[0].toggle
self.update_zoom_evt = LAPI.Evt.makeListener
self.hide_zoom_evt = LAPI.Evt.makeListener
self.show_zoom
LAPI.Evt.attach
LAPI.Evt.attach
LAPI.DOM.removeChildren
self.viewers[0].msg.appendChild
(ImageAnnotator.UI.get
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 && conf.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');
self.viewers[0].main_div.appendChild(self.button_div);
self.add_button =
LAPI.DOM.makeButton(
'ImageAnnotationAddButton'
, ImageAnnotator.UI.get('wpImageAnnotatorAddButtonText', true)
, add_new
);
var add_plea = ImageAnnotator.UI.needs_plea;
self.button_div.appendChild(self.add_button);
self.help_link = self.createHelpLink();
if (self.help_link) {
self.button_div.appendChild(document.createTextNode('\xa0'));
self.button_div.appendChild(self.help_link);
}
if (add_plea && wgServer.contains('/commons'))
self.button_div.appendChild(ImageAnnotator.UI.get_plea());
} // end if may_edit
// Get the file description pages of thumbnails. Figure out for which viewers we need to do this.
var cache
var get_local
var get_foreign = [];
Array.forEach(self.viewers, function (viewer, idx) {
if (viewer.setup_done || viewer.isLocal && !viewer.has_page) return;
// Handle only images that either are foreign or local and do have a page.
cache[viewer.realName][cache[viewer.realName].length] = idx;
}
if
}
}
});
if (get_local.length === 0 && get_foreign.length === 0) return;
// Now we have unique page names in the cache and in to_get. Go get the corresponding file
// description pages. We make a series of simultaneous asynchronous calls to avoid hitting
// API limits and to keep the URL length below the limit for the foreign_repo calls.
function make_calls
function composer (list, from, length, url_limit) {
function
var done = 0;
for (var i = from; i < from + length; i++) {
Line 2,541 ⟶ 2,494:
;
if (url_limit) {
new_text = encodeURIComponent
if (text.length
}
text = text + new_text;
Line 2,550 ⟶ 2,503:
// in the result, which would make us load the full images, which is desastrous if there are
// many thumbs to large images on the page.
if (done === 5) break;
}
return {text: text, n: done};
Line 2,556 ⟶ 2,509:
var param = compose (list, from, length, url_limit);
execute_call
return param.n;
}
Line 2,562 ⟶ 2,515:
var start = 0, chunk = 0, to_do = list.length;
while (to_do > 0) {
chunk = composer (list, start, Math.min
to_do -= chunk;
start += chunk;
Line 2,568 ⟶ 2,521:
}
var divRE
var blockStart
var inlineNameEnd = '</span>';
var noteStart
var noteControlRE = /<div\s*class="(wpImageAnnotatorInlinedRules|image_annotation_colors")(\S|\s)*?\/div>/g;
Line 2,578 ⟶ 2,531:
// additional images we'd rather not load when we add this (X)HTML to the DOM. Therefore, we
// strip out everything but the notes.
function strip_noise
var result =
var m;
// First, get rid of HTML comments and scripts
html = html.replace(/<\!--(.|\s)*?--\>/g,
var i = html.indexOf
// Now collect pages
while (idx < l && i >= idx) {
var j = html.indexOf
if (j < i + blockStart.length) break;
result += html.substring
idx = j + inlineNameEnd.length;
// Now collect all image image notes for that page
var note_begin = 0, next_block = html.indexOf
// Do we have image note control or color templates?
j = idx;
for (;;) {
noteControlRE.lastIndex = j;
if (!m || next_block >= idx && m.index > next_block) break;
result += m[0];
Line 2,602 ⟶ 2,556:
// Collect notes
for (;;) {
note_begin = html.indexOf
// Check whether next wrapper comes first
if (note_begin < idx || (next_block >= idx && note_begin > next_block)) break;
Line 2,609 ⟶ 2,563:
while (level > 0 && k < l) {
divRE.lastIndex = k;
if (!m || m.length < 2) {
k = l; // Nothing found at all?
Line 2,622 ⟶ 2,576:
}
} // end loop for nested divs
result += html.substring
while (level-- > 0) result += '</div>'; // Missing ends.
idx = k;
Line 2,632 ⟶ 2,586:
}
function setup_thumb_viewers
var node = LAPI.make('div', null, {display: 'none'});
document.body.appendChild(node);
try {
node.innerHTML = strip_noise
var pages = node.getElementsByClassName
for (var i = 0; pages && i < pages.length; i++) {
var notes =
if (!notes || notes.length === 0) continue;
var page = self.getItem
if (!page) continue;
page = page.replace
var viewers = cache[page] || cache['File:' + page.substring
if (!viewers || viewers.length === 0) continue;
// Update rules.
var rules =
var local_rules = {
if (rules && rules.length
rules = rules[0];
if (
&& LAPI.DOM.hasClass
)
local_rules.inline.show = false;
}
if (
&& LAPI.DOM.hasClass
)
local_rules.inline.icon = true;
}
if (
&& LAPI.DOM.hasClass
)
local_rules.thumbs.show = false;
}
if (
&& LAPI.DOM.hasClass
)
local_rules.thumbs.icon = true;
}
Line 2,682 ⟶ 2,631:
// Make sure all are set
local_rules.inline.show =
typeof
local_rules.thumbs.show =
typeof
local_rules.inline.icon =
typeof
local_rules.thumbs.icon =
typeof
if (!local_rules.inline.show) continue;
// Now use pages[i] as a scope shared by all the viewers using it. Since we clone note
// contents for note display, this works. Otherwise, we'd have to copy the notes into
// each viewer's scope.
document.body.appendChild
// Set viewers' scopes and finish their setup.
Array.forEach(viewers, function (v) {
if
|| self.viewers[v].
}
});
}
} catch (ex) {}
LAPI.DOM.removeNode
}
ImageAnnotator.script_callbacks = [];
function make_script_calls
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)
make_calls
list
, function (text) {
var idx = ImageAnnotator.script_callbacks.length;
ImageAnnotator.script_callbacks[idx] = {
}
}, done: false
}; ImageAnnotator.script_callbacks[idx].script =
IA.getScript
template.replace
.replace
, true // No local caching!
);
if (
&& ImageAnnotator.script_callbacks[idx].done && ImageAnnotator.script_callbacks[idx].script
) {
LAPI.DOM.removeNode
ImageAnnotator.script_callbacks[idx].script = null;
}
Line 2,751 ⟶ 2,696:
);
}
if ((!window.XMLHttpRequest && !!window.ActiveXObject) || !self.haveAjax) {
make_script_calls
} else {
make_calls
get_local
, function (text) {
LAPI.Ajax.parseWikitext
text
, function (html_text) {if (html_text) setup_thumb_viewers
, function () {}
, false
Line 2,775 ⟶ 2,720:
// character (2048 in the path part), and servers also may impose limits (on the WMF servers,
// the limit appears to be 8kB).
make_script_calls
},
show_zoom
var self = IA;
if (
&& self.viewers[0].factors.dy < self.zoom_threshold
)
|| Math.max
)
// Below zoom threshold, or full image not even twice the size of the preview
return;
Line 2,792 ⟶ 2,735:
if (!self.zoom) {
self.zoom =
LAPI.make
{ id: '
top: '0px',
left: '0px', border:
zIndex:
}
);
var src = self.viewers[0].img.getAttribute
// Adjust zoom_factor
if (self.zoom_factor > self.viewers[0].factors.dx || self.zoom_factor > self.viewers[0].factors.dy)
self.zoom_factor = Math.min
self.zoom.appendChild
// Calculate zoom size and source link
var zoom_width
var zoom_height = Math.floor
// For SVGs, always use a scaled PNG for the zoom.
if (zoom_width > 0.9 * self.viewers[0].full_img.width && src.search
// If the thumb we'd be loading was within about 80% of the full image size, we may just as
// well get the full image instead of a scaled version.
self.zoom_factor = Math.min
zoom_width
zoom_height
}
// Construct the initial zoomed image. We need to clone; if we create a completely
// new DOM node ourselves, it may not work on IE6...
var zoomed = self.viewers[0].img.cloneNode
zoomed.width =
zoomed.height =
Object.merge
self.zoom.firstChild.appendChild
// Crosshair
self.zoom.firstChild.appendChild
LAPI.make
'div', null
, { width
,height
,borderLeft
,position
,top
,left
}
)
);
self.zoom.firstChild.appendChild
LAPI.make
'div', null
, { width
,height
,borderTop
,position
,top
,left
}
)
);
document.body.appendChild
LAPI.DOM.loadImage
self.viewers[0].imgName
, src
, zoom_width
, zoom_height
, ImageAnnotator_config.thumbnailsGeneratedAutomatically
, function (img) {
// Replace the image in self.zoom by self.zoom_loader, making sure we keep the offsets
Line 2,868 ⟶ 2,810:
img.style.top = self.zoom.firstChild.firstChild.style.top;
img.style.left = self.zoom.firstChild.firstChild.style.left;
img.style.display =
self.zoom.firstChild.replaceChild
}
);
Line 2,876 ⟶ 2,818:
},
update_zoom
if (!evt) return; // We need an event to calculate positions!
var self = IA;
if (!self.zoom) return;
var mouse_pos = LAPI.Pos.mousePosition
var origin
if (!LAPI.Pos.isWithin
IA.hide_zoom
return;
}
Line 2,890 ⟶ 2,831:
var dy = mouse_pos.y - origin.y;
// dx, dy is the offset within the preview image. Align the zoom image accordingly.
var top
var left = - dx * self.zoom_factor + 100;
self.zoom.firstChild.firstChild.style.top
self.zoom.firstChild.firstChild.style.left =
self.zoom.style.top
// Horizontally keep it in view.
var x = (self.is_rtl ? mouse_pos.x - 10 : mouse_pos.x + 10);
if (x < 0) x = 0;
self.zoom.style.left = x + 'px';
self.zoom.style.display =
// Now that we have offsetWidth, correct the position.
if (self.is_rtl) {
Line 2,905 ⟶ 2,846:
if (x < 0) x = 0;
} else {
var off
var view = LAPI.Pos.viewport
if (x + self.zoom.offsetWidth > off.x + view.x) x = off.x + view.x - self.zoom.offsetWidth;
if (x < 0) x = 0;
Line 2,913 ⟶ 2,854:
},
hide_zoom
if (!IA.zoom) return;
if (evt) {
var mouse_pos = LAPI.Pos.mousePosition
if (LAPI.Pos.isWithin
}
IA.zoom.style.display = 'none';
},
createHelpLink
var msg = ImageAnnotator.UI.get('wpImageAnnotatorHelp', false, true);
if (!msg || !msg.lastChild) return null;
// Make sure we use the right protocol for all images:
var imgs = msg.getElementsByTagName('img');
var text;
var tgt;
if (imgs) {
for (var i = 0; i < imgs.length; i++) {
var srcFixed = imgs[i].getAttribute
imgs[i].src = srcFixed;
}
}
if (
&& msg.firstChild.nodeName.toLowerCase
&& !LAPI.DOM.hasClass
) {
msg.firstChild.id = 'ImageAnnotationHelpButton';
Line 2,944 ⟶ 2,885:
// Otherwise, it's either a sequence of up to three images, or a span, followed by a
// link.
if (tgt.nodeName.toLowerCase
tgt =
else
tgt = tgt.href;
function make_handler
var target = tgt;
return function (evt) {
var e = evt || window.event;
if (e) return LAPI.Evt.kill
return false;
};
}
if (!imgs || !imgs.length
// We're supposed to have a spans giving the button text
if (text.nodeName.toLowerCase
text = LAPI.DOM.getInnerText
else
text = 'Help';
return LAPI.DOM.makeButton
'ImageAnnotationHelpButton'
, text
, make_handler
);
} else {
return Buttons.makeButton
}
},
get_cover
var self = IA;
var shim;
if (!self.cover) {
var pos = { position
,left
,top
,width
,height
};
self.cover = LAPI.make
self.border = self.cover.cloneNode
Object.merge
{border: '3px solid green', top: '-3px', left: '-3px'}, self.border.style);
self.cover.style.zIndex
if (LAPI.Browser.is_ie) {
shim.style.filter
// Unfortunately, IE6/SP2 has a "security setting" called "Binary and script
// behaviors". If that is disabled, filters don't work, and our iframe would
Line 3,004 ⟶ 2,945:
var imgZ = self.viewers[0].img.style.zIndex;
if (isNaN (imgZ)) imgZ = 10; // Arbitrary, positive, > 1, < 500
shim.style.zIndex
self.ieFix = shim;
// And now the bgImage div...
shim = LAPI.make
Object.merge
{ top
,backgroundImage: 'url(' + self.viewers[0].img.src + ')'
,zIndex
}
, shim.style
Line 3,021 ⟶ 2,962:
// Hence we have to ensure that these events are killed even if our cover doesn't
// handle them.
shim.style.zIndex = self.cover.style.zIndex - 1;
LAPI.Evt.attach
function (evt) { return LAPI.Evt.kill
LAPI.Evt.attach
function (evt) { return LAPI.Evt.kill
LAPI.Evt.attach
function (evt) { return LAPI.Evt.kill
shim.style.cursor = 'default';
self.eventFix = shim;
Line 3,036 ⟶ 2,977:
return self.cover;
},
show_cover
var self = IA;
if (self.cover && !self.cover_visible) {
if (self.ieFix) {
self.viewers[0].img_div.appendChild
self.viewers[0].img_div.appendChild
}
if (self.eventFix) self.viewers[0].img_div.appendChild
self.viewers[0].img_div.appendChild
self.cover_visible = true;
}
},
hide_cover
var self = IA;
if (self.cover && self.cover_visible) {
if (self.ieFix) {
LAPI.DOM.removeNode
LAPI.DOM.removeNode
}
if (self.eventFix) LAPI.DOM.removeNode
LAPI.DOM.removeNode
self.cover_visible = false;
}
},
getRawItem
var node = null;
if (!scope || scope === document) {
node = LAPI.$ ('image_annotation_' + what);
} else {
node = scope.getElementsByClassName
if (node && node.length
}
return node;
},
getItem
var node = IA.getRawItem(what, scope);
if (!node) return null;
return LAPI.DOM.getInnerText
},
/** @return {number|null} */
getIntItem: function (what, scope) {
var x = IA.getItem
if (x !== null) x = parseInt (x, 10);
return x;
},
findNote
function find (text, id, delim) {
var start = delim.start.replace
var start_match = text.indexOf
if (start_match < 0) return null;
var end = delim.end.replace
var end_match = text.indexOf
if (end_match < start_match + start.length) return null;
return {start: start_match, end: end_match + end.length};
}
Line 3,110 ⟶ 3,046:
},
setWikitext
var self = IA;
if (self.wiki_read) return;
Array.forEach
if
// Now
var code = pagetext.substring(span.start, span.end);
for
if (j
return;
}
}
}
});
self.wiki_read = true;
},
setSummary
if (initial_text.contains('$1')) {
var max = (summary.maxlength || 200) - initial_text.length;
if (note_text)
initial_text =
initial_text.replace
else
initial_text = initial_text.replace
}
summary.value = initial_text;
},
getScript
// Don't use LAPI here, it may not yet be available
if (bypass_caches) {
url += ((url.indexOf
}
// Avoid protocol-relative URIs (IE7 bug)
if (url.length >= 2 && url.substring(0, 2) === '//') url = document.___location.protocol + url;
if (bypass_local_cache) {
var s = document.createElement
s.setAttribute
s.setAttribute
document.getElementsByTagName
return s;
} else {
Line 3,169 ⟶ 3,100:
},
canEdit
var self = IA;
if (self.may_edit) {
if (!self.ajaxQueried) {
self.haveAjax = (LAPI.Ajax.getRequest
self.ajaxQueried = true;
self.may_edit = self.haveAjax;
if (!self.may_edit && self.button_div) {
LAPI.DOM.removeChildren
self.button_div.appendChild
(ImageAnnotator.UI.get
self.viewers[0].msg.style.display =
self.viewers[0].cannotEdit
}
}
Line 3,189 ⟶ 3,119:
}
}; // end IA (private)
// Public interface
window.ImageAnnotator = {
install: function (config) {
mw.loader.using(['mediawiki.util'], function () {
IA.install(config);
});
}
};
// Start it. Bypass caches; but allow for 4 hours client-side caching. Small file.
IA.getScript
// Cache 4
+ '&dummy=' + Math.floor true
);
|