[marginalia] r537 committed - Update to match Marginalia M 2.0 beta 3 for Moodle 2.0

1 view
Skip to first unread message

margi...@googlecode.com

unread,
May 30, 2012, 5:52:18 PM5/30/12
to marginali...@googlegroups.com
Revision: 537
Author: geof.glass
Date: Wed May 30 14:51:38 2012
Log: Update to match Marginalia M 2.0 beta 3 for Moodle 2.0

http://code.google.com/p/marginalia/source/detail?r=537

Added:
/marginalia-lib/trunk/marginalia/3rd-party/date.js
Deleted:
/marginalia-lib/trunk/marginalia/3rd-party/cssQuery-standard.js
/marginalia-lib/trunk/marginalia/log-service.js
/marginalia-lib/trunk/marginalia/log.css
/marginalia-lib/trunk/marginalia/marginalia-direct.css
/marginalia-lib/trunk/marginalia/marginalia-direct.js
/marginalia-lib/trunk/marginalia/smartcopy.js
Modified:
/marginalia-lib/trunk/marginalia/3rd-party/jquery.dates.js
/marginalia-lib/trunk/marginalia/RangeInfo.js
/marginalia-lib/trunk/marginalia/SequenceRange.js
/marginalia-lib/trunk/marginalia/XPathRange.js
/marginalia-lib/trunk/marginalia/annotation.js
/marginalia-lib/trunk/marginalia/blockmarker-ui.js
/marginalia-lib/trunk/marginalia/domutil.js
/marginalia-lib/trunk/marginalia/highlight-ui.js
/marginalia-lib/trunk/marginalia/link-ui-clicktolink.js
/marginalia-lib/trunk/marginalia/link-ui-simple.js
/marginalia-lib/trunk/marginalia/link-ui.js
/marginalia-lib/trunk/marginalia/linkable.js
/marginalia-lib/trunk/marginalia/log.js
/marginalia-lib/trunk/marginalia/marginalia.css
/marginalia-lib/trunk/marginalia/marginalia.js
/marginalia-lib/trunk/marginalia/note-ui.js
/marginalia-lib/trunk/marginalia/post-micro.js
/marginalia-lib/trunk/marginalia/prefs.js
/marginalia-lib/trunk/marginalia/ranges.js
/marginalia-lib/trunk/marginalia/rest-annotate.js
/marginalia-lib/trunk/marginalia/rest-keywords.js
/marginalia-lib/trunk/marginalia/rest-prefs.js
/marginalia-lib/trunk/marginalia/restutil.js
/marginalia-lib/trunk/marginalia/track-changes.js
/marginalia-lib/trunk/marginalia/user-count.js

=======================================
--- /dev/null
+++ /marginalia-lib/trunk/marginalia/3rd-party/date.js Wed May 30 14:51:38
2012
@@ -0,0 +1,104 @@
+/**
+ * Version: 1.0 Alpha-1
+ * Build Date: 13-Nov-2007
+ * Copyright (c) 2006-2007, Coolite Inc. (http://www.coolite.com/). All
rights reserved.
+ * License: Licensed under The MIT License. See license.txt and
http://www.datejs.com/license/.
+ * Website: http://www.datejs.com/ or http://www.coolite.com/datejs/
+ */
+Date.CultureInfo={name:"en-US",englishName:"English (United
States)",nativeName:"English (United
States)",dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],abbreviatedDayNames:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],shortestDayNames:["Su","Mo","Tu","We","Th","Fr","Sa"],firstLetterDayNames:["S","M","T","W","T","F","S"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],abbreviatedMonthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],amDesignator:"AM",pmDesignator:"PM",firstDayOfWeek:0,twoDigitYearMax:2029,dateElementOrder:"mdy",formatPatterns:{shortDate:"M/d/yyyy",longDate:"dddd,
MMMM dd, yyyy",shortTime:"h:mm tt",longTime:"h:mm:ss
tt",fullDateTime:"dddd, MMMM dd, yyyy h:mm:ss
tt",sortableDateTime:"yyyy-MM-ddTHH:mm:ss",universalSortableDateTime:"yyyy-MM-dd
HH:mm:ssZ",rfc1123:"ddd, dd MMM yyyy HH:mm:ss GMT",monthDay:"MMMM
dd",yearMonth:"MMMM,
yyyy"},regexPatterns:{jan:/^jan(uary)?/i,feb:/^feb(ruary)?/i,mar:/^mar(ch)?/i,apr:/^apr(il)?/i,may:/^may/i,jun:/^jun(e)?/i,jul:/^jul(y)?/i,aug:/^aug(ust)?/i,sep:/^sep(t(ember)?)?/i,oct:/^oct(ober)?/i,nov:/^nov(ember)?/i,dec:/^dec(ember)?/i,sun:/^su(n(day)?)?/i,mon:/^mo(n(day)?)?/i,tue:/^tu(e(s(day)?)?)?/i,wed:/^we(d(nesday)?)?/i,thu:/^th(u(r(s(day)?)?)?)?/i,fri:/^fr(i(day)?)?/i,sat:/^sa(t(urday)?)?/i,future:/^next/i,past:/^last|
past|prev(ious)?/i,add:/^(\+|after|from)/i,subtract:/^(\-|before|
ago)/i,yesterday:/^yesterday/i,today:/^t(oday)?/i,tomorrow:/^tomorrow/i,now:/^n(ow)?/i,millisecond:/^ms|
milli(second)?s?/i,second:/^sec(ond)?s?/i,minute:/^min(ute)?s?/i,hour:/^h(ou)?rs?/i,week:/^w(ee)?k/i,month:/^m(o(nth)?s?)?/i,day:/^d(ays?)?/i,year:/^y((ea)?rs?)?/i,shortMeridian:/^(a|
p)/i,longMeridian:/^(a\.?m?\.?|p\.?m?\.?)/i,timezone:/^((e(s|d)t|c(s|d)t|
m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|
gmt)/i,ordinalSuffix:/^\s*(st|nd|rd|th)/i,timeContext:/^\s*(\:|a|
p)/i},abbreviatedTimeZoneStandard:{GMT:"-000",EST:"-0400",CST:"-0500",MST:"-0600",PST:"-0700"},abbreviatedTimeZoneDST:{GMT:"-000",EDT:"-0500",CDT:"-0600",MDT:"-0700",PDT:"-0800"}};
+Date.getMonthNumberFromName=function(name){var
n=Date.CultureInfo.monthNames,m=Date.CultureInfo.abbreviatedMonthNames,s=name.toLowerCase();for(var
i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s){return
i;}}
+return-1;};Date.getDayNumberFromName=function(name){var
n=Date.CultureInfo.dayNames,m=Date.CultureInfo.abbreviatedDayNames,o=Date.CultureInfo.shortestDayNames,s=name.toLowerCase();for(var
i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s){return
i;}}
+return-1;};Date.isLeapYear=function(year){return(((year%4===0)&&(year%100!==0))|
|
(year%400===0));};Date.getDaysInMonth=function(year,month){return[31,(Date.isLeapYear(year)?29:28),31,30,31,30,31,31,30,31,30,31][month];};Date.getTimezoneOffset=function(s,dst){return(dst|
|
false)?Date.CultureInfo.abbreviatedTimeZoneDST[s.toUpperCase()]:Date.CultureInfo.abbreviatedTimeZoneStandard[s.toUpperCase()];};Date.getTimezoneAbbreviation=function(offset,dst){var
n=(dst|
|
false)?Date.CultureInfo.abbreviatedTimeZoneDST:Date.CultureInfo.abbreviatedTimeZoneStandard,p;for(p
in n){if(n[p]===offset){return p;}}
+return null;};Date.prototype.clone=function(){return new
Date(this.getTime());};Date.prototype.compareTo=function(date){if(isNaN(this)){throw
new Error(this);}
+if(date instanceof
Date&&!isNaN(date)){return(this>date)?1:(this<date)?-1:0;}else{throw new
TypeError(date);}};Date.prototype.equals=function(date){return(this.compareTo(date)===0);};Date.prototype.between=function(start,end){var
t=this.getTime();return
t>=start.getTime()&&t<=end.getTime();};Date.prototype.addMilliseconds=function(value){this.setMilliseconds(this.getMilliseconds()+value);return
this;};Date.prototype.addSeconds=function(value){return
this.addMilliseconds(value*1000);};Date.prototype.addMinutes=function(value){return
this.addMilliseconds(value*60000);};Date.prototype.addHours=function(value){return
this.addMilliseconds(value*3600000);};Date.prototype.addDays=function(value){return
this.addMilliseconds(value*86400000);};Date.prototype.addWeeks=function(value){return
this.addMilliseconds(value*604800000);};Date.prototype.addMonths=function(value){var
n=this.getDate();this.setDate(1);this.setMonth(this.getMonth()+value);this.setDate(Math.min(n,this.getDaysInMonth()));return
this;};Date.prototype.addYears=function(value){return
this.addMonths(value*12);};Date.prototype.add=function(config){if(typeof
config=="number"){this._orient=config;return this;}
+var x=config;if(x.millisecond||
x.milliseconds){this.addMilliseconds(x.millisecond||x.milliseconds);}
+if(x.second||x.seconds){this.addSeconds(x.second||x.seconds);}
+if(x.minute||x.minutes){this.addMinutes(x.minute||x.minutes);}
+if(x.hour||x.hours){this.addHours(x.hour||x.hours);}
+if(x.month||x.months){this.addMonths(x.month||x.months);}
+if(x.year||x.years){this.addYears(x.year||x.years);}
+if(x.day||x.days){this.addDays(x.day||x.days);}
+return this;};Date._validate=function(value,min,max,name){if(typeof
value!="number"){throw new TypeError(value+" is not a Number.");}else
if(value<min||value>max){throw new RangeError(value+" is not a valid value
for "+name+".");}
+return true;};Date.validateMillisecond=function(n){return
Date._validate(n,0,999,"milliseconds");};Date.validateSecond=function(n){return
Date._validate(n,0,59,"seconds");};Date.validateMinute=function(n){return
Date._validate(n,0,59,"minutes");};Date.validateHour=function(n){return
Date._validate(n,0,23,"hours");};Date.validateDay=function(n,year,month){return
Date._validate(n,1,Date.getDaysInMonth(year,month),"days");};Date.validateMonth=function(n){return
Date._validate(n,0,11,"months");};Date.validateYear=function(n){return
Date._validate(n,1,9999,"seconds");};Date.prototype.set=function(config){var
x=config;if(!x.millisecond&&x.millisecond!==0){x.millisecond=-1;}
+if(!x.second&&x.second!==0){x.second=-1;}
+if(!x.minute&&x.minute!==0){x.minute=-1;}
+if(!x.hour&&x.hour!==0){x.hour=-1;}
+if(!x.day&&x.day!==0){x.day=-1;}
+if(!x.month&&x.month!==0){x.month=-1;}
+if(!x.year&&x.year!==0){x.year=-1;}
+if(x.millisecond!=-1&&Date.validateMillisecond(x.millisecond)){this.addMilliseconds(x.millisecond-this.getMilliseconds());}
+if(x.second!=-1&&Date.validateSecond(x.second)){this.addSeconds(x.second-this.getSeconds());}
+if(x.minute!=-1&&Date.validateMinute(x.minute)){this.addMinutes(x.minute-this.getMinutes());}
+if(x.hour!=-1&&Date.validateHour(x.hour)){this.addHours(x.hour-this.getHours());}
+if(x.month!==-1&&Date.validateMonth(x.month)){this.addMonths(x.month-this.getMonth());}
+if(x.year!=-1&&Date.validateYear(x.year)){this.addYears(x.year-this.getFullYear());}
+if(x.day!=-1&&Date.validateDay(x.day,this.getFullYear(),this.getMonth())){this.addDays(x.day-this.getDate());}
+if(x.timezone){this.setTimezone(x.timezone);}
+if(x.timezoneOffset){this.setTimezoneOffset(x.timezoneOffset);}
+return
this;};Date.prototype.clearTime=function(){this.setHours(0);this.setMinutes(0);this.setSeconds(0);this.setMilliseconds(0);return
this;};Date.prototype.isLeapYear=function(){var
y=this.getFullYear();return(((y%4===0)&&(y%100!==0))||
(y%400===0));};Date.prototype.isWeekday=function(){return!(this.is().sat()||
this.is().sun());};Date.prototype.getDaysInMonth=function(){return
Date.getDaysInMonth(this.getFullYear(),this.getMonth());};Date.prototype.moveToFirstDayOfMonth=function(){return
this.set({day:1});};Date.prototype.moveToLastDayOfMonth=function(){return
this.set({day:this.getDaysInMonth()});};Date.prototype.moveToDayOfWeek=function(day,orient){var
diff=(day-this.getDay()+7*(orient||+1))%7;return
this.addDays((diff===0)?diff+=7*(orient||
+1):diff);};Date.prototype.moveToMonth=function(month,orient){var
diff=(month-this.getMonth()+12*(orient||+1))%12;return
this.addMonths((diff===0)?diff+=12*(orient||
+1):diff);};Date.prototype.getDayOfYear=function(){return
Math.floor((this-new
Date(this.getFullYear(),0,1))/86400000);};Date.prototype.getWeekOfYear=function(firstDayOfWeek){var
y=this.getFullYear(),m=this.getMonth(),d=this.getDate();var
dow=firstDayOfWeek||Date.CultureInfo.firstDayOfWeek;var offset=7+1-new
Date(y,0,1).getDay();if(offset==8){offset=1;}
+var daynum=((Date.UTC(y,m,d,0,0,0)-Date.UTC(y,0,1,0,0,0))/86400000)+1;var
w=Math.floor((daynum-offset+7)/7);if(w===dow){y--;var prevOffset=7+1-new
Date(y,0,1).getDay();if(prevOffset==2||prevOffset==8){w=53;}else{w=52;}}
+return w;};Date.prototype.isDST=function(){console.log('isDST');return
this.toString().match(/(E|C|M|P)(S|
D)T/)[2]=="D";};Date.prototype.getTimezone=function(){return
Date.getTimezoneAbbreviation(this.getUTCOffset,this.isDST());};Date.prototype.setTimezoneOffset=function(s){var
here=this.getTimezoneOffset(),there=Number(s)*-6/10;this.addMinutes(there-here);return
this;};Date.prototype.setTimezone=function(s){return
this.setTimezoneOffset(Date.getTimezoneOffset(s));};Date.prototype.getUTCOffset=function(){var
n=this.getTimezoneOffset()*-10/6,r;if(n<0){r=(n-10000).toString();return
r[0]+r.substr(2);}else{r=(n+10000).toString();return"+"+r.substr(1);}};Date.prototype.getDayName=function(abbrev){return
abbrev?Date.CultureInfo.abbreviatedDayNames[this.getDay()]:Date.CultureInfo.dayNames[this.getDay()];};Date.prototype.getMonthName=function(abbrev){return
abbrev?Date.CultureInfo.abbreviatedMonthNames[this.getMonth()]:Date.CultureInfo.monthNames[this.getMonth()];};Date.prototype._toString=Date.prototype.toString;Date.prototype.toString=function(format){var
self=this;var p=function
p(s){return(s.toString().length==1)?"0"+s:s;};return
format?format.replace(/dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|
zz?z?/g,function(format){switch(format){case"hh":return
p(self.getHours()<13?self.getHours():(self.getHours()-12));case"h":return
self.getHours()<13?self.getHours():(self.getHours()-12);case"HH":return
p(self.getHours());case"H":return self.getHours();case"mm":return
p(self.getMinutes());case"m":return self.getMinutes();case"ss":return
p(self.getSeconds());case"s":return self.getSeconds();case"yyyy":return
self.getFullYear();case"yy":return
self.getFullYear().toString().substring(2,4);case"dddd":return
self.getDayName();case"ddd":return self.getDayName(true);case"dd":return
p(self.getDate());case"d":return
self.getDate().toString();case"MMMM":return
self.getMonthName();case"MMM":return
self.getMonthName(true);case"MM":return
p((self.getMonth()+1));case"M":return self.getMonth()+1;case"t":return
self.getHours()<12?Date.CultureInfo.amDesignator.substring(0,1):Date.CultureInfo.pmDesignator.substring(0,1);case"tt":return
self.getHours()<12?Date.CultureInfo.amDesignator:Date.CultureInfo.pmDesignator;case"zzz":case"zz":case"z":return"";}}):this._toString();};
+Date.now=function(){return new Date();};Date.today=function(){return
Date.now().clearTime();};Date.prototype._orient=+1;Date.prototype.next=function(){this._orient=+1;return
this;};Date.prototype.last=Date.prototype.prev=Date.prototype.previous=function(){this._orient=-1;return
this;};Date.prototype._is=false;Date.prototype.is=function(){this._is=true;return
this;};Number.prototype._dateElement="day";Number.prototype.fromNow=function(){var
c={};c[this._dateElement]=this;return
Date.now().add(c);};Number.prototype.ago=function(){var
c={};c[this._dateElement]=this*-1;return
Date.now().add(c);};(function(){var
$D=Date.prototype,$N=Number.prototype;var dx=("sunday monday tuesday
wednesday thursday friday saturday").split(/\s/),mx=("january february
march april may june july august september october november
december").split(/\s/),px=("Millisecond Second Minute Hour Day Week Month
Year").split(/\s/),de;var df=function(n){return
function(){if(this._is){this._is=false;return this.getDay()==n;}
+return this.moveToDayOfWeek(n,this._orient);};};for(var
i=0;i<dx.length;i++){$D[dx[i]]=$D[dx[i].substring(0,3)]=df(i);}
+var mf=function(n){return function(){if(this._is){this._is=false;return
this.getMonth()===n;}
+return this.moveToMonth(n,this._orient);};};for(var
j=0;j<mx.length;j++){$D[mx[j]]=$D[mx[j].substring(0,3)]=mf(j);}
+var ef=function(j){return
function(){if(j.substring(j.length-1)!="s"){j+="s";}
+return this["add"+j](this._orient);};};var nf=function(n){return
function(){this._dateElement=n;return this;};};for(var
k=0;k<px.length;k++){de=px[k].toLowerCase();$D[de]=$D[de+"s"]=ef(px[k]);$N[de]=$N[de+"s"]=nf(de);}}());Date.prototype.toJSONString=function(){return
this.toString("yyyy-MM-ddThh:mm:ssZ");};Date.prototype.toShortDateString=function(){return
this.toString(Date.CultureInfo.formatPatterns.shortDatePattern);};Date.prototype.toLongDateString=function(){return
this.toString(Date.CultureInfo.formatPatterns.longDatePattern);};Date.prototype.toShortTimeString=function(){return
this.toString(Date.CultureInfo.formatPatterns.shortTimePattern);};Date.prototype.toLongTimeString=function(){return
this.toString(Date.CultureInfo.formatPatterns.longTimePattern);};Date.prototype.getOrdinal=function(){switch(this.getDate()){case
1:case 21:case 31:return"st";case 2:case 22:return"nd";case 3:case
23:return"rd";default:return"th";}};
+(function(){Date.Parsing={Exception:function(s){this.message="Parse error
at '"+s.substring(0,10)+" ...'";}};var $P=Date.Parsing;var
_=$P.Operators={rtoken:function(r){return function(s){var
mx=s.match(r);if(mx){return([mx[0],s.substring(mx[0].length)]);}else{throw
new $P.Exception(s);}};},token:function(s){return function(s){return
_.rtoken(new RegExp("^\s*"+s+"\s*"))(s);};},stoken:function(s){return
_.rtoken(new RegExp("^"+s));},until:function(p){return function(s){var
qx=[],rx=null;while(s.length){try{rx=p.call(this,s);}catch(e){qx.push(rx[0]);s=rx[1];continue;}
+break;}
+return[qx,s];};},many:function(p){return function(s){var
rx=[],r=null;while(s.length){try{r=p.call(this,s);}catch(e){return[rx,s];}
+rx.push(r[0]);s=r[1];}
+return[rx,s];};},optional:function(p){return function(s){var
r=null;try{r=p.call(this,s);}catch(e){return[null,s];}
+return[r[0],r[1]];};},not:function(p){return
function(s){try{p.call(this,s);}catch(e){return[null,s];}
+throw new $P.Exception(s);};},ignore:function(p){return p?function(s){var
r=null;r=p.call(this,s);return[null,r[1]];}:null;},product:function(){var
px=arguments[0],qx=Array.prototype.slice.call(arguments,1),rx=[];for(var
i=0;i<px.length;i++){rx.push(_.each(px[i],qx));}
+return rx;},cache:function(rule){var cache={},r=null;return
function(s){try{r=cache[s]=(cache[s]||
rule.call(this,s));}catch(e){r=cache[s]=e;}
+if(r instanceof $P.Exception){throw r;}else{return
r;}};},any:function(){var px=arguments;return function(s){var
r=null;for(var i=0;i<px.length;i++){if(px[i]==null){continue;}
+try{r=(px[i].call(this,s));}catch(e){r=null;}
+if(r){return r;}}
+throw new $P.Exception(s);};},each:function(){var px=arguments;return
function(s){var rx=[],r=null;for(var
i=0;i<px.length;i++){if(px[i]==null){continue;}
+try{r=(px[i].call(this,s));}catch(e){throw new $P.Exception(s);}
+rx.push(r[0]);s=r[1];}
+return[rx,s];};},all:function(){var px=arguments,_=_;return
_.each(_.optional(px));},sequence:function(px,d,c){d=d||
_.rtoken(/^\s*/);c=c||null;if(px.length==1){return px[0];}
+return function(s){var r=null,q=null;var rx=[];for(var
i=0;i<px.length;i++){try{r=px[i].call(this,s);}catch(e){break;}
+rx.push(r[0]);try{q=d.call(this,r[1]);}catch(ex){q=null;break;}
+s=q[1];}
+if(!r){throw new $P.Exception(s);}
+if(q){throw new $P.Exception(q[1]);}
+if(c){try{r=c.call(this,r[1]);}catch(ey){throw new $P.Exception(r[1]);}}
+return[rx,(r?r[1]:s)];};},between:function(d1,p,d2){d2=d2||d1;var
_fn=_.each(_.ignore(d1),p,_.ignore(d2));return function(s){var
rx=_fn.call(this,s);return[[rx[0][0],r[0][2]],rx[1]];};},list:function(p,d,c){d=d|
|_.rtoken(/^\s*/);c=c||null;return(p instanceof
Array?_.each(_.product(p.slice(0,-1),_.ignore(d)),p.slice(-1),_.ignore(c)):_.each(_.many(_.each(p,_.ignore(d))),px,_.ignore(c)));},set:function(px,d,c){d=d|
|_.rtoken(/^\s*/);c=c||null;return function(s){var
r=null,p=null,q=null,rx=null,best=[[],s],last=false;for(var
i=0;i<px.length;i++){q=null;p=null;r=null;last=(px.length==1);try{r=px[i].call(this,s);}catch(e){continue;}
+rx=[[r[0]],r[1]];if(r[1].length>0&&!last){try{q=d.call(this,r[1]);}catch(ex){last=true;}}else{last=true;}
+if(!last&&q[1].length===0){last=true;}
+if(!last){var qx=[];for(var j=0;j<px.length;j++){if(i!=j){qx.push(px[j]);}}
+p=_.set(qx,d).call(this,q[1]);if(p[0].length>0){rx[0]=rx[0].concat(p[0]);rx[1]=p[1];}}
+if(rx[1].length<best[1].length){best=rx;}
+if(best[1].length===0){break;}}
+if(best[0].length===0){return best;}
+if(c){try{q=c.call(this,best[1]);}catch(ey){throw new
$P.Exception(best[1]);}
+best[1]=q[1];}
+return best;};},forward:function(gr,fname){return function(s){return
gr[fname].call(this,s);};},replace:function(rule,repl){return
function(s){var
r=rule.call(this,s);return[repl,r[1]];};},process:function(rule,fn){return
function(s){var
r=rule.call(this,s);return[fn.call(this,r[0]),r[1]];};},min:function(min,rule){return
function(s){var rx=rule.call(this,s);if(rx[0].length<min){throw new
$P.Exception(s);}
+return rx;};}};var _generator=function(op){return function(){var
args=null,rx=[];if(arguments.length>1){args=Array.prototype.slice.call(arguments);}else
if(arguments[0]instanceof Array){args=arguments[0];}
+if(args){for(var
i=0,px=args.shift();i<px.length;i++){args.unshift(px[i]);rx.push(op.apply(null,args));args.shift();return
rx;}}else{return op.apply(null,arguments);}};};var gx="optional not ignore
cache".split(/\s/);for(var
i=0;i<gx.length;i++){_[gx[i]]=_generator(_[gx[i]]);}
+var _vector=function(op){return function(){if(arguments[0]instanceof
Array){return op.apply(null,arguments[0]);}else{return
op.apply(null,arguments);}};};var vx="each any all".split(/\s/);for(var
j=0;j<vx.length;j++){_[vx[j]]=_vector(_[vx[j]]);}}());(function(){var
flattenAndCompact=function(ax){var rx=[];for(var
i=0;i<ax.length;i++){if(ax[i]instanceof
Array){rx=rx.concat(flattenAndCompact(ax[i]));}else{if(ax[i]){rx.push(ax[i]);}}}
+return rx;};Date.Grammar={};Date.Translator={hour:function(s){return
function(){this.hour=Number(s);};},minute:function(s){return
function(){this.minute=Number(s);};},second:function(s){return
function(){this.second=Number(s);};},meridian:function(s){return
function(){this.meridian=s.slice(0,1).toLowerCase();};},timezone:function(s){return
function(){var
n=s.replace(/[^\d\+\-]/g,"");if(n.length){this.timezoneOffset=Number(n);}else{this.timezone=s.toLowerCase();}};},day:function(x){var
s=x[0];return
function(){this.day=Number(s.match(/\d+/)[0]);};},month:function(s){return
function(){this.month=((s.length==3)?Date.getMonthNumberFromName(s):(Number(s)-1));};},year:function(s){return
function(){var
n=Number(s);this.year=((s.length>2)?n:(n+(((n+2000)<Date.CultureInfo.twoDigitYearMax)?2000:1900)));};},rday:function(s){return
function(){switch(s){case"yesterday":this.days=-1;break;case"tomorrow":this.days=1;break;case"today":this.days=0;break;case"now":this.days=0;this.now=true;break;}};},finishExact:function(x){x=(x
instanceof Array)?x:[x];var now=new
Date();this.year=now.getFullYear();this.month=now.getMonth();this.day=1;this.hour=0;this.minute=0;this.second=0;for(var
i=0;i<x.length;i++){if(x[i]){x[i].call(this);}}
+this.hour=(this.meridian=="p"&&this.hour<13)?this.hour+12:this.hour;if(this.day>Date.getDaysInMonth(this.year,this.month)){throw
new
RangeError(this.day+" is not a valid value for days.");}
+var r=new
Date(this.year,this.month,this.day,this.hour,this.minute,this.second);if(this.timezone){r.set({timezone:this.timezone});}else
if(this.timezoneOffset){r.set({timezoneOffset:this.timezoneOffset});}
+return r;},finish:function(x){x=(x instanceof
Array)?flattenAndCompact(x):[x];if(x.length===0){return null;}
+for(var i=0;i<x.length;i++){if(typeof x[i]=="function"){x[i].call(this);}}
+if(this.now){return new Date();}
+var today=Date.today();var method=null;var expression=!!(this.days!=null||
this.orient||this.operator);if(expression){var
gap,mod,orient;orient=((this.orient=="past"||
this.operator=="subtract")?-1:1);if(this.weekday){this.unit="day";gap=(Date.getDayNumberFromName(this.weekday)-today.getDay());mod=7;this.days=gap?((gap+(orient*mod))%mod):(orient*mod);}
+if(this.month){this.unit="month";gap=(this.month-today.getMonth());mod=12;this.months=gap?((gap+(orient*mod))%mod):(orient*mod);this.month=null;}
+if(!this.unit){this.unit="day";}
+if(this[this.unit+"s"]==null||
this.operator!=null){if(!this.value){this.value=1;}
+if(this.unit=="week"){this.unit="day";this.value=this.value*7;}
+this[this.unit+"s"]=this.value*orient;}
+return
today.add(this);}else{if(this.meridian&&this.hour){this.hour=(this.hour<13&&this.meridian=="p")?this.hour+12:this.hour;}
+if(this.weekday&&!this.day){this.day=(today.addDays((Date.getDayNumberFromName(this.weekday)-today.getDay()))).getDate();}
+if(this.month&&!this.day){this.day=1;}
+return today.set(this);}}};var
_=Date.Parsing.Operators,g=Date.Grammar,t=Date.Translator,_fn;g.datePartDelimiter=_.rtoken(/^([\s\-\.\,\/\x27]+)/);g.timePartDelimiter=_.stoken(":");g.whiteSpace=_.rtoken(/^\s*/);g.generalDelimiter=_.rtoken(/^(([\s\,]|
at|on)+)/);var _C={};g.ctoken=function(keys){var fn=_C[keys];if(!fn){var
c=Date.CultureInfo.regexPatterns;var kx=keys.split(/\s+/),px=[];for(var
i=0;i<kx.length;i++){px.push(_.replace(_.rtoken(c[kx[i]]),kx[i]));}
+fn=_C[keys]=_.any.apply(null,px);}
+return fn;};g.ctoken2=function(key){return
_.rtoken(Date.CultureInfo.regexPatterns[key]);};g.h=_.cache(_.process(_.rtoken(/^(0[0-9]|
1[0-2]|[1-9])/),t.hour));g.hh=_.cache(_.process(_.rtoken(/^(0[0-9]|
1[0-2])/),t.hour));g.H=_.cache(_.process(_.rtoken(/^([0-1][0-9]|2[0-3]|
[0-9])/),t.hour));g.HH=_.cache(_.process(_.rtoken(/^([0-1][0-9]|
2[0-3])/),t.hour));g.m=_.cache(_.process(_.rtoken(/^([0-5][0-9]|
[0-9])/),t.minute));g.mm=_.cache(_.process(_.rtoken(/^[0-5][0-9]/),t.minute));g.s=_.cache(_.process(_.rtoken(/^([0-5][0-9]|
[0-9])/),t.second));g.ss=_.cache(_.process(_.rtoken(/^[0-5][0-9]/),t.second));g.hms=_.cache(_.sequence([g.H,g.mm,g.ss],g.timePartDelimiter));g.t=_.cache(_.process(g.ctoken2("shortMeridian"),t.meridian));g.tt=_.cache(_.process(g.ctoken2("longMeridian"),t.meridian));g.z=_.cache(_.process(_.rtoken(/^(\+|
\-)?\s*\d\d\d\d?/),t.timezone));g.zz=_.cache(_.process(_.rtoken(/^(\+|
\-)\s*\d\d\d\d/),t.timezone));g.zzz=_.cache(_.process(g.ctoken2("timezone"),t.timezone));g.timeSuffix=_.each(_.ignore(g.whiteSpace),_.set([g.tt,g.zzz]));g.time=_.each(_.optional(_.ignore(_.stoken("T"))),g.hms,g.timeSuffix);g.d=_.cache(_.process(_.each(_.rtoken(/^([0-2]\d|
3[0-1]|
\d)/),_.optional(g.ctoken2("ordinalSuffix"))),t.day));g.dd=_.cache(_.process(_.each(_.rtoken(/^([0-2]\d|
3[0-1])/),_.optional(g.ctoken2("ordinalSuffix"))),t.day));g.ddd=g.dddd=_.cache(_.process(g.ctoken("sun
mon tue wed thu fri sat"),function(s){return
function(){this.weekday=s;};}));g.M=_.cache(_.process(_.rtoken(/^(1[0-2]|
0\d|\d)/),t.month));g.MM=_.cache(_.process(_.rtoken(/^(1[0-2]|
0\d)/),t.month));g.MMM=g.MMMM=_.cache(_.process(g.ctoken("jan feb mar apr
may jun jul aug sep oct nov
dec"),t.month));g.y=_.cache(_.process(_.rtoken(/^(\d\d?)/),t.year));g.yy=_.cache(_.process(_.rtoken(/^(\d\d)/),t.year));g.yyy=_.cache(_.process(_.rtoken(/^(\d\d?\d?\d?)/),t.year));g.yyyy=_.cache(_.process(_.rtoken(/^(\d\d\d\d)/),t.year));_fn=function(){return
_.each(_.any.apply(null,arguments),_.not(g.ctoken2("timeContext")));};g.day=_fn(g.d,g.dd);g.month=_fn(g.M,g.MMM);g.year=_fn(g.yyyy,g.yy);g.orientation=_.process(g.ctoken("past
future"),function(s){return
function(){this.orient=s;};});g.operator=_.process(g.ctoken("add
subtract"),function(s){return
function(){this.operator=s;};});g.rday=_.process(g.ctoken("yesterday
tomorrow today now"),t.rday);g.unit=_.process(g.ctoken("minute hour day
week month year"),function(s){return
function(){this.unit=s;};});g.value=_.process(_.rtoken(/^\d\d?(st|nd|rd|
th)?/),function(s){return
function(){this.value=s.replace(/\D/g,"");};});g.expression=_.set([g.rday,g.operator,g.value,g.unit,g.orientation,g.ddd,g.MMM]);_fn=function(){return
_.set(arguments,g.datePartDelimiter);};g.mdy=_fn(g.ddd,g.month,g.day,g.year);g.ymd=_fn(g.ddd,g.year,g.month,g.day);g.dmy=_fn(g.ddd,g.day,g.month,g.year);g.date=function(s){return((g[Date.CultureInfo.dateElementOrder]|
|
g.mdy).call(this,s));};g.format=_.process(_.many(_.any(_.process(_.rtoken(/^(dd?d?d?|
MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|
zz?z?)/),function(fmt){if(g[fmt]){return g[fmt];}else{throw
Date.Parsing.Exception(fmt);}}),_.process(_.rtoken(/^[^dMyhHmstz]+/),function(s){return
_.ignore(_.stoken(s));}))),function(rules){return
_.process(_.each.apply(null,rules),t.finishExact);});var _F={};var
_get=function(f){return _F[f]=(_F[f]||
g.format(f)[0]);};g.formats=function(fx){if(fx instanceof Array){var
rx=[];for(var i=0;i<fx.length;i++){rx.push(_get(fx[i]));}
+return _.any.apply(null,rx);}else{return
_get(fx);}};g._formats=g.formats(["yyyy-MM-ddTHH:mm:ss","ddd, MMM dd, yyyy
H:mm:ss tt","ddd MMM d yyyy HH:mm:ss
zzz","d"]);g._start=_.process(_.set([g.date,g.time,g.expression],g.generalDelimiter,g.whiteSpace),t.finish);g.start=function(s){try{var
r=g._formats.call({},s);if(r[1].length===0){return r;}}catch(e){}
+return
g._start.call({},s);};}());Date._parse=Date.parse;Date.parse=function(s){var
r=null;if(!s){return null;}
+try{r=Date.Grammar.start.call({},s);}catch(e){return null;}
+return((r[1].length===0)?r[0]:null);};Date.getParseFunction=function(fx){var
fn=Date.Grammar.formats(fx);return
function(s){var
r=null;try{r=fn.call({},s);}catch(e){return null;}
+return((r[1].length===0)?r[0]:null);};};Date.parseExact=function(s,fx){return
Date.getParseFunction(fx)(s);};
=======================================
--- /marginalia-lib/trunk/marginalia/3rd-party/cssQuery-standard.js Fri Jul
20 15:26:07 2007
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- cssQuery, version 2.0.2 (2005-08-19)
- Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
- License: http://creativecommons.org/licenses/LGPL/2.1/
-*/
-
-cssQuery.addModule("css-standard", function() { // override IE optimisation
-
-// cssQuery was originally written as the CSS engine for IE7. It is
-// optimised (in terms of size not speed) for IE so this module is
-// provided separately to provide cross-browser support.
-
-// -----------------------------------------------------------------------
-// browser compatibility
-// -----------------------------------------------------------------------
-
-// sniff for Win32 Explorer
-isMSIE = eval("false;/*@cc_on@if(@\x5fwin32)isMSIE=true@end@*/");
-
-if (!isMSIE) {
- getElementsByTagName = function($element, $tagName, $namespace) {
- return $namespace ? $element.getElementsByTagNameNS("*", $tagName) :
- $element.getElementsByTagName($tagName);
- };
-
- compareNamespace = function($element, $namespace) {
- return !$namespace || ($namespace == "*") || ($element.prefix ==
$namespace);
- };
-
- isXML = document.contentType ? function($element) {
- return /xml/i.test(getDocument($element).contentType);
- } : function($element) {
- return getDocument($element).documentElement.tagName != "HTML";
- };
-
- getTextContent = function($element) {
- // mozilla || opera || other
- return $element.textContent || $element.innerText ||
_getTextContent($element);
- };
-
- function _getTextContent($element) {
- var $textContent = "", $node, i;
- for (i = 0; ($node = $element.childNodes[i]); i++) {
- switch ($node.nodeType) {
- case 11: // document fragment
- case 1: $textContent += _getTextContent($node); break;
- case 3: $textContent += $node.nodeValue; break;
- }
- }
- return $textContent;
- };
-}
-}); // addModule
=======================================
--- /marginalia-lib/trunk/marginalia/log-service.js Mon Jun 4 22:31:49 2007
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * log-service.js
- *
- * Marginalia has been developed with funding and support from
- * BC Campus, Simon Fraser University, and the Government of
- * Canada, and units and individuals within those organizations.
- * Many thanks to all of them. See CREDITS.html for details.
- * Copyright (C) 2005-2007 Geoffrey Glass www.geof.net
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.
- *
- * $Id$
- */
-
-function LogService( serviceUrl, user )
-{
- this.serviceUrl = serviceUrl;
- this.user = user;
- return this;
-}
-
-/**
- * Create an annotation on the server
- * When successful, calls a function f with one parameter: the URL of the
created annotation
- */
-LogService.prototype.record = function( topic, severity, message )
-{
- var serviceUrl = this.serviceUrl;
-
- var body
- = "url=" + encodeURIParameter( window.location )
- + "&topic=" + encodeURIParameter( topic )
- + "&severity=" + encodeURIParameter( severity )
- + "&message=" + encodeURIParameter( message );
- var xmlhttp = createAjaxRequest( );
-
- xmlhttp.open( 'POST', serviceUrl, true );
-
xmlhttp.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded;
charset=UTF-8' );
- xmlhttp.setRequestHeader( 'Content-length', body.length );
- // No point checking whether it succeeded - where would that be reported
to?
- xmlhttp.send( body );
-}
-
=======================================
--- /marginalia-lib/trunk/marginalia/log.css Mon Jun 4 22:31:49 2007
+++ /dev/null
@@ -1,11 +0,0 @@
-/*
- * $Id$
- */
-
-#log li
-{
- margin: 1em 0;
- padding: 0;
- list-style-type: none;
- color: red;
-}
=======================================
--- /marginalia-lib/trunk/marginalia/marginalia-direct.css Mon Jun 4
22:31:49 2007
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * $Id$
- */
-
-#marginalia-direct {
- position: absolute;
- width: 90%;
- top: 5%;
- left: 5%;
- background: #eee;
- border: #666 6px outset;
- padding: 1ex;
-}
-
-#marginalia-direct h1 {
- font-family: sans-serif;
- font-weight: bold;
- font-size: large;
- background: white;
- padding: .25ex;
- margin: 1% 1% 1.5em 1%;
- width: 98%;
- text-align: center;
-}
-
-#marginalia-direct fieldset {
- clear: both;
- display: block;
- margin: 1em 0;
- position: relative;
-}
-
-#marginalia-direct .field {
- position: relative;
- height: 1.5em;
- margin: 1ex 0;
-}
-
-#marginalia-direct label {
- width: 15%;
- position: absolute;
- left: 0;
- bottom: 3px;
-}
-
-#marginalia-direct input {
- width: 81%;
- margin-left: 15%;
-}
-
-#marginalia-direct button {
- margin: 1em 1ex;
-}
-
-#marginalia-direct ul {
- margin: 0;
- padding: 0;
-}
-
-#marginalia-direct ul li {
- clear: both;
- list-style-type: none;
- border: #bbb 1px solid;
- margin: 2em 1ex;
- padding: 0 1ex;
- display: block;
- position: relative;
-}
=======================================
--- /marginalia-lib/trunk/marginalia/marginalia-direct.js Sat Nov 29
22:50:46 2008
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * marginalia-direct.js
- *
- * Call directly in to Marginalia for debugging purposes.
- * Activate MarginaliaDirect with Shift-Ctrl-S
- *
- * Marginalia has been developed with funding and support from
- * BC Campus, Simon Fraser University, and the Government of
- * Canada, the UNDESA Africa i-Parliaments Action Plan, and
- * units and individuals within those organizations. Many
- * thanks to all of them. See CREDITS.html for details.
- * Copyright (C) 2005-2007 Geoffrey Glass; the United Nations
- * http://www.geof.net/code/annotation
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.
- *
- * $Id$
- */
-
-function MarginaliaDirect( marginaliaService )
-{
- this.marginaliaService = marginaliaService;
- window.marginaliaDirect = this;
- return this;
-}
-
-MarginaliaDirect.prototype.init = function( )
-{
- addEvent( window, 'keyup', _marginaliaDirectKeypressHandler );
-}
-
-function _marginaliaDirectKeypressHandler( event )
-{
- var direct = window.marginaliaDirect;
- var character = String.fromCharCode( event.which );
- if ( ( 'm' == character || 'M' == character ) && event.shiftKey &&
event.ctrlKey )
- {
- var box = document.getElementById( 'marginalia-direct' );
- if ( null != box )
- direct.hide( );
- else
- direct.show( );
- }
-}
-
-MarginaliaDirect.prototype.show = function( )
-{
- var direct = this;
-
- document.body.appendChild( domutil.element( 'div', {
- id: 'marginalia-direct'
- }, [
- domutil.element( 'h1', null, 'Marginalia Direct Console' ),
- domutil.element( 'fieldset', null, [
- domutil.element( 'legend', null, 'Find Annotations' ),
- this.newInputField( 'md-annotation-user', null, 'User',
window.marginalia.displayUserId, true ),
- this.newInputField( 'md-annotation-url', null, 'URL', window.location,
true ),
- domutil.element( 'button', {
- id: 'md-find',
- onclick: function() { direct.listAnnotations( ) }
- }, 'Find' )
- ] ),
- domutil.element( 'div', {
- id: 'md-annotation-list' } ),
- domutil.element( 'button', {
- id: 'md-close',
- onclick: function() { direct.hide() }
- }, 'Close' )
- ]
- ) );
-}
-
-MarginaliaDirect.prototype.listAnnotations = function( )
-{
- var user = document.getElementById( 'md-annotation-user' );
- var url = document.getElementById( 'md-annotation-url' );
-
- var direct = this;
-
- // Clear out any existing list items
- var annotationList = document.getElementById( 'md-annotation-list' );
- while ( annotationList.firstChild )
- annotationList.removeChild( annotationList.firstChild );
-
- this.marginaliaService.listAnnotations( url.value, user.value, null,
function( xml ) { direct.showAnnotations( xml ); } );
-}
-
-MarginaliaDirect.prototype.deleteAnnotation = function( annotation )
-{
- var direct = this;
- this.marginaliaService.deleteAnnotation( annotation.id, function() {
direct.annotationDeleted( annotation.id ); } );
-}
-
-MarginaliaDirect.prototype.annotationDeleted = function( id )
-{
- var annotationList = document.getElementById( 'md-annotation-list' );
- for ( var item = annotationList.firstChild; item; item =
item.nextSibling )
- {
- if ( item.annotation && item.annotation.getId() == id )
- {
- item.annotation = null;
- annotationList.removeChild( item );
- }
- }
-}
-
-MarginaliaDirect.prototype.updateAnnotation = function( listItem )
-{
- var direct = this;
- var annotation = listItem.annotation;
- annotation.setUrl( this.getFieldInput( listItem, 'md-annotation-url'
).value );
- annotation.setSequenceRange( SequenceRange.fromString(
this.getFieldInput( listItem, 'md-annotation-sequence-range' ).value ) );
- annotation.setXPathRange( XPathRange.fromString( this.getFieldInput(
listItem, 'md-annotation-xpath-range' ).value ) );
- annotation.setQuote( this.getFieldInput( listItem, 'md-annotation-quote'
).value );
- annotation.setNote( this.getFieldInput( listItem, 'md-annotation-note'
).value );
- annotation.setLink( this.getFieldInput( listItem, 'md-annotation-link'
).value );
- this.marginaliaService.updateAnnotation( annotation, null );
-}
-
-MarginaliaDirect.prototype.getFieldInput = function( listItem, fieldName )
-{
- var field = domutil.childByTagClass( listItem, null, fieldName );
- field = domutil.childByTagClass( field, 'input', null );
- return field;
-}
-
-
-MarginaliaDirect.prototype.showAnnotations = function( xml )
-{
- var annotations = parseAnnotationXml( xml );
- for ( var i = 0; i < annotations.length; ++i )
- this.showAnnotation( annotations[ i ] );
- return 0;
-}
-
-MarginaliaDirect.prototype.showAnnotation = function( annotation )
-{
- var direct = this;
- var annotationList = document.getElementById( 'md-annotation-list' );
-
- var xpathRange = annotation.getXPathRange( );
- var sequenceRange = annotation.getSequenceRange( );
-
- var listItem = domutil.element( 'fieldset', {
- annotation: annotation
- }, domutil.element( 'legend', null, '#' + annotation.getId() + ' by ' +
annotation.getUserId() ) );
- annotationList.appendChild( listItem );
-
- domutil.addContent( listItem, null, [
- // URL, Range, Access
- this.newInputField( null, 'md-annotation-url', 'URL',
annotation.getUrl(), true ),
- this.newInputField( null, 'md-annotation-sequence-range', 'Sequence
Range', sequenceRange.toString(), true ),
- this.newInputField( null, 'md-annotation-xpath-range', 'XPath Range',
- xpathRange ? xpathRange.toString() : '', true ),
- this.newInputField( null, 'md-annotation-access', 'Access',
annotation.getAccess(), true ),
-
- // Quote, Note, Link
- this.newInputField( null, 'md-annotation-quote', 'Quote',
annotation.getQuote(), true ),
- this.newInputField( null, 'md-annotation-note', 'Note',
annotation.getNote(), true ),
- this.newInputField( null, 'md-annotation-link', 'Link',
annotation.getLink(), true ),
-
- // Last updated
- domutil.element( 'p', {
- className: 'updated'
- }, 'Last updated ' + annotation.updated ),
-
- domutil.element( 'button', {
- id: 'md-annotation-update',
- onclick: function() { direct.updateAnnotation( listItem ); }
- }, 'Update' ),
- domutil.element( 'button', {
- id: 'md-annotation-delete',
- onclick: function() { direct.deleteAnnotation( annotation ); }
- }, 'Delete' )
- ] );
-}
-
-
-
-MarginaliaDirect.prototype.newInputField = function( id, className, text,
value, enabled )
-{
- var input = domutil.element( 'input', {
- id: id,
- value: value } );
- if ( ! enabled )
- input.setAttribute( 'disabled', 'disabled' );
-
- return domutil.element ( 'div', {
- className: 'field ' + className
- }, [
- domutil.element( 'label', {
- id: id + 'label',
- 'for': id
- }, text ),
- input
- ]
- );
-}
-
-MarginaliaDirect.prototype.hide = function( )
-{
- var box = document.getElementById( 'marginalia-direct' );
- box.parentNode.removeChild( box );
-}
-
=======================================
--- /marginalia-lib/trunk/marginalia/smartcopy.js Wed Nov 26 15:31:50 2008
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * smartcopy.js
- *
- * Marginalia has been developed with funding and support from
- * BC Campus, Simon Fraser University, and the Government of
- * Canada, the UNDESA Africa i-Parliaments Action Plan, and
- * units and individuals within those organizations. Many
- * thanks to all of them. See CREDITS.html for details.
- * Copyright (C) 2005-2007 Geoffrey Glass; the United Nations
- * http://www.geof.net/code/annotation
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.
- *
- * $Id$
- */
-
-// Initialize smartcopy. Installs the keyhandler to switch the feature on
and off.
-// Return false if the browser doesn't support the feature.
-function smartcopyInit( preferences )
-{
- window.smartcopy = new Smartcopy( preferences );
- // No point supporting IE here
- if ( document.addEventListener )
- {
- document.addEventListener ('keyup', _smartcopyKeypressHandler, true );
- }
-}
-
-function Smartcopy( preferences )
-{
- this.preferences = preferences;
- this.active = false;
-}
-
-Smartcopy.prototype.alertSmartcopyStatus = function( )
-{
- var msg;
- var smartcopyStatus = document.getElementById( 'smartcopy-status' );
-
- if ( null == smartcopyStatus )
- {
- // Add box for showing the status of smartcopy
- // Don't do this in init because the page hasn't yet loaded at that point
- smartcopyStatus = document.createElement( 'div' );
- smartcopyStatus.id = 'smartcopy-status';
-
- var bodyElement = document.getElementsByTagName( 'body' )[ 0 ];
- bodyElement.appendChild( smartcopyStatus );
- }
-
- msg = getLocalized( this.active ? 'smartcopy on' : 'smartcopy off' );
- window.status = msg;
-
- while ( smartcopyStatus.firstChild )
- smartcopyStatus.removeChild( smartcopyStatus.firstChild );
- smartcopyStatus.appendChild( document.createTextNode( msg ) );
- smartcopyStatus.style.display = 'block';
- smartcopyStatus.style.opacity = 1;
-
- var smartcopy = this;
- setTimeout( fadeSmartcopyStatus, 3000 );
-}
-
-fadeSmartcopyStatus = function( )
-{
- var smartcopyStatus = document.getElementById( 'smartcopy-status' );
- var opacity = smartcopyStatus.style.opacity - .1;
- smartcopyStatus.style.opacity = opacity;
- if ( opacity > 0 )
- setTimeout( fadeSmartcopyStatus, 100 );
- else
- smartcopyStatus.style.display = 'none';
-}
-
-function _smartcopyKeypressHandler( event )
-{
- var character = String.fromCharCode( event.which );
- if ( ( 's' == character || 'S' == character ) && event.shiftKey &&
event.ctrlKey )
- {
- var smartcopy = window.smartcopy;
- if ( smartcopy.active )
- {
- smartcopy.smartcopyOff( );
- smartcopy.preferences.setPreference( 'smartcopy', 'false' );
- }
- else
- {
- smartcopy.smartcopyOn( );
- smartcopy.preferences.setPreference( 'smartcopy', 'true' );
- }
- smartcopy.alertSmartcopyStatus();
- }
-}
-
-// Switch on smartcopy. Returns false if the browser doesn't support the
feature.
-Smartcopy.prototype.smartcopyOn = function( )
-{
- if ( document.addEventListener )
- {
- document.addEventListener( 'mouseup', addSmartcopy, false );
- document.addEventListener( 'mousedown', _smartcopyDownHandler, false );
- this.active = true;
- window.status = 'Smartcopy is on. Press Shift-Ctrl-S to switch it off.';
- return true;
- }
- else
- return false;
-}
-
-// Switch off smartcopy
-Smartcopy.prototype.smartcopyOff = function( )
-{
- if ( document.removeEventListener )
- {
- document.removeEventListener( 'mouseup', addSmartcopy, false );
- document.removeEventListener( 'mousedown', _smartcopyDownHandler, false
);
- this.active = false;
- window.status = 'Smartcopy is off. Press Shift-Ctrl-S to switch it on.';
- }
-}
-
-// Smartcopy function called when the mouse button goes down
-function _smartcopyDownHandler( )
-{
- domutil.stripSubtree( document.documentElement, null, 'smart-copy' );
-}
-
-function addSmartcopy( )
-{
- var smartcopy = window.smartcopy;
-
- // this won't work with IE
- var selection = window.getSelection();
- var t = selection.type;
-
- // Verify W3C range support
- if ( selection.rangeCount == null )
- return false;
-
- // Check that some text has been selected
- if ( selection.rangeCount == 0 )
- return false;
- var range = selection.getRangeAt( 0 );
-
- // Check that the selection is within a post
- var postElement = domutil.parentByTagClass( range.startContainer, null,
PM_POST_CLASS, true, PostMicro.skipPostContent );
- if ( null == postElement )
- return false;
- var postInfo = PostPageInfo.getPostPageInfo( document );
- var post = postInfo.getPostMicro( postElement );
- var contentElement = post.getContentElement( );
-
- // Check that both the start and end of the selection are within the post
content
- if ( ! domutil.isElementDescendant( range.startContainer, contentElement
) ||
- ! domutil.isElementDescendant( range.endContainer, contentElement ) )
- return false;
-
- // Check that there is actually some selected text
- var t = range.toString( );
- if ( 0 == t.length )
- return false;
-
- // OK, now we have a valid selection range to work with.
- var normRange = new NormalizedRange( range, contentElement );
- var oldStart = range.startContainer;
- var oldOffset = range.startOffset;
- var rangeLen = normRange.length;
- // trace( null, "Initial (" + range.startContainer + "," +
range.startOffset + ")" );
-
- // Insert the link node
- var span = document.createElement( 'span' );
- span.className = 'smart-copy';
- span.appendChild( document.createTextNode( 'From ') );
- var a = document.createElement( 'a' );
- a.setAttribute( 'href', post.getUrl( ) );
- a.appendChild( document.createTextNode( post.getTitle( ) ) );
- span.appendChild( a );
- if ( null == post.getDate( ) )
- span.appendChild( document.createTextNode( ' by ' + post.getAuthor( )
+ ': ') );
- else
- span.appendChild( document.createTextNode( ' by ' + post.getAuthor( )
+ ' on ' + post.getDate( ).toLocaleString() + ': ') );
- span.appendChild( document.createElement( 'br' ) );
- range.insertNode( span );
-
- // Find where the end of the range would be now
- var walker = new DOMWalker( span );
- walker.endTag = true;
- // Walk rangeLen characters forward
- var remain = rangeLen;
- while ( walker.walk( true ) )
- {
- if ( TEXT_NODE == walker.node.nodeType )
- {
- if ( remain < walker.node.length )
- break;
- else
- remain -= walker.node.length;
- }
- }
-
- if ( selection.removeAllRanges && selection.addRange )
- {
- selection.removeAllRanges( );
- range.setStart( oldStart, oldOffset );
- range.setEnd( walker.node, remain );
- selection.addRange( range );
- }
- else
- {
- range.setStart( oldStart, oldOffset );
- range.setEnd( walker.node, remain );
- }
-}
-
-/*
- * Passed to range functions so they will ignore smartcopy nodes
- */
-function _skipSmartcopy( node )
-{
- if ( ELEMENT_NODE == node.nodeType )
- return domutil.hasClass( node, 'smart-copy' ) ? true : false;
- return false;
-}
=======================================
--- /marginalia-lib/trunk/marginalia/3rd-party/jquery.dates.js Wed Jan 27
15:57:06 2010
+++ /marginalia-lib/trunk/marginalia/3rd-party/jquery.dates.js Wed May 30
14:51:38 2012
@@ -976,7 +976,7 @@

//apply the event handlers
this.calendarEvents(data);
- },
+ }

};

=======================================
--- /marginalia-lib/trunk/marginalia/RangeInfo.js Mon Dec 22 17:57:53 2008
+++ /marginalia-lib/trunk/marginalia/RangeInfo.js Wed May 30 14:51:38 2012
@@ -11,7 +11,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
+ * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
=======================================
--- /marginalia-lib/trunk/marginalia/SequenceRange.js Thu Nov 13 13:32:55
2008
+++ /marginalia-lib/trunk/marginalia/SequenceRange.js Wed May 30 14:51:38
2012
@@ -13,7 +13,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
+ * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
=======================================
--- /marginalia-lib/trunk/marginalia/XPathRange.js Wed May 30 14:19:12 2012
+++ /marginalia-lib/trunk/marginalia/XPathRange.js Wed May 30 14:51:38 2012
@@ -13,7 +13,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
+ * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -51,6 +51,8 @@

XPathRange.fromString = function( path )
{
+ if ( ! path )
+ return null;
var parts = path.split( ';' );
if ( null == parts || 2 != parts.length )
throw "XPathRange parse error";
@@ -218,16 +220,11 @@
var rel; // will be the result
var xpath = this.path;
var myroot = root;
-
+
var startTime = new Date( );
trace( 'xpath-range', 'XPathPoint.getReferenceElement for path ' + xpath
);

- // Screen out document(), as it is a security risk
- // I would prefer to use a whitelist, but full processing of the xpath
- // expression is expensive and complex. I'm doing some of this on the
- // server, so unless someone can hijack the returned xpath expressions
- // this should never happen anyway.
- if ( xpath.match( /[^a-zA-Z_]document\s*\(/ ) )
+ if ( ! this.isXPathSafe( xpath ) )
return null;
else if ( xpath == '' )
return root;
@@ -251,7 +248,7 @@
// of this behavior.
try
{
- result = root.ownerDocument.evaluate( xpath, root,
domutil.nsPrefixResolver, XPathResult.ANY_TYPE, null );
+ result = root.ownerDocument.evaluate( xpath, root, null
/*domutil.nsPrefixResolver*/, XPathResult.ANY_TYPE, null );
rel = result.iterateNext( );
}
catch( e )
@@ -282,4 +279,64 @@
return rel;
}

-
+// This is not a generic xpath checker. It whitelists just enough to
handle
+// the auto-generated paths used by XPathPoint
+XPathPoint.prototype.isXPathSafe = function( xpath )
+{
+ // Path could be blank
+ if ( '' == xpath )
+ return true;
+ // Path may start with .//
+ if ( './/' == xpath.substr(0,3) )
+ xpath = xpath.substr(3);
+ var parts = xpath.split( '/' );
+ for ( var i = 0; i < parts.length; ++i )
+ {
+ var part = parts[ i ];
+ // should perhaps trim it, but won't bother
+ var matches = part.match( '^[a-zA-Z0-9_:\*-]+\s*(.*)$' );
+ if ( matches )
+ {
+ var tail = matches[ 1 ];
+ // Simple tag name witho or without axis or namespace
+ if ( '' == tail )
+ ;
+ // Qualification in [brackets]
+ else if ( matches = tail.match( /^\[([^\]]+)\]\s*$/ ) )
+ {
+ var test = matches[ 1 ];
+ // Simple number index
+ if ( test.match( /^\d+$/ ) )
+ ;
+ // Comparison of an attribute with a quoted value
+ else if ( matches = test.match(
/^@[a-zA-Z0-9_-]+\s*=\s*([\'"])[a-zA-Z0-9:._-]+([\'"])$/ ) )
+ {
+ if ( matches[ 1 ] == matches[ 1 ] )
+ ;
+ else
+ {
+ trace( 'isXPathSafe', 'XPath test failed: @attribute="value"' );
+ return false;
+ }
+ }
+ else
+ {
+ trace( 'isXPathSafe', 'XPath test failed: unknown subscript' );
+ return false;
+ }
+ }
+ else
+ {
+ trace ('isXPathSafe', 'XPath test failed: improper text after tag
name: ' + tail );
+ return false;
+ }
+ }
+ else
+ {
+ trace( 'isXPathSafe', 'XPath test failed: bad tag name' );
+ return false;
+ }
+ }
+ return true;
+}
+
=======================================
--- /marginalia-lib/trunk/marginalia/annotation.js Wed May 30 14:19:12 2012
+++ /marginalia-lib/trunk/marginalia/annotation.js Wed May 30 14:51:38 2012
@@ -11,7 +11,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
+ * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -31,16 +31,6 @@
NS_ATOM = 'http://www.w3.org/2005/Atom';
NS_XHTML = 'http://www.w3.org/1999/xhtml';

-// values for annotation.access
-AN_PUBLIC_ACCESS = 'public';
-AN_PRIVATE_ACCESS = 'private';
-
-// values for annotation.editing (field is deleted when not editing)
-AN_EDIT_NOTE_FREEFORM = 'note freeform';
-AN_EDIT_NOTE_KEYWORDS = 'note keywords';
-AN_EDIT_LINK = 'link';
-
-
/* ************************ Annotation Class ************************ */
/*
* This is a data-only class with (almost) no methods. This is because
all annotation
@@ -64,7 +54,7 @@
this.id = params.id || 0;
this.quote = params.quote || '';
this.note = params.note || '';
- this.access = params.access || ANNOTATION_ACCESS_DEFAULT;
+ this.sheet = params.sheet || '';
this.action = params.action || '';
this.quote = params.quote || '';
this.quoteAuthorId = params.quoteAuthorId || '';
@@ -73,12 +63,8 @@
// this.editing = null; -- deleted when not needed
this.link = params.link || '';

- // The fetch count is like a lock count. It reflects how many reasons
there were to
- // request the annotation from the server. This is used with per-block
annotation display
- // to ensure that if an annotation is fetched once for each of two
blocks, it won't be
- // removed (which would affect both blocks) unless it is hidden for both.
- this.fetchCount = 0;
this.updated = new Date( );
+ this.lastRead = null;
}

/**
@@ -217,15 +203,15 @@
}
}

-Annotation.prototype.getAccess = function( )
-{ return this.access ? this.access : ''; }
-
-Annotation.prototype.setAccess = function( access )
-{
- if ( this.access != access )
- {
- this.access = access;
- this.changes[ 'access' ] = true;
+Annotation.prototype.getSheet = function( )
+{ return this.sheet ? this.sheet : ''; }
+
+Annotation.prototype.setSheet = function( sheet )
+{
+ if ( this.sheet != sheet )
+ {
+ this.sheet = sheet;
+ this.changes[ 'sheet' ] = true;
}
}

@@ -301,6 +287,30 @@
}
}

+Annotation.prototype.getUpdated = function( )
+{ return this.updated; }
+
+Annotation.prototype.getLastRead = function( )
+{ return this.lastRead; }
+
+Annotation.prototype.setLastRead = function( lastRead )
+{
+ if ( this.lastRead != lastRead )
+ {
+ this.lastRead = lastRead;
+ this.changes[ 'lastRead' ] = true;
+ }
+}
+
+Annotation.prototype.isRecent = function( )
+{
+ var r;
+ if ( this.getLastRead( ) )
+ r = this.getUpdated( ).getTime( ) > this.getLastRead( ).getTime( );
+ else
+ r = true;
+ return r;
+}

Annotation.prototype.fieldsFromPost = function( post )
{
@@ -348,6 +358,7 @@
if ( null == range )
return null; // The range is probably invalid (e.g. whitespace only)
var annotation = new Annotation ( {
+ sheet: marginalia.defaultSheet,
url: post.getUrl( ),
sequenceRange: textRange.toSequenceRange( ),
xpathRange: textRange.toXPathRange( ),
@@ -384,18 +395,17 @@
*/
Annotation.prototype.defaultNoteEditMode = function( preferences,
keywordService )
{
- if ( ! keywordService )
- return AN_EDIT_NOTE_FREEFORM;
- else if ( '' == this.note )
- {
- var pref = preferences.getPreference( PREF_NOTEEDIT_MODE );
- return pref ? pref : AN_EDIT_NOTE_KEYWORDS;
- }
- else
- return keywordService.isKeyword( this.note )
- ? AN_EDIT_NOTE_KEYWORDS : AN_EDIT_NOTE_FREEFORM;
+ return Marginalia.EDIT_NOTE_FREEFORM;
}

+
+Annotation.idFromUrl = function( url )
+{
+ if ( matches = url.match( /\/(\w+)[^\/]*$/ ) )
+ return matches[ 1 ];
+ else if ( matches = url.match( /\/(\w+)\/[^\/]*$/ ) )
+ return matches[ 1 ];
+}

Annotation.prototype.fromAtom = function( entry )
{
@@ -403,57 +413,62 @@
var rangeStr = null;
for ( var field = entry.firstChild; field != null; field =
field.nextSibling )
{
- if ( field.namespaceURI == NS_ATOM && domutil.getLocalName( field )
== 'content' )
- {
- if ( 'xhtml' == field.getAttribute( 'type' ) )
- {
- // Find the enclosed div
- var child;
- for ( child = field.firstChild; child; child = child.nextSibling )
- if ( child.namespaceURI == NS_XHTML && child.nodeName == 'div' &&
domutil.hasClass( child, 'annotation' ) )
- break;
- if ( child )
- this.fromAtomContent( child );
- }
- }
- else if ( field.namespaceURI == NS_ATOM && domutil.getLocalName( field )
== 'link' )
- {
- var rel = field.getAttribute( 'rel' );
- var href = field.getAttribute( 'href' );
- // What is the role of this link element? (there are several links in
each entry)
- if ( 'self' == rel )
- this.id = href.substring( href.lastIndexOf( '/' ) + 1 );
- else if ( 'related' == rel )
- this.link = href;
- else if ( 'alternate' == rel )
- this.url = href;
- }
- else if ( NS_ATOM == field.namespaceURI && 'author' ==
domutil.getLocalName( field ) )
- {
- for ( var nameElement = field.firstChild; null != nameElement;
nameElement = nameElement.nextSibling )
- {
- if ( NS_ATOM == nameElement.namespaceURI && 'name' ==
domutil.getLocalName( nameElement ) )
- this.userName = nameElement.firstChild ?
nameElement.firstChild.nodeValue : null;
- else if ( NS_PTR == nameElement.namespaceURI && 'userid' ==
domutil.getLocalName( nameElement ) )
- this.userid = nameElement.firstChild ?
nameElement.firstChild.nodeValue : null;
- }
- }
- else if ( field.namespaceURI == NS_PTR && domutil.getLocalName( field )
== 'range' )
- {
- var format = field.getAttribute( 'format' );
- // These ranges may throw parse errors
- if ( 'sequence' == format )
- this.setSequenceRange( SequenceRange.fromString( domutil.getNodeText(
field ) ) );
- else if ( 'xpath' == format )
- this.setXPathRange( XPathRange.fromString( domutil.getNodeText( field
) ) );
- // ignore unknown formats
- }
- else if ( field.namespaceURI == NS_PTR && domutil.getLocalName( field )
== 'access' )
- this.access = null == field.firstChild ? 'private' :
domutil.getNodeText( field );
- else if ( field.namespaceURI == NS_PTR && domutil.getLocalName( field )
== 'action' )
- this.action = null == field.firstChild ? '' : domutil.getNodeText(
field );
- else if ( field.namespaceURI == NS_ATOM && domutil.getLocalName( field )
== 'updated' )
- this.updated = domutil.parseIsoDate( domutil.getNodeText( field ) );
+ if ( ELEMENT_NODE == field.nodeType )
+ {
+ if ( field.namespaceURI == NS_ATOM && domutil.getLocalName( field )
== 'content' )
+ {
+ if ( 'xhtml' == field.getAttribute( 'type' ) )
+ {
+ // Find the enclosed div
+ var child;
+ for ( child = field.firstChild; child; child = child.nextSibling )
+ if ( child.namespaceURI == NS_XHTML && child.nodeName == 'div' &&
domutil.hasClass( child, 'annotation' ) )
+ break;
+ if ( child )
+ this.fromAtomContent( child );
+ }
+ }
+ else if ( field.namespaceURI == NS_ATOM && domutil.getLocalName( field
) == 'link' )
+ {
+ var rel = field.getAttribute( 'rel' );
+ var href = field.getAttribute( 'href' );
+ // What is the role of this link element? (there are several links in
each entry)
+ if ( 'self' == rel )
+ this.id = Annotation.idFromUrl( href );
+ else if ( 'related' == rel )
+ this.link = href;
+ else if ( 'alternate' == rel )
+ this.url = href;
+ }
+ else if ( NS_ATOM == field.namespaceURI && 'author' ==
domutil.getLocalName( field ) )
+ {
+ for ( var nameElement = field.firstChild; null != nameElement;
nameElement = nameElement.nextSibling )
+ {
+ if ( NS_ATOM == nameElement.namespaceURI && 'name' ==
domutil.getLocalName( nameElement ) )
+ this.userName = nameElement.firstChild ?
nameElement.firstChild.nodeValue : null;
+ else if ( NS_PTR == nameElement.namespaceURI && 'userid' ==
domutil.getLocalName( nameElement ) )
+ this.userid = nameElement.firstChild ?
nameElement.firstChild.nodeValue : null;
+ }
+ }
+ else if ( field.namespaceURI == NS_PTR && domutil.getLocalName( field )
== 'range' )
+ {
+ var format = field.getAttribute( 'format' );
+ // These ranges may throw parse errors
+ if ( 'sequence' == format )
+ this.setSequenceRange( SequenceRange.fromString( domutil.getNodeText(
field ) ) );
+ else if ( 'xpath' == format )
+ this.setXPathRange( XPathRange.fromString( domutil.getNodeText( field
) ) );
+ // ignore unknown formats
+ }
+ else if ( field.namespaceURI == NS_PTR && domutil.getLocalName( field )
== 'sheet' )
+ this.sheet = null == field.firstChild ? 'private' :
domutil.getNodeText( field );
+ else if ( field.namespaceURI == NS_PTR && domutil.getLocalName( field )
== 'action' )
+ this.action = null == field.firstChild ? '' : domutil.getNodeText(
field );
+ else if ( field.namespaceURI == NS_ATOM && domutil.getLocalName( field
) == 'updated' )
+ this.updated = domutil.parseIsoDate( domutil.getNodeText( field ) );
+ else if ( field.namespaceURI == NS_PTR && domutil.getLocalName( field )
== 'lastread' )
+ this.lastRead = domutil.parseIsoDate( domutil.getNodeText( field ) );
+ }
}
// This is here because annotations are only parsed from XML when being
initialized.
// In future who knows, this might not be the case - and the reset would
have to
@@ -515,7 +530,7 @@

Annotation.prototype.atomContentText = function( parent, css )
{
- var node = cssQuery( css, parent );
+ var node = jQuery( css, parent );
if ( node && node.length > 0 )
{
var s = domutil.getNodeText( node[0] );
@@ -527,7 +542,7 @@

Annotation.prototype.atomContentAttrib = function( parent, css, attrib )
{
- var node = cssQuery( css, parent );
+ var node = jQuery( css, parent );
if ( node && node.length > 0 )
return node[0].getAttribute( attrib );
trace( null, 'CSS (' + css + ') did not resolve for "' + attrib + '" in '
+ parent.innerHtml );
=======================================
--- /marginalia-lib/trunk/marginalia/blockmarker-ui.js Wed May 30 14:19:12
2012
+++ /marginalia-lib/trunk/marginalia/blockmarker-ui.js Wed May 30 14:51:38
2012
@@ -11,7 +11,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
+ * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -26,10 +26,9 @@
* $Id$
*/

-AN_MARKERS_CLASS = 'markers'; // markers column (usually on the left)
-AN_MARKER_CLASS = 'marker'; // individual block marker
-AN_USERCOUNT_CLASS = 'annotation-user-count'; // contains user count in
block marker
-AN_ANNOTATIONSFETCHED_CLASS = 'fetched'; // indicates a block's
annotations have been fetched
+Marginalia.C_MARKER = Marginalia.PREFIX + 'marker'; // individual block
marker
+Marginalia.C_USERCOUNT = Marginalia.PREFIX + 'annotation-user-count'; //
contains user count in block marker
+Marginalia.C_ANNOTATIONSFETCHED = Marginalia.PREFIX + 'fetched'; //
indicates a block's annotations have been fetched


/**
@@ -54,7 +53,7 @@
for ( var j = 0; j < info.users.length; ++j )
{
var user = info.users[ j ];
- if ( user.noteCount > 0 && user.userid != marginalia.displayUserId )
+ if ( user.noteCount > 0 )
{
var post = marginalia.listPosts( ).getPostByUrl( info.url,
marginalia.baseUrl );
post.showPerBlockUserCount( marginalia, info );
@@ -99,14 +98,14 @@
{
var postMicro = this;
return function() {
- if ( domutil.hasClass( markerElement, AN_ANNOTATIONSFETCHED_CLASS ) )
- {
- domutil.removeClass( markerElement, AN_ANNOTATIONSFETCHED_CLASS );
+ if ( domutil.hasClass( markerElement, Marginalia.C_ANNOTATIONSFETCHED ) )
+ {
+ domutil.removeClass( markerElement, Marginalia.C_ANNOTATIONSFETCHED );
postMicro.hideBlockAnnotations( marginalia, pointStr );
}
else
{
- domutil.addClass( markerElement, AN_ANNOTATIONSFETCHED_CLASS );
+ domutil.addClass( markerElement, Marginalia.C_ANNOTATIONSFETCHED );
marginalia.showBlockAnnotations( url, pointStr );
}
};
@@ -130,8 +129,11 @@
var range = annotation.getSequenceRange( );
if ( range )
{
- if ( annotation.getUserId( ) != marginalia.displayUserId )
- {
+ // displayUserId replaced by displayAccess, too tired to figure
+ // out exactly what this test was for so commenting instead of deleting
+ // 20100221: access is obsolete anyway, now relpaced by sheet
+ //if ( annotation.getUserId( ) != marginalia.displayUserId )
+ //{
// if we've run past the last relevant annotation, don't bother with
the rest
if ( range.start.comparePath( point ) > 0 )
break;
@@ -149,7 +151,7 @@
repositionNote = nextNote;
}
}
- }
+ //}
}
}
if ( repositionNote )
@@ -158,7 +160,7 @@

PostMicro.prototype.showBlockMarker = function( marginalia, info, block,
point )
{
- var markers = domutil.childByTagClass( this.getElement( ), null,
AN_MARKERS_CLASS, marginalia.skipContent );
+ var markers = marginalia.selectors[ 'mia_markers' ].node(
this.getElement( ) );
if ( markers )
{
var countElement;
@@ -170,11 +172,11 @@
block.blockMarkerUsers = [ ];

markerElement = domutil.element( 'div', {
- className: AN_MARKER_CLASS,
+ className: Marginalia.C_MARKER,
blockElement: block
} );
countElement = domutil.element( 'span', {
- className: AN_USERCOUNT_CLASS,
+ className: Marginalia.C_USERCOUNT,
onclick: this.getBlockMarkerClickFcn( window.marginalia,
markerElement, info.url, point.toString() )
} );
markerElement.appendChild( countElement );
@@ -196,7 +198,7 @@
{
var user = info.users[ i ];
// Don't include the currently-displayed user
- if ( user.noteCount > 0 && user.userid != marginalia.displayUserId )
+ if ( user.noteCount > 0 ) //&& user.userid != marginalia.displayUserId )
block.blockMarkerUsers[ block.blockMarkerUsers.length ] = user;
}

@@ -252,10 +254,10 @@
*/
PostMicro.prototype.repositionBlockMarkers = function( marginalia )
{
- var markers = domutil.childByTagClass( this.getElement( ), null,
AN_MARKERS_CLASS, marginalia.skipContent );
+ var markers = marginalia.selectors[ 'mia_markers' ].nodes(
this.getElement( ) );
if ( markers )
{
- var markerElements = domutil.childrenByTagClass( this.getElement( ),
null, AN_MARKER_CLASS, null );
+ var markerElements = domutil.childrenByTagClass( this.getElement( ),
null, Marginalia.C_MARKER, null );
for ( var i = 0; i < markerElements.length; ++i )
this.positionBlockMarker( marginalia, markers, markerElements[ i ] );
}
=======================================
--- /marginalia-lib/trunk/marginalia/domutil.js Wed May 30 14:19:12 2012
+++ /marginalia-lib/trunk/marginalia/domutil.js Wed May 30 14:51:38 2012
@@ -11,7 +11,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
+ * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -41,7 +41,7 @@
isXhtml: function( doc )
{
if ( doc.contentType )
- return /xml/i.test( doc.contentType );
+ return /xml/i.test( doc.contentType );
else
return doc.documentElement.tagName != "HTML";
},
@@ -62,7 +62,7 @@

parseIsoDate: function( s )
{
- var matches = s.match( /(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})([+-]\d{4})/
);
+ var matches = s.match(
/(\d{4})-?(\d{2})-?(\d{2})T(\d{2}):?(\d{2}):?(\d{2})([+-]\d{2}:?\d{2})/ );
if ( null == matches )
return null;
else
@@ -73,6 +73,12 @@
}
},

+formatIsoDate: function( d )
+{
+ return d.getFullYear( ) + '-' + ( d.getMonth( ) + 1 ) + '-' + d.getDate(
) + 'T'
+ + d.getHours( ) + ':' + d.getMinutes( ) + ':' + d.getSeconds( );
+},
+
htmlEncode: function( s )
{
s = s.replace( /&/, '&amp;' );
@@ -226,6 +232,17 @@
return 'unknown';
},

+/**
+ * Given an HTML string, return only the contents of the <body> element
+ * Requires that <body> and </body> not occur anywhere else in the document
+ * (are rare occurrence, but it could happen - so this function should only
+ * be used for documents that you generate).
+ */
+extractBody: function( html )
+{
+ return html.replace( /<body>(.*)<\/body>/i, '$1' );
+},
+
/**
* Determine whether a given HTML element is block-level (as opposed to
inline,
* although there are some elements, such as script, that are neither)
@@ -374,14 +391,28 @@
},

/*
- * I considered cssQuery instead of getChildByTagClass etc., but it has
several weaknesses:
+ * I considered jQuery instead of getChildByTagClass etc., but it has
several weaknesses:
* - in many cases I need to exclude subtrees using fskip
* - CSS queries only look down the tree, not up or at siblings
* CSS (or XPath) could be used by filtering out elements that are
ancestors of a filtered
* node. But then why waste the time scanning what could be a long
document? (Especially when
- * Marginalia already has performance problems). Better to update
cssQuery.
+ * Marginalia already has performance problems).
*/

+
+/**
+ * Apply a CSS selector using an implementation from jQuery
+ * here because originally domutil was not dependent on jQuery
+ */
+select: function( selector, root, first )
+{
+ var results = jQuery( selector, root );
+ if ( first )
+ return results && results.length ? results[ 0 ] : null;
+ else
+ return results;
+},
+
/**
* Fetch the first child with a given class attribute value
*/
@@ -621,13 +652,27 @@
return null;
},

+// Should be a jQuery function, but right now I just want to make it work
+closestPrecedingFlat: function( rel, selector, orself )
+{
+ if ( orself && $( rel ).is( selector ) )
+ return rel;
+ else
+ {
+ return domutil.closestPrecedingMatchingElement( rel, function( node, tag
) {
+ return $( node ).is( selector ); } );
+ }
+},
+
/*
* Find the start of the closest preceding breaking element *in document
order*
* This is not the same as the closest preceding element at the same depth
as the passed element
* E.g., for <a> <b>...</b> </a> <rel/>, the closest preceding element for
rel is b - not a
*/
-closestPrecedingBreakingElement: function( rel )
-{
+closestPrecedingBreakingElement: function( rel, orself )
+{
+ if ( orself && ELEMENT_NODE == rel.nodeType && domutil.isBreakingElement(
rel.tagName ) )
+ return rel;
return domutil.closestPrecedingMatchingElement( rel, function( node,
isStartTag ) {
return isStartTag && domutil.isBreakingElement( node.tagName ); } );
},
@@ -635,8 +680,10 @@
/**
* Find the start of the closest preceding block-level element in document
order
*/
-closestPrecedingBlockElement: function( rel )
-{
+closestPrecedingBlockElement: function( rel, orself )
+{
+ if ( orself && ELEMENT_NODE == rel.nodeType && domutil.isBreakingElement(
rel.tagName ) )
+ return rel;
return domutil.closestPrecedingMatchingElement( rel, function( node,
isStartTag ) {
return isStartTag && domutil.isBlockElement( node.tagName ); } );
},
@@ -867,6 +914,64 @@
return domutil.element( 'button', spec );
},

+/**
+ * Given a plain text string, build text nodes and <a> nodes for any
contained URLs
+ * Appends constructed nodes to the passed node
+ */
+urlize: function( node )
+{
+ var child = node.firstChild;
+ while ( child )
+ {
+ if ( TEXT_NODE == child.nodeType || CDATA_SECTION_NODE == child.nodeType
)
+ {
+ var tail = child.nodeValue;
+ var next = child.nextSibling;
+
+ // Rebuild node content with text and links
+ while ( tail.length > 0 )
+ {
+ var match = tail.match( /https?:\/\/([a-zA-Z0-9\.-]+)(?:\/(?:[^
]*[a-zA-Z0-9\/#])?)?/ );
+ var url = null;
+ if ( match )
+ url = match[ 0 ];
+ else
+ {
+ match = tail.match( /(www\.[a-zA-Z0-9\.-]+\.[a-zA-Z]{2,4})/ );
+ if ( match )
+ url = 'http://' + match[ 1 ] + '/';
+ }
+ if ( url )
+ {
+ var head = tail.substr( 0, match.index );
+ if ( head.length )
+ node.appendChild( document.createTextNode( head ) );
+ domain = match[ 1 ];
+ //if ( 'www.' == domain.substr( 0, 4 ) )
+ // domain = domain.substr( 4 );
+ node.insertBefore( domutil.element( 'a', {
+ href: url,
+ title: url,
+ onclick: domutil.stopPropagation,
+ content: domain }), next);
+ tail = tail.substr( head.length + url.length );
+ }
+ else
+ {
+ node.insertBefore( document.createTextNode( tail ), next );
+ break;
+ }
+ }
+
+ // now remove the old text node
+ node.removeChild( child );
+ child = next;
+ }
+ else
+ child = child.nextSibling;
+ }
+},
+

/* ********** Element Position/Dimension Manipulation ********** */

@@ -926,13 +1031,24 @@
return document.body.scrollLeft;
},

-scrollWindowToNode: function( node )
+SCROLL_POS_TOP: 0,
+SCROLL_POS_CENTER: 1,
+SCROLL_POS_CENTER_NODE: 2,
+SCROLL_POS_UP_NODE: 3,
+scrollWindowToNode: function( node, position, params )
{
if ( null != node )
{
var xoffset = domutil.getWindowXScroll( );
var yoffset = domutil.getElementYOffset( node,
node.ownerDocument.documentElement );
- window.scrollTo( xoffset, yoffset );
+ if ( domutil.SCROLL_POS_CENTER == position )
+ yoffset -= document.documentElement.clientHeight / 2;
+ else if ( domutil.SCROLL_POS_CENTER_NODE == position )
+ yoffset -= document.documentElement.clientHeight / 2 - Math.ceil( $(
node ).innerHeight( ) / 2 );
+ else if ( domutil.SCROLL_POS_UP_NODE == position )
+ yoffset -= document.documentElement.clientHeight / 4;
+ $( 'body' ).scrollTo( { left:xoffset, top:yoffset}, params );
+// window.scrollTo( xoffset, yoffset );
}
},

@@ -942,10 +1058,13 @@
*/
createAjaxRequest: function( )
{
+ var request;
if ( window.XMLHttpRequest )
- return new XMLHttpRequest( ); // Gecko / XHTML / WebKit
+ request = new XMLHttpRequest( ); // Gecko / XHTML / WebKit
else
- return new ActiveXObject( "Microsoft.XMLHTTP" ); // MS IE
+ request = new ActiveXObject( "Microsoft.XMLHTTP" ); // MS IE
+// request.setRequestHeader( 'X-Requested-With', 'XMLHttpRequest' );
+ return request;
},

/*
@@ -1335,8 +1454,197 @@
return null != s.constructor.toString( ).match( /string/i );
else
return false;
-}
-};
+},
+
+// Thanks to Dan Allen (?) at mojavelinux for the hash implementation
+// http://www.mojavelinux.com/articles/javascript_hashes.html
+Hash: function( )
+{
+ this.length = 0;
+ this.items = new Array();
+ for (var i = 0; i < arguments.length; i += 2) {
+ if (typeof(arguments[i + 1]) != 'undefined') {
+ this.items[arguments[i]] = arguments[i + 1];
+ this.length++;
+ }
+ }
+
+ this.removeItem = function(in_key)
+ {
+ var tmp_previous;
+ if (typeof(this.items[in_key]) != 'undefined') {
+ this.length--;
+ var tmp_previous = this.items[in_key];
+ delete this.items[in_key];
+ }
+
+ return tmp_previous;
+ }
+
+ this.getItem = function(in_key) {
+ return this.items[in_key];
+ }
+
+ this.setItem = function(in_key, in_value)
+ {
+ var tmp_previous;
+ if (typeof(in_value) != 'undefined') {
+ if (typeof(this.items[in_key]) == 'undefined') {
+ this.length++;
+ }
+ else {
+ tmp_previous = this.items[in_key];
+ }
+
+ this.items[in_key] = in_value;
+ }
+
+ return tmp_previous;
+ }
+
+ this.hasItem = function(in_key)
+ {
+ return typeof(this.items[in_key]) != 'undefined';
+ }
+
+ this.clear = function()
+ {
+ for (var i in this.items) {
+ delete this.items[i];
+ }
+
+ this.length = 0;
+ }
+}
+}
+
+
+/**
+ * include: CSS selector or function determining which nodes to include
+ * exclude: CSS selector or function determining which nodes to exclude
+ * text: when extracting a value, take this attribute or node text or call
this function
+ */
+function Selector( include, exclude, text )
+{
+ this.include = include;
+ this.exclude = exclude;
+ this.text = text;
+}
+
+/**
+ * Used internally to determine whether a selector is a CSS selector string
+ * or a function, call it, and return the result
+ */
+Selector.prototype.select = function( sel, root )
+{
+ // If it's not a string, assume it's a function
+ return domutil.isString( sel ) ? domutil.select( sel, root ) : sel( root
);
+}
+
+/**
+ * Return all nodes matching the selector
+ */
+Selector.prototype.nodes = function( root )
+{
+ if ( this.exclude )
+ {
+ var result0 = this.select( this.include, root );
+ if ( ! result0 )
+ return [ ];
+ else
+ {
+ var subtract = this.select( this.exclude, root );
+ if ( subtract )
+ {
+ // This is god-awful inefficient. I'd like to use a hash,
+ // but don't think I can use an object as an array index.
+ // Though it isn't as horribly bad as it might be because though
+ // it is O(n*m), m is expected to be very small.
+ var result = [ ];
+ for ( var i = 0; i < result0.length; ++i )
+ {
+ var b = true;
+ for ( var j = 0; j < subtract.length; ++j )
+ {
+ if ( subtract[ j ] == result[ i ] )
+ {
+ b = false;
+ break;
+ }
+ }
+ if ( b )
+ result.push( result0[ i ] );
+ }
+ return result;
+ }
+ else
+ return result0;
+ }
+ }
+ else
+ return this.select( this.include, root );
+}
+
+/**
+ * Return the first node matching the selector
+ */
+Selector.prototype.node = function( root )
+{
+ var v = this.nodes( root );
+ return ( v && v.length ) ? v[ 0 ] : null;
+}
+
+/**
+ * Return the values of all nodes matching the selector
+ */
+Selector.prototype.values = function( root )
+{
+ var nodes = this.nodes( root );
+
+ if ( ! nodes || ! nodes.length )
+ return [ ];
+
+ var results = [ ];
+ var resolved = false;
+ if ( ! this.text || this.text == 'text()' ) // default, or if ( 'text()'
== this.text )
+ {
+ for ( var i = 0; i < nodes.length; ++i )
+ results[ i ] = domutil.getNodeText( nodes[ i ] );
+ }
+ else
+ {
+ if ( domutil.isString( this.text ) )
+ {
+ if ( '@' == this.text.charAt( 0 ) && nodes )
+ {
+ var attribute = this.text.substring( 1 );
+ for ( var i = 0; i < nodes.length; ++i )
+ results[ i ] = nodes[ i ].getAttribute( attribute );
+ resolved = true;
+ }
+ else
+ throw 'Bad selector text value ' + this.text;
+ }
+ else
+ {
+ for ( i = 0; i < nodes.length; ++i )
+ results[ i ] = this.text( nodes[ i ] );
+ resolved = true;
+ }
+ }
+
+ return results;
+}
+
+/**
+ * Return the value of the first node matching the selector
+ */
+Selector.prototype.value = function( root )
+{
+ var v = this.values( root );
+ return ( v && v.length ) ? v[ 0 ]: null;
+}
+

/**
* Walk through nodes in document order
@@ -1420,6 +1728,12 @@
* have read a given publication
* <name>_publication_n - content of publication #n
* <name>_publish_count - maximum publication # used so far
+ *
+ * An item on the bus consists of the following:
+ * name - an identifier
+ * text - the item value
+ * once - a control field indicating that only one subscriber should see
the item
+ * forward - a control field indicating that future subscribers should see
the item
*/
function CookieBus( cookieName )
{
@@ -1427,6 +1741,7 @@
this.subscribed = false;
this.interval = null;
this.readPubs = new Object( );
+ this.nextToRead = 0;
}

/**
@@ -1437,14 +1752,32 @@
var rawPub = domutil.readCookie( this.cookieName + '_publication_' + name
);
if ( rawPub )
{
+ var r = CookieBus.parseCookieValue( rawPub );
return {
name: name,
- value: rawPub
+ once: r[ 'once' ] == 'true' ? true : false,
+ forward: r[ 'forward' ] == 'true' ? true : false,
+ value: r.value
};
}
else
return null;
-}
+}
+
+CookieBus.parseCookieValue = function( raw )
+{
+ raw = raw.split( ':' );
+ var control = raw[ 0 ];
+ var text = raw[ 1 ];
+ var paramstrs = control.split( '&' );
+ var params = { value: raw[ 1 ] };
+ for ( var i = 0; i < paramstrs.length; ++i )
+ {
+ var p = paramstrs[ i ].split( '=' );
+ params[ p[ 0 ] ] = p[ 1 ];
+ }
+ return params;
+}

CookieBus.prototype.getAllPublications = function( )
{
@@ -1454,9 +1787,12 @@
for ( var i = 0; i < rawPubs.length; ++i )
{
var pub = rawPubs[ i ];
+ var r = CookieBus.parseCookieValue( pub.value );
publications[ publications.length ] = {
name: pub.name.substring( prefix.length, pub.name.length ),
- value: pub.value
+ value: r.value,
+ once: r.once ? true : false,
+ forward: r.forward ? true : false
};
}
return publications;
@@ -1494,33 +1830,44 @@
if ( ! this.subscribed )
{
this.subscribed = true;
+ this.nextToRead = 0;

// Get and update the subscriber count (expires in 2 minutes)
var n = domutil.readCookie( this.cookieName + '_subscriber_count' );
n = n ? Number( n ) + 1 : 1;
domutil.createCookie( this.cookieName + '_subscriber_count', n, 0, 0, 2
);

- // We won't read existing publications, so record them as read
+ // We won't read existing items unless the forward flag is set,
+ // so record them as read
var publications = this.getAllPublications( );
for ( var i = 0; i < publications.length; ++i )
{
var pub = publications[ i ];
- this.readPubs[ pub.name ] = true;
+ if ( ! pub.forward )
+ this.readPubs[ pub.name ] = true;
}

if ( interval && f )
{
var bus = this;
this.interval = setInterval( function( ) {
- while ( bus.subscribed )
- {
+// while ( bus.subscribed )
+// {
bus.getUpdateSubscriberCount( );
- var pub = bus.read( );
+ var pub = bus.nextUnread( );
if ( pub )
- f( pub );
- else
- break;
- }
+ {
+ // Only mark as read if the the callback indicates
+ // that it has handled the cookie.
+ if ( f( pub ) )
+ {
+ bus.markAsRead( pub.name );
+ if ( pub.once );
+ bus.unpublish( pub.name );
+ return;
+ }
+ }
+// }
}, interval );
}
}
@@ -1553,17 +1900,46 @@
domutil.removeCookie( publications[ i ].name );
}

-CookieBus.prototype.publish = function( s )
+/**
+ * Publish on the bus
+ * s - the text to publish
+ * once - if true, the first subscriber to see this will delete it
+ * forward - if true the item should be seen by subscribers that appear
+ * after it has been published
+ */
+CookieBus.prototype.publish = function( s, params )
{
// Find the highest publish count
var n = domutil.readCookie( this.cookieName + '_publish_count' );
n = n ? Number( n ) + 1 : 1;
-
+
+ var once = false, forward = false;
+ for ( var p in params )
+ {
+ var setting = params[ p ];
+ switch ( p )
+ {
+ case 'once':
+ once = setting ? true : false;
+ break;
+
+ case 'forward':
+ forward = setting ? true : false;
+ break;
+
+ default:
+ throw "Unknown CookieBus parameter.";
+ }
+ }
+
+ var control = 'once=' + ( once ? 'true' : 'false' )
+ + '&forward=' + ( forward ? 'true' : 'false');
+ var text = control + ':' + s;
// Publish a new item
// Item expires in 1 minute
// Count expires in 2 minutes
domutil.createCookie( this.cookieName + '_publish_count', n, 0, 0, 2 );
- domutil.createCookie( this.cookieName + '_publication_' + n, s, 0, 0, 1 );
+ domutil.createCookie( this.cookieName + '_publication_' + n, text, 0, 0,
1 );
return n;
}

@@ -1572,6 +1948,41 @@
domutil.removeCookie( this.cookieName + '_publication_' + n );
}

+/**
+ * Fetch the next unread item
+ * This is a bit tricky, because it's possible that this particular item
might
+ * not be read successfully. In that case the subsequent call to this
should
+ * continue from the publication following this one. This is done with the
+ * nextToRead index. That's not foolproof is publications are removed from
+ * the list, but it's probably good enough.
+ */
+CookieBus.prototype.nextUnread = function( )
+{
+ if ( this.subscribed )
+ {
+ var publications = this.getAllPublications( );
+ for ( var i = 0; i < publications.length; ++i )
+ {
+ var x = ( i + this.nextToRead ) % publications.length;
+ this.nextToRead = ( x + 1 ) % publications.length;
+ var pub = publications[ i ];
+ if ( ! this.readPubs[ pub.name ] )
+ return pub;
+ }
+ }
+ return null;
+}
+
+/**
+ * When a publication is handled it should be marked as read so it won't be
+ * seen again.
+ */
+CookieBus.prototype.markAsRead = function( name )
+{
+ this.readPubs[ name ] = true;
+}
+
+/*
CookieBus.prototype.read = function( )
{
if ( this.subscribed )
@@ -1589,3 +2000,4 @@
}
return null;
}
+*/
=======================================
--- /marginalia-lib/trunk/marginalia/highlight-ui.js Wed May 30 14:19:12
2012
+++ /marginalia-lib/trunk/marginalia/highlight-ui.js Wed May 30 14:51:38
2012
@@ -11,7 +11,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
+ * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -26,11 +26,11 @@
* $Id$
*/

-AN_HIGHLIGHT_CLASS = 'annotation';// class given to em nodes for
highlighting
+Marginalia.C_HIGHLIGHT = Marginalia.PREFIX + 'annotation';// class given
to em nodes for highlighting

PostMicro.prototype.wordRangeFromAnnotation = function( marginalia,
annotation )
{
- var wordRange;
+ var wordRange = null;
// XPath range is faster, but we can only use it if the browser supports
it
if ( annotation.getXPathRange( ) && this.getContentElement(
).ownerDocument.evaluate )
wordRange = WordRange.fromXPathRange( annotation.getXPathRange( ),
this.getContentElement( ), marginalia.skipContent );
@@ -113,12 +113,12 @@
// replace node content with annotation
newNode = document.createElement( 'em' );

- newNode.className = AN_HIGHLIGHT_CLASS + ' ' + AN_ID_PREFIX +
annotation.getId();
+ newNode.className = Marginalia.C_HIGHLIGHT + ' ' + Marginalia.ID_PREFIX
+ annotation.getId();
if ( marginalia.showActions && annotation.getAction() )
- newNode.className += ' ' + AN_ACTIONPREFIX_CLASS +
annotation.getAction();
+ newNode.className += ' ' + Marginalia.C_ACTIONPREFIX +
annotation.getAction();
newNode.onmouseover = _hoverAnnotation;
newNode.onmouseout = _unhoverAnnotation;
- newNode.annotation = annotation;
+ newNode[ Marginalia.F_ANNOTATION ] = annotation;
node.parentNode.replaceChild( newNode, node );

if ( marginalia.showActions && 'edit' == annotation.getAction() &&
annotation.getQuote() )
@@ -148,7 +148,7 @@

if ( lastHighlight )
{
- domutil.addClass( lastHighlight, AN_LASTHIGHLIGHT_CLASS );
+ domutil.addClass( lastHighlight, Marginalia.C_LASTHIGHLIGHT );
// If this was a substitution or insertion action, insert the text
if ( marginalia.showActions && 'edit' == annotation.getAction() &&
annotation.getNote() )
this.showActionInsert( marginalia, annotation );
@@ -167,10 +167,10 @@
PostMicro.prototype.showActionInsert = function( marginalia, annotation )
{
trace( 'actions', 'showActionInsert for ' + annotation.getQuote() );
- var highlights = domutil.childrenByTagClass( this.getContentElement(
), 'em', AN_ID_PREFIX + annotation.getId(), null, marginalia.skipContent );
+ var highlights = domutil.childrenByTagClass( this.getContentElement(
), 'em', Marginalia.ID_PREFIX + annotation.getId(), null,
marginalia.skipContent );
for ( var i = 0; i < highlights.length; ++i )
{
- if ( domutil.hasClass( highlights[ i ], AN_LASTHIGHLIGHT_CLASS ) )
+ if ( domutil.hasClass( highlights[ i ], Marginalia.C_LASTHIGHLIGHT ) )
{
// TODO: should check whether <ins> is valid in this position
var lastHighlight = highlights[ i ];
@@ -183,7 +183,7 @@
lastHighlight.parentNode.insertBefore( insNode,
lastHighlight.nextSibling );
else
lastHighlight.parentNode.appendChild( insNode );
-*/ domutil.addClass( insNode, AN_ID_PREFIX + annotation.getId() );
+*/ domutil.addClass( insNode, Marginalia.ID_PREFIX + annotation.getId()
);
}
}
}
@@ -194,9 +194,9 @@
*/
PostMicro.prototype.highlightStripTest = function( tnode, emclass )
{
- if ( domutil.matchTagClass( tnode, 'em', AN_HIGHLIGHT_CLASS ) && ( !
emclass || domutil.hasClass( tnode, emclass ) ) )
+ if ( domutil.matchTagClass( tnode, 'em', Marginalia.C_HIGHLIGHT ) && ( !
emclass || domutil.hasClass( tnode, emclass ) ) )
return domutil.STRIP_TAG;
- else if ( tnode.parentNode && domutil.hasClass( tnode.parentNode,
AN_HIGHLIGHT_CLASS )
+ else if ( tnode.parentNode && domutil.hasClass( tnode.parentNode,
Marginalia.C_HIGHLIGHT )
&& ( ! emclass || domutil.hasClass( tnode.parentNode, emclass ) ) )
{
if ( domutil.matchTagClass( tnode, 'ins', null ) ||
domutil.matchTagClass( tnode, 'a', null ) )
@@ -216,13 +216,13 @@

var post = this;
var contentElement = this.getContentElement( );
- var emClass = annotation ? AN_ID_PREFIX + annotation.getId() :
AN_HIGHLIGHT_CLASS;
+ var emClass = annotation ? Marginalia.ID_PREFIX + annotation.getId() :
Marginalia.C_HIGHLIGHT;
var stripTest = function( tnode ) { return post.highlightStripTest(
tnode, emClass ); };

var highlights = domutil.childrenByTagClass( contentElement, 'em',
emClass, null, null );
for ( var i = 0; i < highlights.length; ++i )
{
- highlights[ i ].annotation = null;
+ highlights[ i ][ Marginalia.F_ANNOTATION ] = null;
if ( highlights[ i ].parentNode) // might already have been stripped
{
domutil.stripMarkup( highlights[ i ], stripTest, true );
@@ -248,7 +248,7 @@
{
var contentElement = this.getContentElement( );
for ( var i = 0; i < highlights.length; ++i )
- highlights[ i ].annotation = null;
+ highlights[ i ][ Marginalia.F_ANNOTATION ] = null;
var stripTest = function( tnode ) { return post.highlightStripTest(
tnode, null ); };
domutil.stripMarkup( contentElement, stripTest, true );
}
=======================================
--- /marginalia-lib/trunk/marginalia/link-ui-clicktolink.js Wed May 30
14:19:12 2012
+++ /marginalia-lib/trunk/marginalia/link-ui-clicktolink.js Wed May 30
14:51:38 2012
@@ -13,7 +13,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
+ * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -39,29 +39,30 @@

ClickToLinkUi.prototype.bind = FreeformNoteEditor.prototype.bind;

-ClickToLinkUi.prototype.clear = function( )
+ClickToLinkUi.prototype.clear = function( marginalia )
{
this.editNode = null;
- _disableLinkTargets( );
- removeCookie( AN_LINKING_COOKIE );
- removeCookie( AN_LINKURL_COOKIE );
- domutil.removeClass( document.body, AN_EDITINGLINK_CLASS );
+ if ( this.linkablePosts )
+ this.linkablePosts.disableLinkTargets( );
+ removeCookie( LinkablePosts.CK_LINKING );
+ removeCookie( LinkablePosts.CK_LINKURL );
+ domutil.removeClass( document.body, Marginalia.C_EDITINGLINK );
}

-ClickToLinkUi.prototype.show = function( )
+ClickToLinkUi.prototype.show = function( marginalia )
{
var marginalia = this.marginalia;
var annotation = this.annotation;
var post = this.postMicro;
var noteElement = this.noteElement;

- var controlId = AN_ID_PREFIX + annotation.getId() + '-linkedit';
+ var controlId = Marginalia.ID_PREFIX + annotation.getId() + '-linkedit';

// add the link label
noteElement.appendChild( domutil.element( 'label', {
title: getLocalized( 'annotation link label' ),
attr_for: controlId,
- content: AN_LINKEDIT_LABEL } ) );
+ content: marginalia.icons[ 'linkEdit' ] } ) );

// Add the URL input field
this.editNode = noteElement.appendChild( domutil.element( 'input', {
@@ -76,29 +77,28 @@

// add the delete button
noteElement.appendChild( domutil.button( {
- className: AN_LINKDELETEBUTTON_CLASS,
+ className: Marginalia.C_LINKDELETEBUTTON,
title: getLocalized( 'delete annotation link button' ),
content: 'x',
annotationId: annotation.getId(),
onclick: SimpleLinkUi._deleteLink } ) );

- domutil.addEventListener( window, 'focus', _enableLinkTargets );
+ this.linkablePosts = new LinkablePosts( marginalia.posts );
domutil.addEventListener( window, 'focus', _updateLinks );

// Tell this window and others to be accept clicks for link creation
- domutil.addClass( document.body, AN_EDITINGLINK_CLASS );
- createCookie( AN_LINKING_COOKIE, annotation.id, 1 );
+ domutil.addClass( document.body, Marginalia.C_EDITINGLINK );
+ createCookie( LinkablePosts.CK_LINKING, annotation.id, 1 );
_enableLinkTargets( );
- domutil.addEventListener( window, 'blur', _disableLinkTargets );
}

-ClickToLinkUi.prototype.focus = function( )
+ClickToLinkUi.prototype.focus = function( marginalia )
{
if ( this.extlinks )
this.editNode.focus( );
}

-ClickToLinkUi.prototype.save = function( )
+ClickToLinkUi.prototype.save = function( marginalia )
{
this.annotation.setLink( this.editNode.value );
this.annotation.setLinkTitle( '' );
@@ -116,18 +116,19 @@
*/
function _updateLinks( )
{
- if ( domutil.hasClass( document.body, AN_EDITINGLINK_CLASS ) )
- {
- var annotationId = readCookie( AN_LINKING_COOKIE );
- var newLink = readCookie( AN_LINKURL_COOKIE );
+ var marginalia = window.marginalia;
+ if ( domutil.hasClass( document.body, Marginalia.C_EDITINGLINK ) )
+ {
+ var annotationId = readCookie( LinkablePosts.CK_LINKING );
+ var newLink = readCookie( LinkablePosts.CK_LINKURL );
if ( annotationId && newLink )
{
- var annotationNode = document.getElementById( AN_ID_PREFIX +
annotationId );
+ var annotationNode = document.getElementById( Marginalia.ID_PREFIX +
annotationId );
if ( annotationNode )
{
var marginalia = window.marginalia;
- var post = domutil.nestedFieldValue( annotationNode, AN_POST_FIELD );
- var annotation = domutil.nestedFieldValue( annotationNode,
AN_ANNOTATION_FIELD );
+ var post = domutil.nestedFieldValue( annotationNode, Marginalia.F_POST
);
+ var annotation = domutil.nestedFieldValue( annotationNode,
Marginalia.F_ANNOTATION );
var editNode = domutil.childByTagClass( annotationNode, 'input', null,
null );
marginalia.noteEditor.setLink( newLink );
_saveAnnotation( );
=======================================
--- /marginalia-lib/trunk/marginalia/link-ui-simple.js Wed May 30 14:19:12
2012
+++ /marginalia-lib/trunk/marginalia/link-ui-simple.js Wed May 30 14:51:38
2012
@@ -11,7 +11,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
+ * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -36,25 +36,25 @@

SimpleLinkUi.prototype.bind = FreeformNoteEditor.prototype.bind;

-SimpleLinkUi.prototype.clear = function( )
+SimpleLinkUi.prototype.clear = function( marginalia )
{
this.editNode = null;
}

-SimpleLinkUi.prototype.show = function( )
+SimpleLinkUi.prototype.show = function( marginalia )
{
var marginalia = this.marginalia;
var annotation = this.annotation;
var post = this.postMicro;
var noteElement = this.noteElement;

- var controlId = AN_ID_PREFIX + annotation.getId() + '-linkedit';
+ var controlId = Marginalia.ID_PREFIX + annotation.getId() + '-linkedit';

// add the link label
noteElement.appendChild( domutil.element( 'label', {
title: getLocalized( 'annotation link label' ),
attr_for: controlId,
- content: AN_LINKEDIT_LABEL } ) );
+ content: marginalia.icons[ 'linkEdit' ] } ) );

// Add the URL input field
this.editNode = noteElement.appendChild( domutil.element( 'input', {
@@ -66,7 +66,7 @@

// add the delete button
noteElement.appendChild( domutil.button( {
- className: AN_LINKDELETEBUTTON_CLASS,
+ className: Marginalia.C_LINKDELETEBUTTON,
title: getLocalized( 'delete annotation link button' ),
content: 'x',
annotationId: annotation.getId(),
=======================================
--- /marginalia-lib/trunk/marginalia/link-ui.js Wed May 30 14:19:12 2012
+++ /marginalia-lib/trunk/marginalia/link-ui.js Wed May 30 14:51:38 2012
@@ -11,7 +11,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
+ * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -26,10 +26,10 @@
* $Id$
*/

-AN_LINK_CLASS = 'annotation-link'; // class given to a nodes for link
annotations
-AN_LINKEDIT_ID = 'editing-link'; // ID assigned to note element whose link
is being edited
-AN_LINKDELETEBUTTON_CLASS = 'delete-link';
-MAX_LINK_LENGTH = 255;
+Marginalia.C_LINK = Marginalia.PREFIX + 'annotation-link'; // class given
to a nodes for link annotations
+Marginalia.ID_LINKEDIT = Marginalia.PREFIX + 'editing-link'; // ID
assigned to note element whose link is being edited
+Marginalia.C_LINKDELETEBUTTON = Marginalia.PREFIX + 'delete-link';
+Marginalia.MAX_LINK_LENGTH = 255;


/**
@@ -43,10 +43,10 @@

if ( null != annotation.link && '' != annotation.link )
{
- var highlights = domutil.childrenByTagClass( this.getContentElement(
), 'em', AN_ID_PREFIX + annotation.getId(), null, null );
+ var highlights = domutil.childrenByTagClass( this.getContentElement(
), 'em', Marginalia.ID_PREFIX + annotation.getId(), null, null );
for ( var i = 0; i < highlights.length; ++i )
{
- if ( domutil.hasClass( highlights[ i ], AN_LASTHIGHLIGHT_CLASS ) )
+ if ( domutil.hasClass( highlights[ i ], Marginalia.C_LASTHIGHLIGHT ) )
{
// TODO: should check whether a link is valid in this location; if
not,
// either refuse to show or insert a clickable Javascript object
instead
@@ -63,18 +63,18 @@
}
if ( ! linkTitle )
{
- linkTitle = annotation.getNote().length > MAX_NOTEHOVER_LENGTH
- ? annotation.getNote().substr( 0, MAX_NOTEHOVER_LENGTH ) + '...'
+ linkTitle = annotation.getNote().length >
marginalia.maxNoteHoverLength
+ ? annotation.getNote().substr( 0, marginalia.maxNoteHoverLength )
+ '...'
: annotation.getNote();
}
}

lastHighlight.appendChild( domutil.element( 'sup', {
content: domutil.element( 'a', {
- className: AN_LINK_CLASS + ' ' + AN_ID_PREFIX + annotation.getId(),
+ className: Marginalia.C_LINK + ' ' + Marginalia.ID_PREFIX +
annotation.getId(),
title: linkTitle,
href: annotation.getLink(),
- content: AN_LINK_ICON } )
+ content: marginalia.icons[ 'link' ] } )
} ) );
}
}
@@ -87,7 +87,7 @@
*/
PostMicro.prototype.hideLink = function( marginalia, annotation )
{
- var existingLink = domutil.childByTagClass( this.getContentElement(
), 'a', AN_ID_PREFIX + annotation.getId(), null );
+ var existingLink = domutil.childByTagClass( this.getContentElement(
), 'a', Marginalia.ID_PREFIX + annotation.getId(), null );
if ( existingLink )
existingLink.parentNode.removeChild( existingLink );
}
@@ -119,15 +119,15 @@
{
var event = marginalia;
var target = domutil.getEventTarget( event );
- annotation = domutil.nestedFieldValue( target, AN_ANNOTATION_FIELD );
- post = domutil.nestedFieldValue( target, AN_POST_FIELD );
+ annotation = domutil.nestedFieldValue( target, Marginalia.F_ANNOTATION );
+ post = domutil.nestedFieldValue( target, Marginalia.F_POST );
marginalia = window.marginalia;
}

annotation.editing = AN_EDIT_LINK;
marginalia.editing = annotation;
- var noteElement = annotation.getNoteElement( );
- domutil.addClass( noteElement, AN_EDITINGLINK_CLASS );
+ var noteElement = annotation.getNoteElement( marginalia );
+ domutil.addClass( noteElement, Marginalia.C_EDITINGLINK );
marginalia.linkUi.showLinkEdit( marginalia, post, annotation, noteElement
);
}

@@ -144,9 +144,9 @@
// Update edit status
delete annotation.editing;
marginalia.editing = null;
- domutil.removeClass( noteElement, AN_EDITINGLINK_CLASS );
-
- this.flagAnnotation( marginalia, annotation, AN_HOVER_CLASS, false );
+ domutil.removeClass( noteElement, Marginalia.C_EDITINGLINK );
+
+ this.flagAnnotation( marginalia, annotation, Marginalia.C_HOVER, false );
marginalia.updateAnnotation( annotation, null );

// Update the link display
@@ -176,8 +176,8 @@
// Update edit status
delete annotation.editing;
marginalia.editing = null;
- var noteElement = annotation.getNoteElement( );
- domutil.removeClass( noteElement, AN_EDITINGLINK_CLASS );
+ var noteElement = annotation.getNoteElement( marginalia );
+ domutil.removeClass( noteElement, Marginalia.C_EDITINGLINK );
}


@@ -187,7 +187,7 @@
*/
function _skipAnnotationLinks( node )
{
- return ELEMENT_NODE == node.nodeType && domutil.hasClass( node,
AN_LINK_CLASS );
+ return ELEMENT_NODE == node.nodeType && domutil.hasClass( node,
Marginalia.C_LINK );
// && node.parentNode
// && 'a' == domutil.getLocalName( node )
// && domutil.hasClass( node.parentNode, AN_HIGHLIGHT_CLASS );
=======================================
--- /marginalia-lib/trunk/marginalia/linkable.js Wed May 30 14:19:12 2012
+++ /marginalia-lib/trunk/marginalia/linkable.js Wed May 30 14:51:38 2012
@@ -11,7 +11,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
+ * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -26,28 +26,62 @@
* $Id$
*/

-AN_MAKELINKTARGET_CLASS = 'make-link-target'; // this content element's
children can be made link targets by clicking
-AN_LINKTARGET_CLASS = 'link-target'; // the node is the clicked target of
a link
-AN_FLASH_CLASS = 'flash'; // the display is flashing this node
-
-AN_LINKING_COOKIE = 'marginalia-linking';
-AN_LINKURL_COOKIE = 'marginalia-link-url';
+/**
+ * Enable link targets for when user clicks on a post
+ */
+function LinkablePosts( postPageInfo )
+{
+ this.postInfo = postPageInfo;
+ this.prefix = prefix;
+
+ var linkablePosts = this;
+
+ this.fenable = function( ) {
+ linkablePosts.enableLinkTargets( );
+ };
+
+ this.fdisable = function( ) {
+ linkablePosts.disableLinkTargets( );
+ };
+
+ this.fclick = function( event ) {
+ linkablePosts.clickLinkTarget( event );
+ };
+
+ this.fflash = function( ) {
+ linkablePosts.flashLinkTarget( );
+ };
+
+ domutil.addEventListener( window, 'focus', this.fenable, false );
+ domutil.addEventListener( window, 'blur', this.fdisable, false );
+}
+
+// These class names do not require a prefix
+LinkablePosts.C_MAKELINKTARGET = 'linkableposts-makelinktarget';// this
content element's children can be made link targets by clicking
+LinkablePosts.C_LINKTARGET = 'linkableposts-linktarget'; // the node is
the clicked target of a link
+LinkablePosts.C_FLASH = 'linkableposts-flash'; // the display is
flashing this node
+
+// Cookies:
+LinkablePosts.CK_LINKING = 'marginalia-linking';
+LinkablePosts.CK_LINKURL = 'marginalia-link-url';
+
+
+

/**
* If the window gains focus and linking is on, enable link targets
*/
-function _enableLinkTargets( )
-{
- if ( readCookie( AN_LINKING_COOKIE ) )
- {
- var postInfo = PostPageInfo.getPostPageInfo( document );
- var posts = postInfo.getAllPosts( );
+LinkablePosts.prototype.enableLinkTargets = function( )
+{
+ if ( readCookie( LinkablePosts.CK_LINKING ) )
+ {
+ var posts = this.postInfo.getAllPosts( );
for ( var i = 0; i < posts.length; ++i )
{
var post = posts[ i ];
var content = post.getContentElement( );
- domutil.addClass( content, AN_MAKELINKTARGET_CLASS );
- domutil.addEventListener( content, 'click', _clickLinkTarget, false );
+ domutil.addClass( content, LinkablePosts.C_MAKELINKTARGET );
+ domutil.addEventListener( content, 'click', this.fclick, false );
}
}
}
@@ -56,7 +90,7 @@
/**
* If the window loses focus, disable link targets
*/
-function _disableLinkTargets( )
+LinkablePosts.prototype.disableLinkTargets = function( )
{
var postInfo = PostPageInfo.getPostPageInfo( document );
var posts = postInfo.getAllPosts( );
@@ -64,10 +98,10 @@
{
var post = posts[ i ];
var content = post.getContentElement( );
- domutil.removeClass( content, AN_MAKELINKTARGET_CLASS );
- domutil.removeEventListener( content, 'click', _clickLinkTarget, false );
- }
- domutil.removeEventListener( window, 'blur', _disableLinkTargets, false );
+ domutil.removeClass( content, LinkablePosts.C_MAKELINKTARGET );
+ domutil.removeEventListener( content, 'click', this.fclick, false );
+ }
+ domutil.removeEventListener( window, 'blur', this.fdisable, false );
}


@@ -75,61 +109,66 @@
* The user has clicked on a link target. Update the relevant annotation
with
* the new link.
*/
-function _clickLinkTarget( event )
+LinkablePosts.prototype.clickLinkTarget = function( event )
{
event = domutil.getEvent( event );
domutil.stopPropagation( event );
- var content = domutil.parentByTagClass( domutil.getEventTarget( event ),
null, PM_CONTENT_CLASS, false, null );
-
- // Need to look at parent targets until a block level element is found
- var target = domutil.getEventTarget( event, target );
- while ( 'block' != domutil.htmlDisplayModel( target.tagName ) )
- target = target.parentNode;
-
- // Calculate path to target
- var point = SequencePoint.fromNode( content, target );
- var path = point.toString( );
- // var path = NodeToPath( content, target );
- var link = '' + window.location;
- if ( -1 != link.indexOf( '#' ) )
- link = link.substring( 0, link.indexOf( '#' ) );
- link = link + '#node-path:' + path;
-
- // Get the annotation
- var annotationId = readCookie( AN_LINKING_COOKIE );
- if ( null != annotationId )
- {
- // TODO: must replace ; characters in cookie
- createCookie( AN_LINKURL_COOKIE, link, 1 );
-
- domutil.removeEventListener( content, 'click', _clickLinkTarget, false );
- domutil.removeClass( content, AN_MAKELINKTARGET_CLASS );
- domutil.addClass( target, AN_FLASH_CLASS );
- target.flashcount = 4;
- setTimeout( _flashLinkTarget, 240 );
+
+ var post = this.postInfo.postByElement( domutil.getEventTarget( event ) );
+ if ( post )
+ {
+ var content = post.getContentElement( );
+
+ // Need to look at parent targets until a block level element is found
+ var target = domutil.getEventTarget( event, target );
+ while ( 'block' != domutil.htmlDisplayModel( target.tagName ) )
+ target = target.parentNode;
+
+ // Calculate path to target
+ var point = SequencePoint.fromNode( content, target );
+ var path = point.toString( );
+ // var path = NodeToPath( content, target );
+ var link = '' + window.location;
+ if ( -1 != link.indexOf( '#' ) )
+ link = link.substring( 0, link.indexOf( '#' ) );
+ link = link + '#node-path:' + path;
+
+ // Get the annotation
+ var annotationId = readCookie( LinkablePosts.CK_LINKING );
+ if ( null != annotationId )
+ {
+ // TODO: must replace ; characters in cookie
+ createCookie( LinkablePosts.CK_LINKURL, link, 1 );
+
+ domutil.removeEventListener( content, 'click', _clickLinkTarget, false
);
+ domutil.removeClass( content, LinkablePosts.C_MAKELINKTARGET );
+ domutil.addClass( target, LinkablePosts.C_FLASH );
+ target.flashcount = 4;
+ setTimeout( this.fflash, 240 );
+ }
}

// If the link was made from this window, leave editing mode
- _disableLinkTargets( );
+ this.disableLinkTargets( );
_updateLinks( );
}

-function _flashLinkTarget( )
-{
- var target = domutil.childByTagClass( document.documentElement, null,
AN_FLASH_CLASS, null );
+LinkablePosts.prototype.flashLinkTarget = function( )
+{
+ var target = domutil.childByTagClass( document.documentElement, null,
LinkablePosts.C_FLASH, null );
if ( target.flashcount > 0 )
{
if ( target.flashcount % 2 )
- domutil.removeClass( target, AN_LINKTARGET_CLASS );
+ domutil.removeClass( target, LinkablePosts.C_LINKTARGET );
else
- domutil.addClass( target, AN_LINKTARGET_CLASS );
+ domutil.addClass( target, LinkablePosts.C_LINKTARGET );
target.flashcount -= 1;
- setTimeout( _flashLinkTarget, 240 );
+ setTimeout( this.fflash, 240 );
}
else
{
- domutil.removeClass( target, AN_LINKTARGET_CLASS );
- domutil.removeClass( target, AN_FLASH_CLASS );
+ domutil.removeClass( target, LinkablePosts.C_LINKTARGET );
+ domutil.removeClass( target, LinkablePosts.C_FLASH );
if ( target.flashcount )
delete target.flashcount;
}
=======================================
--- /marginalia-lib/trunk/marginalia/log.js Wed May 30 14:19:12 2012
+++ /marginalia-lib/trunk/marginalia/log.js Wed May 30 14:51:38 2012
@@ -11,7 +11,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
+ * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -124,6 +124,8 @@

ErrorLogger.prototype.trace = function( topic, s )
{
+ if ( ! this.on )
+ alert( 'logging not on' );
for ( var i = 0; i < this.indentLevel; ++i )
s = ' ' + s;
if ( this.on && ( !topic || this.traceSettings[ topic ] ) )
=======================================
--- /marginalia-lib/trunk/marginalia/marginalia.css Wed May 30 14:19:12 2012
+++ /marginalia-lib/trunk/marginalia/marginalia.css Wed May 30 14:51:38 2012
@@ -4,24 +4,24 @@
* Use darker shades of yellow for overlapping highlights
*/

-.hentry .entry-content em.annotation {
+em.mia_annotation {
background: #fdf377;
font-style: inherit;
}

-.hentry .entry-content em.annotation em.annotation {
+em.mia_annotation em.mia_annotation {
background: #f4e373;
}

-.hentry .entry-content em.annotation em.annotation em.annotation {
+em.mia_annotation em.mia_annotation em.mia_annotation {
background: #ecd470;
}

-.hentry .entry-content em.annotation em.annotation em.annotation
em.annotation {
+em.mia_annotation em.mia_annotation em.mia_annotation em.mia_annotation {
background: #e0ce6c;
}

-.hentry .entry-content em.annotation em.annotation em.annotation
em.annotation em.annotation {
+em.mia_annotation em.mia_annotation em.mia_annotation em.mia_annotation
em.mia_annotation {
background: #d8c666;
}

@@ -32,22 +32,22 @@
* Strikethrough etc.
*/

-.hentry .entry-content em.annotation.action-edit {
+em.mia_annotation.action-edit {
background: none;
}

-.hentry .entry-content em.annotation del {
+em.mia_annotation del {
color: darkred;
text-decoration: line-through;
}

-.hentry .entry-content em.annotation ins {
+em.mia_annotation ins {
color: darkblue;
text-decoration: none;
}

-.hentry .entry-content em.annotation a,
-.hentry .entry-content a em.annotation {
+em.mia_annotation a,
+a em.mia_annotation {
text-decoration: underline;
}

@@ -56,7 +56,7 @@
* Inserted links
*/

-.hentry .entry-content em.annotation sup a {
+em.mia_annotation sup a {
text-decoration: none;
background-color: inherit;
cursor: pointer;
@@ -67,14 +67,14 @@
* Annotation margin notes
*/

-.hentry .notes ol {
+.mia_margin ol {
list-style-type: none;
margin: 0;
padding: 0 .5ex ;
height: 100%;
}

-.hentry .notes li {
+.mia_margin li {
position: relative;
font-family: sans-serif;
font-size: 80%;
@@ -83,11 +83,15 @@
padding: 0;
min-height: 1.2em;
line-height: 1.2em;
- width: 100%;
cursor: pointer;
}

-.hentry .notes li.dummy {
+.mia_margin li p.empty {
+ font-size: 150%;
+ font-family: serif;
+}
+
+.mia_margin li.mia_dummyfirst {
height: 1px;
min-height: 1px;
margin: 0 0 -1px 0;
@@ -95,32 +99,45 @@
}

/* notes by another user are in a lighter grey */
-.hentry .notes li.other-user {
- color: #555;
-}
-.hentry .notes .username {
- font-style: italic;
+.mia_margin li.mia_other-user {
+ color: #444;
+ cursor: inherit;
+}
+
+.mia_margin .mia_username {
+ /*font-variant: small-caps;*/
+ /*font-size: 90%;*/
}

-
-.hentry .notes li.quote-error p:before {
+.mia_recent .mia_username {
+ xfont-weight: bold;
+ xcolor: #444;
+}
+
+.mia_recent .mia_username:before {
+ content: '*';
+ font-size: 120%;
+ color: #d00;
+}
+
+.mia_margin li.mia_quote-error p:before {
content: '!';
padding: 0 .75ex;
margin-right: .2ex;
- background: red;
+ background: #d00;
color: white;
font-weight: bold;
}

-.hentry .notes li.collapsed {
+.mia_margin li.mia_collapsed {
overflow: hidden;
}

-.hentry .notes li.collapsed p {
+.mia_margin li.mia_collapsed p {
white-space: nowrap;
}

-.hentry .notes li.dummyfirst {
+.mia_margin li.mia_dummyfirst {
height: 1px; /* force minimal display so later note margins will be
correctly calculated */
cursor: default;
border: none;
@@ -129,13 +146,13 @@
padding: 0;
}

-.hentry .notes li .controls {
+.mia_margin li .controls {
float: right;
vertical-align: top;
/*display: none;*/
}

-.hentry .notes li .select-action {
+.mia_margin li .select-action {
margin: .5ex;
padding: 0 0 0 .5ex;
list-style-type: none;
@@ -143,13 +160,13 @@
border-left: #bbb .5ex solid;
}

-.hentry .notes li .select-action li {
+.mia_margin li .select-action li {
margin: 0;
padding: 0;
position: relative;
}

-.hentry .notes li .select-action button {
+.mia_margin li .select-action button {
border: none;
padding: 0;
cursor: pointer;
@@ -159,12 +176,12 @@
}


-.hentry .notes li .select-action button:hover {
+.mia_margin li .select-action button:hover {
background-color: #bbb;
}

-.hentry .notes li .controls button,
-.hentry .notes li button.expand-edit {
+.mia_margin li .controls button,
+.mia_margin li button.expand-edit {
line-height: 1em;
border: none;
font-size: inherit;
@@ -177,12 +194,14 @@
z-index: 1000;
}

-.hentry .notes li .controls button:hover,
-.hentry .notes li button.expand-edit {
+/*
+.mia_margin li .controls button:hover,
+.mia_margin li button.mia_expand-edit {
font-weight: bold;
}
-
-.hentry .notes li textarea {
+*/
+
+.mia_margin li textarea {
font-size: inherit;
font-family: sans-serif;
border: none;
@@ -191,51 +210,95 @@
width: 95%;
}

-.hentry .notes li button.annotation-access {
+.mia_margin li button.mia_annotation-access {
heigh: 1.2em;
width: 1em;
}

/*
-.hentry .notes li.hover,
-.hentry .entry-content em.annotation.hover,
-.hentry .entry-content em.annotation.hover ins,
-.hentry .entry-content em.annotation.hover del {
+.mia_margin li.mia_hover,
+.hentry .entry-content em.mia_annotation.mia_hover,
+.hentry .entry-content em.mia_annotation.mia_hover ins,
+.hentry .entry-content em.mia_annotation.mia_hover del {
color: red;
}
*/

-.hentry .notes li button.expand-edit {
+.mia_margin li {
+ padding-left: 2px;
+}
+
+.mia_margin li.mia_hover {
+ color: #d00;
+}
+em.mia_annotation.mia_hover {
+ color: #d00;
+ outline: #d00 1px solid;
+}
+.mia_margin li.mia_hover:before {
+ background-color: #d00;
+ height: 100%;
position: absolute;
+ right: 100%;
+ width: .2px;
+ padding-right: 2px;
+ top: 0;
+ content: ' ';
+ display: block;
+}
+
+
+.mia_margin li button.mia_expand-edit {
+ position: absolute;
top: 0;
right: 100%;
}

-.hentry .notes .editing-link label {
+.mia_margin .mia_editing-link label {
position: absolute;
top: 0;
left: 0;
width: 1em;
}

-.hentry .notes .editing-link input {
+.mia_margin .mia_editing-link input {
margin-left: 1.5em;
margin-right: 1.5em;
max-width: 80%;
}

-.hentry .notes .editing-link button {
+.mia_margin .mia_editing-link button {
position: absolute;
top: 0;
right: 0;
width: 1em;
}

-
-/*
- * Hide smart copy additional info
- */
-.hentry .smart-copy {
- display: none;
+p.mia_charsremaining {
+ margin: 0;
+ text-align: right;
+ color: #999;
+}
+
+/** Default service error display */
+.mia_errorbox {
+ position: fixed;
+ width: 50%;
+ top: 30%;
+ left: 25%;
+ background: white;
+ border: 3px #ddd outset;
+ font-size: large;
}

+.mia_errorbox h3 {
+ margin: 0;
+ padding: .5ex;
+ background: yellow;
+ font-size: x-large;
+ text-align: center;
+}
+
+.mia_errorbox p {
+ margin: 1ex 1em 1em 1em;
+}
=======================================
--- /marginalia-lib/trunk/marginalia/marginalia.js Wed May 30 14:19:12 2012
+++ /marginalia-lib/trunk/marginalia/marginalia.js Wed May 30 14:51:38 2012
@@ -1,4 +1,4 @@
-/*
+/*
* marginalia.js
*
* Marginalia has been developed with funding and support from
@@ -11,7 +11,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
+ * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -26,81 +26,71 @@
* $Id$
*/

-// Features that can be switched on and off
-AN_BLOCKMARKER_FEAT = 'show block markers';
-AN_ACCESS_FEAT = 'public/private access settings';
-AN_LINKING_FEAT = 'allow external links';
-AN_EXTLINKS_FEAT = 'allow external links';
-AN_ACTIONS_FEAT = 'allow (edit) actions';
-
-// The names of HTML/CSS classes used by the annotation code.
-AN_HOVER_CLASS = 'hover'; // assigned to highlights and notes when the
mouse is over the other
-AN_ANNOTATED_CLASS = 'annotated'; // class added to fragment when
annotation is on
-AN_SELFANNOTATED_CLASS = 'self-annotated'; // annotations are by the
current user (and therefore editable)
-AN_EDITINGNOTE_CLASS = 'editing-note'; // (on body) indicates a note is
being edited
-AN_EDITINGLINK_CLASS = 'editing-link';
-AN_LASTHIGHLIGHT_CLASS = 'last'; // used to flag the last highlighted
regin for a single annotation
-AN_ACTIONPREFIX_CLASS = 'action-'; // prefix for class names for actions
(e.g. action-delete)
-
-AN_RANGECARET_ID = 'range-caret'; // identifies caret used to show
zero-length ranges
-
-AN_ANNOTATION_FIELD = 'annotation'; // reference to Annotation object
-AN_POST_FIELD = 'post'; // reference to PostMicro object
-
-AN_ID_PREFIX = 'annot'; // prefix for annotation IDs in element classes
and IDs
-
-// Length limits
-MAX_QUOTE_LENGTH = 1000;
-
-// The timeout between coop multitasking calls. Should be short so most
time is spent doing
-// something rather than timing out.
-AN_COOP_TIMEOUT = 50;
-
-// The maximum time to spend on one coop multitasking call. Should be
short enough to be
-// fairly unnoticeable, but long enough to get some work done.
-AN_COOP_MAXTIME = 200;
-
/* ************************ User Functions ************************ */

/**
* Must be called before any other annotation functions
* service - used to connect to the server side
* loginUserId - the current user
- * displayUserId - the user whose annotations are to be shown (may differ
from username)
- * urlBase - if null, annotation URLs are used as normal. Otherwise, they
are searched for this
* string and anything preceeding it is chopped off. This is necessary
because IE lies about
* hrefs: it provides an absolute URL for that attribute, rather than the
actual text. In some
* cases, absolute URLs aren't desirable (e.g. because the annotated
resources might be moved
* to another host, in which case the URLs would all break).
*/
-function Marginalia( service, loginUserId, displayUserId, features )
+function Marginalia( service, loginUserId, sheet, features )
{
this.annotationService = service;
this.loginUserId = loginUserId;
- this.displayUserId = displayUserId;
+ this.sheet = sheet;
this.editing = null; // annotation currently being edited (if any)
this.noteEditor = null; // state for note currently being edited (if any)
- should replace editing, above
-
+ this.annotationCache = [ ]; // array of arrays of annotations that have
been fetched from the server
+ this.annotationCacheIndex = 0; // entries below this have already been
displayed
+
+ this.maxNoteLength = 250;
+ this.maxQuoteLength = 1000;
+ this.maxNoteHoverLength = 24;
this.userInRequest = false;
this.preferences = null;
this.keywordService = null;
this.urlBase = null;
- this.blockMarkers = false;
- this.access = false;
this.actions = false;
this.defaultAction = null;
this.skipContent = function(node) {
return _skipAnnotationLinks(node) || _skipAnnotationActions(node) ||
_skipCaret(node); };
- this.saveEditPrefs = Marginalia.saveEditPrefs;
this.displayNote = Marginalia.defaultDisplayNote;
this.allowAnyUserPatch = false;
this.onMarginHeight = null;
+ this.serviceErrorCallback = Marginalia.defaultErrorCallback;
+ this.enableRecentFlag = false;
+ this.lastUpdate = null;
+
+ this.selectors = {
+ post: new Selector( '.hentry', '.hentry .hentry' ),
+ post_content: new
Selector( '.entry-content', '.entry-content .entry-content' ),
+ post_title: new
Selector( '.entry-title', '.entry-content .title', 'text()' ),
+ post_author: new Selector( '.author', '.entry-content .author', 'text()'
),
+ post_authorid: new
Selector( '.author', '.entry-content .author', '@title' ),
+ post_date: new Selector( 'abbr.published', '.entry-content
abbr.published', '@title' ),
+ post_url: new Selector( 'a[rel="bookmark"]', '.entry-content
a[rel="bookmark"]', '@href' ),
+ mia_notes: new Selector( '.notes', '.entry-content .notes' ),
+ mia_markers: new Selector( '.markers', '.entry-content .markers' )
+ };

this.editors = {
'default': Marginalia.newDefaultEditor,
- freeform: Marginalia.newEditorFunc( FreeformNoteEditor ),
- keyword: Marginalia.newEditorFunc( KeywordNoteEditor ),
- link: Marginalia.newEditorFunc( SimpleLinkUi )
+ freeform: Marginalia.newEditorFunc( FreeformNoteEditor )
+// link: Marginalia.newEditorFunc( SimpleLinkUi )
+ };
+
+ this.icons = {
+ 'public': '\u25cb', // SUN_SYMBOL '\u263c'; '\u26aa';
+ 'private': '\u25c6', // MOON_SYMBOL '\u2641';
+ link: '\u263c', //'\u238b'; circle-arrow // \u2318 (point of interest)
\u2020 (dagger) \u203b (reference mark) \u238b (circle arrow)
+ linkEdit: '\u263c', //'\u238b'; circle-arrow// \u2021 (double dagger)
+ collapsed: '+', // '\u25b7'; triangle
+ expanded: '-', // '\u25bd';
+ 'delete': '\u00d7'
};

for ( var feature in features )
@@ -108,9 +98,15 @@
var value = features[ feature ];
switch ( feature )
{
+ // The default sheet (e.g. public or private)
+ case 'sheetDefault':
+ this.defaultSheet = value;
+ break;
+
// Set the default action for a new annotation ("edit" for track
changes)
case 'action':
this.defaultAction = value;
+ break;

// Allow this user to submit patches for out-of-date annotations by any
other user
case 'allowAnyUserPatch':
@@ -133,11 +129,37 @@
this.editors[ name ] = value[ name ];
break;

+ // Show flag for recent annotations
+ case 'enableRecentFlag':
+ this.enableRecentFlag = value;
+ break;
+
+ // Override or add icons
+ case 'icons':
+ for ( var name in value )
+ this.icons[ name ] = value[ name ];
+ break;
+
// The keyword service to provide the drop-down list of keywords
case 'keywordService':
this.keywordService = value;
break;

+ // The maximum length of a margin note, in characters
+ case 'maxNoteLength':
+ this.maxNoteLength = value;
+ break;
+
+ // The maximum length of hover text over a note or link
+ case 'maxNoteHoverLength':
+ this.maxNoteHoverLength = value;
+ break;
+
+ // Maximum length of a quote, in characters
+ case 'maxQuoteLength':
+ this.maxQuoteLength = value;
+ break;
+
// Toggle: Create an annotation when the user presses the Enter key
case 'onkeyCreate':
if ( value )
@@ -154,11 +176,19 @@
this.preferences = value;
break;

- // Toggle: Display the private/public access button for each margin note
- case 'showAccess':
- this.showAccess = value;
+ // Callback for displaying an error when a service call to the server
+ // fails
+ case 'serviceErrorCallback':
+ this.serviceErrorCallback = value;
+ break;
+
+ // Selectors for finding parts of the document (posts, urls, titles,
etc.)
+ // Default selectors can be overriden individually
+ case 'selectors':
+ for ( var selector in value )
+ this.selectors[ selector ] = value[ selector ];
break;
-
+
case 'showActions':
this.showActions = value;
break;
@@ -173,9 +203,9 @@
break;

// Show block markers in the left margin, indicating how many users
have annotated a block
- case 'showBlockMarkers':
- this.showBlockMarkers = value;
- break;
+// case 'showBlockMarkers':
+// this.showBlockMarkers = value;
+// break;

// Function for ignoring elements embedded in annotatable content
case 'skipContent':
@@ -194,10 +224,6 @@
this.warnDelete = value;
break;

- case 'saveEditPrefs':
- this.saveEditPrefs = value;
- break;
-
default:
if ( typeof( this[ feature ] ) != 'undefined' )
throw 'Attempt to override feature: ' + feature;
@@ -211,6 +237,51 @@
// Recorded in every annotation created by this client
Marginalia.VERSION = 2;

+// Fields (i.e. properties added to DOM nodes)
+Marginalia.F_ANNOTATION = 'mia_annotation'; // reference to Annotation
object
+Marginalia.F_POST = 'mia_post'; // reference to PostMicro object
+
+// The timeout between coop multitasking calls. Should be short so most
time is spent doing
+// something rather than timing out.
+Marginalia.COOP_TIMEOUT = 50;
+
+// The maximum time to spend on one coop multitasking call. Should be
short enough to be
+// fairly unnoticeable, but long enough to get some work done.
+Marginalia.COOP_MAXTIME = 200;
+
+// Prefix for all Marginalia class and ID values
+Marginalia.PREFIX = 'mia_';
+
+// Class and ID prefixes
+Marginalia.ID_PREFIX = Marginalia.PREFIX + 'id_'; // prefix for annotation
IDs
+
+// The names of HTML/CSS classes used by the annotation code.
+Marginalia.C_HOVER = Marginalia.PREFIX + 'hover'; // assigned to
highlights and notes when the mouse is over the other
+Marginalia.C_ANNOTATED = Marginalia.PREFIX + 'annotated'; // class added
to fragment when annotation is on
+Marginalia.C_SELFANNOTATED = Marginalia.PREFIX + 'self-annotated'; //
annotations are by the current user (and therefore editable)
+Marginalia.C_EDITINGNOTE = Marginalia.PREFIX + 'editing-note'; // (on
body) indicates a note is being edited
+Marginalia.C_EDITINGLINK = Marginalia.PREFIX + 'editing-link';
+Marginalia.C_LASTHIGHLIGHT = Marginalia.PREFIX + 'last'; // used to flag
the last highlighted regin for a single annotation
+Marginalia.C_ACTIONPREFIX = Marginalia.PREFIX + 'action-'; // prefix for
class names for actions (e.g. action-delete)
+Marginalia.C_ERRORBOX = Marginalia.PREFIX + 'errorbox'; // used for
displaying pop-up errors
+Marginalia.ID_RANGECARET = Marginalia.PREFIX + 'range-caret'; //
identifies caret used to show zero-length ranges
+
+// Preferences
+Marginalia.P_SHEET = 'annotations.sheet';
+Marginalia.P_SHOWANNOTATIONS = 'annotations.show';
+Marginalia.P_NOTEEDITMODE = 'annotations.note-edit-mode';
+Marginalia.P_SPLASH = 'annotations.splash';
+
+// Default values for annotation.sheet (other sheets are possible)
+Marginalia.SHEET_PUBLIC = 'public';
+Marginalia.SHEET_PRIVATE = 'private';
+
+// values for annotation.editing (field is deleted when not editing)
+Marginalia.EDIT_NOTE_FREEFORM = 'note freeform';
+Marginalia.EDIT_NOTE_KEYWORDS = 'note keywords';
+Marginalia.EDIT_LINK = 'link';
+
+
Marginalia.prototype.newEditor = function( annotation, editorName )
{
var f = editorName ? this.editors[ editorName ] : this.editors[ 'default'
];
@@ -238,8 +309,8 @@
return new FreeformNoteEditor( );
else if ( ! annotation || '' == annotation.getNote() )
{
- var pref = marginalia.preferences.getPreference( AN_NOTEEDITMODE_PREF );
- if ( pref == AN_EDIT_NOTE_KEYWORDS )
+ var pref = marginalia.preferences.getPreference(
Marginalia.P_NOTEEDITMODE );
+ if ( pref == Marginalia.EDIT_NOTE_KEYWORDS )
return new KeywordNoteEditor( );
else
return new FreeformNoteEditor( );
@@ -250,15 +321,24 @@
return new FreeformNoteEditor( );
}

-Marginalia.saveEditPrefs = function( marginalia, annotation, editor )
-{
- if ( editor.constructor == KeywordNoteEditor )
- marginalia.preferences.setPreference( AN_NOTEEDITMODE_PREF,
AN_EDIT_NOTE_KEYWORDS );
- else if ( editor.constructor == FreeformNoteEditor )
- marginalia.preferences.setPreference( AN_NOTEEDITMODE_PREF,
AN_EDIT_NOTE_FREEFORM );
-}
-
-
+Marginalia.defaultErrorCallback = function( object, operation, status,
text )
+{
+ var msgKey = object + '.' + operation;
+ var node = domutil.element( 'div', {
+ className: Marginalia.C_ERRORBOX }, [
+ domutil.element( 'h3', { },
+ getLocalized( 'service error title ' + msgKey ) ),
+ domutil.element( 'p', { },
+ getLocalized( 'service error ' + msgKey ) + ' ' ),
+ domutil.element( 'p', { },
+ getLocalized( 'service error ' + status ) ) ] );
+ document.body.appendChild( node );
+ setTimeout( function( ) {
+ jQuery( node ).fadeOut( 'def ', function( ) {
+ document.body.removeChild( node ); } ) },
+ 7000 );
+}
+

/**
* Could do this in the initializer, but by leaving it until now we can
avoid
@@ -267,22 +347,27 @@
Marginalia.prototype.listPosts = function( )
{
if ( ! this.posts )
- this.posts = PostPageInfo.getPostPageInfo( document );
+ {
+ this.posts = PostPageInfo.getPostPageInfo( document, this.selectors );
+ for ( var i = 0; i < this.posts.posts.length; ++i )
+ this.posts.posts[ i ].initMargin( this );
+ }
return this.posts;
}

-Marginalia.prototype.createAnnotation = function( annotation, f )
-{
- var f2 = null;
+Marginalia.prototype.createAnnotation = function( annotation, ok, fail )
+{
+ var ok2 = null;
if ( this.keywordService )
{
var keywordService = this.keywordService;
- f2 = function( url ) {
- f( url );
+ ok2 = function( url ) {
+ ok( url );
keywordService.refresh( );
};
}
- this.annotationService.createAnnotation( annotation, f2 ? f2 : f );
+
+ this.annotationService.createAnnotation( annotation, ok2 ? ok2 : ok, fail
);
}

Marginalia.prototype.updateAnnotation = function( annotation )
@@ -290,26 +375,34 @@
if ( annotation.hasChanged() )
{
var marginalia = this;
- var f = function( xml ) {
+ var ok = function( xml ) {
annotation.resetChanges();
if ( marginalia.keywordService )
marginalia.keywordService.refresh( );
};
- this.annotationService.updateAnnotation( annotation, f );
+ fail = function( status, text ) {
+ if ( marginalia.serviceErrorCallback )
+ marginalia.serviceErrorCallback( 'annotation', 'update', status, text
);
+ };
+ this.annotationService.updateAnnotation( annotation, ok, fail );
}
}

-Marginalia.prototype.deleteAnnotation = function( annotationId )
-{
- var f = null;
+Marginalia.prototype.deleteAnnotation = function( annotation, ok )
+{
+ var ok = null;
if ( this.keywordService )
{
var keywordService = this.keywordService;
- f = function( xml ) {
+ ok = function( xml ) {
keywordService.refresh( );
};
}
- this.annotationService.deleteAnnotation( annotationId, f );
+ fail = function( status, text ) {
+ if ( marginalia.serviceErrorCallback )
+ marginalia.serviceErrorCallback( 'annotation', 'delete', status, text );
+ };
+ this.annotationService.deleteAnnotation( annotation, ok, fail );
}


@@ -320,43 +413,53 @@
* apply to individual posts on a page. Unused, I removed them - they
added
* complexity because annotations needed to be stored but not displayed.
IMHO,
* the best way to do this is with simple dynamic CSS (using display:none).
- * TODO: If the url is a wildcard matching multiple posts, per block user
markers
- * won't show up as a result of calling this!
+ *
+ * params:
+ * - recent: fetch only recent annotations
+ * - callback: call this function before showing retrieved annotations,
if it
+ * returns true go ahead and show them. Useful for polling
+ * for updates.
*/
-Marginalia.prototype.showAnnotations = function( url, block )
+Marginalia.prototype.showAnnotations = function( url, params )
{
// Must set the class here so that annotations margins will expand so
that,
// in turn, any calculations done by the caller (e.g. to resize margin
// buttons) will take the correct size into account.
- domutil.addClass( document.body, AN_ANNOTATED_CLASS );
- if ( this.loginUserId == this.displayUserId || '' == this.displayUserId )
- domutil.addClass( document.body, AN_SELFANNOTATED_CLASS );
- // marginalia.hideAnnotations( );
+ domutil.addClass( document.body, Marginalia.C_ANNOTATED );
var marginalia = this;
- this.annotationService.listAnnotations( url, this.displayUserId, block,
- function(xmldoc) { _showAnnotationsCallback( marginalia, url, xmldoc,
true ) } );
-}
-
-Marginalia.prototype.showBlockAnnotations = function( url, block )
-{
- // TODO: Push down calculations must be repaired where new annotations
are added.
- // Ideally this would happen automatically.
- var marginalia = this;
- this.annotationService.listAnnotations( url, null, block,
- function(xmldoc) { _showAnnotationsCallback( marginalia, url, xmldoc,
false, true ) } );
-}
+ this.annotationService.listAnnotations( url, this.sheet, {
+ mark: 'read',
+ since: marginalia.lastUpdate,
+ recent: params && params.recent },
+ function( xmldoc ) {
+ var annotations = parseAnnotationXml( xmldoc );
+ if ( annotations.length && ( ! params || ! params.callback ||
params.callback( annotations ) ) )
+ marginalia.loadAnnotations( annotations );
+ } );
+}
+

/**
- * This is the callback function called by listAnnotations when data first
comes back
- * from the server.
+ * Process annotations returned from the server.
+ * The new annotations are appended to the annotationCache
+ * (so that we don't have two update interval going on at once)
+ * and an interval is created (if necessary) to actually
+ * show them. Showing can be slow, so we're doing coop
+ * multitasking to give control back to the browser.
*/
-function _showAnnotationsCallback( marginalia, url, xmldoc, doBlockMarkers
)
-{
- domutil.addClass( document.body, AN_ANNOTATED_CLASS );
- if ( marginalia.loginUserId == marginalia.displayUserId || '' ==
marginalia.displayUserId )
- domutil.addClass( document.body, AN_SELFANNOTATED_CLASS );
- marginalia.annotationXmlCache = xmldoc;
- _annotationDisplayCallback( marginalia, url, doBlockMarkers );
+Marginalia.prototype.loadAnnotations = function( annotations )
+{
+ domutil.addClass( document.body, Marginalia.C_ANNOTATED );
+ this.annotationCache = this.annotationCache.concat( annotations );
+ // Danger: coopLoadAnnotations says it needs annotations
+ // sorted by url. So should sort the new array. #geof#
+ if ( ! this.loadInterval )
+ {
+ var marginalia = this;
+ this.loadInterval = setInterval( function( ) {
+ marginalia.coopLoadAnnotations( );
+ }, Marginalia.COOP_TIMEOUT );
+ }
}

/**
@@ -365,116 +468,115 @@
* all in that time, it will call setTimeout to trigger continued display
later. This
* is basically a way to implement cooperative multitasking so that if
many annotations
* need to be displayed the browser won't lock up.
- *
- * It will also fetch and display block markers if that feature is set and
the doBlockMarkers
- * flag is true. Block markers must be displayed *after* the annotations
so that their
- * height will be correct.
- *
- * - noCountIncrement: set this if the annotation is simlpy being
redisplayed, and is not part
- * of an attempt to fetch and display more annotations
*/
-function _annotationDisplayCallback( marginalia, callbackUrl,
doBlockMarkers, noCountIncrement )
+Marginalia.prototype.coopLoadAnnotations = function( )
{
var startTime = new Date( );
- var curTime;
-
- // Parse the XML, if that hasn't been done already
- if ( marginalia.annotationXmlCache )
- {
- marginalia.annotationCache = parseAnnotationXml(
marginalia.annotationXmlCache );
- delete marginalia.annotationXmlCache;
- curTime = new Date( );
- if ( curTime - startTime >= AN_COOP_MAXTIME )
- {
- setTimeout( function() { _annotationDisplayCallback( marginalia,
callbackUrl, doBlockMarkers ) }, AN_COOP_TIMEOUT );
- return;
- }
- }

// Display cached annotations
// Do this by merging the new annotations with those already displayed
// For this to work, annotations must be sorted by URL
- var annotations = marginalia.annotationCache;
- if ( annotations )
- {
- var url = null; // there may be annotations for multiple URLs; this
is the current one
- var post = null; // post for the current url
- var notes = null; // current notes element
- var nextNode = null;
- for ( var annotation_i = 0; annotation_i < annotations.length;
++annotation_i )
- {
- // Don't want to fail completely just because one or more annotations
are malformed
- if ( null != annotations[ annotation_i ] )
- {
- var annotation = annotations[ annotation_i ];
-
- // Determine whether we're moving on to a new post (hence a new note
list)
- if ( annotation.getUrl( ) != url )
- {
- // Margin height callback
- if ( post && marginalia.onMarginHeight )
- marginalia.onMarginHeight( post );
-
- url = annotation.getUrl( );
- post = marginalia.listPosts( ).getPostByUrl( url, marginalia.baseUrl
);
-
- // Find the first note in the list (if there is one)
- if ( post )
- {
- notes = post.getNotesElement( );
- nextNode = notes.firstCild;
- }
- else
- logError( 'Post not found for URL "' + url + "'" );
+ var url = null; // there may be annotations for multiple URLs; this is
the current one
+ var post = null; // post for the current url
+ var notes = null; // current notes element
+ var nextNode = null;
+
+ var mostRecent = null;
+ while ( this.annotationCacheIndex < this.annotationCache.length )
+ {
+ var annotation = this.annotationCache[ this.annotationCacheIndex ];
+ this.annotationCacheIndex += 1;
+
+ // Don't want to fail completely just because an annotation is malformed
+ if ( annotation )
+ {
+ // Determine whether we're moving on to a new post (hence a new note
list)
+ if ( annotation.getUrl( ) != url )
+ {
+ // Margin height callback
+ if ( post && marginalia.onMarginHeight )
+ marginalia.onMarginHeight( post );
+
+ url = annotation.getUrl( );
+ post = marginalia.listPosts( ).getPostByUrl( url, marginalia.baseUrl );
+
+ // Find the first note in the list (if there is one)
+ if ( post )
+ {
+ notes = post.getNotesElement( marginalia );
+ nextNode = notes.firstCild;
+ }
+ else
+ logError( 'Post not found for URL "' + url + "'" );
+ }
+
+ // The server shouldn't normally return URLs that not on this page, but
it
+ // could (e.g. if the target has been deleted). In that case, don't
crash!
+ if ( post )
+ {
+ // Find the position of the annotation by walking through the note list
+ // (binary search would be nice here, but not practical unless the
list is
+ // stored somewhere other than in the DOM - plus, since multiple
annotations
+ // are dealt with here at once, the speed hit shouldn't be too bad)
+ while ( nextNode )
+ {
+ if ( ELEMENT_NODE == nextNode.nodeType && nextNode[
Marginalia.F_ANNOTATION ] )
+ {
+ if ( annotation.compareRange( nextNode[ Marginalia.F_ANNOTATION ] )
< 0 )
+ break;
+ }
+ nextNode = nextNode.nextSibling;
}

- // The server shouldn't normally return URLs that not on this page,
but it
- // could (e.g. if the target has been deleted). In that case, don't
crash!
- if ( post )
- {
- // Find the position of the annotation by walking through the note
list
- // (binary search would be nice here, but not practical unless the
list is
- // stored somewhere other than in the DOM - plus, since multiple
annotations
- // are dealt with here at once, the speed hit shouldn't be too bad)
- while ( nextNode )
- {
- if ( ELEMENT_NODE == nextNode.nodeType && nextNode.annotation )
- {
- if ( annotation.compareRange( nextNode.annotation ) < 0 )
- break;
- }
- nextNode = nextNode.nextSibling;
- }
-
- // If the annotation is already present, increment its fetch count
- if ( ! noCountIncrement )
- annotation.fetchCount += 1;
- // Now insert before beforeNote
- var success = post.addAnnotation( marginalia, annotation, nextNode );
-
- if ( success && annotation.getUserId( ) == marginalia.loginUserId ||
marginalia.allowAnyUserPatch )
+ // Now insert before beforeNote
+ var success = post.addAnnotation( marginalia, annotation, nextNode );
+
+ // If the annotation was added it may need to be patched (to update
the XPathRange
+ // or SequenceRange to the current format)
+ if ( success )
+ {
+ if ( annotation.getUserId( ) == marginalia.loginUserId ||
marginalia.allowAnyUserPatch )
marginalia.patchAnnotation( annotation, post );
}
- }
-
- annotations[ annotation_i ] = null;
- if ( curTime - startTime >= AN_COOP_MAXTIME )
- break;
+ // If the highlight could not be located, try to fix the annotation
+ else
+ {
+ if ( annotation.getUserId( ) == marginalia.loginUserId ||
marginalia.allowAnyUserPatch )
+ marginalia.fixAnnotation( annotation, post );
+ }
+
+ // Use the incoming annotations to figure out how recent our last
update was.
+ // This info needs to come from the server so we have a single point
of truth
+ // about time, and doing this avoids having the server explicitly send
a
+ // last update value.
+ if ( ! this.lastUpdate || annotation.getUpdated( ) > this.lastUpdate )
+ this.lastUpdate = annotation.getUpdated( );
+ }
}

- if ( annotations.length == annotation_i )
- {
- delete marginalia.annotationCache;
- if ( doBlockMarkers && marginalia.showBlockMarkers )
- marginalia.showPerBlockUserCounts( callbackUrl );
- }
- else
- setTimeout( function() { _annotationDisplayCallback( marginalia,
callbackUrl, doBlockMarkers ) }, AN_COOP_TIMEOUT );
- }
- // Finally, reposition block markers, as the annotations may have altered
paragraph lengths
- else if ( doBlockMarkers && marginalia.showBlockMarkers )
- {
- marginalia.showPerBlockUserCounts( callbackUrl );
+ if ( Date( ) - startTime >= Marginalia.COOP_MAXTIME )
+ break;
+ }
+
+ if ( this.annotationCache.length == this.annotationCacheIndex )
+ {
+ this.annotationCache = [ ];
+ this.annotationCacheIndex = 0;
+ clearInterval( this.loadInterval );
+ this.loadInterval = null;
+
+ // Now that annotation display is complete, check whether there is
+ // a fragment identifier in the URL telling us to scroll to a
+ // particular annotation.
+ var url = '' + window.location;
+ var match = url.match( /#annotation@(\w+)$/ );
+ if ( match )
+ {
+ var id = Marginalia.ID_PREFIX + match[ 1 ];
+ var node = document.getElementById( id );
+ if ( node )
+ domutil.scrollWindowToNode( node, domutil.SCROLL_POS_CENTER );
+ }
}
}

@@ -542,17 +644,60 @@
post.repositionNotes( this, noteElement.nextSibling );

// Reposition block markers
- post.repositionBlockMarkers( this );
+// post.repositionBlockMarkers( this );
}
}
+
+/**
+ * Fix a broken annotation range by searching for the quote text
+ */
+Marginalia.prototype.fixAnnotation = function( annotation, post )
+{
+ while( window.find( annotation.getQuote( ) ) )
+ {
+ // the find function places a text range over the found text
+ var textRange = marginalia.getSelection( );
+ var contentElement = post.getContentElement( );
+ // if the found text is within the post then we're good to go
+ if ( ( domutil.isElementDescendant( textRange.startContainer,
contentElement )
+ || textRange.startContainer == contentElement )
+ && ( domutil.isElementDescendant( textRange.endContainer,
contentElement )
+ || textRange.endContainer == contentElement ) )
+ {
+ // Calculate the ranges
+ var wordRange = WordRange.fromTextRange( textRange, contentElement,
marginalia.skipContent );
+ var sequenceRange = wordRange.toSequenceRange( contentElement );
+ var xpathRange = wordRange.toXPathRange( contentElement );
+ annotation.setSequenceRange( sequenceRange );
+ annotation.setXPathRange( xpathRange );
+
+ // Update the annotation
+ marginalia.updateAnnotation( annotation, null );
+
+ // Show the highlight
+ post.showHighlight( marginalia, annotation );
+
+ // Replace the editable note display
+ post.removeNote( this, annotation );
+ var nextNode = post.getAnnotationNextNote( this, annotation );
+ noteElement = post.showNote( this, annotation, nextNode );
+ post.repositionNotes( this, noteElement.nextSibling );
+
+ // Reposition block markers
+// post.repositionBlockMarkers( this );
+ return true;
+ }
+ }
+ return false;
+}

/**
* Hide all annotations on the page
*/
Marginalia.prototype.hideAnnotations = function( )
{
- domutil.removeClass( document.body, AN_ANNOTATED_CLASS );
- domutil.removeClass( document.body, AN_SELFANNOTATED_CLASS );
+ domutil.removeClass( document.body, Marginalia.C_ANNOTATED );
+ domutil.removeClass( document.body, Marginalia.C_SELFANNOTATED );

var posts = this.listPosts( ).getAllPosts( );
for ( var i = 0; i < posts.length; ++i )
@@ -565,6 +710,28 @@
// normalizeSpace( post.element );
}
}
+
+
+/**
+ * Display a tip to the user in the margin
+ * current implementation only shows the tip in the top post
+ */
+Marginalia.prototype.showTip = function( tip, onclose )
+{
+ var posts = this.listPosts( ).getAllPosts( );
+ if ( posts.length >= 1 )
+ return posts[ 0 ].showTip( this, tip, onclose );
+ else
+ return null;
+}
+
+Marginalia.prototype.hideTip = function ( tipNode )
+{
+ var post = marginalia.posts.getPostByElement( tipNode );
+ if ( post )
+ post.hideTip( this, tipNode );
+}
+

/* *****************************
* Additions to Annotation class
@@ -573,9 +740,9 @@
/**
* Convenience method for getting the note element for a given annotation
*/
-Annotation.prototype.getNoteElement = function( )
-{
- return document.getElementById( AN_ID_PREFIX + this.getId() );
+Annotation.prototype.getNoteElement = function( marginalia )
+{
+ return document.getElementById( Marginalia.ID_PREFIX + this.getId() );
}


@@ -593,7 +760,8 @@
*/

/**
- * Add an annotation to the local annotation list and display.
+ * Add an annotation to the local annotation list and display, or
+ * replace an existing one that matches.
* Returns true if the annotation highlight was located successfully
*/
PostMicro.prototype.addAnnotation = function( marginalia, annotation,
nextNode, editor )
@@ -601,7 +769,7 @@
if ( ! nextNode )
nextNode = this.getAnnotationNextNote( marginalia, annotation );
// If the annotation is already displayed, remove the existing display
- var existing = annotation.getNoteElement( );
+ var existing = annotation.getNoteElement( marginalia );
if ( existing )
this.removeAnnotation( marginalia, annotation );
var quoteFound = this.showHighlight( marginalia, annotation );
@@ -626,8 +794,8 @@
var annotations = new Array( );
while ( null != child )
{
- if ( child.annotation )
- annotations[ annotations.length ] = child.annotation;
+ if ( child[ Marginalia.F_ANNOTATION ] )
+ annotations[ annotations.length ] = child[ Marginalia.F_ANNOTATION ];
child = child.nextSibling;
}
return annotations;
@@ -647,10 +815,10 @@
var annotations = new Array( );
while ( null != child )
{
- if ( child.annotation )
- {
- annotations[ annotations.length ] = child.annotation;
- child.annotation = null;
+ if ( child[ Marginalia.F_ANNOTATION ] )
+ {
+ annotations[ annotations.length ] = child[ Marginalia.F_ANNOTATION ];
+ child[ Marginalia.F_ANNOTATION ] = null;
}
notesElement.removeChild( child );
child = notesElement.firstChild;
@@ -659,7 +827,7 @@
var stripTest = function( tnode )
{ return micro.highlightStripTest( tnode, null ); };
domutil.stripMarkup( this.getContentElement( ), stripTest, true );
- domutil.removeClass( this.getElement( ), AN_ANNOTATED_CLASS );
+ domutil.removeClass( this.getElement( ), Marginalia.C_ANNOTATED );
return annotations;
}

@@ -672,10 +840,10 @@
this.removeHighlight( marginalia, annotation );

// Reposition markers if necessary
- if ( 'edit' == annotation.action )
- this.repositionBlockMarkers( marginalia );
-
- return null == next ? null : next.annotation;
+// if ( 'edit' == annotation.action )
+// this.repositionBlockMarkers( marginalia );
+
+ return null == next ? null : next[ Marginalia.F_ANNOTATION ];
}

/* ************************ Display Actions ************************ */
@@ -691,19 +859,19 @@
PostMicro.prototype.flagAnnotation = function( marginalia, annotation,
className, flag )
{
// Activate the note
- var noteNode = document.getElementById( AN_ID_PREFIX + annotation.getId()
);
+ var noteNode = document.getElementById( Marginalia.ID_PREFIX +
annotation.getId() );
if ( flag )
domutil.addClass( noteNode, className );
else
domutil.removeClass( noteNode, className );

// Activate the highlighted areas
- var highlights = domutil.childrenByTagClass( this.getContentElement( ),
null, AN_HIGHLIGHT_CLASS, null, null );
+ var highlights = domutil.childrenByTagClass( this.getContentElement( ),
null, Marginalia.C_HIGHLIGHT, null, null );
for ( var i = 0; i < highlights.length; ++i )
{
var node = highlights[ i ];
// Need to change to upper case in case this is HTML rather than XHTML
- if ( node.tagName.toUpperCase( ) == 'EM' && node.annotation ==
annotation )
+ if ( node.tagName.toUpperCase( ) == 'EM' && node[
Marginalia.F_ANNOTATION ] == annotation )
{
if ( flag )
domutil.addClass( node, className );
@@ -739,7 +907,7 @@
if ( marginalia.noteEditor )
{
// Focus on the text edit
- var noteElement = document.getElementById( AN_ID_PREFIX +
annotation.getId() );
+ var noteElement = document.getElementById( Marginalia.ID_PREFIX +
annotation.getId() );
// Sequencing here (with focus last) is important
this.repositionNotes( marginalia, noteElement.nextSibling );
marginalia.noteEditor.focus( );
@@ -769,8 +937,8 @@
this.addAnnotation( marginalia, annotation );
}

- this.flagAnnotation( marginalia, annotation, AN_EDITINGNOTE_CLASS, false
);
- domutil.removeClass( document.body, AN_EDITINGNOTE_CLASS );
+ this.flagAnnotation( marginalia, annotation, Marginalia.C_EDITINGNOTE,
false );
+ domutil.removeClass( document.body, Marginalia.C_EDITINGNOTE );
}


@@ -792,7 +960,7 @@
// Check the length of the note. If it's too long, do nothing, but
restore focus to the note
// (which is awkward, but we can't save a note that's too long, we can't
allow the note
// to appear saved, and truncating it automatically strikes me as an even
worse solution.)
- if ( marginalia.noteEditor.annotation.getNote().length > MAX_NOTE_LENGTH )
+ if ( marginalia.noteEditor.annotation.getNote().length >
marginalia.maxNoteLength )
{
alert( getLocalized( 'note too long' ) );
marginalia.noteEditor.focus( );
@@ -800,7 +968,7 @@
}

// Similarly for the length of a link
- if ( marginalia.noteEditor.annotation.getLink().length > MAX_LINK_LENGTH )
+ if ( marginalia.noteEditor.annotation.getLink().length >
Marginalia.MAX_LINK_LENGTH )
{
alert( getLocalized( 'link too long' ) );
marginalia.noteEditor.focus( );
@@ -816,9 +984,6 @@
return false;
}

- // Now that validation's complete, start storing things
- Marginalia.saveEditPrefs( marginalia, annotation, marginalia.noteEditor );
-
// Ensure the window doesn't scroll by saving and restoring scroll
position
var scrollY = domutil.getWindowYScroll( );
var scrollX = domutil.getWindowXScroll( );
@@ -827,7 +992,7 @@
this.stopEditing( marginalia, annotation );

// TODO: listItem is an alias for noteElement
- var listItem = document.getElementById( AN_ID_PREFIX + annotation.getId()
);
+ var listItem = document.getElementById( Marginalia.ID_PREFIX +
annotation.getId() );

// For annotations with links; insert, or substitute actions, must update
highlight also
if ( 'edit' == annotation.action && annotation.hasChanged( 'note' ) ||
annotation.hasChanged( 'link' ) )
@@ -842,25 +1007,31 @@
if ( annotation.isLocal )
{
var postMicro = this;
- var f = function( url ) {
+ // Callback for successful creation
+ var ok = function( url ) {
// update the annotation with the created ID
- var id = url.substring( url.lastIndexOf( '/' ) + 1 );
- annotation.setId( id );
+ annotation.setId( Annotation.idFromUrl( url ) );
annotation.resetChanges( );
annotation.isLocal = false;
- var noteElement = document.getElementById( AN_ID_PREFIX + '0' );
- noteElement.id = AN_ID_PREFIX + annotation.getId();
- var highlightElements = domutil.childrenByTagClass(
postMicro.getContentElement( ), 'em', AN_ID_PREFIX + '0', null, null );
+ var noteElement = document.getElementById( Marginalia.ID_PREFIX + '0' );
+ noteElement.id = Marginalia.ID_PREFIX + annotation.getId();
+ var highlightElements = domutil.childrenByTagClass(
postMicro.getContentElement( ), 'em', Marginalia.ID_PREFIX + '0', null,
null );
for ( var i = 0; i < highlightElements.length; ++i )
{
- domutil.removeClass( highlightElements[ i ], AN_ID_PREFIX + '0' );
- domutil.addClass( highlightElements[ i ], AN_ID_PREFIX +
annotation.getId() );
+ domutil.removeClass( highlightElements[ i ], Marginalia.ID_PREFIX
+ '0' );
+ domutil.addClass( highlightElements[ i ], Marginalia.ID_PREFIX +
annotation.getId() );
}
};
- annotation.setUrl( this.getUrl( ) );
-
+
+ var fail = function( status, text ) {
+ if ( marginalia.serviceErrorCallback )
+ marginalia.serviceErrorCallback( 'annotation', 'create', status, text
);
+ postMicro.deleteAnnotation( marginalia, annotation, false );
+ };
+
+ annotation.setUrl( this.getUrl( ) );
// IE may have made a relative URL absolute, which could cause problems
- if ( null != marginalia.baseUrl
+ if ( null != marginalia.baseUrl && annotation.url
&& annotation.url.substring( 0, marginalia.baseUrl.length ) ==
marginalia.baseUrl )
***The diff for this file has been truncated for email.***
=======================================
--- /marginalia-lib/trunk/marginalia/note-ui.js Wed May 30 14:19:12 2012
+++ /marginalia-lib/trunk/marginalia/note-ui.js Wed May 30 14:51:38 2012
@@ -11,7 +11,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
+ * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -26,31 +26,17 @@
* $Id$
*/

-AN_NOTES_CLASS = 'notes'; // the notes portion of a fragment
-AN_DUMMY_CLASS = 'dummy'; // used for dummy item in note list
-AN_QUOTENOTFOUND_CLASS = 'quote-error'; // note's corresponding highlight
region not found
-AN_NOTECOLLAPSED_CLASS = 'collapsed'; // only the first line of the note
shows
-AN_EDITCHANGED_CLASS = 'changed'; // indicates content of a text edit
changed
-
-MAX_NOTE_LENGTH = 250;
-MAX_NOTEHOVER_LENGTH = 24;
+Marginalia.C_DUMMY = Marginalia.PREFIX + 'dummy'; // used for dummy
item in note list
+Marginalia.C_QUOTENOTFOUND = Marginalia.PREFIX + 'quote-error'; // note's
corresponding highlight region not found
+Marginalia.C_NOTECOLLAPSED = Marginalia.PREFIX + 'collapsed'; // only the
first line of the note shows
+Marginalia.C_EDITCHANGED = Marginalia.PREFIX + 'changed'; // indicates
content of a text edit changed
+Marginalia.C_OTHERUSER = Marginalia.PREFIX + 'other-user';
+Marginalia.C_USERNAME = Marginalia.PREFIX + 'username'; // name (initials)
of user who created annotation
+Marginalia.C_RECENT = Marginalia.PREFIX + 'recent'; // this annotation is
recent

// Classes to identify specific controls
-AN_LINKBUTTON_CLASS = 'annotation-link';
-AN_ACCESSBUTTON_CLASS = 'annotation-access';
-AN_DELETEBUTTON_CLASS = 'annotation-delete';
-AN_EXPANDBUTTON_CLASS = 'expand-edit';
-AN_KEYWORDSCONTROL_CLASS = 'keywords';
-
-AN_SUN_SYMBOL = '\u25cb'; //'\u263c';
-//AN_SUN_SYMBOL = '\u26aa';
-AN_MOON_SYMBOL = '\u25c6'; //'\u2641';
-AN_LINK_ICON = '\u263c'; //'\u238b'; circle-arrow // \u2318 (point of
interest) \u2020 (dagger) \u203b (reference mark) \u238b (circle arrow)
-AN_LINK_EDIT_ICON = '\u263c'; //'\u238b'; circle-arrow// \u2021 (double
dagger)
-AN_COLLAPSED_ICON = '+'; // '\u25b7'; triangle
-AN_EXPANDED_ICON = '-'; // '\u25bd';
-AN_DELETE_ICON = '\u00d7';
-AN_LINKEDIT_LABEL = '\u263c'; // '\u238b'; circle-arrow
+Marginalia.C_LINKBUTTON = Marginalia.PREFIX + 'annotation-link';
+Marginalia.C_DELETEBUTTON = Marginalia.PREFIX + 'annotation-delete';


/**
@@ -63,14 +49,81 @@
// Make sure it has the additional annotation properties added
if ( ! this.notesElement )
{
- var t = domutil.childByTagClass( this.getElement( ), null,
AN_NOTES_CLASS, PostMicro.skipPostContent );
- this.notesElement = t.getElementsByTagName( 'ol' )[ 0 ];
+ var t = marginalia.selectors[ 'mia_notes' ].node( this.getElement( ) );
+ this.notesElement = 'ol' == t.tagName.toLowerCase( ) ? t :
t.getElementsByTagName( 'ol' )[ 0 ];
}
return this.notesElement;
}


- /**
+PostMicro.prototype.initMargin = function( marginalia )
+{
+ var margin = marginalia.selectors[ 'mia_notes' ].node( this.getElement( )
);
+
+ //var postId = this.getElement();
+// var margin = this.getNotesElement( marginalia );
+ margin.onmousedown = function( ) {
+ marginalia.cachedSelection = marginalia.getSelection( );
+// trace( null, 'cache selection: ' + marginalia.cachedSelection );
+ };
+ var post = this;
+ margin.onclick = function( event ) {
+ event = domutil.getEvent( event );
+ // only create an annotation if a) this click was on the margin itself,
+ // note a child node, and b) there is no edit in progress
+ if ( ! marginalia.noteEditor && domutil.getEventTarget( event ) ==
margin )
+ {
+// trace( null, 'clicked' );
+ createAnnotation( post );
+ domutil.stopPropagation( event );
+ }
+// trace( null, 'clear selection cache' );
+ marginalia.cachedSelection = null;
+ };
+}
+
+
+PostMicro.prototype.showTip = function( marginalia, tip, onclose )
+{
+ var notesElement = this.getNotesElement( marginalia );
+ var tipNode = domutil.element( 'li', {
+ className: Marginalia.PREFIX + 'tip'
+ }, tip );
+
+ var postMicro = this;
+ var f = function( ) {
+ postMicro.hideTip( marginalia, tipNode );
+ if ( onclose )
+ onclose( );
+ };
+
+ var controls = domutil.element( 'div', { className: 'controls' } );
+ // delete button
+ controls.appendChild( domutil.button( {
+ className: Marginalia.C_DELETEBUTTON,
+ title: getLocalized( 'delete tip button' ),
+ content: marginalia.icons[ 'delete' ],
+ onclick: f
+ } ) );
+ tipNode.appendChild( controls );
+
+ for ( var node = notesElement.firstChild; node && ! node[
Marginalia.F_ANNOTATION ]; node = node.nextSibling )
+ ;
+ notesElement.insertBefore( tipNode, node );
+ this.repositionSubsequentNotes( tipNode );
+ return tipNode;
+}
+
+PostMicro.prototype.hideTip = function( marginalia, tipNode )
+{
+ var notesElement = this.getNotesElement( marginalia );
+ var next = tipNode.nextSibling;
+ notesElement.removeChild( tipNode );
+ this.repositionNotes( marginalia, next );
+}
+
+
+/**
* Get the node that will follow this one once it is inserted in the node
list
* Slow, but necessary when the annotation has not yet been inserted in
the node list
* A return value of null indicates the annotation would be at the end of
the list
@@ -83,12 +136,13 @@
for ( var prevNode = notesElement.lastChild; null != prevNode; prevNode
= prevNode.previousSibling )
{
// In case it's a dummy list item or other
- if ( ELEMENT_NODE == prevNode.nodeType && prevNode.annotation )
- {
+ if ( ELEMENT_NODE == prevNode.nodeType && prevNode[
Marginalia.F_ANNOTATION ] )
+ {
+ var prevAnnotation = prevNode[ Marginalia.F_ANNOTATION ];
// Why on earth would this happen??
- if ( prevNode.annotation.getId() == annotation.getId() )
+ if ( prevAnnotation.getId() == annotation.getId() )
break;
- else if ( annotation.compareRange( prevNode.annotation ) >= 0 )
+ else if ( annotation.compareRange( prevAnnotation ) >= 0 )
break;
}
}
@@ -101,7 +155,7 @@
var nextNode;
for ( nextNode = notesElement.firstChild; nextNode; nextNode =
nextNode.nextSibling )
{
- if ( ELEMENT_NODE == nextNode.nodeType && nextNode.annotation )
+ if ( ELEMENT_NODE == nextNode.nodeType && nextNode[
Marginalia.F_ANNOTATION ] )
break;
}
return nextNode; // will be null if no annotations in the list
@@ -109,10 +163,10 @@
}


-PostMicro.prototype.getNoteId = function( annotation )
+PostMicro.prototype.getNoteId = function( marginalia, annotation )
{
// assert( typeof annotationId == 'number' );
- return AN_ID_PREFIX + annotation.getId();
+ return Marginalia.ID_PREFIX + annotation.getId();
}

/**
@@ -127,29 +181,40 @@
// Will need to align the note with the highlight.
// If the highlight is not found, then the quote doesn't match - display
// the annotation, but with an error and deactivate some behaviors.
- var highlightElement = domutil.childByTagClass( this.getContentElement(
), 'em', AN_ID_PREFIX + annotation.getId(), null );
+ var highlightElement = domutil.childByTagClass( this.getContentElement(
), 'em',
+ Marginalia.ID_PREFIX + annotation.getId(), null );
var quoteFound = highlightElement != null;

// Find or create the list item
- var noteElement = document.getElementById( this.getNoteId( annotation ) );
+ var noteElement = document.getElementById( this.getNoteId( marginalia,
annotation ) );
if ( noteElement )
{
trace( 'showNote', ' Note already present' );
this.clearNote( marginalia, annotation );
if ( ! quoteFound )
- domutil.setClass( noteElement, AN_QUOTENOTFOUND_CLASS, quoteFound );
+ domutil.setClass( noteElement, Marginalia.C_QUOTENOTFOUND, quoteFound );
}
else
{
trace( 'showNote', ' Create new note' );
+
+ // Is this a recent post?
+ // Don't flag this for one's own notes - that would be cluttered and
confusing.
+ // Currently, moodle does not seem to be storing anything in the
forum_read table, so this doesn't work
+ var isRecent = annotation.isRecent( );
+// isRecent = isRecent && marginalia.loginUserId && annotation.getUserId(
) != marginalia.loginUserId;
+ var className = ( quoteFound ? '' : Marginalia.C_QUOTENOTFOUND ) + ' '
+ + ( isRecent && marginalia.enableRecentFlag ? Marginalia.C_RECENT : ''
);
+
var noteElement = domutil.element( 'li', {
- id: AN_ID_PREFIX + annotation.getId(),
- className: quoteFound ? '' : AN_QUOTENOTFOUND_CLASS,
- annotation: annotation } );
+ id: Marginalia.ID_PREFIX + annotation.getId(),
+ className: className,
+ title: ' ' } );
+ noteElement[ Marginalia.F_ANNOTATION ] = annotation;

// Align the note (takes no account of subsequent notes, which is OK
because this note
// hasn't yet been filled out)
- var alignElement = highlightElement ? highlightElement :
this.getNoteAlignElement( annotation );
+ var alignElement = highlightElement ? highlightElement :
this.getNoteAlignElement( marginalia, annotation );
if ( null != alignElement )
{
// The margin must be relative to a preceding list item.
@@ -166,7 +231,7 @@
// If there is no preceding note, create a dummy
if ( null == prevNode )
{
- prevNode = domutil.element( 'li', { className: AN_DUMMY_CLASS } );
+ prevNode = domutil.element( 'li', { className: Marginalia.C_DUMMY } );
noteList.insertBefore( prevNode, nextNode );
}

@@ -189,7 +254,7 @@

/**
* Show a note in the margin
- * Regular annotation display with control buttons (delete, access, link)
+ * Regular annotation display with control buttons (delete, link)
* Call showEdit if you want to show an editor instead
*/
PostMicro.prototype.showNote = function( marginalia, annotation, nextNode )
@@ -200,13 +265,14 @@

// Mark the action
if ( marginalia.showActions && annotation.getAction() )
- domutil.addClass( noteElement, AN_ACTIONPREFIX_CLASS +
annotation.getAction() );
+ domutil.addClass( noteElement, Marginalia.C_ACTIONPREFIX +
annotation.getAction() );

// Calculating these parameters here makes it much easier to implement
note display
var params = {
isCurrentUser: null != marginalia.loginUserId && annotation.getUserId( )
== marginalia.loginUserId,
linkingEnabled: marginalia.editors[ 'link' ] ? true : false,
- quoteFound: null != domutil.childByTagClass( this.getContentElement(
), 'em', AN_ID_PREFIX + annotation.getId(), null),
+ quoteFound: null != domutil.childByTagClass( this.getContentElement(
), 'em',
+ Marginalia.ID_PREFIX + annotation.getId(), null),
keyword: marginalia.keywordService ?
marginalia.keywordService.getKeyword( annotation.getNote() ) : null
};

@@ -219,13 +285,13 @@
Marginalia.prototype.bindNoteBehaviors = function( annotation,
parentElement, behaviors )
{
var marginalia = this;
- var postMicro = domutil.nestedFieldValue( parentElement, AN_POST_FIELD );
+ var postMicro = domutil.nestedFieldValue( parentElement,
Marginalia.F_POST );

// Apply behavior rules
// These are separated out to insulate display implementations from
changes to internal APIs
for ( var i = 0; i < behaviors.length; ++i )
{
- var nodes = cssQuery( behaviors[ i ][ 0 ], parentElement );
+ var nodes = jQuery( behaviors[ i ][ 0 ], parentElement );
if ( nodes.length == 1 )
{
var node = nodes[ 0 ];
@@ -245,7 +311,6 @@
{
// Functions to associate with events (click etc.)
var eventMappings = {
- access: _toggleAnnotationAccess,
'delete': _deleteAnnotation,
// edit: _editAnnotation,
save: _saveAnnotation };
@@ -295,34 +360,23 @@
}
}

- var aButton = null;
if ( params.isCurrentUser )
{
// add the link button
if ( params.linkingEnabled )
{
controls.appendChild( domutil.button( {
- className: AN_LINKBUTTON_CLASS,
+ className: Marginalia.C_LINKBUTTON,
title: getLocalized( 'annotation link button' ),
- content: AN_LINK_EDIT_ICON
+ content: marginalia.icons[ 'linkEdit' ]
} ) );
}

- // add the access button
- if ( marginalia.showAccess )
- {
- controls.appendChild( domutil.button( {
- className: AN_ACCESSBUTTON_CLASS,
- title: getLocalized( annotation.getAccess() ==
AN_PUBLIC_ACCESS ? 'public annotation' : 'private annotation' ),
- content: annotation.getAccess() == AN_PUBLIC_ACCESS ? AN_SUN_SYMBOL :
AN_MOON_SYMBOL
- } ) );
- }
-
// add the delete button
controls.appendChild( domutil.button( {
- className: AN_DELETEBUTTON_CLASS,
+ className: Marginalia.C_DELETEBUTTON,
title: getLocalized( 'delete annotation button' ),
- content: AN_DELETE_ICON
+ content: marginalia.icons[ 'delete' ]
} ) );

// KLUDGE ALERT #geof#
@@ -342,9 +396,60 @@
}

// add the text content
+ // Substitutes urls with actual hyperlinks if possible
+ // You may wonder why I do this with the DOM, rather using a regex to
+ // insert <a> tags in the string. I do this because the DOM is safer - I
+ // *know* I can't possibly create any elements or entities other than
those
+ // I explicitly code here. Escaping may be easy, but with the DOM it
can't
+ // be forgotten. I only see two potential security risks here: 1)
allowing
+ // bad url schemes (so I limit to http and https), 2) linking to dangerous
+ // sites. The latter is unavoidable, and is presumably already a risk on
+ // any site that allows user content. At least displaying the domain
name
+ // (a la Slashdot) gives some indication of safety.
var noteText = domutil.element( 'p', {
- content: annotation.getNote() ? annotation.getNote() : '\xa0'
- } );
+ className: annotation.getNote() ? '' : 'empty',
+ title: params.isCurrentUser ? getLocalized( 'edit annotation click'
) : '',
+ content: annotation.getNote() ?
annotation.getNote() : '\u261c'/*'\u203b'*/ } );
+ domutil.urlize( noteText );
+
+/* while ( tail.length > 0 )
+ {
+ var match = tail.match( /https?:\/\/([a-zA-Z0-9\.-]+)(?:\/(?:[^
]*[a-zA-Z0-9\/#])?)?/ );
+ var url = null;
+ if ( match )
+ url = match[ 0 ];
+ else
+ {
+ match = tail.match( /(www\.[a-zA-Z0-9\.-]+\.[a-zA-Z]{2,4})/ );
+ if ( match )
+ url = 'http://' + match[ 1 ] + '/';
+ }
+ if ( url )
+ {
+ var head = tail.substr( 0, match.index );
+ if ( head.length )
+ noteText.appendChild( document.createTextNode( head ) );
+ domain = match[ 1 ];
+ //if ( 'www.' == domain.substr( 0, 4 ) )
+ // domain = domain.substr( 4 );
+ noteText.appendChild( domutil.element( 'a', {
+ href: url,
+ title: getLocalized( 'visit annotation link' ),
+ onclick: domutil.stopPropagation,
+ content: domain }));
+ tail = tail.substr( head.length + url.length );
+ }
+ else
+ {
+ noteText.appendChild( document.createTextNode( tail ) );
+ break;
+ }
+ }
+ */
+
+// var noteText = domutil.element( 'p', {
+// content: annotation.getNote() ? annotation.getNote() : '\u203b'
+// } );
var titleText = null;

if ( ! params.quoteFound || ! annotation.getSequenceRange( ) )
@@ -358,14 +463,20 @@
// This doesn't belong to the current user, add the name of the owning
user
if ( ! params.isCurrentUser )
{
- domutil.addClass( noteElement, 'other-user' );
- // If multiple users' notes are being displayed, show the owner's name
-// if ( annotation.getUserId( ) != marginalia.displayUserId )
-// {
- noteText.insertBefore( domutil.element( 'span', {
- className: 'username',
- content: annotation.getUserName( ) + ': ' } ), noteText.firstChild );
-// }
+ domutil.addClass( noteElement, Marginalia.C_OTHERUSER );
+ var username = annotation.getUserName( );
+ if ( annotation.isRecent( ) )
+ titleText = getLocalized( 'note user recent title' );
+ else
+ titleText = getLocalized( 'note user title' );
+ titleText += annotation.getUpdated( ).toString( 'yyyy-MM-d H:mm' ); //
tt' );
+ // The space is not part of the note, nor is it part of the username.
This is important so
+ // that it doesn't get underlined or otherwise styled with the username.
+ noteText.insertBefore( document.createTextNode( ' ' ),
noteText.firstChild );
+ noteText.insertBefore( domutil.element( 'span', {
+ className: Marginalia.C_USERNAME,
+ title: titleText,
+ content: username + ':' } ), noteText.firstChild );
}
noteElement.appendChild( noteText );

@@ -373,9 +484,8 @@
if ( params.isCurrentUser )
{
marginalia.bindNoteBehaviors( annotation, noteElement, [
- [ 'button.annotation-link', { click: 'edit link' } ],
- [ 'button.annotation-access', { click: 'access' } ],
- [ 'button.annotation-delete', { click: 'delete' } ],
+ [ 'button.' + Marginalia.C_LINKBUTTON, { click: 'edit link' } ],
+ [ 'button.' + Marginalia.C_DELETEBUTTON, { click: 'delete' } ],
[ 'p', { click: 'edit' } ]
] );
}
@@ -402,9 +512,9 @@
{
editor.annotationOrig = marginalia.noteEditor.annotationOrig;
if ( marginalia.noteEditor.save )
- marginalia.noteEditor.save( );
+ marginalia.noteEditor.save( marginalia );
if ( marginalia.noteEditor.clear )
- marginalia.noteEditor.clear( );
+ marginalia.noteEditor.clear( marginalia );
if ( marginalia.noteEditor.annotation != annotation )
_saveAnnotation( );
}
@@ -412,8 +522,8 @@
if ( ! marginalia.noteEditor || marginalia.noteEditor.noteElement !=
noteElement )
{
// Since we're editing, set the appropriate class on body
- domutil.addClass( document.body, AN_EDITINGNOTE_CLASS );
- this.flagAnnotation( marginalia, annotation, AN_EDITINGNOTE_CLASS, true
);
+ domutil.addClass( document.body, Marginalia.C_EDITINGNOTE );
+ this.flagAnnotation( marginalia, annotation, Marginalia.C_EDITINGNOTE,
true );

setEvents = true;
editor.annotationOrig = clone( annotation );
@@ -426,24 +536,53 @@
editor.bind( marginalia, this, annotation, noteElement );

marginalia.noteEditor = editor;
- editor.show( );
+ editor.show( marginalia );
this.repositionNotes( marginalia, this.nextSibling );
- editor.focus( );
+ editor.focus( marginalia );
window.scrollTo( scrollX, scrollY );

// If anywhere outside the note area is clicked, the annotation will be
saved.
// Beware serious flaws in IE's model (see addAnonBubbleEventListener
code for details),
// so this only works because I can figure out which element was clicked
by looking for
// AN_EDITINGNOTE_CLASS.
+
+ /*
+ * FIXED: Turns out a <script/></script> combination caused this! Wow.
+ * Removed closing slash on first tag and problem went away.
+ *
+ // Another problem: Safari 4 scrolls the page down 240px between the
+ // firing of textInput and keyup. There appears to be no way to stop
this. So
+ // this event handler checks for the scroll, and undoes it if necessary.
+ // Bleargh. Thanks Safari.
+ var yscroll = domutil.getWindowYScroll( );
+ var fixSafariScroll = function( event )
+ {
+ if ( domutil.getWindowYScroll() != yscroll )
+ {
+ var xscroll = domutil.getWindowXScroll();
+ window.scrollTo( xscroll, yscroll );
+ window.status = 'Apparently a Safari bug scrolls the window when you
type. Marginalia is trying to undo the damage.';
+ }
+ }
+ */
+
if ( setEvents )
{
addEvent( document.documentElement, 'click', _saveAnnotation );
addEvent( noteElement, 'click', domutil.stopPropagation );
+ addEvent( noteElement, 'keypress', domutil.stopPropagation );
+ // addEvent( noteElement, 'keyup', fixSafariScroll );
}

return noteElement;
}

+
+function debugSafariScroll( event )
+{
+ trace( null, 'Key event ' + event.type + ', vertical position ' +
domutil.getWindowYScroll() );
+ domutil.stopPropagation( event );
+}

/**
* Freeform margin note editor
@@ -461,50 +600,52 @@
this.noteElement = noteElement;
}

-FreeformNoteEditor.prototype.clear = function( )
+FreeformNoteEditor.prototype.clear = function( marginalia )
{
this.editNode = null;
}

-FreeformNoteEditor.prototype.save = function( )
+FreeformNoteEditor.prototype.save = function( marginalia )
{
this.annotation.setNote( this.editNode.value );
}

-FreeformNoteEditor.prototype.show = function( )
+FreeformNoteEditor.REMAINING_THRESHOLD = 50;
+FreeformNoteEditor.prototype.show = function( marginalia )
{
var postMicro = this.postMicro;
var marginalia = this.marginalia;
var annotation = this.annotation;
var noteElement = this.noteElement;
-
- // If keywords are enabled, show the expand/collapse control
- if ( this.marginalia.keywordService )
- {
- var f = function( event ) {
- postMicro.showNoteEditor( marginalia, annotation, new
KeywordNoteEditor( ) );
- };
- this.noteElement.appendChild( domutil.button( {
- className: AN_EXPANDBUTTON_CLASS,
- title: getLocalized( 'annotation expand edit button' ),
- content: AN_EXPANDED_ICON,
- onclick: f } ) );
- }
-
+ var noteText = annotation.getNote( );
+
// Create the edit box
this.editNode = document.createElement( "textarea" );
this.editNode.rows = 3;
- this.editNode.appendChild( document.createTextNode( annotation.getNote()
) );
+ this.editNode.appendChild( document.createTextNode( noteText ) );
+ this.noteElement.appendChild( this.editNode );
+
+ // Create the place for showing how many characters remain
+ var threshold = marginalia.maxNoteLength -
FreeformNoteEditor.REMAINING_THRESHOLD;
+ var remainingNode = domutil.element( 'p', {
+ className: Marginalia.PREFIX + 'charsremaining',
+ style: 'display:none' } );
+ noteElement.appendChild( remainingNode, null );
+ FreeformNoteEditor.showCharsRemaining( marginalia, postMicro,
this.editNode, remainingNode, noteElement, prompt );

// Set focus after making visible later (IE requirement; it would be OK
to do it here for Gecko)
+ var editNode = this.editNode;
+ var prompt = getLocalized( 'chars remaining' );
+ var onkey = function( e ) {
+ FreeformNoteEditor.showCharsRemaining( marginalia, postMicro, editNode,
remainingNode, noteElement, prompt );
+ _editChangedKeyup( e );
+ };
this.editNode.annotationId = this.annotation.getId();
addEvent( this.editNode, 'keypress', _editNoteKeypress );
- addEvent( this.editNode, 'keyup', _editChangedKeyup );
-
- this.noteElement.appendChild( this.editNode );
+ addEvent( this.editNode, 'keyup', onkey );
}

-FreeformNoteEditor.prototype.focus = function( )
+FreeformNoteEditor.prototype.focus = function( marginalia )
{
this.editNode.focus( );
// Yeah, ain't IE great. You gotta focus TWICE for it to work. I don't
@@ -513,85 +654,25 @@
this.editNode.focus( );
}

-
-/**
- * Keyword margin note editor
- */
-function KeywordNoteEditor( )
-{
- this.selectNode = null;
-}
-
-KeywordNoteEditor.prototype.bind = FreeformNoteEditor.prototype.bind;
-
-KeywordNoteEditor.prototype.clear = function( )
-{
- this.selectNode = null;
-}
-
-KeywordNoteEditor.prototype.save = function( )
-{
- if ( -1 != this.selectNode.selectedIndex )
- this.annotation.setNote( this.selectNode.options[
this.selectNode.selectedIndex ].value );
-}
-
-KeywordNoteEditor.prototype.show = function( )
-{
- var postMicro = this.postMicro;
- var marginalia = this.marginalia;
- var annotation = this.annotation;
- var noteElement = this.noteElement;
-
- // Show the expand/collapse control
- this.noteElement.appendChild( domutil.button( {
- className: AN_EXPANDBUTTON_CLASS,
- title: getLocalized( 'annotation collapse edit button' ),
- content: AN_COLLAPSED_ICON } ) );
-
- this.selectNode = document.createElement( 'select' );
-
- this.selectNode.className = AN_KEYWORDSCONTROL_CLASS;
- var keywords = marginalia.keywordService.keywords;
- addEvent( this.selectNode, 'keypress', _editNoteKeypress );
-
- // See if the current value of the note is a keyword
- if ( ! marginalia.keywordService.isKeyword( annotation.getNote() ) &&
annotation.getNote() )
- {
- // First option is the freeform edit value for the note
- var opt = document.createElement( 'option' );
- opt.appendChild( document.createTextNode(
- annotation.getNote().length > 12 ? annotation.getNote().substring( 0,
12 ) : annotation.getNote() ) );
- opt.setAttribute( 'value', annotation.getNote() );
- this.selectNode.appendChild( opt );
- }
-
- var value = annotation.getNote();
- for ( var i = 0; i < keywords.length; ++i )
- {
- var keyword = keywords[ i ];
- opt = document.createElement( 'option' );
- if ( value == keyword.name )
- opt.setAttribute( 'selected', 'selected' );
- opt.appendChild( document.createTextNode( keyword.name ) );
- opt.setAttribute( 'value', keyword.name );
- opt.setAttribute( 'title', keyword.description );
- this.selectNode.appendChild( opt );
- }
-
- this.noteElement.appendChild( this.selectNode );
-
- marginalia.bindNoteBehaviors( annotation, noteElement, [
- [ '.'+AN_EXPANDBUTTON_CLASS, { click: 'edit freeform' } ]
- ] );
+FreeformNoteEditor.showCharsRemaining = function( marginalia, postMicro,
editNode, remainingNode, noteElement, prompt )
+{
+ var threshold = marginalia.maxNoteLength -
FreeformNoteEditor.REMAINING_THRESHOLD;
+ var reposition = false;
+ if ( editNode.value.length > threshold && ! editNode.mia_showremaining )
+ {
+ jQuery( remainingNode ).css( 'display', 'block' );
+ editNode.mia_showremaining = true;
+ reposition = true;
+ }
+ if ( editNode.mia_showremaining )
+ {
+ var remaining = Math.max( marginalia.maxNoteLength -
editNode.value.length, 0 );
+ jQuery( remainingNode ).text( remaining + ' ' + prompt );
+ if ( reposition )
+ postMicro.repositionSubsequentNotes( marginalia, noteElement );
+ }
}

-KeywordNoteEditor.prototype.focus = function( )
-{
- this.selectNode.focus( );
- if ( 'exploder' == domutil.detectBrowser( ) )
- this.selectNode.focus( );
-}
-

/**
* YUI Autocomplete margin note editor
@@ -610,26 +691,22 @@
YuiAutocompleteNoteEditor.prototype.save =
FreeformNoteEditor.prototype.save;
YuiAutocompleteNoteEditor.prototype.focus =
FreeformNoteEditor.prototype.focus;

-YuiAutocompleteNoteEditor.prototype.show = function( )
+YuiAutocompleteNoteEditor.prototype.show = function( marginalia )
{
var postMicro = this.postMicro;
var marginalia = this.marginalia;
var annotation = this.annotation;
var noteElement = this.noteElement;
+ var noteText = annotation.getNote( );

// Create the edit box
this.editNode = document.createElement( "textarea" );
this.editNode.rows = 3;
- this.editNode.appendChild( document.createTextNode( annotation.getNote()
) );
+ this.editNode.appendChild( document.createTextNode( noteText ) );

// Create the query results box
this.queryNode = domutil.element( 'div' );

- // Set focus after making visible later (IE requirement; it would be OK
to do it here for Gecko)
- this.editNode.annotationId = this.annotation.getId();
- addEvent( this.editNode, 'keypress', _editNoteKeypress );
- addEvent( this.editNode, 'keyup', _editChangedKeyup );
-
var wrapperNode = domutil.element( 'div', { className: 'yui-skin-sam' } );
wrapperNode.appendChild( this.editNode );
wrapperNode.appendChild( this.queryNode );
@@ -671,6 +748,27 @@

wrapperNode.style.height = String( wrapperHeight ) + 'px';
}
+
+ // Set focus after making visible later (IE requirement; it would be OK
to do it here for Gecko)
+ // Create the place for showing how many characters remain
+ var editNode = this.editNode;
+ var prompt = getLocalized( 'chars remaining' );
+ var threshold = marginalia.maxNoteLength -
FreeformNoteEditor.REMAINING_THRESHOLD;
+ var remainingNode = domutil.element( 'p', {
+ className: Marginalia.PREFIX + 'charsremaining',
+ style: 'display:none' } );
+ noteElement.appendChild( remainingNode, null );
+ FreeformNoteEditor.showCharsRemaining( marginalia, postMicro, editNode,
remainingNode, noteElement, prompt );
+
+ // Set focus after making visible later (IE requirement; it would be OK
to do it here for Gecko)
+ var onkey = function( e ) {
+ FreeformNoteEditor.showCharsRemaining( marginalia, postMicro, editNode,
remainingNode, noteElement, prompt );
+ _editChangedKeyup( e );
+ };
+
+ this.editNode.annotationId = this.annotation.getId();
+ addEvent( this.editNode, 'keypress', _editNoteKeypress );
+ addEvent( this.editNode, 'keyup', onkey );
}


@@ -681,14 +779,17 @@
*/
PostMicro.prototype.positionNote = function( marginalia, annotation )
{
- var note = annotation.getNoteElement( );
+ var note = annotation.getNoteElement( marginalia );
while ( null != note )
{
- var alignElement = this.getNoteAlignElement( annotation );
+ var alignElement = this.getNoteAlignElement( marginalia, annotation );
// Don't push down if no align element was found
if ( null != alignElement )
{
- var pushdown = this.calculateNotePushdown( marginalia,
note.previousSibling, alignElement );
+ // #geof# doesn't work right. Current fix is to have no intervening
text nodes:
+ // <div class="mia_margin"><ol><li
class="mia_dummyfirst"></li></ol></div>
+ var prev = jQuery( note ).prev( ); // note.previousSibling
+ var pushdown = this.calculateNotePushdown( marginalia, prev,
alignElement );
note.style.marginTop = ( pushdown > 0 ? String( pushdown ) : '0' )
+ 'px';
}
note = note.nextSibling;
@@ -698,10 +799,10 @@
/**
* Determine where an annotation note should be aligned vertically
*/
-PostMicro.prototype.getNoteAlignElement = function( annotation )
+PostMicro.prototype.getNoteAlignElement = function( marginalia, annotation
)
{
// Try to find the matching highlight element
- var alignElement = domutil.childByTagClass( this.getContentElement(
), 'em', AN_ID_PREFIX + annotation.getId(), null );
+ var alignElement = domutil.childByTagClass( this.getContentElement(
), 'em', Marginalia.ID_PREFIX + annotation.getId(), null );
// If there is no matching highlight element, pick the paragraph. Prefer
XPath range representation.
if ( null == alignElement && annotation.getXPathRange( ) )
alignElement = annotation.getXPathRange( ).start.getReferenceElement(
this.getContentElement( ) );
@@ -743,21 +844,24 @@

PostMicro.prototype.repositionNote = function( marginalia, element )
{
- var annotation = element.annotation;
+ var annotation = element[ Marginalia.F_ANNOTATION ];
if ( annotation )
{
- var alignElement = this.getNoteAlignElement( annotation );
+ var alignElement = this.getNoteAlignElement( marginalia, annotation );
if ( alignElement )
{
var goback = false;
- var previous = element.previousSibling;
+
+ for ( var previous = element.previousSibling; previous; previous =
previous.previousSibling )
+ if ( ELEMENT_NODE == previous.nodeType )
+ break;
var pushdown = this.calculateNotePushdown( marginalia, previous,
alignElement );

/* uncomment this to automatically collapse some notes: *
// If there's negative pushdown, check whether the preceding note also
has pushdown
if ( pushdown < 0
&& previous
- && previous.annotation
+ && previous[ Marginalia.F_ANNOTATION ]
&& ! hasClass( previous, AN_NOTECOLLAPSED_CLASS )
&& previous.pushdown
&& previous.pushdown < 0 )
@@ -766,7 +870,7 @@
// Go back two elements and collapse, then restart pushdown
// calculations at the previous element.
var collapseElement = previous.previousSibling;
- if ( collapseElement && collapseElement.annotation )
+ if ( collapseElement && collapseElement[ Marginalia.F_ANNOTATION ] )
{
addClass( collapseElement, AN_NOTECOLLAPSED_CLASS );
element = previous;
@@ -779,7 +883,7 @@
if ( ! goback )
{
element.style.marginTop = ( pushdown > 0 ? String( pushdown ) : '0' )
+ 'px';
- domutil.removeClass( element, AN_NOTECOLLAPSED_CLASS );
+ domutil.removeClass( element, Marginalia.C_NOTECOLLAPSED );
element.pushdown = pushdown;
}
}
@@ -795,9 +899,9 @@
{
for ( var note = firstNote; note; note = note.nextSibling )
{
- if ( ELEMENT_NODE == note.nodeType && note.annotation )
- {
- var alignElement = this.getNoteAlignElement( note.annotation );
+ if ( ELEMENT_NODE == note.nodeType && note[ Marginalia.F_ANNOTATION ] )
+ {
+ var alignElement = this.getNoteAlignElement( marginalia, note[
Marginalia.F_ANNOTATION ] );
if ( alignElement )
{
var pushdown = this.calculateNotePushdown( marginalia,
note.previousSibling, alignElement );
@@ -817,11 +921,13 @@
*/
PostMicro.prototype.removeNote = function( marginalia, annotation )
{
- var listItem = annotation.getNoteElement( );
+ var listItem = annotation.getNoteElement( marginalia );
var next = domutil.nextByTagClass( listItem, 'li' );
listItem.parentNode.removeChild( listItem );
- listItem.annotation = null; // dummy item won't have this field
+ listItem[ Marginalia.F_ANNOTATION ] = null; // dummy item won't have this
field
domutil.clearEventHandlers( listItem, true );
+// if ( next )
+// this.repositionNotes( marginalia, next );
return next;
}

@@ -831,7 +937,7 @@
*/
PostMicro.prototype.clearNote = function( marginalia, annotation )
{
- var note = annotation.getNoteElement( );
+ var note = annotation.getNoteElement( marginalia );
domutil.clearEventHandlers( note, true, true );
while ( note.firstChild )
note.removeChild( note.firstChild );
@@ -846,8 +952,8 @@
{
var marginalia = window.marginalia;

- var post = domutil.nestedFieldValue( this, AN_POST_FIELD );
- var annotation = domutil.nestedFieldValue( this, AN_ANNOTATION_FIELD );
+ var post = domutil.nestedFieldValue( this, Marginalia.F_POST );
+ var annotation = domutil.nestedFieldValue( this, Marginalia.F_ANNOTATION
);

// If an annotation is already being edited and it's not *this*
annotation,
// return (don't stop propagation)
@@ -873,8 +979,8 @@
function _editNoteKeypress( event )
{
var target = domutil.getEventTarget( event );
- var post = domutil.nestedFieldValue( target, AN_POST_FIELD );
- var annotation = domutil.nestedFieldValue( target, AN_ANNOTATION_FIELD );
+ var post = domutil.nestedFieldValue( target, Marginalia.F_POST );
+ var annotation = domutil.nestedFieldValue( target,
Marginalia.F_ANNOTATION );
if ( event.keyCode == 13 )
{
post.saveAnnotation( window.marginalia, annotation );
@@ -894,12 +1000,13 @@
*/
function _editChangedKeyup( event )
{
+ var marginalia = window.marginalia;
var target = domutil.getEventTarget( event );
- var annotation = domutil.nestedFieldValue( target, AN_ANNOTATION_FIELD );
+ var annotation = domutil.nestedFieldValue( target,
Marginalia.F_ANNOTATION );
if ( target.value != annotation.note )
- domutil.addClass( target, AN_EDITCHANGED_CLASS );
+ domutil.addClass( target, Marginalia.C_EDITCHANGED );
else
- domutil.removeClass( target, AN_EDITCHANGED_CLASS );
+ domutil.removeClass( target, Marginalia.C_EDITCHANGED );
}


@@ -930,54 +1037,8 @@
function _deleteAnnotation( event )
{
event.stopPropagation( );
- var post = domutil.nestedFieldValue( this, AN_POST_FIELD );
- var annotation = domutil.nestedFieldValue( this, AN_ANNOTATION_FIELD );
- post.deleteAnnotation( window.marginalia, annotation );
-}
-
-/**
- * Click the expand/collapse edit button
- */
-function _expandEdit( event )
-{
- event.stopPropagation( );
- var target = domutil.getEventTarget( event );
- var annotation = domutil.nestedFieldValue( this, AN_ANNOTATION_FIELD );
- var post = domutil.nestedFieldValue( this, AN_POST_FIELD );
- var noteElement = domutil.parentByTagClass( target, 'li', null, false,
null );
- var expandControl = domutil.childByTagClass( noteElement, 'button',
AN_EXPANDBUTTON_CLASS, null );
- while ( expandControl.firstChild )
- expandControl.removeChild( expandControl.firstChild );
-
- if ( AN_EDIT_NOTE_KEYWORDS == annotation.editing )
- {
- expandControl.appendChild( document.createTextNode( AN_EXPANDED_ICON ) );
- post.showNoteEditor( marginalia, annotation, new FreeformNoteEditor( ) );
- }
- else
- {
- expandControl.appendChild( document.createTextNode( AN_COLLAPSED_ICON )
);
- post.showNoteEditor( marginalia, annotation, new KeywordNoteEditor( ) );
- }
-}
-
-/**
- * Click annotation access button
- */
-function _toggleAnnotationAccess( event )
-{
- event.stopPropagation( );
- var target = domutil.getEventTarget( event );
-
- var annotation = domutil.nestedFieldValue( this, AN_ANNOTATION_FIELD );
- var accessButton = target;
-
- annotation.setAccess( annotation.getAccess()
== 'public' ? 'private' : 'public' );
- window.marginalia.updateAnnotation( annotation, null );
- while ( accessButton.firstChild )
- accessButton.removeChild( accessButton.firstChild );
- accessButton.appendChild( document.createTextNode( annotation.getAccess()
== 'public' ? AN_SUN_SYMBOL : AN_MOON_SYMBOL ) );
- accessButton.setAttribute( 'title', annotation.getAccess() == 'public' ?
- getLocalized( 'public annotation' ) : getLocalized( 'private annotation'
) );
+ var post = domutil.nestedFieldValue( this, Marginalia.F_POST );
+ var annotation = domutil.nestedFieldValue( this, Marginalia.F_ANNOTATION
);
+ post.deleteAnnotation( window.marginalia, annotation,
marginalia.warnDelete );
}

=======================================
--- /marginalia-lib/trunk/marginalia/post-micro.js Wed May 30 14:19:12 2012
+++ /marginalia-lib/trunk/marginalia/post-micro.js Wed May 30 14:51:38 2012
@@ -14,7 +14,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
+ * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -29,65 +29,55 @@
* $Id$
*/

-// These class names will change once there's a microformat standard.
-PM_POST_CLASS = 'hentry'; // this is an addressable fragment for
annotation
-PM_CONTENT_CLASS = 'entry-content'; // the content portion of a fragment
-PM_TITLE_CLASS = 'entry-title'; // the title of an annotated fragment
-PM_AUTHOR_CLASS = 'author'; // the author of the fragment
-PM_DATE_CLASS = 'published'; // the creation/modification date of the
fragment
-PM_URL_REL = 'bookmark'; // the url of this fragment (uses rel rather
than class attribute)
-
/*
* This class keeps track of PostMicro stuff on a page
* Initially that information was integrated into individual DOM nodes
(especially
* as PostMicro objects), but because of memory leak problems I'm moving
it here.
*/
-function PostPageInfo( doc )
+function PostPageInfo( doc, selectors )
{
this.doc = doc;
this.posts = new Array( );
this.postsById = new Object( );
this.postsByUrl = new Object( );
+ this.selectors = selectors;
this.IndexPosts( doc.documentElement );
}
+

/**
* In order to avoid creating multiple instances for a given document,
* keep a cache in the window. The linear search here shouldn't be a
problem
* as I don't expect more than one to exist - but just in case, it's there.
*/
-PostPageInfo.getPostPageInfo = function( doc )
+PostPageInfo.cachedPostPageInfos = [ ];
+PostPageInfo.getPostPageInfo = function( doc, selectors )
{
var info;
- if ( window.PostPageInfos )
- {
- for ( var i = 0; i < window.PostPageInfos.length; ++i )
- {
- info = PostPageInfos[ i ];
- if ( info.doc == doc )
- return info;
- }
- }
- else
- window.PostPageInfos = [ ];
- info = new PostPageInfo( doc );
- window.PostPageInfos[ window.PostPageInfos.length ] = info;
+ for ( var i = 0; i < PostPageInfo.cachedPostPageInfos.length; ++i )
+ {
+ info = PostPageInfo.cachedPostPageInfos[ i ];
+ if ( info.doc == doc && info.selectors == selectors)
+ return info;
+ }
+ info = new PostPageInfo( doc, selectors );
+ PostPageInfo.cachedPostPageInfos.push( info );
return info;
}

PostPageInfo.prototype.IndexPosts = function( root )
{
- var posts = domutil.childrenByTagClass( root, null, PM_POST_CLASS, null,
PostMicro.skipPostContent );
+ var posts = this.selectors[ 'post' ].nodes( root );
for ( var i = 0; i < posts.length; ++i )
{
var postElement = posts[ i ];
var post = new PostMicro( this, postElement );
this.posts[ this.posts.length ] = post;
- if ( null != posts[ i ].id && '' != posts[ i ].id )
+ if ( null != post.getId( ) && '' != post.getId( ) )
this.postsById[ posts[ i ].id ] = post;
if ( null != post.getUrl( ) && '' != post.getUrl( ) )
this.postsByUrl[ post.getUrl( ) ] = post;
- postElement.post = post;
+ postElement[ Marginalia.F_POST ] = post;
}
}

@@ -96,6 +86,23 @@
return this.postsById[ id ];
}

+/**
+ * Get a post that is the parent of a given element
+ */
+PostPageInfo.prototype.getPostByElement = function( element )
+{
+ // Inefficient. Probably not an issue as it isn't called that often.
+ // Hard to fix when using selectors for everything (can't just walk up
+ // to parents and check for a given ID). Alternative would be to set
+ // a field on posts when indexing, then look for that.
+ for ( var i = 0; i < this.posts.length; ++i )
+ {
+ if ( domutil.isElementDescendant( element, this.posts[ i ].getElement( )
) )
+ return this.posts[ i ];
+ }
+ return null;
+}
+
/*
* Return a post with a matching URL or, if that does not exist, try
stripping baseUrl off the passed Url
*/
@@ -114,56 +121,20 @@
{
return this.posts;
}
-
-PostPageInfo.prototype.getPostMicro = function( element )
-{
- var post = null;
-
- if ( element.post )
- post = element.post;
- else
- {
- var postElement = null;
- for ( var node = element; node; node = node.parentNode )
- {
- if ( ! postElement && ELEMENT_NODE == node.nodeType &&
domutil.hasClass( node, PM_POST_CLASS ) )
- postElement = node;
- else if ( PostMicro.skipPostContent( node ) )
- postElement = null;
- }
- if ( postElement )
- {
- if ( ! postElement.post )
- postElement.post = new PostMicro( this, postElement );
- post = postElement.post;
- }
- }
- return post;
-}
-

/*
* Post Class
* This is attached to the root DOM node of a post (not the document node,
rather the node
- * with the appropriate class and ID for a post). It stores references to
child nodes
+ * that matches a post selector). It stores references to child nodes
* containing relevant metadata. The class also provides a place to hang
useful functions,
* e.g. for annotation or smart copy support.
*/
function PostMicro( postInfo, element )
{
// Point the post and DOM node to each other
+ this.postInfo = postInfo;
this._element = element;
}
-
-/*
- * For ignoring post content when looking for specially tagged nodes, so
that authors
- * of that content (i.e. users) can't mess things up.
- */
-PostMicro.skipPostContent = function( node )
-{
- return ( ELEMENT_NODE == node.nodeType && domutil.hasClass( node,
PM_CONTENT_CLASS ) );
-}
-

/*
* Accessor for related element
@@ -181,8 +152,10 @@
if ( ! this._fetchedTitle )
{
// The title
- var metadata = domutil.childByTagClass( this._element, null,
PM_TITLE_CLASS, PostMicro.skipPostContent );
- this._title = metadata == null ? '' : domutil.getNodeText( metadata );
+ if ( this.postInfo.selectors[ 'post_title' ] )
+ this._title = this.postInfo.selectors[ 'post_title' ].value(
this._element );
+ else
+ this._title = null;
this._fetchedTitle = true;
}
return this._title;
@@ -193,8 +166,10 @@
if ( ! this._fetchedAuthorId )
{
// The author
- metadata = domutil.childByTagClass( this._element, null,
PM_AUTHOR_CLASS, PostMicro.skipPostContent );
- this._authorId = metadata == null ? '' : metadata.getAttribute( 'title'
);
+ if ( this.postInfo.selectors[ 'post_authorid' ] )
+ this._authorId = this.postInfo.selectors[ 'post_authorid' ].value(
this._element );
+ else
+ this._authorId = null;
this._fetchedAuthorId = true;
}
return this._authorId;
@@ -205,8 +180,10 @@
if ( ! this._fetchedAuthorName )
{
// The author
- metadata = domutil.childByTagClass( this._element, null,
PM_AUTHOR_CLASS, PostMicro.skipPostContent );
- this._authorName = metadata == null ? '' : domutil.getNodeText( metadata
);
+ if ( this.postInfo.selectors[ 'post_author' ] )
+ this._authorName = this.postInfo.selectors[ 'post_author' ].value(
this._element );
+ else
+ this._authorName = null;
this._fetchedAuthorName = true;
}
return this._authorName;
@@ -216,12 +193,9 @@
{
if ( ! this._fetchedDate )
{
- metadata = domutil.childByTagClass( this._element, 'abbr',
PM_DATE_CLASS, PostMicro.skipPostContent );
- if ( null == metadata )
- this._date = null;
- else
- {
- var s = metadata.getAttribute( 'title' );
+ if ( this.postInfo.selectors[ 'post_date' ] )
+ {
+ var s = this.postInfo.selectors[ 'post_date' ].value( this._element,
true );
if ( null == s )
this._date = null;
else
@@ -235,20 +209,38 @@
this._date = new Date( matches[1], matches[2]-1, matches[3],
matches[4], matches[5] );
}
}
+ else
+ this._date = null;
this._fetchedDate = true;
}

return this._date;
}
+
+PostMicro.prototype.getId = function( )
+{
+ if ( ! this._fetchedId )
+ {
+ this._id = this.postInfo.selectors[ 'post_id' ].node( this._element );
+ }
+ return this._id;
+}

PostMicro.prototype.getUrl = function( baseUrl )
{
if ( ! this._fetchedUrl )
{
// The node containing the url
- metadata = domutil.childAnchor( this._element, PM_URL_REL,
PostMicro.skipPostContent );
- if ( metadata )
- this._url = metadata.getAttribute( 'href' );
+ if ( this.postInfo.selectors[ 'post_url' ] )
+ this._url = this.postInfo.selectors[ 'post_url' ].value( this._element
);
+ // Otherwise grab the request url, but strip it of any fragment
identifier
+ else
+ {
+ this._url = String( window.location );
+ var parts = this._url.split( '#' );
+ if ( parts.length > 1 )
+ this._url = parts[ 0 ];
+ }
this._fetchedUrl = true;
}
return ( baseUrl && this._url && this._url.substring( 0, baseUrl.length )
== baseUrl )
@@ -267,7 +259,7 @@
{
// The node containing the content
// Any offsets (e.g. as used by annotations) are from the start of this
node's children
- this._contentElement = domutil.childByTagClass( this._element, null,
PM_CONTENT_CLASS );
+ this._contentElement = this.postInfo.selectors[ 'post_content' ].node(
this._element );
}
return this._contentElement;
}
=======================================
--- /marginalia-lib/trunk/marginalia/prefs.js Wed May 30 14:19:12 2012
+++ /marginalia-lib/trunk/marginalia/prefs.js Wed May 30 14:51:38 2012
@@ -9,7 +9,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
+ * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -24,13 +24,6 @@
* $Id$
*/

-AN_USER_PREF = 'annotations.user';
-AN_SHOWANNOTATIONS_PREF = 'annotations.show';
-AN_NOTEEDITMODE_PREF = 'annotations.note-edit-mode';
-AN_SPLASH_PREF = 'annotations.splash';
-SMARTCOPY_PREF = 'smartcopy';
-
-
/**
* Preferences creation
*/
=======================================
--- /marginalia-lib/trunk/marginalia/ranges.js Wed May 30 14:19:12 2012
+++ /marginalia-lib/trunk/marginalia/ranges.js Wed May 30 14:51:38 2012
@@ -16,7 +16,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
+ * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -91,7 +91,7 @@

// First apply the offset
if ( TEXT_NODE != node.nodeType && startOffset > 0)
- node = node.childNodes.item( startOffset );
+ node = node.childNodes.item( startOffset - 1 );

var foundOther = false;
var walker = new DOMWalker( node );
@@ -128,7 +128,7 @@

// First apply the offset
if ( TEXT_NODE != endContainer.nodeType && endOffset > 0 )
- node = node.childNodes.item( endOffset );
+ node = node.childNodes.item( endOffset - 1 );

var foundOther = false;
var walker = new DOMWalker( node, true );
@@ -1417,12 +1417,22 @@

/**
* Get the text inside a TextRange
- * While the built-in toString() method would do this, we need to skip
content
- * (such as smart copy text). This is in fact designed to work with
smartcopy, so there
- * are certain cases it may not handle. This also assumes that the range
points to
- * text nodes at the start and end (otherwise walking won't work).
+ * All whitespace (including that resulting from breaking elements) is
reduced to a single space.
+ * While the built-in toString() method would do this, we need the ability
to skip
+ * content (e.g. marginalia insertions). There are certain cases this may
not handle.
+ * This also assumes that the range points to text nodes at the start and
end (otherwise
+ * walking won't work).
+ * mode can be one of:
+ * 'text' - text content only (default)
+ * 'breaking-tags' - embed breaking tags (no attributes)
+ * 'tags' - embed all tags (no attributes)
+ * If tags are embedded, breaking tags are always preceeded/folowed by a
space. Thus tags can be stripped
+ * and &amp; &lt; &gt; replaced and the result will be the same as 'text'
mode.
*/
-function getTextRangeContent( range, fskip )
+RANGETEXT_TEXT = 0;
+RANGETEXT_BREAKING = 1;
+RANGETEXT_TAGS = 2;
+function getTextRangeContent( range, fskip, mode )
{
var s;
// Special case
@@ -1436,9 +1446,26 @@
while ( null != walker.node && walker.node != range.endContainer )
{
if ( TEXT_NODE == walker.node.nodeType )
- s += walker.node.nodeValue;
- else if ( ELEMENT_NODE == walker.node.nodeType &&
domutil.isBreakingElement( walker.node.tagName ) )
- s += ' ';
+ {
+ if ( RANGETEXT_TEXT == mode )
+ s += walker.node.nodeValue;
+ else
+ s +=
walker.node.nodeValue.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
+ }
+ else if ( ELEMENT_NODE == walker.node.nodeType )
+ {
+ if ( domutil.isBreakingElement( walker.node.tagName ) )
+ {
+ if ( RANGETEXT_TEXT == mode )
+ s += ' ';
+ else if ( RANGETEXT_BREAKING == mode || RANGETEXT_TAGS == mode )
+ s += ( walker.startTag ? ' <' : '</' ) + walker.node.tagName + (
walker.startTag ? '>' : '> ' );
+ }
+ else if ( RANGETEXT_TAGS == mode )
+ {
+ s += '<' + ( walker.startTag ? '' : '/' ) + walker.node.tagName + '>';
+ }
+ }
walker.walk( ! fskip( walker.node ) );
}

=======================================
--- /marginalia-lib/trunk/marginalia/rest-annotate.js Wed May 30 14:19:12
2012
+++ /marginalia-lib/trunk/marginalia/rest-annotate.js Wed May 30 14:51:38
2012
@@ -12,7 +12,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
+ * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -27,49 +27,25 @@
* $Id$
*/

-// If this is true, uses paths like annotate/nnn
-// if false, use paths like annotation/annotate.php?id=nnn
-ANNOTATION_NICE_URLS = false;
-
-
-/**
- * Oops. This didn't conform to the URI spec - all those nice juice
characters are
- * reserved. So now all it does is replace %20 with + to make URIs easier
to read.
- *
- * I'm tired of the standard Javascript encodeURIComponent encoding slashes
- * and colons in query parameters. This makes debugging information
difficult
- * to read, and there's really no point to it (at least for URI
parameters).
- * This function uses encodeURIComponent, then converts back some
translations
- * to make everything easier to read and debug.
- */
-function encodeURIParameter( s )
-{
- s = encodeURIComponent( s );
-// s = s.replace( /%2[fF]/g, '/' );
-// s = s.replace( /%3[aA]/g, ':' );
- s = s.replace( /%20/g, '+' );
-// s = s.replace( /%5[bB]/g, '[' );
-// s = s.replace( /%5[dD]/g, ']' );
-// s = s.replace( /%2[cC]/g, ',' );
-// s = s.replace( /%3[bB]/g, ';' );
- return s;
-}
-

/**
* Initialize the REST annotation service
*/
function RestAnnotationService( serviceUrl, features )
{
- this.serviceUrl = serviceUrl;
- this.niceUrls = false;
this.noPutDelete = false;

+ // Fixed service URL. Use this if not using nice URLs.
+ if ( 'string' == typeof( serviceUrl ) )
+ this.urlTemplate = new restutil.UrlTemplate( serviceUrl );
+ else
+ this.urlTemplate = new restutil.UrlTemplateDictionary( serviceUrl );
+
if ( features )
{
for ( feature in features )
{
- value = features[ feature ];
+ var value = features[ feature ];
switch ( feature )
{
// Name of cookie to use for preventing cross-site request forgery
@@ -77,23 +53,26 @@
this.csrfCookie = value;
break;

- // Use nice service URLs (currently unsupported)
- case 'niceUrls':
- this.niceUrls = value;
- break;
-
- // Use HTTP POST instead of PUT and DELETE. In this case, the
operation can be determined as follows:
+ // Use HTTP POST instead of PUT and DELETE. In this case, the
operation could be determined as follows:
// create: no id or search parameters in URL, body contains new
annotation data
// delete: id in URL with no body
// update: id in URL, body contains new annotation data
// bulkUpdate: search parameters in URL, body contains substitution
data
+ // But that's a bad idea, as it rules out certain future possible
parameters to calls.
// Why not pass method=PUT or method=DELETE in body or URL?
// - in URL is incorrect as this does not help identify the resource
// - in body is incorrect if mime type is not
application/x-www-url-encoded; makes no sense in XML body
- case noPutDelete:
+ case 'noPutDelete':
this.noPutDelete = value;
break;

+ // include curuser as get parameter to requests
+ // should be set to actual user ID
+ // cheap way to do simple logging
+ case 'sendCurUser':
+ this.sendCurUser = value;
+ break;
+
default:
if ( typeof( this[ feature ] ) != 'undefined' )
throw 'Attempt to override feature: ' + feature;
@@ -103,216 +82,120 @@
}
}
}
-
-
-/**
- * Fetch a list of annotated blocks
- */
-RestAnnotationService.prototype.listBlocks = function( url, f )
-{
- var serviceUrl = this.serviceUrl + '?format=blocks&url=' +
encodeURIParameter( url );
-
- // For demo debugging only
- if ( window.marginalia && window.marginalia.userInRequest )
- serviceUrl += '&curuser=' + encodeURIParameter(
window.marginalia.loginUserId );
-
- var xmlhttp = domutil.createAjaxRequest( );
- xmlhttp.open( 'GET', serviceUrl );
- //xmlhttp.setRequestHeader( 'Accept', 'application/xml' );
- xmlhttp.onreadystatechange = function( ) {
- if ( xmlhttp.readyState == 4 ) {
- if ( xmlhttp.status == 200 ) {
- if ( null != f )
- {
- trace( 'block-users-xml', "listBlocks result:\n" +
xmlhttp.responseText );
- // alert( serviceUrl + "\n" + xmlhttp.responseText );
- f( xmlhttp.responseXML );
- }
- }
- else {
- trace( "listBlocks Server request failed with code " + xmlhttp.status
+ ":\n" + serviceUrl );
- }
- xmlhttp = null;
- }
- }
- // Decode the URI to make it easier to read and debug
- trace( 'annotation-service', "AnnotationService.listBlocks " + decodeURI(
serviceUrl ));
- xmlhttp.send( null );
-}
-

/**
* Fetch a list of annotations from the server
*/
-RestAnnotationService.prototype.listAnnotations = function( url, userid,
block, f )
-{
- // exclude content to lighten the size across the wire
- var serviceUrl = this.serviceUrl;
- serviceUrl += '?format=atom';
- if ( block )
- serviceUrl += '&block=' + encodeURIParameter( block );
- if ( userid )
- serviceUrl += '&user=' + encodeURIParameter( userid );
- serviceUrl += '&url=' + encodeURIParameter( url );
-
- // For demo debugging only
- if ( window.marginalia && window.marginalia.userInRequest )
- serviceUrl += '&curuser=' + encodeURIParameter(
window.marginalia.loginUserId );
-
- var xmlhttp = domutil.createAjaxRequest( );
- xmlhttp.open( 'GET', serviceUrl );
- //xmlhttp.setRequestHeader( 'Accept', 'application/xml' );
- xmlhttp.onreadystatechange = function( ) {
- if ( xmlhttp.readyState == 4 ) {
- if ( xmlhttp.status == 200 ) {
- if ( null != f )
- {
- trace( 'list-annotations-xml', "listAnnotations result:\n" +
xmlhttp.responseText );
- // alert( serviceUrl + "\n" + xmlhttp.responseText );
- f( xmlhttp.responseXML );
- }
- }
- else {
- trace( "ListAnnotations Server request failed with code " +
xmlhttp.status + ":\n" + serviceUrl );
- }
- xmlhttp = null;
- }
- }
+// Recent change (2009-09-29): userid replaced by sheet. This filters
which
+// annotations are fetched.
+RestAnnotationService.prototype.listAnnotations = function( url, sheet,
params, ok, fail )
+{
+ var block = params.block;
+ var mark = params.mark;
+ var since = params.since;
+
+ var serviceUrl = this.urlTemplate.match( [
+ [ 'sheet', sheet, true ],
+ [ 'mark', mark ],
+ [ 'since', since ? o2s.format( since, 'yyyy-mm-ddTHH:ii:ss' ) : '' ],
+ [ 'format', 'atom' ],
+ [ 'url', url ],
+ [ 'curuser', this.sendCurUser, this.sendCurUser ]
+ ], 'listAnnotations' );
+ if ( ! serviceUrl )
+ throw "No matching service URL template for listAnnotations.";
+
+ fail2 = function( status, text ) {
+ logError( "AnnotationService.listAnnotations failed with code " + status
+ ":\n" + serviceUrl + "\n" + text );
+ if ( fail )
+ fail( status, text );
+ };
+ restutil.getResource( serviceUrl, ok, fail2, { okXml: true } );
trace( 'annotation-service', "AnnotationService.listAnnotations " +
decodeURI( serviceUrl ) );
- xmlhttp.send( null );
}

/**
* Create an annotation on the server
* When successful, calls a function f with one parameter: the URL of the
created annotation
*/
-RestAnnotationService.prototype.createAnnotation = function( annotation, f
)
-{
- var serviceUrl = this.serviceUrl;
-
- // For demo debugging only
- if ( window.marginalia && window.marginalia.userInRequest )
- serviceUrl += '?curuser=' + encodeURIParameter(
window.marginalia.loginUserId );
-
- var body
- = 'url=' + encodeURIParameter( annotation.getUrl() )
- + '&note=' + encodeURIParameter( annotation.getNote() )
- + '&access=' + encodeURIParameter( annotation.getAccess() )
- + '&quote=' + encodeURIParameter( annotation.getQuote() )
- + '&quote_title=' + encodeURIParameter( annotation.getQuoteTitle() )
- + '&quote_author_id=' + encodeURIParameter(
annotation.getQuoteAuthorId() )
- + '&quote_author_name=' + encodeURIParameter(
annotation.getQuoteAuthorName() )
- + '&link=' + encodeURIParameter( annotation.getLink() )
- + '&userid=' + encodeURIParameter( annotation.getUserId() );
- // userid shouldn't be trusted by the server of course, except for demo
applications for
- // which it can be useful.
-
- if ( annotation.getAction() )
- body += '&action=' + encodeURIParameter (annotation.getAction() );
- if ( annotation.getSequenceRange( ) )
- body += '&sequence-range=' + encodeURIParameter(
annotation.getSequenceRange( ).toString( ) );
- if ( annotation.getXPathRange( ) )
- body += '&xpath-range=' + encodeURIParameter( annotation.getXPathRange(
).toString( ) );
- if ( annotation.getLinkTitle( ) )
- + '&linkTitle=' + encodeURIParameter( annotation.getLinkTitle( ) );
-
- // Cross-site request forgery protection (if present)
- if ( this.csrfCookie )
- body += '&' + encodeURIComponent( this.csrfCookie ) + '=' +
encodeURIParameter( readCookie( this.csrfCookie ) );
-
- // May need to pass method name instead of using PUT or DELETE
- if ( this.noPutDelete )
- serviceUrl += '&method=POST';
-
- var xmlhttp = domutil.createAjaxRequest( );
-
- xmlhttp.open( 'POST', serviceUrl, true );
-
xmlhttp.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded;
charset=UTF-8' );
- //xmlhttp.setRequestHeader( 'Accept', 'application/xml' );
- xmlhttp.setRequestHeader( 'Content-length', body.length );
- xmlhttp.onreadystatechange = function( ) {
- if ( xmlhttp.readyState == 4 ) {
- // No need for Safari hack, since Safari can't create annotations
anyway.
- if ( xmlhttp.status == 201 ) {
- var url = xmlhttp.getResponseHeader( 'Location' );
- if ( null != f )
- {
- trace( 'annotation-service', 'Create annotation body: ' +
xmlhttp.responseText );
- f( url );
- }
- }
- else {
- logError( "AnnotationService.createAnnotation failed with code " +
xmlhttp.status + ":\n" + serviceUrl + "\n" + xmlhttp.responseText );
- }
- xmlhttp = null;
- }
- }
+RestAnnotationService.prototype.createAnnotation = function( annotation,
ok, fail )
+{
+ // Small flaw here: the url gets sent both in the query string *and* in
the
+ // body when niceurls are not in use. When the URLs are nice, it can be
part of
+ // the URL path. Though that limits to annotations only of the current
site.
+ // Hmmm.
+ var serviceUrl = this.urlTemplate.match( [
+ [ 'url', annotation.getUrl( ) ],
+ [ 'method', 'POST', this.noPutDelete ],
+ [ 'curuser', this.sendCurUser, this.sendCurUser ]
+ ], 'createAnnotation' );
+ if ( ! serviceUrl )
+ throw "No matching service URL template for createAnnotation.";
+
+ var params = [
+ [ 'url', annotation.getUrl( ), true ],
+ [ 'note', annotation.getNote( ), true ],
+ [ 'sheet', annotation.getSheet( ), true ],
+ [ 'quote', annotation.getQuote( ), true ],
+ [ 'quote_title', annotation.getQuoteTitle( ) ],
+ [ 'quote_author_id', annotation.getQuoteAuthorId( ) ],
+ [ 'quote_author_name', annotation.getQuoteAuthorName( ) ],
+ [ 'link', annotation.getLink( ), true ],
+ [ 'userid', annotation.getUserId( ), true ], // not trustworthy, but
good for demo apps
+ [ 'action', annotation.getAction( ) ],
+ [ 'sequence-range', annotation.getSequenceRange( ) ?
annotation.getSequenceRange( ).toString( ) : '' ],
+ [ 'xpath-range', annotation.getXPathRange( ) ? annotation.getXPathRange(
).toString( ) : '' ],
+ [ 'link_title' , annotation.getLinkTitle( ) ],
+ [ 'csrf', readCookie( this.csrfCookie ), this.csrfCookie ]
+ ];
+ var body = restutil.queryArgsToString( params );
+
+ fail2 = function( status, text ) {
+ logError( "AnnotationService.createAnnotation failed with code " +
status + ":\n" + serviceUrl + "\n" + text );
+ if ( fail )
+ fail( status, text );
+ };
+ restutil.postResource( serviceUrl, body, ok, fail2 );
trace( 'annotation-service', "AnnotationService.createAnnotation " +
decodeURI( serviceUrl ) + "\n" + body );
- xmlhttp.send( body );
}

/**
* Update an annotation on the server
* Only updates the fields that have changed
*/
-RestAnnotationService.prototype.updateAnnotation = function( annotation, f
)
-{
- var serviceUrl = this.serviceUrl;
- serviceUrl += this.niceUrls ? ( '/' + annotation.getId() ) : ( '?id=' +
annotation.getId() );
-
- // For demo debugging only
- if ( window.marginalia && window.marginalia.userInRequest )
- serviceUrl += ( this.niceUrls ? '?' : '&' )
- + 'curuser=' + encodeURIParameter( window.marginalia.loginUserId );
-
- var body = '';
- if ( annotation.hasChanged( 'note' ) )
- body = 'note=' + encodeURIParameter( annotation.getNote() );
- if ( annotation.hasChanged( 'access' ) )
- body += ( body == '' ? '' : '&' ) + 'access=' + encodeURIParameter(
annotation.getAccess() );
- if ( annotation.hasChanged( 'link' ) )
- body += ( body == '' ? '' : '&' ) + 'link=' + encodeURIParameter(
annotation.getLink() );
- if ( annotation.hasChanged( 'linkTitle' ) )
- body += ( body == '' ? '' : '&' ) + 'link_title=' + encodeURIParameter(
annotation.getLinkTitle( ) );
- if ( annotation.hasChanged( 'range/sequence' ) )
- body += '&sequence-range=' + encodeURIParameter(
annotation.getSequenceRange( ).toString( ) );
- if ( annotation.hasChanged( 'range/xpath' ) )
- body += '&xpath-range=' + encodeURIParameter( annotation.getXPathRange(
).toString( ) );
-
-// Cross-site request forgery protection (if present)
- if ( this.csrfCookie )
- body += '&' + encodeURIComponent( this.csrfCookie ) + '=' +
encodeURIParameter( readCookie( this.csrfCookie ) );
+RestAnnotationService.prototype.updateAnnotation = function( annotation,
ok, fail )
+{
+ var serviceUrl = this.urlTemplate.match( [
+ [ 'url', annotation.getUrl( ), true, false ],
+ [ 'id', annotation.getId( ), true ],
+ [ 'method', 'PUT', this.noPutDelete ],
+ [ 'curuser', this.sendCurUser, this.sendCurUser ]
+ ], 'updateAnnotations' );
+ if ( ! serviceUrl )
+ throw "No matching service URL for updateAnnotation.";

// May need to pass method name instead of using PUT or DELETE
- var method = 'PUT';
- if ( this.noPutDelete )
- {
- serviceUrl += '&method=PUT';
- method = 'POST';
- }
-
- var xmlhttp = domutil.createAjaxRequest( );
- xmlhttp.open( method, serviceUrl, true );
-
xmlhttp.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded;
charset=UTF-8' );
- //xmlhttp.setRequestHeader( 'Accept', 'application/xml' );
- xmlhttp.setRequestHeader( 'Content-length', body.length );
- xmlhttp.onreadystatechange = function( ) {
- if ( xmlhttp.readyState == 4 ) {
- // Safari is braindead here: any status code other than 200 is
converted to undefined
- // IE invents its own 1223 status code
- // See http://www.trachtenberg.com/blog/?p=74
- if ( 204 == xmlhttp.status || xmlhttp.status == null || xmlhttp.status
== 1223 ) {
- if ( null != f )
- f( xmlhttp.responseXML );
- }
- else
- logError( "AnnotationService.updateAnnotation failed with code " +
xmlhttp.status + " (" + xmlhttp.statusText + ")\n" + xmlhttp.statusText
+ "\n" + xmlhttp.responseText );
- xmlhttp = null;
- }
- }
+ var method = this.noPutDelete ? 'POST' : 'PUT';
+
+ var params = [
+ [ 'note', annotation.getNote( ), annotation.hasChanged( 'note' ) ],
+ [ 'sheet', annotation.getSheet( ), annotation.hasChanged( 'sheet' ) ],
+ [ 'link', annotation.getLink( ), annotation.hasChanged( 'link' ) ],
+ [ 'link_title', annotation.getLinkTitle( ),
annotation.hasChanged( 'linkTitle' ) ],
+ [ 'sequence-range', annotation.getSequenceRange( ) ?
annotation.getSequenceRange( ).toString( ) : '',
annotation.hasChanged( 'range/sequence' ) ],
+ [ 'xpath-range', annotation.getXPathRange( ) ? annotation.getXPathRange(
).toString( ) : '', annotation.hasChanged( 'range/xpath' ) ],
+ [ 'csrf', readCookie( this.csrfCookie ) ]
+ ];
+ var body = restutil.queryArgsToString( params );
+
+ fail2 = function( status, text ) {
+ logError( "AnnotationService.updateAnnotation failed with code " +
status + ":\n" + serviceUrl + "\n" + text );
+ if ( fail )
+ fail( status, text );
+ };
+ restutil.putResource( serviceUrl, body, ok, fail2, { okXml: true,
noPutDelete: this.noPutDelete } );
trace( 'annotation-service', "AnnotationService.updateAnnotation " +
decodeURI( serviceUrl ) );
trace( 'annotation-service', " " + body );
- xmlhttp.send( body );
}


@@ -321,114 +204,54 @@
* The method signature will likely change in future; for now it only
deals with updates to
* the note field.
*/
-RestAnnotationService.prototype.bulkUpdate = function( oldNote, newNote, f
)
-{
- var serviceUrl = this.serviceUrl;
- serviceUrl += '?note=' + encodeURIComponent( oldNote );
-
- var body = 'note=' + encodeURIComponent( newNote );
-
- // Cross-site request forgery protection (if present)
- if ( this.csrfCookie )
- body += '&' + encodeURIComponent( this.csrfCookie ) + '=' +
encodeURIComponent( readCookie( this.csrfCookie ) );
-
- // May need to pass method name instead of using PUT or DELETE
- var method = 'PUT';
- if ( this.noPutDelete )
- {
- serviceUrl += '&method=PUT';
- method = 'POST';
- }
-
- var xmlhttp = domutil.createAjaxRequest( );
-
- // This use of PUT is suspect, as it does not send a full representation
of the resource -
- // instead it sends a delta for the resource (or rather for child
resources of which this
- // resource is composed)
- xmlhttp.open( method, serviceUrl, true );
-
xmlhttp.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded;
charset=UTF-8' );
- //xmlhttp.setRequestHeader( 'Accept', 'application/xml' );
- xmlhttp.setRequestHeader( 'Content-length', body.length );
- xmlhttp.onreadystatechange = function( ) {
- if ( xmlhttp.readyState == 4 ) {
- // No need for Safari hack, since Safari can't create annotations
anyway.
- if ( xmlhttp.status == 200 ) {
- var url = xmlhttp.getResponseHeader( 'Location' );
- if ( null != f )
- {
- trace( 'annotation-service', 'Create annotation body: ' +
xmlhttp.responseText );
- f( xmlhttp.responseText, url );
- }
- }
- else {
- logError( "AnnotationService.bulkUpdate failed with code " +
xmlhttp.status + ":\n" + serviceUrl + "\n" + xmlhttp.responseText );
- }
- xmlhttp = null;
- }
- }
+RestAnnotationService.prototype.bulkUpdate = function( oldNote, newNote,
ok, fail )
+{
+ var serviceUrl = this.urlTemplate.match( [
+ [ 'note', oldNote, true ],
+ [ 'method', 'PUT', this.noPutDelete ],
+ [ 'curuser', this.sendCurUser, this.sendCurUser ]
+ ], 'updateAnnotation' );
+ if ( ! serviceUrl )
+ throw "No matching service URL template for bulkUpdate.";
+
+ var params = [
+ [ 'note', newNote, true ],
+ [ 'csrf', readCookie( this.csrfCookie ) ]
+ ];
+
+ var body = restutil.queryArgsToString( params );
+
+ fail2 = function( status, text ) {
+ logError( "AnnotationService.bulkUpdate failed with code " + status
+ ":\n" + serviceUrl + "\n" + text );
+ if ( fail )
+ fail( status, text );
+ };
+ restutil.putResource( serviceUrl, body, ok, fail2, { noPutDelete:
this.noPutDelete } );
trace( 'annotation-service', "AnnotationService.bulkUpdate " + decodeURI(
serviceUrl ) + "\n" + body );
- xmlhttp.send( body );
}


/**
* Delete an annotation on the server
*/
-RestAnnotationService.prototype.deleteAnnotation = function( annotationId,
f )
-{
- var serviceUrl = this.serviceUrl;
- var hasParams = false;
-
- if ( this.niceUrls )
- serviceUrl += '/' + annotationId;
- else
- {
- serviceUrl += '?id=' + annotationId;
- hasParams = true;
- }
-
- // Cross-site request forgery protection (if present)
- if ( this.csrfCookie )
- {
- serviceUrl += ( hasParams ? '&' : '?' )
- + encodeURIComponent( this.csrfCookie ) + '=' + encodeURIComponent(
readCookie( this.csrfCookie ) );
- hasParams = true;
- }
-
- // For demo debugging only
- if ( window.marginalia && window.marginalia.userInRequest )
- {
- serviceUrl += ( hasParams ? '&' : '?' )
- + 'curuser=' + encodeURIParameter( window.marginalia.loginUserId );
- hasParams = true;
- }
-
- // May need to pass method name instead of using PUT or DELETE
- var method = 'DELETE';
- if ( this.noPutDelete )
- {
- serviceUrl += ( hasParams ? '&' : '?' ) + 'method=DELETE';
- method = 'POST';
- }
-
- var xmlhttp = domutil.createAjaxRequest( );
- xmlhttp.open( method, serviceUrl, true );
-
- //xmlhttp.setRequestHeader( 'Accept', 'application/xml' );
- xmlhttp.onreadystatechange = function( ) {
- if ( xmlhttp.readyState == 4 ) {
- // Safari is braindead here: any status code other than 200 is
converted to undefined
- // IE invents its own 1223 status code
- if ( 204 == xmlhttp.status || xmlhttp.status == null || xmlhttp.status
== 1223 ) {
- if ( null != f )
- f( xmlhttp.responseXML );
- }
- else
- logError( "AnnotationService.deleteAnnotation failed with code " +
xmlhttp.status + "\n" + xmlhttp.responseText );
- xmlhttp = null;
- }
- }
+RestAnnotationService.prototype.deleteAnnotation = function( annotation,
ok, fail )
+{
+ var serviceUrl = this.urlTemplate.match( [
+ [ 'url', annotation.getUrl( ), true, false ],
+ [ 'id', annotation.id, true ],
+ [ 'method', 'DELETE', this.noPutDelete ],
+ [ 'csrf', readCookie( this.csrfCookie ) ],
+ [ 'curuser', this.sendCurUser, this.sendCurUser ]
+ ], 'updateAnnotations' );
+ if ( ! serviceUrl )
+ throw "No matching service URL template for deleteAnnotation.";
+
+ fail2 = function( status, text ) {
+ logError( "AnnotationService.deleteAnnotation failed with code " +
status + ":\n" + serviceUrl + "\n" + text );
+ if ( fail )
+ fail( status, text );
+ };
+ restutil.deleteResource( serviceUrl, ok, fail2, { noPutDelete:
this.noPutDelete } );
trace( 'annotation-service', "AnnotationService.deleteAnnotation " +
decodeURI( serviceUrl ) );
- xmlhttp.send( null );
}

=======================================
--- /marginalia-lib/trunk/marginalia/rest-keywords.js Fri Feb 27 22:19:49
2009
+++ /marginalia-lib/trunk/marginalia/rest-keywords.js Wed May 30 14:51:38
2012
@@ -12,7 +12,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
+ * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -137,7 +137,7 @@
xmlhttp.open( 'POST', serviceUrl, true );

xmlhttp.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded;
charset=UTF-8' );
//xmlhttp.setRequestHeader( 'Accept', 'application/xml' );
- xmlhttp.setRequestHeader( 'Content-length', body.length );
+ //xmlhttp.setRequestHeader( 'Content-length', body.length );
xmlhttp.onreadystatechange = function( ) {
if ( xmlhttp.readyState == 4 ) {
// No need for Safari hack, since Safari can't create annotations
anyway.
@@ -172,7 +172,7 @@
xmlhttp.open( 'PUT', serviceUrl, true );

xmlhttp.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded;
charset=UTF-8' );
//xmlhttp.setRequestHeader( 'Accept', 'application/xml' );
- xmlhttp.setRequestHeader( 'Content-length', body.length );
+ //xmlhttp.setRequestHeader( 'Content-length', body.length );
xmlhttp.onreadystatechange = function( ) {
if ( xmlhttp.readyState == 4 ) {
// Safari is braindead here: any status code other than 200 is
converted to undefined
=======================================
--- /marginalia-lib/trunk/marginalia/rest-prefs.js Wed May 30 14:19:12 2012
+++ /marginalia-lib/trunk/marginalia/rest-prefs.js Wed May 30 14:51:38 2012
@@ -11,7 +11,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
+ * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -26,14 +26,36 @@
* $Id$
*/

-NICE_PREFERENCE_SERVICE_URL = '/preference';
-UGLY_PREFERENCE_SERVICE_URL = '/preference';
-
-function RestPreferenceService( serviceUrl, niceUrls )
+function RestPreferenceService( serviceUrl, features )
{
this.serviceUrl = serviceUrl;
- this.niceUrls = niceUrls;
- return this;
+ this.niceUrls = false;
+
+ if ( features )
+ {
+ for ( feature in features )
+ {
+ var value = features[ feature ];
+ switch ( feature )
+ {
+ // Name of cookie to use for preventing cross-site request forgery
+ case 'csrfCookie':
+ this.csrfCookie = value;
+ break;
+
+ // Use nice service URLs (currently unsupported)
+ case 'niceUrls':
+ this.niceUrls = value;
+ break;
+
+ default:
+ if ( typeof( this[ feature ] ) != 'undefined' )
+ throw 'Attempt to override feature: ' + feature;
+ else
+ this[ feature ] = value;
+ }
+ }
+ }
}

/**
@@ -70,7 +92,7 @@
var xmlhttp = domutil.createAjaxRequest( );
xmlhttp.open( 'POST', serviceUrl, true );

xmlhttp.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded;
charset=UTF-8' );
- xmlhttp.setRequestHeader( 'Content-length', body.length );
+// xmlhttp.setRequestHeader( 'Content-length', body.length );
xmlhttp.onreadystatechange = function( ) {
if ( xmlhttp.readyState == 4 )
{
=======================================
--- /marginalia-lib/trunk/marginalia/restutil.js Thu Jun 10 20:26:59 2010
+++ /marginalia-lib/trunk/marginalia/restutil.js Wed May 30 14:51:38 2012
@@ -285,7 +285,9 @@
if ( xmlhttp.readyState == 4 ) {
// No need for Safari hack, since Safari can't create annotations
anyway.
// Status could should ideally be 201, but some services don't support
that (django 1.0)
+ // console.log( 'status=' + xmlhttp.status );
if ( xmlhttp.status == 201 || xmlhttp.status == 200 ) {
+ // console.log( 'got response ' + xmlhttp.responseText + "\nOR\n" +
xmlhttp.responseXml );
var url = xmlhttp.getResponseHeader( 'Location' );
if ( ok )
ok( url, args && args.okXml ? xmlhttp.responseXml :
xmlhttp.responseText );
=======================================
--- /marginalia-lib/trunk/marginalia/track-changes.js Wed May 30 14:19:12
2012
+++ /marginalia-lib/trunk/marginalia/track-changes.js Wed May 30 14:51:38
2012
@@ -14,7 +14,7 @@
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
+ * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -37,7 +37,7 @@

SelectActionNoteEditor.prototype.bind = FreeformNoteEditor.prototype.bind;

-SelectActionNoteEditor.prototype.clear = function( )
+SelectActionNoteEditor.prototype.clear = function( marginalia )
{
while ( this.noteElement.firstChild )
{
@@ -47,7 +47,7 @@
}
}

-SelectActionNoteEditor.prototype.show = function( )
+SelectActionNoteEditor.prototype.show = function( marginalia )
{
var postMicro = this.postMicro;
var marginalia = this.marginalia;
@@ -106,7 +106,7 @@
}
}

-SelectActionNoteEditor.prototype.focus = function( )
+SelectActionNoteEditor.prototype.focus = function( marginalia )
{ }


@@ -125,25 +125,25 @@
this.editor.bind( marginalia, postMicro, annotation, noteElement );
}

-ActionNoteEditor.prototype.show = function( )
+ActionNoteEditor.prototype.show = function( marginalia )
{
this.faction( this );
- this.editor.show( );
+ this.editor.show( marginalia );
}

-ActionNoteEditor.prototype.focus = function( )
-{
- this.editor.focus( );
+ActionNoteEditor.prototype.focus = function( marginalia )
+{
+ this.editor.focus( marginalia );
}

ActionNoteEditor.prototype.clear = function( )
{
- this.editor.clear( );
+ this.editor.clear( marginalia );
}

ActionNoteEditor.prototype.save = function( )
{
- this.editor.save( );
+ this.editor.save( marginalia );
}


@@ -160,18 +160,18 @@
this.noteElement = noteElement;
}

-DummyEditor.prototype.show = function( )
+DummyEditor.prototype.show = function( marginalia )
{
this.faction( this );
}

-DummyEditor.prototype.focus = function( )
+DummyEditor.prototype.focus = function( marginalia )
{ }

-DummyEditor.prototype.clear = function( )
+DummyEditor.prototype.clear = function( marginalia )
{ }

-DummyEditor.prototype.save = function( )
+DummyEditor.prototype.save = function( marginalia )
{ }

trackchanges = {
=======================================
--- /marginalia-lib/trunk/marginalia/user-count.js Thu Jun 21 21:11:43 2007
+++ /marginalia-lib/trunk/marginalia/user-count.js Wed May 30 14:51:38 2012
@@ -1,4 +1,32 @@
-function parseBlockUserCountsXml( xmldoc )
+/*
+ * user-count.js
+ *
+ * Marginalia has been developed with funding and support from
+ * BC Campus, Simon Fraser University, and the Government of
+ * Canada, the UNDESA Africa i-Parliaments Action Plan, and
+ * units and individuals within those organizations. Many
+ * thanks to all of them. See CREDITS.html for details.
+ * Copyright (C) 2005-2007 Geoffrey Glass; the United Nations
+ * http://www.geof.net/code/annotation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.
+ *
+ * $Id$
+ */
+
+ function parseBlockUserCountsXml( xmldoc )
{
var listElement = xmldoc.documentElement;
if ( listElement.tagName != 'block-users' )
Reply all
Reply to author
Forward
0 new messages