iTIP/iMIP improvements:
- Add Accept/Decline buttons to the summary display; both currently have the same effect: - Add the (someday: "accepted") item to the user's first calendar. Also, teach the timezone heuristics about Eastern Time. NPTOB. Not as cool as vlad.
This commit is contained in:
Родитель
2b6804c5fa
Коммит
da5cc1b648
|
@ -47,6 +47,7 @@ MODULE = lightning
|
||||||
|
|
||||||
EXTRA_COMPONENTS = \
|
EXTRA_COMPONENTS = \
|
||||||
lightningTextCalendarConverter.js \
|
lightningTextCalendarConverter.js \
|
||||||
|
calItipProtocolHandler.js \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
include $(topsrcdir)/config/rules.mk
|
include $(topsrcdir)/config/rules.mk
|
||||||
|
|
|
@ -0,0 +1,188 @@
|
||||||
|
/* -*- Mode: javascript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||||
|
|
||||||
|
const CI = Components.interfaces;
|
||||||
|
|
||||||
|
const ITIP_HANDLER_MIMETYPE = "application/x-itip-internal";
|
||||||
|
const ITIP_HANDLER_PROTOCOL = "moz-cal-handle-itip";
|
||||||
|
|
||||||
|
const CAL_ITIP_PROTO_HANDLER_CID =
|
||||||
|
Components.ID("{6E957006-B4CE-11D9-B053-001124736B74}");
|
||||||
|
const CAL_ITIP_PROTO_HANDLER_CONTRACTID =
|
||||||
|
"@mozilla.org/network/protocol;1?name=" + ITIP_HANDLER_PROTOCOL;
|
||||||
|
|
||||||
|
const CALMGR_CONTRACTID = "@mozilla.org/calendar/manager;1";
|
||||||
|
|
||||||
|
const ItipProtocolHandlerFactory =
|
||||||
|
{
|
||||||
|
createInstance: function (outer, iid) {
|
||||||
|
if (outer != null)
|
||||||
|
throw Components.results.NS_ERROR_NO_AGGREGATION;
|
||||||
|
|
||||||
|
return (new ItipProtocolHandler()).QueryInterface(iid);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const CAL_ITIP_CONTENT_HANDLER_CID =
|
||||||
|
Components.ID("{47C31F2B-B4DE-11D9-BFE6-001124736B74}");
|
||||||
|
const CAL_ITIP_CONTENT_HANDLER_CONTRACTID =
|
||||||
|
"@mozilla.org/uriloader/content-handler;1?type=" +
|
||||||
|
ITIP_HANDLER_MIMETYPE;
|
||||||
|
|
||||||
|
const ItipContentHandlerFactory =
|
||||||
|
{
|
||||||
|
createInstance: function (outer, iid) {
|
||||||
|
if (outer != null)
|
||||||
|
throw Components.results.NS_ERROR_NO_AGGREGATION;
|
||||||
|
|
||||||
|
return (new ItipContentHandler()).QueryInterface(iid);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function NYI()
|
||||||
|
{
|
||||||
|
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ItipChannel(URI)
|
||||||
|
{
|
||||||
|
this.URI = this.originalURI = URI;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItipChannel.prototype = {
|
||||||
|
QueryInterface: function (aIID) {
|
||||||
|
if (!aIID.equals(CI.nsISupports) &&
|
||||||
|
!aIID.equals(CI.nsIChannel) &&
|
||||||
|
!aIID.equals(CI.nsIRequest))
|
||||||
|
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
contentType: ITIP_HANDLER_MIMETYPE,
|
||||||
|
loadAttributes: null,
|
||||||
|
contentLength: 0,
|
||||||
|
owner: null,
|
||||||
|
loadGroup: null,
|
||||||
|
notificationCallbacks: null,
|
||||||
|
securityInfo: null,
|
||||||
|
|
||||||
|
open: NYI,
|
||||||
|
asyncOpen: function (observer, ctxt) {
|
||||||
|
observer.onStartRequest(this, ctxt);
|
||||||
|
},
|
||||||
|
asyncRead: function (listener, ctxt) {
|
||||||
|
return listener.onStartRequest(this, ctxt);
|
||||||
|
},
|
||||||
|
|
||||||
|
isPending: function () { return true; },
|
||||||
|
status: Components.results.NS_OK,
|
||||||
|
cancel: function (status) { this.status = status; },
|
||||||
|
suspend: NYI,
|
||||||
|
resume: NYI,
|
||||||
|
};
|
||||||
|
|
||||||
|
function ItipProtocolHandler() { }
|
||||||
|
|
||||||
|
ItipProtocolHandler.prototype = {
|
||||||
|
QueryInterface: function (aIID) {
|
||||||
|
if (!aIID.equals(CI.nsISupports) &&
|
||||||
|
!aIID.equals(CI.nsIProtocolHandler))
|
||||||
|
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
protocolFlags: CI.nsIProtocolHandler.URI_NORELATIVE,
|
||||||
|
allowPort: function () { return false; },
|
||||||
|
isSecure: false,
|
||||||
|
newURI: function (spec, charSet, baseURI)
|
||||||
|
{
|
||||||
|
var cls = Components.classes["@mozilla.org/network/standard-url;1"];
|
||||||
|
var url = cls.createInstance(CI.nsIStandardURL);
|
||||||
|
url.init(CI.nsIStandardURL.URLTYPE_STANDARD, 0, spec, charSet, baseURI);
|
||||||
|
dump("Creating new URI for " + spec + "\n");
|
||||||
|
return url.QueryInterface(CI.nsIURI);
|
||||||
|
},
|
||||||
|
|
||||||
|
newChannel: function (URI) {
|
||||||
|
dump("Creating new ItipChannel for " + URI + "\n");
|
||||||
|
return new ItipChannel(URI);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function ItipContentHandler() { }
|
||||||
|
|
||||||
|
ItipContentHandler.prototype = {
|
||||||
|
QueryInterface: function (aIID) {
|
||||||
|
if (!aIID.equals(CI.nsISupports) &&
|
||||||
|
!aIID.equals(CI.nsIContentHandler))
|
||||||
|
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleContent: function (contentType, windowTarget, request)
|
||||||
|
{
|
||||||
|
dump("Handling some itip content, whee\n");
|
||||||
|
var channel = request.QueryInterface(CI.nsIChannel);
|
||||||
|
var uri = channel.URI.spec;
|
||||||
|
if (uri.indexOf(ITIP_HANDLER_PROTOCOL + ":") != 0) {
|
||||||
|
dump("unexpected uri " + uri + "\n");
|
||||||
|
return Components.results.NS_ERROR_FAILURE;
|
||||||
|
}
|
||||||
|
// moz-cal-handle-itip:///?
|
||||||
|
var paramString = uri.substring(ITIP_HANDLER_PROTOCOL.length + 4);
|
||||||
|
var paramArray = paramString.split("&");
|
||||||
|
var paramBlock = { };
|
||||||
|
paramArray.forEach(function (v) {
|
||||||
|
var parts = v.split("=");
|
||||||
|
paramBlock[parts[0]] = unescape(unescape(parts[1]));
|
||||||
|
});
|
||||||
|
// dump("content-handler: have params " + paramBlock.toSource() + "\n");
|
||||||
|
var event = Components.classes["@mozilla.org/calendar/event;1"].
|
||||||
|
createInstance(CI.calIEvent);
|
||||||
|
event.icalString = paramBlock.data;
|
||||||
|
dump("Processing iTIP event '" + event.title + "' from " +
|
||||||
|
event.organizer.id + " (" + event.id + ")\n");
|
||||||
|
var calMgr = Components.classes[CALMGR_CONTRACTID].getService(CI.calICalendarManager);
|
||||||
|
var cals = calMgr.getCalendars({});
|
||||||
|
cals[0].addItem(event, null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var myModule = {
|
||||||
|
registerSelf: function (compMgr, fileSpec, location, type) {
|
||||||
|
debug("*** Registering Lightning " + ITIP_HANDLER_PROTOCOL + ": handler\n");
|
||||||
|
compMgr = compMgr.QueryInterface(CI.nsIComponentRegistrar);
|
||||||
|
compMgr.registerFactoryLocation(CAL_ITIP_PROTO_HANDLER_CID,
|
||||||
|
"Lightning " + ITIP_HANDLER_PROTOCOL + ": handler",
|
||||||
|
CAL_ITIP_PROTO_HANDLER_CONTRACTID,
|
||||||
|
fileSpec, location, type);
|
||||||
|
debug("*** Registering Lightning " + ITIP_HANDLER_MIMETYPE + " handler\n");
|
||||||
|
compMgr.registerFactoryLocation(CAL_ITIP_CONTENT_HANDLER_CID,
|
||||||
|
"Lightning " + ITIP_HANDLER_MIMETYPE + " handler",
|
||||||
|
CAL_ITIP_CONTENT_HANDLER_CONTRACTID,
|
||||||
|
fileSpec, location, type);
|
||||||
|
},
|
||||||
|
|
||||||
|
getClassObject: function (compMgr, cid, iid) {
|
||||||
|
if (!iid.equals(Components.interfaces.nsIFactory))
|
||||||
|
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||||
|
|
||||||
|
if (cid.equals(CAL_ITIP_PROTO_HANDLER_CID))
|
||||||
|
return ItipProtocolHandlerFactory;
|
||||||
|
|
||||||
|
if (cid.equals(CAL_ITIP_CONTENT_HANDLER_CID))
|
||||||
|
return ItipContentHandlerFactory;
|
||||||
|
|
||||||
|
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||||
|
},
|
||||||
|
|
||||||
|
canUnload: function(compMgr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function NSGetModule(compMgr, fileSpec) {
|
||||||
|
return myModule;
|
||||||
|
}
|
|
@ -2,6 +2,22 @@
|
||||||
|
|
||||||
const CI = Components.interfaces;
|
const CI = Components.interfaces;
|
||||||
|
|
||||||
|
function makeTableRow(val) {
|
||||||
|
return "<tr><td>" + val[0] + "</td><td>" + val[1] + "</td></tr>\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeButton(type, content) {
|
||||||
|
return "<button type='submit' value='" + type + "'>" + content + "</button>";
|
||||||
|
}
|
||||||
|
|
||||||
|
function startForm(calendarData) {
|
||||||
|
var form = "<form method='GET' action='moz-cal-handle-itip:'>\n";
|
||||||
|
form += "<input type='hidden' name='preferredCalendar' value=''>\n";
|
||||||
|
// We use escape instead of encodeURI*, because we need to deal with single quotes, sigh
|
||||||
|
form += "<input type='hidden' name='data' value='" + escape(calendarData) + "'>\n";
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
|
||||||
function ltnMimeConverter() { }
|
function ltnMimeConverter() { }
|
||||||
|
|
||||||
ltnMimeConverter.prototype = {
|
ltnMimeConverter.prototype = {
|
||||||
|
@ -14,21 +30,33 @@ ltnMimeConverter.prototype = {
|
||||||
},
|
},
|
||||||
|
|
||||||
convertToHTML: function(contentType, data) {
|
convertToHTML: function(contentType, data) {
|
||||||
dump("converting " + contentType + " to HTML\n");
|
// dump("converting " + contentType + " to HTML\n");
|
||||||
|
|
||||||
var event = Components.classes["@mozilla.org/calendar/event;1"]
|
var event = Components.classes["@mozilla.org/calendar/event;1"]
|
||||||
.createInstance(CI.calIEvent);
|
.createInstance(CI.calIEvent);
|
||||||
event.icalString = data;
|
event.icalString = data;
|
||||||
|
|
||||||
var html = "<center><table bgcolor='#CCFFFF'>\n";
|
var html = "<script src='chrome://lightning/content/text-calendar-handler.js'></script>\n";
|
||||||
|
html += "<center>\n";
|
||||||
|
html += startForm(data);
|
||||||
|
html += "<table bgcolor='#CCFFFF'>\n";
|
||||||
var organizer = event.organizer;
|
var organizer = event.organizer;
|
||||||
html += "<tr><td>Invitation from</td><td><a href='" +
|
var rows = [["Invitation from", "<a href='" + organizer.id + "'>" +
|
||||||
organizer.id + "'>" + organizer.commonName + "</a></td></tr>\n";
|
organizer.commonName + "</a>"],
|
||||||
html += "<tr><td>Subject:</td><td>" + event.title + "</td></tr>\n";
|
["Topic:", event.title],
|
||||||
html += "</table></center>";
|
["Start:", event.startDate.jsDate.toLocaleTimeString()],
|
||||||
|
["End:", event.endDate.jsDate.toLocaleTimeString()]];
|
||||||
|
html += rows.map(makeTableRow).join("\n");
|
||||||
|
html += "<tr><td colspan='2'>";
|
||||||
|
html += makeButton("accept", "Accept meeting") + " ";
|
||||||
|
html += makeButton("decline", "Decline meeting") + " ";
|
||||||
|
html += "</td></tr>\n";
|
||||||
|
html += "</table>\n</form>\n</center>";
|
||||||
|
|
||||||
|
// dump("Generated HTML:\n\n" + html + "\n\n");
|
||||||
return html;
|
return html;
|
||||||
}
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var myModule = {
|
var myModule = {
|
||||||
|
@ -61,17 +89,9 @@ var myModule = {
|
||||||
|
|
||||||
myCID: Components.ID("{c70acb08-464e-4e55-899d-b2c84c5409fa}"),
|
myCID: Components.ID("{c70acb08-464e-4e55-899d-b2c84c5409fa}"),
|
||||||
|
|
||||||
/* ProgID for this class */
|
|
||||||
myContractID: "@mozilla.org/lightning/mime-converter;1",
|
myContractID: "@mozilla.org/lightning/mime-converter;1",
|
||||||
|
|
||||||
/* factory object */
|
|
||||||
myFactory: {
|
myFactory: {
|
||||||
/*
|
|
||||||
* Construct an instance of the interface specified by iid, possibly
|
|
||||||
* aggregating it with the provided outer. (If you don't know what
|
|
||||||
* aggregation is all about, you don't need to. It reduces even the
|
|
||||||
* mightiest of XPCOM warriors to snivelling cowards.)
|
|
||||||
*/
|
|
||||||
createInstance: function (outer, iid) {
|
createInstance: function (outer, iid) {
|
||||||
if (outer != null)
|
if (outer != null)
|
||||||
throw Components.results.NS_ERROR_NO_AGGREGATION;
|
throw Components.results.NS_ERROR_NO_AGGREGATION;
|
||||||
|
@ -80,17 +100,6 @@ var myModule = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
|
||||||
* The canUnload method signals that the component is about to be unloaded.
|
|
||||||
* C++ components can return false to indicate that they don't wish to be
|
|
||||||
* unloaded, but the return value from JS components' canUnload is ignored:
|
|
||||||
* mark-and-sweep will keep everything around until it's no longer in use,
|
|
||||||
* making unconditional ``unload'' safe.
|
|
||||||
*
|
|
||||||
* You still need to provide a (likely useless) canUnload method, though:
|
|
||||||
* it's part of the nsIModule interface contract, and the JS loader _will_
|
|
||||||
* call it.
|
|
||||||
*/
|
|
||||||
canUnload: function(compMgr) {
|
canUnload: function(compMgr) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,7 +147,9 @@ function calendarDefaultTimezone() {
|
||||||
// XXX this table needs a _lot_ more stuff in it.
|
// XXX this table needs a _lot_ more stuff in it.
|
||||||
const tzTable = {
|
const tzTable = {
|
||||||
"GMT-0700 Pacific Daylight Time" : "/mozilla.org/20050126_1/America/Los_Angeles",
|
"GMT-0700 Pacific Daylight Time" : "/mozilla.org/20050126_1/America/Los_Angeles",
|
||||||
"GMT-0800 Pacific Standard Time" : "/mozilla.org/20050126_1/America/Los_Angeles"
|
"GMT-0800 Pacific Standard Time" : "/mozilla.org/20050126_1/America/Los_Angeles",
|
||||||
|
"GMT-0400 EDT" : "/mozilla.org/20050126_1/America/New_York",
|
||||||
|
"GMT-0500 EST" : "/mozilla.org/20050126_1/America/New_York",
|
||||||
};
|
};
|
||||||
|
|
||||||
// returns a ICS timezone string
|
// returns a ICS timezone string
|
||||||
|
|
Загрузка…
Ссылка в новой задаче