webnote.js
Summary
The Webnote specific classes.
The two main classes are workspace and Note. workspace represents the workspace as a whole. It contains refernces to all the notes and methods for operations such as saving the notes or undo-ing/redo-ing an action.
|
var debugOn = 1;
var notePadding = 5;
var noteBorder = 3;
var noteBorderColor = '#000';
var miniWidth = 51;
var exposeSize = "70";
var adminEmail = 'tony at ponderer dot org';
var baseURI = 'http://www.aypwip.org/webnote/';
var colorMap = {
'#ffff30': 'yellow',
'#8facff': 'blue',
'#7fff70': 'green',
'#ff6060': 'red',
'#ffb440': 'orange',
'#ff6bef': 'purple',
'#ffffff': 'white',
'#b0b0b0': 'grey'
};
var bgColors = new Array();
for (var c in colorMap)
{
bgColors.push(c);
}
document.write('<script type="text/javascript" src="objects"></script>');
var isIE = (document.all) ? true : false;
var isSaf = (navigator.userAgent.toLowerCase().indexOf('safari') != -1);
function Note(note, p, text)
{
if (typeof note == 'string')
note = get(note);
if (!text) text = ''
this.parent = p;
this.selectable = true;
this.editable = true;
this.id = note.id;
this.size = new Point(parseInt(note.style.width), parseInt(note.style.height));
var size = 6;
var dotElt = document.createElement('div');
dotElt.setAttribute('id', 'm' + this.id);
dotElt.setAttribute('onmousedown', 'workspace.notes.' + this.id + '.miniMouseDown(event);');
get('mini').appendChild(dotElt);
var dot = get('m' + this.id);
dot.style.width = size + 'px';
dot.style.height = (size+1) + 'px';
dot.style.border = '1px #000 solid';
dot.style.backgroundColor = note.style.backgroundColor;
dot.style.margin = '1px 1px 2px 1px';
dot.style.styleFloat = 'left';
dot.style.fontSize = '1px';
dot.style.cssFloat = 'left';
if (isIE)
{
var self = this;
dotElt.onmousedown = function() {
self.miniMouseDown(event);
};
}
this.setColor(note.style.backgroundColor, true);
this.setText(text);
this.updateSize();
}
Note.prototype.mouseDown = function(ev)
{
if (!this.selectable)
return true;
if (ev.altKey && 2 == ev.button)
{
this.sendToBack();
return true;
}
var lbutton = false;
if (isIE || isSaf) // IE and Safari's button mapping (1, 2, 4)
lbutton = 1 == ev.button;
else // mozilla and w3c's button mapping (0, 1, 2)
lbutton = 0 == ev.button;
if (!lbutton || ev.metaKey || (ev.ctrlKey && ev.altKey))
return true;
ev.cancelBubble = true;
this.parent.mouse.select(this, ev);
return true;
}
Note.prototype.miniMouseDown = function(ev)
{
if (ev.ctrlKey) // bring all notes of the same color to the front
{
for (var n in this.parent.notes)
{
var note = this.parent.notes[n];
if (note != this && note.bgColor.toString() == this.bgColor.toString())
note.select();
}
}
else if (ev.shiftKey)
{
workspace.filter('color:' + colorMap[this.bgColor.toString()]);
}
this.select();
var size = this.getCorrectedSize();
var pos = this.getPosition();
var elt = get(this.id);
if (size.x + pos.x < 5)
elt.style.left = "1px";
if (size.y + pos.y < 5)
elt.style.top = "1px";
}
Note.prototype.mouseUp = function() { this.parent.mouse.deselect(); }
Note.prototype.keyDown = function(ev)
{
if (this.parent.edit == this)
return;
var idx = parseInt(String.fromCharCode(ev.keyCode)) - 1;
if (idx >= 0 && idx < bgColors.length)
this.setColor(bgColors[idx]);
}
Note.prototype.mouseMove = function(ev)
{
var elt = get(this.id);
if (!this.selectable)
{
elt.style.cursor = 'auto';
return;
}
if (this.parent.mouse.selObj)
return;
if (isIE)
{
ev.layerX = ev.offsetX + noteBorder;
ev.layerY = ev.offsetY + noteBorder;
}
var top = false;
var left = false;
var right = false;
var bottom = false;
var size = this.getCorrectedSize();
if (ev.layerX <= noteBorder)
left = true;
if (size.x - ev.layerX <= noteBorder)
right = true;
if (ev.layerY <= noteBorder)
top = true;
if (size.y - ev.layerY <= noteBorder)
bottom = true;
if ( (top && left) || (bottom && right) )
elt.style.cursor = 'nw-resize';
else if ( (top && right) || (bottom && left) )
elt.style.cursor = 'ne-resize';
else if (top || bottom)
elt.style.cursor = 'n-resize';
else if (left || right)
elt.style.cursor = 'e-resize';
else
elt.style.cursor = 'move';
this.parent.mouse.notePosRel['top'] = top;
this.parent.mouse.notePosRel['bottom'] = bottom;
this.parent.mouse.notePosRel['left'] = left;
this.parent.mouse.notePosRel['right'] = right;
}
Note.prototype.mouseOver = function()
{
var elt = get(this.id);
elt.style.background = (new Color(this.bgColor.toString())).hsvadj(0, 0, -0.1);
this.parent.mouse.noteOver = this;
}
Note.prototype.mouseOut = function()
{
var elt = get(this.id);
elt.style.backgroundColor = this.bgColor.toString();
delete this.parent.mouse.noteOver;
}
Note.prototype.mouseDblClick = function()
{
if (!this.editable)
return;
if (this.parent.edit == this)
{
this.parent.editOff();
return;
}
this.parent.editOff();
var pSize = this.getCorrectedSize();
pSize.x -= 2 * (noteBorder + notePadding + 1);
pSize.y -= 2 * (noteBorder + notePadding + 1) + 16;
var html = "<div style='text-align:right;margin: 0 2px 1px 0;'>"
for (var c in bgColors)
{
html += "<div style='width: 12px;height:12px;font-size:1px;float:left;background: "
+ bgColors[c] + ";border: 1px #000 solid; margin:0 1px 1px 0;cursor:auto;' "
+ "onmousedown='workspace.notes." + this.id
+ ".setColor(\"" + bgColors[c] + "\");event.cancelBubble=true;' title='press "
+ (parseInt(c)+1) + " when not in edit mode change to this color'></div>";
}
html += "<img onclick='workspace.notes." + this.id
+ ".destroy(true);' src='images/close.gif' alt='close button'"
+ " title='click to destroy note' style='cursor:auto;border:0;height:12px;width:12px;' />"
+ "</div><textarea wrap='virtual' id='"
+ this.id + "text' style='width:" + pSize.x
+ "px;height:" + pSize.y + "px' onmousedown='event.cancelBubble=true;' ondblclick='event.cancelBubble=true;'>"
+ this.text.replace(/>/g, '>').replace(/</g, '<') + '</textarea>';
var elt = get(this.id);
elt.innerHTML = html;
elt.title = '';
get(this.id + 'text').focus();
this.parent.edit = this;
}
Note.prototype.destroy = function(addToHistory)
{
if (this.parent.edit == this)
this.parent.editOff();
var elt = get(this.id);
if (addToHistory)
{
var pos = this.getPosition();
var ws = this.parent;
var f = {
title : 'delete note',
noteData : {
'id' : this.id,
'xPos' : pos.x,
'yPos' : pos.y,
'height' : this.size.y,
'width' : this.size.x,
'bgcolor' : this.bgColor.toString(),
'zIndex' : elt.style.zIndex,
'text' : this.text
},
undo : function()
{
ws.createNote(this.noteData);
},
redo : function()
{
ws.notes[this.noteData.id].destroy(false);
}
};
this.parent.history.add(f);
}
elt.parentNode.removeChild(elt);
elt = get('m' + this.id);
elt.parentNode.removeChild(elt);
delete this.parent.notes[this.id];
this.parent.numNotes--;
this.parent.updateMiniBox();
}
Note.prototype.getPosition = function()
{
var ret = new Point(0, 0);
var elt = get(this.id);
ret = new Point(parseInt(elt.style.left), parseInt(elt.style.top));
return ret;
}
Note.prototype.getSize = function()
{
return new Point(this.size.x, this.size.y);
}
Note.prototype.getCorrectedSize = function()
{
var ret = this.getSize();
if (!isIE)
{
ret.x += 2*(noteBorder+notePadding);
ret.y += 2*(noteBorder+notePadding);
}
return ret;
}
Note.prototype.setColor = function(hex, ignoreHistory)
{
var newColor = new Color(hex);
if (!ignoreHistory)
{
var f = {
title : 'change color',
note : this,
ucolor : this.bgColor,
rcolor : newColor,
undo : function()
{
this.note.setColor(this.ucolor.toString(), true);
},
redo : function()
{
this.note.setColor(this.rcolor.toString(), true);
}
}
this.parent.history.add(f);
}
this.bgColor = newColor;
var elt = get(this.id);
if (this.parent.mouse.noteOver && this.parent.mouse.noteOver == this)
elt.style.background = (new Color(this.bgColor.toString())).hsvadj(0, 0, -0.1);
else
elt.style.background = this.bgColor.toString();
get('m' + this.id).style.background = this.bgColor;
}
Note.prototype.getHTML = function() // wikification
{
var sCopy = this.text.replace(/\n/g,"<br />\n");
var lines = sCopy.split('\n');
for (var i = 0; i < lines.length; i++)
{
if (lines[i].length > 0)
{
switch(lines[i].charAt(0))
{
case '!': // headings
var c = 0;
while ('!' == lines[i].charAt(c))
c++;
lines[i] = lines[i].substring(c);
c = Math.min(c, 3); // h3 is the biggest
c = 4 - c;
lines[i] = '<h' + c + '>' + lines[i] + '</h' + c + '>';
break;
default:
}
}
}
return lines.join('');
}
Note.prototype.toXML = function()
{
var ret = "<note noteid='" + this.id + "'";
ret += " bgcolor='" + this.bgColor + "'";
ret += " xposition='" + this.getPosition().x + "'";
ret += " yposition='" + this.getPosition().y + "'";
ret += " height='" + this.getSize().y + "'";
ret += " width='" + this.getSize().x + "'";
ret += " zindex='" + get(this.id).style.zIndex + "'";
ret += ">\n" + escape(this.text) + "\n";
return ret + "</note>"
}
Note.prototype.setText = function(str)
{
var chars = new Array();
var i;
for (i = 0; i < str.length; i++)
{
var c = str.charCodeAt(i);
if (c >= 160 && c <= 255)
chars.push("&#" + c + ";");
else
chars.push(str.charAt(i));
}
str = chars.join('');
if (str != this.text)
{
this.parent.changed = true;
this.text = str;
}
var elt = get(this.id);
elt.innerHTML = this.getHTML();
var imgs = elt.getElementsByTagName('img');
for (i = 0; i < imgs.length; i++)
{
if (isIE)
imgs[i].setAttribute('unselectable', 'on');
else
imgs[i].onmousedown = function() { return false; };
}
elt.title = 'double click to edit';
get('m' + this.id).title = str;
}
Note.prototype.updateSize = function()
{
var elt = get(this.id);
this.size.x = parseInt(elt.style.width);
this.size.y = parseInt(elt.style.height);
}
Note.prototype.disable = function()
{
this.selectable = this.editable = false;
var elt = get(this.id);
elt.title = '';
}
Note.prototype.enable = function()
{
this.selectable = this.editable = true;
var elt = get(this.id);
elt.title = 'double click to edit';
}
Note.prototype.select = function()
{
this.parent.reZOrder(this.id);
var self = this;
var elt = get(this.id);
elt.style.backgroundColor = (new Color(this.bgColor.toString()))
.hsvadj(0, 0, -0.1);
var d = document.createElement('div');
d.innerHTML = this.id;
d.style.position = 'absolute';
d.style.top = (parseInt(elt.style.top) + 5) + 'px';
d.style.left = (parseInt(elt.style.left) + 5) + 'px';
d.style.zIndex = 1000;
d.style.backgroundColor = '#fff';
d.style.padding = '4px';
d.style.opacity = 0.8;
document.body.appendChild(d);
setTimeout(function() {
elt.style.backgroundColor = self.bgColor.toString();
d.parentNode.removeChild(d);
}, 500);
}
Note.prototype.sendToBack = function()
{
var elt = get(this.id);
elt.style.zIndex = 0;
this.parent.reZOrder();
}
Note.prototype.move = function(delta) {
var newpos = this.getPosition().add(delta);
var elt = get(this.id);
elt.style.left = newpos.x + 'px';
elt.style.top = newpos.y + 'px';
}
function cmpNotesZ(a, b)
{
var av = parseInt(get(a.id).style.zIndex);
var bv = parseInt(get(b.id).style.zIndex);
if (av < bv)
return -1;
if (av > bv)
return 1;
return 0;
}
var Mouse =
{
notePosRel : {},
curPos : 0,
update : function(x, y)
{
this.curPos.x = x;
this.curPos.y = y;
if (this.selObj)
this.selObj.update(this);
},
select : function(note, ev)
{
if (this.selObj) // something already selected
return;
if (get(note.id).style.cursor != "move")
this.selObj = new SelectedObjectResize(note, this.notePosRel);
else
{
if (ev.altKey)
{
this.selObj = new SelectedObjectResize(note,
{'top': false, 'bottom':true, 'left':false, 'right':true});
}
else if (ev.ctrlKey)
this.selObj = new SelectedObjectDrag(note, true)
else
this.selObj = new SelectedObjectDrag(note, false);
}
this.downPos = this.curPos.copy();
workspace.reZOrder(note.id);
},
deselect : function()
{
if (this.selObj)
{
this.selObj.deselect();
delete this.selObj;
}
}
};
function SelectedObjectDrag(note, isGroup)
{
this.notes = new Array();
if (isGroup)
{
for (var n in workspace.notes)
{
if (workspace.notes[n].bgColor.toString() == note.bgColor.toString())
{
this.notes.push( { 'id' : workspace.notes[n].id,
'pos' : workspace.notes[n].getPosition() } );
}
}
}
else // single note move
this.notes.push( { 'id' : note.id, 'pos' : note.getPosition() } );
var elt;
for (n in this.notes)
{
elt = get(this.notes[n].id);
elt.style.border = noteBorder + 'px #980000 solid';
}
}
SelectedObjectDrag.prototype.update = function(md)
{
var offset = md.curPos.sub(md.downPos);
var elt;
for (n in this.notes)
{
elt = get(this.notes[n].id);
var newPos = this.notes[n].pos.add(offset);
elt.style.left = newPos.x + 'px';
elt.style.top = newPos.y + 'px';
}
};
SelectedObjectDrag.prototype.deselect = function()
{
var md = workspace.mouse;
var offset = md.curPos.sub(md.downPos);
if (!offset.equals(new Point(0, 0)))
{
var f = {
title : 'move note(s)',
notes : this.notes,
off : offset,
undo : function()
{
var elt;
for (n in this.notes)
{
elt = get(this.notes[n].id);
pos = this.notes[n].pos;
elt.style.left = pos.x + 'px';
elt.style.top = pos.y + 'px';
}
},
redo : function()
{
var elt;
for (n in this.notes)
{
elt = get(this.notes[n].id);
pos = this.notes[n].pos.add(this.off);
elt.style.left = pos.x + 'px';
elt.style.top = pos.y + 'px';
}
}
};
workspace.history.add(f);
}
var elt;
for (var n in this.notes)
{
elt = get(this.notes[n].id);
elt.style.border = noteBorder + 'px ' + noteBorderColor + ' solid';
}
};
function SelectedObjectResize(note, pnotePosRel)
{
this.note = note;
this.size = note.getSize();
this.pos = note.getPosition();
this.edges = pnotePosRel;
}
SelectedObjectResize.prototype.update = function(md)
{
var elt = get(this.note.id);
var minSize = 10;
var offset = md.curPos.sub(md.downPos);
if (this.edges['top'])
{
if (this.size.y - offset.y > minSize)
{
elt.style.top = (this.pos.y + offset.y) + 'px';
elt.style.height = (this.size.y - offset.y) + 'px';
}
}
else if (this.edges['bottom'])
elt.style.height = Math.max(this.size.y + offset.y, minSize) + 'px';
if (this.edges['left'])
{
if (this.size.x - offset.x > minSize)
{
elt.style.left = (this.pos.x + offset.x) + 'px';
elt.style.width = (this.size.x - offset.x) + 'px';
}
}
else if (this.edges['right'])
elt.style.width = Math.max(this.size.x + offset.x, minSize) + 'px';
if (this.note.parent.edit == this.note)
{
var edit = get(this.note.id + 'text');
var pSize = this.note.getCorrectedSize();
pSize.x -= 2*(noteBorder+notePadding+1);
pSize.y -= 2*(noteBorder+notePadding+1) + 16;
edit.style.height = pSize.y + 'px';
edit.style.width = pSize.x + 'px';
}
this.note.updateSize();
};
SelectedObjectResize.prototype.deselect = function()
{
var curSize = this.note.getSize();
if (!this.size.equals(curSize))
{
var f = {
title : 'resize note',
usize : this.size,
upos : this.pos,
rsize : curSize,
rpos : this.note.getPosition(),
id : this.note.id,
undo : function()
{
this.set(this.usize, this.upos);
},
redo : function()
{
this.set(this.rsize, this.rpos);
},
set : function(size, pos)
{
var elt = get(this.id);
elt.style.top = pos.y + 'px';
elt.style.left = pos.x + 'px';
elt.style.height = size.y + 'px';
elt.style.width = size.x + 'px';
workspace.notes[this.id].updateSize();
}
};
workspace.history.add(f);
}
};
var History =
{
maxSize : 40,
undoStack : new Array(), // each item in the array is an object
redoStack : new Array(), // with methods called undo and redo
add : function(funcPtr)
{
this.redoStack = new Array();
this.undoStack.push(funcPtr);
if (this.undoStack.length > this.maxSize)
this.undoStack.shift();
this.updateTitles();
},
undo : function()
{
if (this.undoStack.length > 0)
{
var f = this.undoStack.pop();
this.redoStack.push(f);
f.undo();
this.updateTitles();
}
},
redo : function()
{
if (this.redoStack.length > 0)
{
var f = this.redoStack.pop();
this.undoStack.push(f);
f.redo();
this.updateTitles();
}
},
updateTitles : function()
{
var elt = get('undoImg');
if (0 == this.undoStack.length)
{
elt.title = 'nothing to undo';
elt.className = 'controlsDisabled';
elt = get('saveImg');
elt.className = 'controlsDisabled';
}
else
{
elt.title = this.undoStack[this.undoStack.length-1].title
+ ' (' + this.undoStack.length + ' action(s))';
elt.className = 'controls';
elt = get('saveImg');
elt.className = 'controls';
}
elt = get('redoImg');
if (0 == this.redoStack.length)
{
elt.title = 'nothing to redo';
elt.className = 'controlsDisabled';
}
else
{
elt.title = this.redoStack[this.redoStack.length-1].title
+ ' (' + this.redoStack.length + ' action(s))';
elt.className = 'controls';
}
}
};
var NotePos =
{
x : 170,
y : 40,
nextPos : function(w, h)
{
var ret = new Point(this.x, this.y);
this.x += 20;
this.y += 20;
var s = getPageSize();
if (this.x + w > s.x || this.y + h > s.y)
{
this.x = 40;
this.y = 40;
}
return ret;
}
};
var workspace =
{
notes : {},
nextNoteNum : 0,
numNotes : 0,
loadedTime : 0,
changed : false,
edit : '',
name : 'Untitled',
history : History,
notePos : NotePos,
mouse : Mouse,
shortcuts : true,
topId: '',
createNote : function(note)
{
if (!note)
note = {};
if (!('id' in note))
{
note.id = "note" + this.nextNoteNum;
var self = this;
var f = {
title : 'create note',
nnn : this.nextNoteNum,
id : note.id,
pos : new Point(this.notePos.x, this.notePos.y),
undo : function()
{
self.notes[this.id].destroy(); // don't add to history
self.nextNoteNum = this.nnn;
},
redo : function()
{
self.createNote({'id': this.id, 'xPos' : this.pos.x,
'yPos' : this.pos.y});
self.nextNoteNum++;
}
};
this.history.add(f);
this.nextNoteNum++;
}
// don't create a layer if it already exists, just move it to the top
if (get(note.id))
{
this.reZOrder(note.id);
return note;
}
if (!('height' in note)) note.height = 150;
if (!('width' in note)) note.width = 250;
var pos;
if (!('xPos' in note) || !('yPos' in note))
pos = this.notePos.nextPos(note.width, note.height);
if (!('xPos' in note)) note.xPos = pos.x;
if (!('yPos' in note)) note.yPos = pos.y;
if (!('bgcolor' in note)) note.bgcolor = bgColors[0].toString();
if (!('text' in note)) note.text = '';
this.editOff();
var newDiv = document.createElement('div');
newDiv.setAttribute('class', 'note');
newDiv.setAttribute('id', note.id);
newDiv.setAttribute('onmouseover', 'workspace.notes.' + note.id + '.mouseOver();');
newDiv.setAttribute('onmouseout', 'workspace.notes.' + note.id + '.mouseOut();');
newDiv.setAttribute('onmousedown', 'workspace.notes.' + note.id + '.mouseDown(event);');
newDiv.setAttribute('onmouseup', 'workspace.notes.' + note.id + '.mouseUp();');
newDiv.setAttribute('onmousemove', 'workspace.notes.' + note.id + '.mouseMove(event);');
newDiv.setAttribute('ondblclick', 'workspace.notes.' + note.id + '.mouseDblClick();');
newDiv.setAttribute('title', 'double click to edit');
if (isSaf)
newDiv.style.opacity = '0.99';
document.body.appendChild(newDiv);
var elt = get(note.id);
elt.style.backgroundColor = note.bgcolor;
elt.style.width = note.width + 'px';
elt.style.height = note.height + 'px';
elt.style.left = note.xPos + 'px';
elt.style.top = note.yPos + 'px';
elt.style.padding = notePadding + 'px';
elt.style.position = 'absolute';
elt.style.border = noteBorder + 'px ' + noteBorderColor + ' solid';
if (!('zIndex' in note)) {
this.reZOrder();
elt.style.zIndex = this.numNotes + 1;
this.topId = note.id;
} else {
elt.style.zIndex = note.zIndex;
var topElt = get(this.topId);
if (topElt) {
if (parseInt(note.zIndex) > parseInt(topElt.style.zIndex)) {
this.topId = note.id;
}
} else {
this.topId = note.id;
}
}
this.numNotes++;
var nNote = new Note(elt, this, note.text);
this.notes[nNote.id] = nNote;
if (isIE) {
var n = nNote;
newDiv.onmouseover = function() {
n.mouseOver();
};
newDiv.onmouseout = function() {
n.mouseOut();
};
newDiv.onmousedown = function() {
n.mouseDown(event);
};
newDiv.onmouseup = function() {
n.mouseUp();
};
newDiv.onmousemove = function() {
n.mouseMove(event);
}
newDiv.ondblclick = function() {
n.mouseDblClick();
};
}
this.filter('');
return nNote;
},
mouseUp : function()
{
if (this.mouse.selObj)
{
this.mouse.selObj.deselect();
delete this.mouse.selObj;
}
},
editOff : function()
{
if (this.edit)
{
var textbox = get(this.edit.id + 'text');
if (textbox.value != this.edit.text)
{
var f = {
title : 'edit note',
utext : this.edit.text,
rtext : textbox.value,
note : this.edit,
undo : function()
{
this.note.setText(this.utext);
},
redo : function()
{
this.note.setText(this.rtext);
}
};
this.history.add(f);
}
this.edit.setText(textbox.value);
this.edit = '';
}
},
reZOrder : function(topNoteID)
{
if (this.notes) {
var nArray = new Array();
var i = 0;
for (nid in this.notes) {
nArray[i] = this.notes[nid];
i++;
}
nArray.sort(cmpNotesZ);
var found = 0;
for (i = 0; i < nArray.length; i++) {
if (nArray[i].id == topNoteID) {
found = 1;
get(nArray[i].id).style.zIndex = this.numNotes;
this.topId = nArray[i].id;
}
else
get(nArray[i].id).style.zIndex = i + 1 - found;
}
}
},
setName : function(n)
{
this.name = n;
var elt = get('wsname');
elt.innerHTML = this.name;
},
expose : function() {
var n, style;
for (n in this.notes) { //loop through the notes
var note = this.notes[n];
var elt = get(note.id); // get the div
style = elt.style; //get the note's style
// save old values
note.lastX = style.left;
note.lastY = style.top;
note.lastWidth = style.width;
note.lastHeight = style.height;
//Set the styles so that the notes nicely tile
style.position = 'relative';
style.margin = '5px';
style.left = '';
style.top = '';
style.cssFloat = 'left';
style.styleFloat = 'left';
style.display = '';
style.width = parseInt(exposeSize / 100.0 * parseInt(style.width)) + 'px';
style.height = parseInt(exposeSize / 100.0 * parseInt(style.height)) + 'px';
style.fontSize = exposeSize + '%';
//get and set the position of the div
var pos = findPos(elt);
style.left = pos.x + 'px';
style.top = pos.y + 'px';
}
//loop through again to make it absolute.
for (n in this.notes) {
style = get(this.notes[n].id).style;
style.position = 'absolute';
style.cssFloat = 'none';
style.styleFloat = 'none';
style.margin = '';
}
},
_expose : function() {
for (var n in this.notes) { //loop through notes
var note = this.notes[n];
var style = get(note.id).style; //get the Div
// restore values
style.top = note.lastY;
style.left = note.lastX;
style.width = note.lastWidth;
style.height = note.lastHeight;
style.fontSize = '100%';
}
},
/**
* Filter the visible notes (kind of like a search). Notes that match
* the regular expression are moved to the front while other notes are
* disabled and become mostly transparent.
* @param {string} text the regular expression to filter by
*/
filter : function(text)
{
var shouldHide;
if (text.trim().substring(0, 6) == 'color:')
{
var color = text.trim().substring(6);
shouldHide = function(note)
{
return (colorMap[note.bgColor.toString()] == color);
};
}
else
{
var reg = new RegExp(text, 'i');
shouldHide = function(note)
{
return reg.test(note.text);
};
}
for (n in this.notes)
{
var note = this.notes[n];
var elt = get(note.id);
if (shouldHide(note))
{
elt.className = 'note';
if (isSaf)
elt.style.opacity = '0.99';
note.enable();
get('m' + note.id).className = 'note';
}
else
{
elt.className = 'noteHide';
elt.style.zIndex = 0;
if (isSaf)
elt.style.opacity = '0.2';
note.disable();
get('m' + note.id).className = 'noteHide';
}
}
get('textfilter').value = text;
this.reZOrder();
this.updateMiniBox();
},
/**
* Update the background color and tool tips of the mini notes.
*/
updateMiniBox : function()
{
var mini = get('mini');
var vNotes = 0;
var total = 0;
for (n in this.notes)
{
total++;
var tmp = new Color(get('m' + n).style.backgroundColor);
if (!(0 == tmp.r && 0 == tmp.g && 0 == tmp.b))
vNotes++;
}
// set the width of the mini div
var diff = parseInt(Math.max(0, total - 9) / 2) * 10;
if (isIE) diff += 5; // padding + border
mini.style.width = (diff + miniWidth) + 'px';
if (0 == total)
mini.title = 'you have no notes';
else if (vNotes == total)
mini.title = 'showing all ' + total + ' notes';
else if (0 == vNotes)
mini.title = 'hiding all ' + total + ' notes';
else
mini.title = 'showing ' + vNotes + ' of ' + total + ' notes ('
+ parseInt(100 * vNotes / total) + '%)';
},
/**
* Create the "load old notes" note.
* @param {int} offset how many saves do we want to go back?
*/
loadlist : function(offset)
{
if (!offset)
offset = 0;
var xmlhttp = getxmlreqobj();
var self = this;
xmlhttp.onreadystatechange = function() {
if (4 == xmlhttp.readyState && 200 == xmlhttp.status)
{
var loadTimes = xmlhttp.responseText.split('|');
self.createNote( {
'id' : 'load',
'height' : '130',
'width' : '270',
'bgcolor' : '#ffff30'
} );
var txt = 'If you wish to load a previous version of this workspace, '
+ 'select the time from the list and press load.'
+ '<div style="text-align: center;"><form method="GET" action="load.py" onmousedown="event.cancelBubble=true;">'
+ '<input type="hidden" name="name" value="'
+ escape(self.name) + '" /><select class="loadfrm" name="time">';
for (var i = 0; i < loadTimes.length && i < 10; i++)
{
txt += "<option value='" + loadTimes[i].trim() + "'>"
+ (new MyDate(loadTimes[i])) + "</option>";
}
txt += "</select><input type='submit' value='load' /><br /><span style='font-size: 0.8em;'>";
if (loadTimes.length > 10)
{
txt += "« <a class='fakelink' onclick='workspace.loadlist(" + (offset+10) + ")'>older</a> ";
if (offset > 0)
txt += "| ";
}
if (offset > 0)
txt += "<a class='fakelink' onclick='workspace.loadlist(" + (offset-10) + ")'>newer</a> »";
txt += "<br />all times are US Eastern Time</span></div>";
self.notes['load'].setText(txt);
self.reZOrder('load');
}
};
this.createNote( {
'id' : 'load',
'height' : '130',
'width' : '270',
'bgcolor' : '#ffff30'
} );
var txt = 'getting load data...';
this.notes['load'].setText(txt);
this.reZOrder('load');
xmlhttp.open('GET', 'getdates.py?name=' + escape(escape(this.name)) + '&offset=' + offset, true);
xmlhttp.send('');
},
/**
* Checks to see if the notes have changed since the last time we loaded
* the notes.
*/
checkUpdated : function()
{
var xmlhttp = getxmlreqobj();
var self = this;
xmlhttp.onreadystatechange = function() {
if (4 == xmlhttp.readyState && 200 == xmlhttp.status)
{
var loadTime = xmlhttp.responseText.trim();
if (loadTime > self.loadedTime)
{
self.createNote( {
'id' : 'updated',
'xPos' : '0',
'yPos' : '40',
'bgcolor' : '#ff6060',
'height' : '100',
'text' : "This workspace has been changed by someone else while you were working on it. If you want to see what the workspace looks like now, you can <a href='"
+ escape(escape(escape(self.name))) + "' target='_new'>open it in a new window</a>."
});
}
else
{
window.setTimeout('workspace.checkUpdated()', 1000*60*10);
}
}
};
xmlhttp.open('GET', 'getrecent.py?name=' + escape(escape(this.name)), true);
xmlhttp.send('');
},
/**
* Generate the xml representation of the workspace (used when saving).
* @type string
*/
toString : function()
{
var ret = "<workspace name='" + escape(this.name) + "'";
ret += " nextNoteNum='" + this.nextNoteNum + "'";
ret += ">\n";
for (nid in this.notes)
ret += this.notes[nid].toXML() + "\n";
return ret + "</workspace>"
},
/**
* Get the next (or previous) note relative to the top note.
* @param {int} diff The offset from the top note (positive mean
* below and negative mean up from the bottom note).
*/
selectNote : function(diff)
{
var max = -1;
var maxNote;
var noteArr = [];
// determine which note is on top
for (var n in this.notes)
{
var cur = parseInt(get(this.notes[n].id).style.zIndex);
if (cur > max)
{
max = cur;
maxNote = noteArr.length;
}
noteArr.push(this.notes[n]);
}
noteArr[ (maxNote+diff+noteArr.length) % noteArr.length ].select();
},
/**
* Save the workspace.
*/
save : function()
{
// there have to be changes to the workspace before saving is enabled
var s = get('saveImg');
if (s.className == 'controlsDisabled')
return;
s.src = 'images/saving.gif';
this.editOff();
var xmlhttp = getxmlreqobj();
var self = this;
xmlhttp.onreadystatechange = function() {
if (4 == xmlhttp.readyState && 200 == xmlhttp.status)
{
var doc = xmlhttp.responseXML;
db("response: " + xmlhttp.responseText);
if (doc && 'ok' == doc.getElementsByTagName('status')[0].getAttribute('value'))
{
self.changed = false;
get('saveImg').className = 'controlsDisabled';
self.loadedTime = doc.getElementsByTagName('status')[0].getAttribute('update');
}
else
alert("Failed to save notes (server error). Please notify the administrator by sending an email to " + adminEmail + " Include what browser you're using and the time the error occurred. Thanks.");
get('saveImg').src = 'images/save.gif';
}
else if (4 == xmlhttp.readyState)
{
db(xmlhttp.status);
alert("Failed to save notes (status error). It looks like the save request failed. Please try again. If the problem persists, notify me by sending an email to " + adminEmail + ". Include what browser you're using and the time the error occurred. Thanks.");
get('saveImg').src = 'images/save.gif';
}
};
xmlhttp.open('POST', 'save.py', true);
xmlhttp.send(workspace.toString());
}
};
///
/// global methods
///
/**
* Write a message to the debug textarea.
* @param {string} msg the message to display
*/
function db(msg)
{
if (debugOn)
get('debug').value += '\n' + msg;
}
/**
* Initialize the workspace.
*/
function init()
{
if (debugOn)
{
get('db').innerHTML = "<form name='debugForm'>" +
"<textarea style='position:absolute;top:26px;right:10px;' id='debug' cols='50' rows='10'></textarea>" +
"<input type='button' onclick='document.debugForm.debug.value=\"\";' value='clear' />" +
"</form>";
get('debug').value = '';
}
// a hack for safari compatability
if (isSaf)
{
escape = myescape;
//unescape = myunescape;
}
// absolute mouse positions; modified from:
// http://www.web-wise-wizard.com/javascript-tutorials/javascript-mouse-event-handlers.html
workspace.mouse.curPos = new Point();
document.onmousemove = function(e)
{
var x, y;
if (isIE)
{
x = window.event.x+document.body.scrollLeft;
y = window.event.y+document.body.scrollTop;
}
else
{
x = e.pageX;
y = e.pageY;
}
workspace.mouse.update(x, y);
};
document.onkeydown = function(ev)
{
// the keydown event only seems to be a document event so we
// can't put the event on the div layer. Instead, events
if (isIE)
ev = window.event;
if (workspace.mouse.noteOver)
workspace.mouse.noteOver.keyDown(ev);
if (workspace.shortcuts && !workspace.edit) {
var key = String.fromCharCode(ev.keyCode);
var n;
if ('P' == key && ev.altKey) {
var note = workspace.createNote();
note.mouseDblClick();
} else if ('S' == key && ev.altKey) {
workspace.save();
} else if (37 == ev.keyCode) { // left
n = workspace.notes[workspace.topId];
if (n) n.move(new Point(-8, 0));
} else if (38 == ev.keyCode) { // up
n = workspace.notes[workspace.topId];
if (n) n.move(new Point(0, -8));
} else if (39 == ev.keyCode) { // right
n = workspace.notes[workspace.topId];
if (n) n.move(new Point(8, 0));
} else if (40 == ev.keyCode) { // down
n = workspace.notes[workspace.topId];
if (n) n.move(new Point(0, 8));
} else if ('O' == key && ev.altKey) {
n = workspace.notes[workspace.topId];
n.mouseDblClick();
} else if ('N' == key) {
workspace.selectNote(1);
} else if ('P' == key) {
workspace.selectNote(-1);
} else if ('B' == key) {
workspace.createNote({
'text':"This is a bookmarklet for quickly adding new notes. Right click on the link and add it as a bookmark. You can now quickly create a note by selecting text on any webpage and then clicking your bookmark.\n"
+ "<a href=\"javascript:var d=document;var e=encodeURIComponent;if(d.getSelection)txt=d.getSelection();if(d.selection)txt=d.selection.createRange().text;location.href='" + baseURI + "load.py?name="
+ escape(escape(escape(workspace.name))) + "&via='+e(location.href)+'&nn='+e(txt);\">new note</a>"});
}
} else if (workspace.shortcuts) { // in edit mode
if (27 == ev.keyCode) { // Esc
workspace.editOff();
}
}
};
window.onbeforeunload = function()
{
if (workspace.changed && workspace.name != "Sample Workspace")
{
return "Your notes have not been saved and you may lose some information.";
}
};
document.onmousedown = function(ev)
{
workspace.editOff();
};
document.onmouseup = function(ev)
{
workspace.mouseUp();
};
window.onunload = function()
{
if (isIE)
{
var cearElementProps = [
'onmouseover',
'onmouseout',
'onmousedown',
'onmouseup',
'onmousemove',
'ondblclick',
'onclick',
'onselectstart',
'ondragstart',
'onkeydown'
];
var el;
for(var d = document.all.length;d--;){
el = document.all[d];
for(var c = cearElementProps.length;c--;){
el[cearElementProps[c]] = null;
}
}
}
};
window.setTimeout('workspace.checkUpdated()', 1000*60*10);
}
function getPageSize()
{
var ret;
if (isIE)
{
ret = new Point(document.documentElement.clientWidth,
document.documentElement.clientHeight);
}
else
ret = new Point(window.innerWidth, window.innerHeight);
return ret;
}
function get(id) { return document.getElementById(id); }
Documentation generated by
JSDoc on Tue May 24 23:13:22 2005