From MediaWiki.org
// Fotonotes DHTML Client (c) 2004-2005 Angus Turnbull http://www.twinhelix.com
// Developed under license to FotoNotes LLC
// WIKI modifications (c) 2006 Dallan Quass
// Released under the Open Source License v2.1 or later.
// Modification 2005.11.17 - add loading scripts - Greg
// See the bottom of this file for configuration.
// *** FNCLIENT CONFIGURATION, VARIABLES AND SETUP ***
// Address of fotonoter.php on the server (this auto-detect should work):
var fnServerPath = "../";
var fnServerFotonotesScript = "fotonotes.php";
var fnServer = fnServerPath + fnServerFotonotesScript;
// XMLHTTPRequest object to communicate with server.
// WIKI - we don't communicate with the server; just set to true to avoid warnings
var fnXMLHTTP = true;
// Permissions (respect previous settings):
// Allowed values are 'allow', 'prompt', 'deny'.
if (!window.FN_ADD) var FN_ADD = 'allow';
if (!window.FN_MODIFY) var FN_MODIFY = 'allow';
if (!window.FN_DELETE) var FN_DELETE = 'allow';
// Internationalisation:
var FN_CREDITS = 'Fotonotes DHTML Viewer\n\n' +
'(c) 2004-2005 Angus Turnbull, http://www.twinhelix.com\n\n' +
'Provided under license to Fotonotes LLC';
var FN_DISALLOWED = 'Sorry, that action is not permitted.\n\n' +
'Please login under a different account.';
var FN_POST_UNSUPPORTED = 'Sorry, your browser does not support editing notes.';
var FN_DELETE_CONFIRM = 'Are you sure you want to delete this note?';
var FN_SAVE_WAIT = 'Loading Fotonotes...';
var FN_SAVE_FAIL = 'An error occurred, and your changes could not be saved.';
var FN_SAVE_FAIL_JPEG_NOT_WRITABLE = "JPEG file is not writable. Please check file permissions on server.";
var FN_SAVE_SUCCESS = 'Changes saved!';
// Other global variables:
var fnDebugMode = false; // Set to true to show XML sent/received.
var fnHideTimer = null; // Hide notes after timeout.
var fnActiveNote = null; // Currently visible note.
var fnActionVerb = ''; // Control bar's current action.
var fnActionTrigger = null; // Control bar's lit item.
var fnEditingData = null; // Data store during note editing process.
var fnAnnotateAll = false; // Indicate annotation should be applied to all images
var fnMinImgWidth = 200; // MinWidth to make to apply to fn-image
var fnMinImgHeight = 150; // MinHeight to make to apply to fn-image
var imageFileSrc = "src"; // Use 'id' for findImage() to use imgObj.id; use "src" (default) for findImage to use imgOb.src
// WIKI - added variables
var fnInputFieldID = 'wpTextbox1';
var fnOuterTag = 'fotonotes';
var fnDefaultImgID = 'fn-default';
var fnNotesDivSuffix = '-notes';
// *** Common API Code ***
var aeOL = [];
function addEvent(o, n, f, l)
{
var a = 'addEventListener', h = 'on'+n, b = '', s = '';
if (o[a] && !l) return o[a](n, f, false);
o._c |= 0;
if (o[h])
{
b = '_f' + o._c++;
o[b] = o[h];
}
s = '_f' + o._c++;
o[s] = f;
o[h] = function(e)
{
e = e || window.event;
var r = true;
if (b) r = o[b](e) != false && r;
r = o[s](e) != false && r;
return r;
};
aeOL[aeOL.length] = { o: o, h: h };
};
addEvent(window, 'unload', function() {
for (var i = 0; i < aeOL.length; i++) with (aeOL[i])
{
o[h] = null;
for (var c = 0; o['_f' + c]; c++) o['_f' + c] = null;
}
});
function cancelEvent(e, c)
{
e.returnValue = false;
if (e.preventDefault) e.preventDefault();
if (c)
{
e.cancelBubble = true;
if (e.stopPropagation) e.stopPropagation();
}
};
// *** FNCLIENT LOAD DIVS ***
// The following functions run after page loaded and retrieve Fotonotes data into the document to show annotations.
addLoadEvent(findImage);
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
oldonload();
func();
}
}
}
function findImage() {
for (i=0;i < document.images.length; i++) {
if (fnDebugMode) alert('img '+document.images[i].className);
// WIKI - changed test to allow for image under 'file' div or under an A tag under 'file' div
if ( (/fn-image/.test(document.images[i].className)) ||
(document.images[i].parentNode.id == 'file') ||
(document.images[i].parentNode.tagName == 'A' && document.images[i].parentNode.parentNode.id == 'file') ||
( (fnAnnotateAll) && (document.images[i].width >= fnMinImgWidth) && (document.images[i].height >= fnMinImgHeight)) ) {
var imgObj = document.images[i];
// get path to image.
if (fnDebugMode) alert("imgObj.src: "+imgObj.src);
// WIKI - note: imageFileSrc and imageFile are no longer used
if (imageFileSrc == "id") {
var imageFile = imgObj.id;
} else {
var imageFile = imgObj.src;
}
if (fnDebugMode) alert('revised imageFile: \n\n' + imageFile);
// WIKI - changed because we don't need to add a link - there's already one below the image - so just remove the anchor tag
if (imgObj.parentNode.tagName == "A") {
imgObj.parentNode.parentNode.replaceChild(imgObj, imgObj.parentNode);
}
createFNImage(imgObj, imageFile);
}
}
}
function createFNImage(imgObj, imageFile) {
getFNDiv(imgObj, imageFile);
}
// WIKI - rewrote this function
function getFNDiv(imgObj, imageFile) {
var editable = /fn-editable/.test(imgObj.className);
// get notes div
var fnNotesDivID = (imgObj.id ? imgObj.id : fnDefaultImgID) + fnNotesDivSuffix;
var notesDiv = document.getElementById(fnNotesDivID);
if (notesDiv) {
var notes = notesDiv.getElementsByTagName('span');
}
var hasNotes = notesDiv && notes.length > 0;
// if the image is not editable and there are no notes to show, return
if (!editable && !hasNotes) return;
// create canvas div
var fnCanvasDiv = document.createElement('div');
fnCanvasDiv.className='fn-canvas fn-container-active';
fnCanvasDiv.style.width=imgObj.width+'px';
fnCanvasDiv.style.height=(imgObj.height+20)+'px';
// create container div and add to canvas
var fnContainerDiv = document.createElement('div');
fnContainerDiv.className='fn-container fn-container-active';
fnContainerDiv.style.width=imgObj.width+'px';
fnContainerDiv.style.height=imgObj.height+'px';
fnContainerDiv.style.top='20px';
fnContainerDiv.style.left='0';
fnCanvasDiv.appendChild(fnContainerDiv);
// move img into container
imgObj.parentNode.insertBefore(fnCanvasDiv,imgObj);
imgObj.parentNode.removeChild(imgObj);
fnContainerDiv.appendChild(imgObj);
// add controlbar div to container
var fnControlBarDiv = document.createElement('div');
fnControlBarDiv.className="fn-controlbar fn-controlbar-active";
fnControlBarDiv.innerHTML = '<span class="fn-controlbar-credits"></span>' +
(editable ?
'<span class="fn-controlbar-del-inactive"></span>' +
'<span class="fn-controlbar-edit-inactive"></span>' +
'<span class="fn-controlbar-add-inactive"></span>'
: '<span class="fn-controlbar-toggle-inactive"></span>');
fnContainerDiv.appendChild(fnControlBarDiv);
// add edit form to container
if (editable)
{
var fnEditForm = document.createElement('form');
fnEditForm.className="fn-editbar fn-editbar-inactive";
fnEditForm.name="fn_editbar";
fnEditForm.id="fn_editbar";
fnEditForm.innerHTML = '<div class="fn-editbar-fields"><p>TITLE:</p>' +
'<input type="input" class="fn-editbar-title" name="title" value="default" /></div>' +
'<div class="fn-editbar-fields"><p>CONTENT:</p>' +
'<textarea class="fn-editbar-content" name="content"></textarea></div>' +
'<div class="fn-editbar-fields"><span class="fn-editbar-ok"></span>' +
'<span class="fn-editbar-cancel"></span></div>';
fnContainerDiv.appendChild(fnEditForm);
}
if (notesDiv)
{
var heightWidth = notesDiv.title.split(':');
// add scale span to container
var scaleSpan = document.createElement('span');
scaleSpan.className='fn-scalefactor';
var sFact=imgObj.width/heightWidth[1];
scaleSpan.title=sFact;
fnContainerDiv.appendChild(scaleSpan);
// add area divs to container
for (var i = 0; i < notes.length; i++)
{
var left=parseInt(notes[i].style.left);
var top=parseInt(notes[i].style.top);
var right=parseInt(notes[i].style.right);
var bottom=parseInt(notes[i].style.bottom);
var id=left+'-'+top+'-'+right+'-'+bottom;
var areaDiv = document.createElement('div');
areaDiv.className='fn-area';
areaDiv.style.left=Math.round(left*sFact)+'px';
areaDiv.style.top=Math.round(top*sFact)+'px';
areaDiv.style.width=Math.round((right-left)*sFact)+'px';
areaDiv.style.height=Math.round((bottom-top)*sFact)+'px';
areaDiv.innerHTML = '<div class="fn-note"><span class="fn-note-title">'+notes[i].title+'</span>' +
'<span class="fn-note-content">'+notes[i].innerHTML+'</span>' +
'<span class="fn-note-id" title="'+id+'"></span></div>' +
'<div class="fn-area-innerborder-left"></div>' +
'<div class="fn-area-innerborder-right"></div>' +
'<div class="fn-area-innerborder-top"></div>' +
'<div class="fn-area-innerborder-bottom"></div>';
fnContainerDiv.appendChild(areaDiv);
}
}
}
// *** Drag and Resize Library Code ***
// (c) 2005 Angus Turnbull http://www.twinhelix.com
function DragResize(myName, config)
{
var props = {
myName: myName, // Name of the object.
enabled: true, // Global toggle of drag/resize.
handles: ['tl', 'tm', 'tr',
'ml', 'mr', 'bl', 'bm', 'br'], // Array of drag handles: top/mid/.
isElement: null, // Function ref to test for an element.
isHandle: null, // Function ref to test for move handle.
element: null, // The currently selected element.
dragging: null, // Active handle reference of the element.
minWidth: 10, minHeight: 10, // Minimum pixel size of elements.
minLeft: 0, maxRight: 9999, // Bounding box area.
minTop: 0, maxBottom: 9999,
zIndex: 1, // The highest Z-Index yet allocated.
mouseX: 0, mouseY: 0, // Current mouse position, recorded live.
lastMouseX: 0, lastMouseY: 0, // Last processed mouse positions.
mOffX: 0, mOffY: 0, // A known offset between position & mouse.
elmX: 0, elmY: 0, // Element position.
elmW: 0, elmH: 0, // Element size.
allowBlur: true, // Whether to allow automatic blur onclick.
ondragfocus: null, // Event handler functions.
ondragstart: null,
ondragmove: null,
ondragend: null,
ondragblur: null
};
for (var p in props)
{
this[p] = (typeof config[p] == 'undefined') ? props[p] : config[p];
}
};
DragResize.prototype.apply = function(node)
{
// Adds object event handlers to the specified DOM node.
var obj = this;
addEvent(node, 'mousedown', function(e) { obj.mouseDown(e) } );
addEvent(node, 'mousemove', function(e) { obj.mouseMove(e) } );
addEvent(node, 'mouseup', function(e) { obj.mouseUp(e) } );
};
DragResize.prototype.handleSet = function(elm, show) { with (this)
{
// Either creates, shows or hides the resize handles within an element.
// If we're showing them, and no handles have been created, create 4 new ones.
if (!elm._handle_tr)
{
for (var h = 0; h < handles.length; h++)
{
// Create 4 news divs, assign each a generic + specific class.
var hDiv = document.createElement('div');
hDiv.className = myName + ' ' + myName + '-' + handles[h];
elm['_handle_' + handles[h]] = elm.appendChild(hDiv);
}
}
// We now have handles. Find them all and show/hide.
for (var h = 0; h < handles.length; h++)
{
elm['_handle_' + handles[h]].style.visibility = show ? 'inherit' : 'hidden';
}
}};
DragResize.prototype.select = function(newElement) { with (this)
{
// Selects an element for dragging.
if (!document.getElementById || !enabled) return;
// Activate and record our new dragging element.
if (newElement && (newElement != element) && enabled)
{
element = newElement;
// Elevate it and give it resize handles.
element.style.zIndex = ++zIndex;
handleSet(element, true);
// Record element attributes for mouseMove().
elmX = parseInt(element.style.left);
elmY = parseInt(element.style.top);
elmW = element.offsetWidth;
elmH = element.offsetHeight;
if (ondragfocus) this.ondragfocus();
}
}};
DragResize.prototype.deselect = function(keepHandles) { with (this)
{
// Immediately stops dragging an element. If 'keepHandles' is false, this
// remove the handles from the element and clears the element flag,
// completely resetting the .
if (!document.getElementById || !enabled) return;
if (!keepHandles)
{
if (ondragblur) this.ondragblur();
handleSet(element, false);
element = null;
}
dragging = null;
mOffX = 0;
mOffY = 0;
}};
DragResize.prototype.mouseDown = function(e) { with (this)
{
// Suitable elements are selected for drag/resize on mousedown.
// We also initialise the resize boxes, and drag parameters like mouse position etc.
if (!document.getElementById || !enabled) return true;
var elm = e.target || e.srcElement,
newElement = null,
newHandle = null,
hRE = new RegExp(myName + '-([trmbl]{2})', '');
while (elm)
{
// Loop up the DOM looking for matching elements. Remember one if found.
if (elm.className)
{
if (!newHandle && (hRE.test(elm.className) || isHandle(elm))) newHandle = elm;
if (isElement(elm)) { newElement = elm; break }
}
elm = elm.parentNode;
}
// If this isn't on the last dragged element, call deselect(false),
// which will hide its handles and clear element.
if (element && (element != newElement) && allowBlur) deselect(false);
// If we have a new matching element, call select().
if (newElement && (!element || (newElement == element)))
{
// Stop mouse selections.
cancelEvent(e);
select(newElement, newHandle);
dragging = newHandle;
if (dragging && ondragstart) this.ondragstart();
}
}};
DragResize.prototype.mouseMove = function(e) { with (this)
{
// This continually offsets the dragged element by the difference between the
// last recorded mouse position (mouseX/Y) and the current mouse position.
if (!document.getElementById || !enabled) return true;
// We always record the current mouse position.
mouseX = e.pageX || e.clientX + document.documentElement.scrollLeft;
mouseY = e.pageY || e.clientY + document.documentElement.scrollTop;
// Record the relative mouse movement, in case we're dragging.
// Add any previously stored&ignored offset to the calculations.
var diffX = mouseX - lastMouseX + mOffX;
var diffY = mouseY - lastMouseY + mOffY;
mOffX = mOffY = 0;
// Update last processed mouse positions.
lastMouseX = mouseX;
lastMouseY = mouseY;
// That's all we do if we're not dragging anything.
if (!dragging) return true;
// Establish which handle is being dragged -- retrieve handle name from className.
var hClass = dragging && dragging.className &&
dragging.className.match(new RegExp(myName + '-([tmblr]{2})')) ? RegExp.$1 : '';
// If the hClass is one of the resize handles, resize one or two dimensions.
// Bounds checking is the hard bit -- basically for each edge, check that the
// element doesn't go under minimum size, and doesn't go beyond its boundary.
var rs = 0, dY = diffY, dX = diffX;
if (hClass.indexOf('t') >= 0)
{
rs = 1;
if (elmH - dY < minHeight) mOffY = (dY - (diffY = elmH - minHeight));
else if (elmY + dY < minTop) mOffY = (dY - (diffY = minTop - elmY));
elmY += diffY;
elmH -= diffY;
}
if (hClass.indexOf('b') >= 0)
{
rs = 1;
if (elmH + dY < minHeight) mOffY = (dY - (diffY = minHeight - elmH));
else if (elmY + elmH + dY > maxBottom) mOffY = (dY - (diffY = maxBottom - elmY - elmH));
elmH += diffY;
}
if (hClass.indexOf('l') >= 0)
{
rs = 1;
if (elmW - dX < minWidth) mOffX = (dX - (diffX = elmW - minWidth));
else if (elmX + dX < minLeft) mOffX = (dX - (diffX = minLeft - elmX));
elmX += diffX;
elmW -= diffX;
}
if (hClass.indexOf('r') >= 0)
{
rs = 1;
if (elmW + dX < minWidth) mOffX = (dX - (diffX = minWidth - elmW));
else if (elmX + elmW + dX > maxRight) mOffX = (dX - (diffX = maxRight - elmX - elmW));
elmW += diffX;
}
// If 'rs' isn't set, we must be dragging the whole element, so move that.
if (dragging && !rs)
{
// Bounds check left-right...
if (elmX + dX < minLeft) mOffX = (dX - (diffX = minLeft - elmX));
else if (elmX + elmW + dX > maxRight) mOffX = (dX - (diffX = maxRight - elmX - elmW));
// ...and up-down.
if (elmY + dY < minTop) mOffY = (dY - (diffY = minTop - elmY));
else if (elmY + elmH + dY > maxBottom) mOffY = (dY - (diffY = maxBottom - elmY - elmH));
elmX += diffX;
elmY += diffY;
}
// Assign new info back to the element, with minimum dimensions.
with (element.style)
{
left = elmX + 'px';
width = elmW + 'px';
top = elmY + 'px';
height = elmH + 'px';
}
// Evil, dirty, hackish Opera select-as-you-drag fix.
if (window.opera && document.documentElement)
{
var oDF = document.getElementById('op-drag-fix');
if (!oDF)
{
var oDF = document.createElement('input');
oDF.id = 'op-drag-fix';
oDF.style.display = 'none';
document.body.appendChild(oDF);
}
oDF.focus();
}
if (ondragmove) this.ondragmove();
// Stop a normal drag event.
cancelEvent(e);
}};
DragResize.prototype.mouseUp = function(e) { with (this)
{
// On mouseup, stop dragging, but don't reset handler visibility.
if (!document.getElementById || !enabled) return;
if (ondragend) this.ondragend();
deselect(true);
}};
// *** FNCLIENT CORE CODE ***
var _f_idcount = 1;
function fnElementFade(elm, show)
{
// Fader function that shows/hides an element.
var speed = show ? 20 : 10;
elm._f_count |= 0;
elm._f_timer |= null;
clearTimeout(elm._f_timer);
if (show && !elm._f_count) elm.style.visibility = 'inherit';
elm._f_count = Math.max(0, Math.min(100, elm._f_count + speed*(show?1:-1)));
var f = elm.filters, done = (elm._f_count==100);
if (f)
{
if (!done && elm.style.filter.indexOf("alpha") == -1)
elm.style.filter += ' alpha(opacity=' + elm._f_count + ')';
else if (f.length && f.alpha) with (f.alpha)
{
if (done) enabled = false;
else { opacity = elm._f_count; enabled=true }
}
}
else elm.style.opacity = elm.style.MozOpacity = elm._f_count/100.1;
if (!show && !elm._f_count) elm.style.visibility = 'hidden';
if (elm._f_count % 100)
elm._f_timer = setTimeout(function() { fnElementFade(elm,show) }, 50);
};
function fnClassSet(elm, active)
{
// Utility function that toggles the "-active" and "-inactive" classnames.
elm.className = elm.className.replace((active ? (/-inactive/) : (/-active/)),
(active ? '-active' : '-inactive'));
};
function fnGetContainer(node)
{
// When passed a DOM node, returns its parent "fn-container".
var container = node;
while (container)
{
if ((/fn-container/).test(container.className)) break;
container = container.parentNode;
}
return container;
};
function fnGetControlBar(container)
{
// When passed a container, returns the control bar within that container.
var controlBar = null;
for (var i = 0; i < container.childNodes.length; i++)
{
if ((/fn-controlbar/).test(container.childNodes.item(i).className))
{
controlBar = container.childNodes.item(i);
break;
}
}
return controlBar;
};
function fnContainerSet(container, active)
{
// Sets the "activated" status of a note container area, and changes
// the appropriate "toggle" item in its control bar.
var controlBar = fnGetControlBar(container);
for (var i = 0; i < controlBar.childNodes.length; i++)
{
if ((/fn-controlbar-toggle/).test(controlBar.childNodes.item(i).className))
{
fnClassSet(controlBar.childNodes.item(i), !active);
break;
}
}
fnClassSet(container, active);
};
function fnAction(action, trigger)
{
// Called on click of control buttons to highlight/dim them.
// Control the state of the trigger buttons, and set the global fnActionVerb variable.
if (fnActionVerb != action)
{
// Set a new action, dim the old button.
if (fnActionTrigger && fnActionVerb) fnClassSet(fnActionTrigger, false);
fnActionVerb = action;
fnActionTrigger = trigger;
if (trigger) fnClassSet(trigger, true);
}
else
{
// Deactivate a trigger that is clicked twice.
fnActionVerb = '';
if (trigger) fnClassSet(trigger, false);
}
};
function fnMouseOverOutHandler(evt, isOver)
{
// Called on document.onmouseover & onmouseout, manages tip visibility.
var node = evt.target || evt.srcElement;
if (node.nodeType != 1) node = node.parentNode;
while (node && !((node.className||'').indexOf('fn-container') > -1))
{
// If the node has an CLASS of "fotonote-area", process it.
// No mouseovers if fnActionVerb is set (i.e. editing/deleting/adding/etc).
if (node && ((node.className||'').indexOf('fn-area') > -1) && !fnActionVerb)
{
var area = node;
// Find the first child element, which will be the note in question.
var note = area.firstChild;
while (note && note.nodeType != 1) note = note.nextSibling;
if (!note) return;
// Clear any hide timeout, and either show the note, or set a timeout for its hide.
// We record the currently active note for the hide timer to work, and also elevate
// its parent area above any previously active area (which is lowered).
clearTimeout(fnHideTimer);
if (isOver)
{
if (fnActiveNote && (note != fnActiveNote)) fnElementFade(fnActiveNote, false);
fnElementFade(note, true);
if (fnActiveNote) fnActiveNote.parentNode.style.zIndex = 1;
note.parentNode.style.zIndex = 2;
fnActiveNote = note;
}
else
{
fnHideTimer = setTimeout('if (fnActiveNote) { ' +
'fnElementFade(fnActiveNote, false); fnActiveNote = null }', 200);
}
}
// Loop up the DOM.
node = node.parentNode;
}
};
function fnClickHandler(evt)
{
// Processes clicks on the document, performs the correct action.
var node = evt.target || evt.srcElement;
if (node.nodeType != 1) node = node.parentNode;
while (node && !((node.className||'').indexOf('fn-container') > -1))
{
// Check buttons within the Edit bar.
if ((/fn-editbar-ok/).test(node.className)) return fnEditButtonHandler(true);
if ((/fn-editbar-cancel/).test(node.className)) return fnEditButtonHandler(false);
// Perform no other if we're currently editing a note.
if (fnEditingData) return;
// If an existing area with a CLASS of the form "fn-area"
// has been clicked, check if we're editing/deleting it.
if ((/fn-area/).test(node.className))
{
var area = node;
if (fnActionVerb == 'del') fnDelNote(area);
if (fnActionVerb == 'edit')
{
var note = area.firstChild;
while (note && note.nodeType != 1) note = note.nextSibling;
if (note) fnEditNote(note);
}
return;
}
// Buttons on/within the Control bar.
if ((/fn-controlbar-logo/).test(node.className))
{
// Logo click toggles control bar, if we're not editing a note.
var isActive = ((/fn-controlbar-active/).test(node.parentNode.className));
fnClassSet(node.parentNode, !isActive);
return;
}
if ((/fn-controlbar-credits/).test(node.className))
{
alert(FN_CREDITS);
return;
}
if ((/fn-controlbar-del/).test(node.className))
{
if (!fnXMLHTTP) return alert(FN_POST_UNSUPPORTED);
if (FN_DELETE == 'deny') return alert(FN_DISALLOWED);
return fnAction('del', node);
}
if ((/fn-controlbar-edit/).test(node.className))
{
if (!fnXMLHTTP) return alert(FN_POST_UNSUPPORTED);
if (FN_MODIFY == 'deny') return alert(FN_DISALLOWED);
return fnAction('edit', node);
}
if ((/fn-controlbar-add/).test(node.className))
{
if (!fnXMLHTTP) return alert(FN_POST_UNSUPPORTED);
if (FN_ADD == 'deny') return alert(FN_DISALLOWED);
return fnAddNote(node);
}
if ((/fn-controlbar-toggle/).test(node.className))
{
// Find the parent container, and toggle its classname to show/hide notes.
var container = fnGetContainer(node);
if (container)
{
var isActive = ((/fn-container-active/).test(container.className));
fnContainerSet(container, !isActive);
}
}
// Otherwise, loop up the hierarchy.
node = node.parentNode;
}
};
function fnEditUISet(show)
{
// Either shows or hides the editing UI.
if (!fnEditingData) return;
with (fnEditingData)
{
// Start or stop dragging the selected area.
if (show) dragresize.select(area, area);
else dragresize.deselect();
// Set area className so its remains visible if editing, or reset it back otherwise.
area.className = show ? 'fn-area-editing' : 'fn-area';
// Fade the editing UI in/out, and toggle its classname so it stays that way.
fnElementFade(form, show);
fnClassSet(form, show);
// Toggle the container class and control bar (for other notes' visibility)
fnContainerSet(container, !show);
fnClassSet(fnGetControlBar(container), !show);
}
};
function fnAddNote(node)
{
// Adds a new note when the specified button is clicked.
// Find the parent container of this node.
var container = fnGetContainer(node);
if (!container) return;
// Highlight the "Add" button.
fnAction('add', node);
// Create a new area in which the note will reside.
var newArea = document.createElement('div');
newArea.className = 'fn-area';
// WIKI - fixed
newArea.style.left = (Math.round(container.offsetWidth/2) - 25) + 'px';
newArea.style.top = (Math.round(container.offsetHeight/2) - 25) + 'px';
newArea.style.width = '50px';
newArea.style.height = '50px';
newArea.id = 'fn-area-new';
var newNote = document.createElement('div');
newNote.className = 'fn-note';
newArea.appendChild(newNote);
// Create note elements.
var newTitle = document.createElement('span');
newTitle.className = 'fn-note-title';
newNote.appendChild(newTitle);
var newContent = document.createElement('span');
newContent.className = 'fn-note-content';
newNote.appendChild(newContent);
var newAuthor = document.createElement('span');
newAuthor.className = 'fn-note-author';
newNote.appendChild(newAuthor);
var newUserid = document.createElement('span');
newUserid.className = 'fn-note-userid';
newNote.appendChild(newUserid);
var newID = document.createElement('span');
newID.className = 'fn-note-id';
newID.title = '';
// WIKI - fixed
newNote.appendChild(newID);
// add in innerborders
var newInnerBorder = document.createElement('div');
newInnerBorder.className = 'fn-area-innerborder-right';
newArea.appendChild(newInnerBorder);
var newInnerBorder = document.createElement('div');
newInnerBorder.className = 'fn-area-innerborder-left';
newArea.appendChild(newInnerBorder);
var newInnerBorder = document.createElement('div');
newInnerBorder.className = 'fn-area-innerborder-top';
newArea.appendChild(newInnerBorder);
var newInnerBorder = document.createElement('div');
newInnerBorder.className = 'fn-area-innerborder-bottom';
newArea.appendChild(newInnerBorder);
// Add newArea to document
container.appendChild(newArea);
// Record this note as editing, and set the "add" action flag.
fnEditingData = {
area: newArea,
note: newNote
};
// Hand over to the editing function.
fnEditNote();
};
function fnEditNote(note)
{
// Edits a passed note reference.
var area = null;
if (note)
{
// If we're editing an existing note, setup the data store.
area = note.parentNode;
fnEditingData = {
area: area,
note: note
};
}
else
{
// New notes: pull the note and area out of the stored data.
area = fnEditingData.area;
note = fnEditingData.note;
}
// Find our container and form references.
var container = fnGetContainer(area);
if (!container) return;
var form = container.getElementsByTagName('form');
if (!form) return;
form = form.item(0);
// Pick up existing values for content from the note.
var oldTitle = '', oldAuthor = '', oldContent = '', noteID = '';
var fields = area.getElementsByTagName('span');
for (var n = 0; n < fields.length; n++)
{
var field = fields.item(n);
if (field.className == 'fn-note-id') noteID = field.getAttribute('title');
if (field.className == 'fn-note-title') oldTitle = field.innerHTML;
if (field.className == 'fn-note-author') oldAuthor = field.innerHTML;
if (field.className == 'fn-note-content') oldContent = field.innerHTML;
}
// Backup the original content, refs and position in our datastore.
// It already has the .note and .area properties.
// And yes, I know innerHTML isn't standard, but it's SO MUCH EASIER here!
fnEditingData.container = container;
fnEditingData.form = form;
fnEditingData.noteID = noteID;
fnEditingData.oldTitle = oldTitle;
fnEditingData.oldAuthor = oldAuthor;
fnEditingData.oldContent = oldContent;
fnEditingData.oldLeft = parseInt(area.style.left);
fnEditingData.oldTop = parseInt(area.style.top);
fnEditingData.oldWidth = area.offsetWidth;
fnEditingData.oldHeight = area.offsetHeight;
// Some values for the post-editing callback handler to populate.
fnEditingData.newTitle = fnEditingData.newAuthor = fnEditingData.newContent = '';
fnEditingData.newLeft = fnEditingData.newTop = 0;
fnEditingData.newWidth = fnEditingData.newHeight = 0;
// Populate the editing UI with its current content.
// WIKI - added calls to unescape Html before putting it in the edit form
var inputs = form.getElementsByTagName('input');
for (var i = 0; i < inputs.length; i++)
{
if ((/title/).test(inputs[i].className)) inputs[i].value = fnUnescapeHTML(oldTitle);
if ((/author/).test(inputs[i].className)) inputs[i].value = fnUnescapeHTML(oldAuthor);
}
var textarea = form.getElementsByTagName('textarea');
if (textarea && (/content/).test(textarea.item(0).className))
textarea.item(0).value = fnUnescapeHTML(oldContent);
// Finally, show the editing UI for the recorded area.
fnEditUISet(true);
};
// WIKI - added /g and " conversion
function fnEscapeHTML(html)
{
// Returns a properly escaped HTML string.
return html.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');
};
// WIKI - new function
function fnUnescapeHTML(html)
{
// Returns a properly unescaped HTML string.
return html.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/&/g, '&');
};
function fnEditButtonHandler(ok)
{
// Button click handler from the editing UI.
// Pass a boolean value indicating if the OK button was clicked (so save should proceed).
if (!fnEditingData) return;
with (fnEditingData)
{
if (ok)
{
// Populate fnEditingData.new* from the edit form fields and area attributes.
// SET default value for all params.
newTitle = newAuthor = newUserid = newEntryid = newContent = newBorderColor = '';
var inputs = form.getElementsByTagName('input');
for (var i = 0; i < inputs.length; i++)
{
if ((/title/).test(inputs[i].className)) {newTitle = inputs[i].value;} //else {newTitle = '';}
if ((/author/).test(inputs[i].className)) {newAuthor = inputs[i].value;} //else {newAuthor = '';}
if ((/userid/).test(inputs[i].className)) {newUserid = inputs[i].value;} //else {newUserid = '';}
if ((/entry_id/).test(inputs[i].className)) {newEntryid = inputs[i].value;} //else {newEntryid = '';}
if ((/border_color/).test(inputs[i].className)) {newBorderColor = inputs[i].value;} //else {newEntryid = '';}
}
var textarea = form.getElementsByTagName('textarea');
if (textarea && (/content/).test(textarea.item(0).className)) {newContent = textarea.item(0).value};
newLeft = parseInt(area.style.left);
newTop = parseInt(area.style.top);
// WIKI - fixed
newWidth = parseInt(area.style.width);
newHeight = parseInt(area.style.height);
if (fnDebugMode) alert('Begin server save operation ' + 'newBorderColor: ' + newBorderColor);
// Get the scalefactor from a hidden SPAN in the container.
var sFact = 1;
for (var n = 0; n < container.childNodes.length; n++)
{
if ((/fn-scalefactor/).test(container.childNodes.item(n).className))
sFact = parseFloat(container.childNodes.item(n).getAttribute('title'));
}
// Begin server save operation.
/* Bordercolor UI elements have been removed fn div elements. See fnclient-0.4.0.bordercolor for elements.*/
// WIKI - changed
fnSaveUpdate(noteID, parseInt(newLeft/sFact), parseInt(newTop/sFact), parseInt((newLeft+newWidth)/sFact), parseInt((newTop+newHeight)/sFact), newTitle, newContent);
}
else
{
// For "cancel" clicks:
if (fnActionVerb == 'add')
{
// Just delete new notes.
area.parentNode.removeChild(area);
}
else
{
// Restore original note area position/size for edited notes.
area.style.left = oldLeft + 'px';
area.style.top = oldTop + 'px';
area.style.width = oldWidth + 'px';
area.style.height = oldHeight + 'px';
}
// Hide the editing UI, reset the control bar, clear the data store.
fnEditUISet(false);
fnAction('', null);
fnEditingData = null;
}
}
};
function fnDelNote(area)
{
// Deletes a note area -- passed a whole area reference.
// Find the ID of this note.
var noteID = '', fields = area.getElementsByTagName('span');
for (var n = 0; n < fields.length; n++)
if (fields.item(n).className == 'fn-note-id')
noteID = fields.item(n).getAttribute('title');
if (!noteID) alert(FN_SAVE_FAIL);
if (noteID && confirm(FN_DELETE_CONFIRM))
{
// Set up our data store to delete this area, and post to the server.
fnEditingData = {
area: area,
note: null,
container: fnGetContainer(area)
};
// WIKI - changed
fnSaveDel(noteID);
}
else
{
// Reset control bar if cancelled.
fnAction('', null);
}
};
// WIKI - new function
function fnGetNoteRegex(noteID) {
var bounds = noteID.split('-');
var regex = "<note\\s+left=\""+bounds[0]+"\"\\s+top=\""+bounds[1]+"\"\\s+right=\""+bounds[2]+"\"\\s+bottom=\""+bounds[3]+"\"[^<]*</note>";
return new RegExp(regex);
}
// WIKI - new function; author and userid omitted because they're captured in MediaWiki
function fnSaveUpdate(noteID, left, top, right, bottom, title, content)
{
// get and update value of input field
var inputField = document.getElementById(fnInputFieldID);
var newNote = "<note left=\""+left+"\" top=\""+top+"\" right=\""+right+"\" bottom=\""+bottom+"\" title=\""+fnEscapeHTML(title)+"\">"+fnEscapeHTML(content)+"</note>";
var noteRegex;
if (noteID)
{
noteRegex = fnGetNoteRegex(noteID);
inputField.value = inputField.value.replace(noteRegex, newNote);
}
else if (fnOuterTag)
{
var pos = inputField.value.indexOf('</'+fnOuterTag+'>');
if (pos < 0) {
inputField.value += '<'+fnOuterTag+'>\n'+newNote+'\n</'+fnOuterTag+'>';
}
else {
inputField.value = inputField.value.substring(0, pos)+newNote+'\n'+inputField.value.substring(pos);
}
}
else
{
if (inputField.value) {
inputField.value += '\n';
}
inputField.value += newNote;
}
with (fnEditingData)
{
// Place new values in the note. It's already in the right position.
for (var n = 0; n < note.childNodes.length; n++)
{
var field = note.childNodes.item(n);
if (field.className == 'fn-note-title') field.innerHTML = fnEscapeHTML(newTitle);
if (field.className == 'fn-note-author') field.innerHTML = newAuthor;
if (field.className == 'fn-note-content') field.innerHTML = fnEscapeHTML(newContent);
// note id is constructed from the bounding box
if (field.className == 'fn-note-id') field.title = left + '-' + top + '-' + right + '-' + bottom;
}
// Hide the editing UI.
fnEditUISet(false);
}
fnAction('', null);
fnEditingData = null;
}
// WIKI - new function
function fnSaveDel(noteID)
{
// update value of input field
// get and update value of input field
var inputField = document.getElementById(fnInputFieldID);
var noteRegex = fnGetNoteRegex(noteID);
inputField.value = inputField.value.replace(noteRegex, '');
with (fnEditingData)
{
// Deleting notes? Just remove the area from the document.
area.parentNode.removeChild(area);
}
fnEditingData = null;
fnAction('', null);
}
// INITIALISATION CODE:
if (document.getElementById)
{
// Create a new DragResize() object, and set it up.
// We apply to the whole document to interoperate with blinds.
var dragresize = new DragResize('dragresize', { allowBlur: false });
dragresize.isElement = function(elm)
{
if (!(/(add|edit)/).test(fnActionVerb)) return false;
if ((/fn-area-editing/).test(elm.className))
{
var container = fnGetContainer(elm);
this.maxRight = container.offsetWidth - 2;
this.maxBottom = container.offsetHeight - 2;
return true;
}
};
dragresize.isHandle = function(elm)
{
if (!(/(add|edit)/).test(fnActionVerb)) return false;
if ((/fn-area-editing/).test(elm.className)) return true;
};
dragresize.ondragfocus = function()
{
this.element.style.cursor = 'move';
};
dragresize.ondragblur = function()
{
this.element.style.cursor = 'default';
};
dragresize.apply(document);
// *** Global event handler setup ***
// These are global, rather than assigned to individual notes, to work with the "blind" code.
// Note show/hide events.
addEvent(document, 'mouseover', new Function('e', 'fnMouseOverOutHandler(e, 1)'));
addEvent(document, 'mouseout', new Function('e', 'fnMouseOverOutHandler(e, 0)'));
// Creation/editing/deletion events.
if (document.createElement && document.documentElement)
{
//addEvent(document, 'mousedown', fnMouseDownHandler);
//addEvent(document, 'mouseup', fnMouseUpHandler);
addEvent(document, 'click', fnClickHandler);
}
}