+2009-05-27 Maciej Stachowiak <mjs@apple.com>
+
+ Not reviewed, demo content.
+
+ - add Calendar demo.
+
+ * demos/calendar: Added.
+ * demos/calendar/Boom.aiff: Added.
+ * demos/calendar/Calendar.css: Added.
+ * demos/calendar/Calendar.html: Added.
+ * demos/calendar/Calendar.js: Added.
+ * demos/calendar/Calendar.manifest: Added.
+ * demos/calendar/CalendarApp.icns: Added.
+ * demos/calendar/Images: Added.
+ * demos/calendar/Images/AirPort4.png: Added.
+ * demos/calendar/Images/AirPortError.png: Added.
+ * demos/calendar/Images/disclosureTriangleSmallDown.png: Added.
+ * demos/calendar/Images/disclosureTriangleSmallRight.png: Added.
+ * demos/calendar/Images/statusbarBackground.png: Added.
+ * demos/calendar/Images/statusbarResizerVertical.png: Added.
+ * demos/calendar/LocationImage.js: Added.
+ * demos/calendar/Utilities.js: Added.
+ * demos/calendar/favicon.ico: Added.
+ * demos/calendar/mime.types: Added.
+
2009-05-20 Mark Rowe <mrowe@apple.com>
Rubber-stamped by Eric Seidel.
--- /dev/null
+body {
+ font-family: "Helvetica";
+ font-size: 11px;
+ cursor: default;
+}
+
+*[contentEditable="true"] {
+ cursor: text;
+}
+
+#sidePanel {
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ width: 170px;
+ background: #d4dde6;
+ border-right: 1px solid #a5a5a5;
+ margin-left: 0;
+ margin-right: 0;
+ overflow: hidden;
+}
+
+#dividerBar {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 24px;
+ background-image: url(Images/statusbarResizerVertical.png), url(Images/statusbarBackground.png);
+ background-repeat: no-repeat, repeat-x;
+ background-position: right center, center;
+ cursor: row-resize;
+}
+
+#searchArea {
+ position: absolute;
+ background-color: #f1f1f1;
+ top: 100%;
+ bottom: 0;
+ left: 0;
+ right: 0;
+}
+
+input[type="search"] {
+ -webkit-appearance: searchfield;
+}
+
+#searchField {
+ position: absolute;
+ left: 10px;
+ right: 10px;
+ top: 10px;
+}
+
+#searchResults {
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 40px;
+ bottom: 0;
+ margin: 0;
+ padding: 0;
+ overflow: auto;
+ list-style-position: inside;
+}
+
+#searchResults li {
+ padding: 10px;
+}
+
+.colorIndicator {
+ display: inline-block;
+ width: 20px;
+ height: 1em;
+ vertical-align: -2px;
+ border: 1px black solid;
+ margin-right: 5px;
+}
+
+.colorIndicator.home {
+ background-color: #00a700
+}
+
+.colorIndicator.work {
+ background-color: #004ee2
+}
+
+#gridView {
+ position: absolute;
+ left: 171px;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ background: #ffffff;
+}
+
+#gridView.inactive {
+ opacity: 0.5;
+}
+
+#eventOverlay {
+ position: absolute;
+ left: 171px;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.2);
+ z-index: auto;
+ opacity: 0;
+ -webkit-transition: opacity 0.2s ease-in;
+}
+
+#eventOverlay.show {
+ opacity: 1;
+ -webkit-transition-property: opacity, z-index;
+ -webkit-transition-duration: 1s, 0;
+ -webkit-transition-delay: 0, 0.5s;
+ -webkit-transition-timing-function: ease-out, linear;
+ z-index: 1000;
+}
+
+#details {
+ position: absolute;
+ background: white;
+ left: 30%;
+ right: 30%;
+ top: 20%;
+ bottom: 20%;
+ overflow: hidden;
+ -webkit-box-shadow: 0 8px 12px rgba(0, 0, 0, 0.5);
+ -webkit-border-radius: 8px;
+ padding: 30px;
+ font-size: 12px;
+ -webkit-transition-property: top, bottom;
+ -webkit-transition-duration: 0.5s, 0.5s;
+}
+
+#eventTitle {
+ font-size: 20px;
+}
+
+#details *:focus {
+ outline: none;
+ -webkit-box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
+}
+
+#details dt {
+ width: 20%;
+ float: left;
+ clear: both;
+ color: #999;
+ text-align: right;
+ margin-top: 10px;
+}
+
+#details dd {
+ display: inline-block;
+ width: 70%;
+ float: left;
+ margin-left: 5%;
+ margin-top: 10px;
+ display: inline-block;
+ text-align: left;
+}
+
+#details input {
+ position: absolute;
+ bottom: 15px;
+ right: 15px;
+}
+
+#eventDisclosureArrow {
+ display: none;
+ float: left;
+ width: 12px;
+ height: 12px;
+ background-position: 1px left;
+ background-repeat: no-repeat;
+ background-image: url("Images/disclosureTriangleSmallRight.png");
+}
+
+#eventDisclosureArrow.show {
+ display: inline-block;
+}
+
+#eventDisclosureArrow.expanded {
+ background-image: url("Images/disclosureTriangleSmallDown.png");
+}
+
+#map {
+ display: none;
+ width: 100%;
+ min-width: 200px;
+ height: 180px;
+ overflow: hidden;
+ opacity: 0;
+ -webkit-transition: opacity 1s linear;
+}
+
+#map.show {
+ opacity: 1;
+ display: block;
+}
+
+#mapImage {
+ width: 100%;
+}
+
+#details.showingMap {
+ top: 10%;
+ bottom: 10%;
+ -webkit-transition-property: top, bottom;
+ -webkit-transition-duration: 0.5s, 0.5s;
+}
+
+#eventFrom .time, #eventTo .time {
+ display: inline-block;
+ min-width: 5px;
+ max-width: 20px;
+}
+
+#sidePanel h3 {
+ color: #5c6e91;
+ font-size: 11px;
+ text-shadow: 0.1em 0.1em #e5ebee;
+ margin-left: 20px;
+ margin-bottom: 4px;
+}
+
+#calendarList {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+#calendarList li {
+ padding-left: 23px;
+}
+
+#calendarList li.selected {
+ border-top: 1px solid #4580c8;
+ background: -webkit-gradient(linear, left top, left bottom, from(#5b93d5), to(#1553aa));
+ color: white;
+ font-weight: bold;
+ text-shadow: 0.1em 0.1em #6c6c6c;
+}
+
+#calendarList input {
+ margin-right: 5px;
+}
+
+#gridView h2 {
+ margin-top: 8px;
+ margin-bottom: 5px;
+ text-align: center;
+ color: #3f3f3f;
+ font-size: 19px;
+ font-weight: bolder;
+}
+
+#gridView .navButtonGroup {
+ text-align: center;
+}
+
+.navButton {
+ font-size: 14px;
+ font-weight: bold;
+ vertical-align: top;
+}
+
+#daysHeader {
+ margin-top: 5px;
+}
+
+.day {
+ display: inline-block;
+ width: 14%;
+ float: left;
+ -webkit-box-sizing: border-box;
+}
+
+/* REVIEW - trying to get these days to have their widths nicely divided */
+.day:nth-child(7n+1), .day:nth-child(7n+7) {
+ width: 15%;
+}
+
+.day.title {
+ color: #626262;
+ text-align: center;
+}
+
+#daysGrid {
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 80px;
+ bottom: 0;
+}
+
+.day.box {
+ height: 20%;
+ border: 1px #ccc;
+ border-top-style: solid;
+ border-right-style: solid;
+ position: relative;
+}
+
+.day.box.today {
+ background-color: #e5ecf7;
+}
+
+.day.box:nth-child(7n+7) {
+ border-right-style: none;
+}
+
+.day .date {
+ color: #212121;
+ text-align: right;
+ font-size: 12px;
+ padding-right: 5px;
+ padding-top: 5px;
+}
+
+.day.box.notThisMonth .date {
+ color: #a6a6a6;
+}
+
+.day .contents {
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 20px;
+ bottom: 0;
+ overflow: auto;
+ margin-top: 0;
+ margin-bottom: 0;
+ margin-left: 0;
+ padding: 2px 0;
+ list-style-position: inside;
+ color: orange;
+}
+
+.day .contents li {
+ background-color: rgba(255, 255, 255, 0);
+ padding-left: 8px;
+ padding-right: 8px;
+}
+
+li.home {
+ color: #00a700;
+}
+
+li.home.selected {
+ color: #fff;
+ background: #00a700;
+}
+
+li.work {
+ color: #004ee2;
+}
+
+li.work.selected {
+ color: #fff;
+ background: #004ee2;
+}
+
+li.highlighted {
+ background-color: #ffa !important;
+}
+
+/* Online Status */
+#onlineStatusIcon {
+ width: 44px;
+ height: 32px;
+ margin-right: 8px;
+ display: inline-block;
+ float: right;
+ background-image: url("Images/AirPort4.png");
+}
+
+#onlineStatusIcon.offline {
+ background-image: url("Images/AirPortError.png");
+}
--- /dev/null
+<!-- Application Cache Manifest -->
+<html manifest="Calendar.manifest">
+
+<head>
+
+<!-- Application Name -->
+<meta name="application-name" content="Offline Calendar">
+
+<!-- Application Icon -->
+<link rel="icon" href="surfin-safari.icns" sizes="32x32 128x128 512x512">
+
+<!-- Normal favicon -->
+<link rel="icon" href="favicon.ico" sizes="16x16">
+
+<title>Calendar</title>
+<link rel="stylesheet" type="text/css" href="Calendar.css">
+<script type="text/javascript" src="LocationImage.js"></script>
+<script type="text/javascript" src="Utilities.js"></script>
+<script type="text/javascript" src="Calendar.js"></script>
+</head>
+
+<body onload="pageLoaded()">
+
+<div id="sidePanel">
+ <h3>CALENDARS</h3>
+ <ul id="calendarList">
+ <li class="selected" onclick="calendarSelected(event)">
+ <span class="colorIndicator home"></span><input type="checkbox" id="home" onchange="calendarClicked(event)">Home
+ </li>
+ <li onclick="calendarSelected(event)">
+ <span class="colorIndicator work"></span><input type="checkbox" id="work" onchange="calendarClicked(event)">Work
+ </li>
+ </ul>
+
+ <div id="dividerBar"></div>
+
+ <div id="searchArea">
+ <input type="search" id="searchField" results="0" incremental="1" placeholder="" value="" onsearch="searchForEvent(this.value)">
+ <ul id="searchResults">
+ </ul>
+ </div>
+</div>
+
+<div id="eventOverlay">
+ <div id="details">
+ <div id="eventTitle" contenteditable="true"></div>
+ <dl>
+ <dt>location</dt>
+ <dd>
+ <span id="eventDisclosureArrow" onclick="toggleArrow()"></span>
+ <div id="eventLocation" contentEditable="true" onfocus="hideMapDisclosureArrow()" onblur="requestMapImage()"></div>
+ <div id="map"></div>
+ </dd>
+ <dt>from</dt>
+ <dd id="eventFrom">
+ <span id="eventFromDate"></span>
+ <span id="eventFromHours" class="time" contentEditable="true">00</span>:<span id="eventFromMinutes" class="time" contentEditable="true">00</span>
+ </dd>
+ <dt>to</dt>
+ <dd id="eventTo">
+ <span id="eventToDate"></span>
+ <span id="eventToHours" class="time" contentEditable="true">00</span>:<span id="eventToMinutes" class="time" contentEditable="true">00</span>
+ </dd>
+ <dt style="line-height: 23px;">calendar</dt>
+ <dd>
+ <select id="eventCalendarType">
+ <option value="home">Home</option>
+ <option value="work">Work</option>
+ </select>
+ </dd>
+ <dt>details</dt>
+ <dd id="eventDetails" contentEditable="true"></dd>
+ </dl>
+ <input type="button" value="Done" onclick="eventDetailsDismissed()">
+ </div>
+</div>
+
+<div id="gridView">
+
+ <h2>
+ <span id="monthTitle"></span>
+ <span id="onlineStatusIcon"></span>
+ </h2>
+
+ <div class="navButtonGroup">
+ <input class="navButton" type="button" onclick="previousMonth()" value="<">
+ <input class="navButton" type="button" onclick="nextMonth()" value=">">
+ </div>
+
+ <div id="daysHeader">
+ <span class="day title">Sunday</span>
+ <span class="day title">Monday</span>
+ <span class="day title">Tuesday</span>
+ <span class="day title">Wednesday</span>
+ <span class="day title">Thursday</span>
+ <span class="day title">Friday</span>
+ <span class="day title">Saturday</span>
+ </div>
+
+ <div id="daysGrid">
+ </div>
+
+</div>
+
+</body>
+</html>
--- /dev/null
+var monthStrings = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
+// Date object that represents the currently displayed month
+var monthOnDisplay;
+// Table that maps a date (in milliseconds) to the corresponding Day object
+var dateToDayObjectMap;
+// Calendar type currently selected
+var selectedCalendarType = "home";
+// Calendar event currently selected
+var selectedCalendarEvent = null;
+// The highest ID number that we have assigned so far
+var highestID = 0;
+// Date (in milliseconds) of the first day displayed in the calendar
+var calendarStartTime = 0;
+// Date (in milliseconds) of the last day displayed in the calendar
+var calendarEndTime = 0;
+// Audio sound for network connection lost event
+var networkDropSound = new Audio("Boom.aiff");
+
+// Override day box height if the number of rows in calendar is not 5
+var insertedStyleRuleIndexForDayBox = -1;
+function updateCalendarRowCount(count)
+{
+ var stylesheet = document.styleSheets[0];
+ if (count == 5) { // The case our stylesheet already handles, and probably the most common case
+ if (insertedStyleRuleIndexForDayBox >= 0) {
+ // Remove the style rule we previously added
+ stylesheet.deleteRule(insertedStyleRuleIndexForDayBox);
+ }
+ insertedStyleRuleIndexForDayBox = -1;
+ return;
+ }
+ if (insertedStyleRuleIndexForDayBox < 0)
+ insertedStyleRuleIndexForDayBox = stylesheet.insertRule(".day.box { }", stylesheet.cssRules.length);
+ var styleRule = stylesheet.cssRules[insertedStyleRuleIndexForDayBox];
+ var h = 100 / count;
+ styleRule.style.height = h + "%";
+}
+
+function initMonthOnDisplay()
+{
+ monthOnDisplay = CalendarState.currentMonth();
+ monthOnDisplayUpdated();
+}
+
+function monthOnDisplayUpdated()
+{
+ var monthTitle = monthStrings[monthOnDisplay.getMonth()] + " " + monthOnDisplay.getFullYear();
+ document.getElementById("monthTitle").innerText = monthTitle;
+ CalendarState.setCurrentMonth(monthOnDisplay.toString());
+ initDaysGrid();
+ CalendarDatabase.openWebKitCalendarEvents();
+}
+
+function initDaysGrid()
+{
+ // Figure out the first day that should be displayed in this grid.
+ var displayedDate = new Date(monthOnDisplay);
+ displayedDate.setDate(displayedDate.getDate() - displayedDate.getDay());
+
+ // Fill out the entire grid
+ var daysGrid = document.getElementById("daysGrid");
+ daysGrid.removeChildren();
+ dateToDayObjectMap = new Object();
+
+ calendarStartTime = displayedDate.getTime();
+ var doneWithThisMonth = false;
+ var rows = 0;
+ while (!doneWithThisMonth) {
+ rows++;
+ for (var i = 0; i < 7; ++i) {
+ var dateTime = displayedDate.getTime();
+ var dayObj = new Day(displayedDate);
+ dateToDayObjectMap[dateTime] = dayObj;
+ dayObj.attach(daysGrid);
+ // Increment by a day
+ displayedDate.setDate(displayedDate.getDate() + 1);
+ }
+ doneWithThisMonth = (displayedDate.getMonth() != monthOnDisplay.getMonth());
+ }
+ updateCalendarRowCount(rows);
+ displayedDate.setDate(displayedDate.getDate() - 1); // Roll back to the last displayed date
+ displayedDate.setHours(23, 59, 59, 999);
+ calendarEndTime = displayedDate.getTime();
+}
+
+function isCalendarTypeVisible(type)
+{
+ var input = document.getElementById(type);
+ if (!input || input.tagName != "INPUT")
+ return;
+ return input.checked;
+}
+
+function addEventsOfCalendarType(calendarType)
+{
+ CalendarDatabase.loadEventsFromDBForCalendarType(calendarType);
+}
+
+function removeEventsOfCalendarType(calendarType)
+{
+ var daysGridElement = document.getElementById("daysGrid");
+ var dayElements = daysGridElement.childNodes;
+ for (var i = 0; i < dayElements.length; i++) {
+ var dayObj = dayObjectFromElement(dayElements[i]);
+ if (!dayObj)
+ continue;
+ dayObj.hideEventsOfCalendarType(calendarType);
+ }
+}
+
+// Event handlers --------------------------------------------------
+
+function pageLoaded()
+{
+ document.getElementById("gridView").addEventListener("selectstart", stopEvent, true);
+ document.getElementById("searchResults").addEventListener("selectstart", stopEvent, true);
+ document.body.addEventListener("keyup", keyUpHandler, false);
+ document.body.addEventListener("online", displayOnlineStatus, true);
+ document.body.addEventListener("offline", displayOnlineStatus, true);
+ displayOnlineStatus();
+
+ initSearchArea();
+ // Initialize the checked states of the calendars
+ var calendarCheckboxes = document.getElementById("calendarList").getElementsByTagName("INPUT");
+ for (var i = 0; i < calendarCheckboxes.length; i++)
+ calendarCheckboxes[i].checked = CalendarState.calendarChecked(calendarCheckboxes[i].id);
+ // Initialize the calendar grid.
+ initMonthOnDisplay();
+}
+
+function previousMonth()
+{
+ monthOnDisplay.setMonth(monthOnDisplay.getMonth() - 1);
+ monthOnDisplayUpdated();
+}
+
+function nextMonth()
+{
+ monthOnDisplay.setMonth(monthOnDisplay.getMonth() + 1);
+ monthOnDisplayUpdated();
+}
+
+function calendarSelected(event)
+{
+ if (event.target.tagName == "INPUT")
+ return;
+ var oldSelectedInput = document.getElementById(selectedCalendarType);
+ var oldListItemElement = oldSelectedInput.findParentOfTagName("LI");
+ oldListItemElement.removeStyleClass("selected");
+ event.target.addStyleClass("selected");
+ selectedCalendarType = event.target.getElementsByTagName("INPUT")[0].id;
+}
+
+function calendarClicked(event)
+{
+ if (event.target.tagName != "INPUT")
+ return;
+ var calendarType = event.target.id;
+ var checked = event.target.checked;
+ CalendarState.setCalendarChecked(calendarType, checked);
+ if (checked)
+ addEventsOfCalendarType(calendarType);
+ else
+ removeEventsOfCalendarType(calendarType);
+}
+
+function keyUpHandler(event)
+{
+ switch (event.keyIdentifier) {
+ case "U+007F": // Delete key
+ if (selectedCalendarEvent && selectedCalendarEvent.day)
+ selectedCalendarEvent.day.deleteEvent(selectedCalendarEvent);
+ break;
+ case "Enter":
+ if (selectedCalendarEvent && !document.getElementById("eventOverlay").hasStyleClass("show"))
+ selectedCalendarEvent.selected();
+ break;
+ default:
+ break;
+ }
+}
+
+function eventDetailsDismissed(event)
+{
+ if (selectedCalendarEvent) {
+ selectedCalendarEvent.listItemNode.removeStyleClass("selected");
+ // Update selected event
+ selectedCalendarEvent.detailsUpdated();
+ }
+
+ // Hide event details
+ var eventOverlayElement = document.getElementById("eventOverlay");
+ // FIXME: would have added transitionend listener here but not supported yet. Use setTimeout for now.
+ // Set timer to hide map and disclosure arrow - this is delayed so things wouldn't change while event details is fading out.
+ setTimeout("hideMapDisclosureArrow()", 1000);
+ eventOverlayElement.removeStyleClass("show");
+
+ selectedCalendarEvent = null;
+
+ // Show the gridView
+ document.getElementById("gridView").removeStyleClass("inactive");
+}
+
+function searchForEvent(query)
+{
+ if (query.length == 0) {
+ var searchResultsList = document.getElementById("searchResults");
+ searchResultsList.removeChildren();
+ unhighlightAllEvents();
+ return;
+ }
+ query = "%" + query + "%";
+
+ CalendarDatabase.queryEventsInDB(query);
+}
+
+// Online status ----------------------------------------------------
+
+function isOnline()
+{
+ return navigator.onLine;
+}
+
+function displayOnlineStatus()
+{
+ var statusIcon = document.getElementById("onlineStatusIcon");
+ if (isOnline())
+ statusIcon.removeStyleClass("offline");
+ else {
+ statusIcon.addStyleClass("offline");
+ networkDropSound.play();
+ }
+}
+
+// Map revelation ---------------------------------------------------
+
+function hideMap()
+{
+ document.getElementById("eventDisclosureArrow").removeStyleClass("expanded");
+ document.getElementById("map").removeStyleClass("show");
+ document.getElementById("details").removeStyleClass("showingMap");
+}
+
+function showMap()
+{
+ document.getElementById("eventDisclosureArrow").addStyleClass("expanded");
+ document.getElementById("map").addStyleClass("show");
+ document.getElementById("details").addStyleClass("showingMap");
+}
+
+function toggleArrow()
+{
+ var newMapShowState = !document.getElementById("map").hasStyleClass("show");
+ if (newMapShowState)
+ showMap();
+ else
+ hideMap();
+}
+
+function hideMapDisclosureArrow()
+{
+ document.getElementById("eventDisclosureArrow").removeStyleClass("show");
+ document.getElementById("map").removeChildren();
+ hideMap();
+}
+
+function showMapDisclosureArrow()
+{
+ document.getElementById("eventDisclosureArrow").addStyleClass("show");
+}
+
+var locationImage;
+
+function mapImageReceived(image)
+{
+ if (!image)
+ return;
+ image.addStyleClass("mapImage");
+ document.getElementById("map").appendChild(image);
+ showMapDisclosureArrow();
+}
+
+function requestMapImage()
+{
+ if (!isOnline())
+ return;
+ if (!locationImage)
+ locationImage = new LocationImage();
+ var address = document.getElementById("eventLocation").innerText;
+ locationImage.requestLocationImage(address, mapImageReceived);
+}
+
+// Search area ------------------------------------------------------
+
+var dividerBarDragOffset = 0;
+var dividerBarHeight = 24;
+
+function initSearchArea()
+{
+ document.getElementById("dividerBar").addEventListener("mousedown", startDividerBarDragging, true);
+}
+
+function startDividerBarDragging(event)
+{
+ var dividerBarElement = document.getElementById("dividerBar");
+ if (event.target !== dividerBarElement)
+ return;
+
+ document.addEventListener("mousemove", dividerBarDragging, true);
+ document.addEventListener("mouseup", endDividerBarDragging, true);
+
+ document.body.style.cursor = "row-resize";
+
+ dividerBarDragOffset = event.pageY - document.getElementById("searchArea").totalOffsetTop;
+ stopEvent(event);
+}
+
+function dividerBarDragging(event)
+{
+ var dividerBarElement = document.getElementById("dividerBar");
+ var searchAreaElement = document.getElementById("searchArea");
+ var sidePanelHeight = document.getElementById("sidePanel").offsetHeight;
+ var calendarListElement = document.getElementById("calendarList");
+ var calendarListBottom = calendarListElement.totalOffsetTop + calendarListElement.offsetHeight;
+
+ var dividerTop = event.pageY + dividerBarDragOffset;
+ dividerTop = Number.constrain(dividerTop, calendarListBottom, sidePanelHeight - dividerBarHeight);
+ var searchAreaTop = dividerTop + dividerBarHeight;
+
+ dividerBarElement.style.top = dividerTop + "px";
+ searchAreaElement.style.top = searchAreaTop + "px";
+
+ stopEvent(event);
+}
+
+function endDividerBarDragging(event)
+{
+ document.removeEventListener("mousemove", dividerBarDragging, true);
+ document.removeEventListener("mouseup", endDividerBarDragging, true);
+
+ document.body.style.removeProperty("cursor");
+ dividerBarDragOffset = 0;
+ stopEvent(event);
+}
+
+// LocalStorage access ---------------------------------------------
+
+var CalendarState = {
+ currentMonth: function()
+ {
+ var month = new Date();
+ // Retrieve the month on display saved from last time this app is launched.
+ if (localStorage.monthOnDisplay)
+ month.setTime(Date.parse(localStorage.monthOnDisplay));
+ // First time we load this page - just use the current month.
+ month.setDate(1);
+ month.setHours(0, 0, 0, 0);
+ return month;
+ },
+
+ setCurrentMonth: function(monthString)
+ {
+ localStorage.monthOnDisplay = monthString;
+ },
+
+ toCalendarKey: function(calendarType)
+ {
+ return calendarType + "CalendarChecked";
+ },
+
+ calendarChecked: function(calendarType)
+ {
+ var value = localStorage.getItem(CalendarState.toCalendarKey(calendarType));
+ if (!value)
+ return true;
+ return (value == "yes") ? true : false;
+ },
+
+ setCalendarChecked: function(calendarType, checked)
+ {
+ localStorage.setItem(CalendarState.toCalendarKey(calendarType), checked ? "yes" : "no");
+ },
+}
+
+// Database access -------------------------------------------------
+
+var CalendarDatabase = {
+ // The Events database object
+ db: null,
+
+ // Flag that tracks if we have opened the database for the very first time
+ dbOpened: false,
+
+ // REVIEW: can probably have a method that takes in a SQL statement, the arguments to the statement, optional callback and error
+ // callback methods and execute the transaction on the database, and then have other methods just call that one method.
+ // But we'll spell out the steps to make the database transaction in each method here for demonstration purposes.
+
+ openWebKitCalendarEvents: function()
+ {
+ // We have already made sure the WebKitCalendarEvents table has been created. Just load events directly.
+ if (CalendarDatabase.dbOpened) {
+ CalendarDatabase.loadEventsFromDB();
+ return;
+ }
+ CalendarDatabase.dbOpened = true;
+
+ // Query for opening WebKitCalendarEvents table
+ var openTableStatement = "CREATE TABLE IF NOT EXISTS WebKitCalendarEvents (id REAL UNIQUE, eventTitle TEXT, eventLocation TEXT, startTime REAL, endTime REAL, eventCalendar TEXT, eventDetails TEXT)";
+
+ // SQLStatementCallback - gets called after the table is created
+ function sqlStatementCallback(result) { CalendarDatabase.loadEventsFromDB(); };
+
+ // SQLStatementErrorCallback - gets called if there's an error opening the table
+ function sqlStatementErrorCallback(tx, err) { alert("Error opening WebKitCalendarEvents: " + err.message); };
+
+ // SQLTransactionCallback
+ function sqlTransactionCallback(tx) { tx.executeSql(openTableStatement, [], sqlStatementCallback, sqlStatementErrorCallback); };
+
+ CalendarDatabase.db.transaction(sqlTransactionCallback);
+ },
+
+ open: function()
+ {
+ try {
+ if (!window.openDatabase) {
+ alert("Couldn't open the database. Please try with a WebKit nightly with the database feature enabled.");
+ return;
+ }
+ CalendarDatabase.db = openDatabase("Events", "1.0", "Events Database", 1000000);
+ if (!CalendarDatabase.db)
+ alert("Failed to open the database on disk.");
+ } catch(err) { }
+ },
+
+ loadEventsFromDB: function()
+ {
+ var self = this;
+
+ // SQL query to retrieve all the events for this month
+ var eventsQuery = "SELECT id, eventTitle, eventLocation, startTime, endTime, eventCalendar, eventDetails FROM WebKitCalendarEvents WHERE (startTime BETWEEN ? and ?)";
+
+ // Arguments to the SQL query above
+ var sqlArguments = [calendarStartTime, calendarEndTime];
+
+ // SQLStatementCallback to process the query result
+ function sqlStatementCallback(tx, result) { self.processLoadedEvents(result.rows); };
+
+ // SQLStatementErrorCallback that handles any error from the query
+ function sqlStatementErrorCallback(tx, error) { alert("Failed to retrieve events from database - " + error.message); };
+
+ // SQLTransactionCallback
+ function sqlTransactionCallback(tx) { tx.executeSql(eventsQuery, sqlArguments, sqlStatementCallback, sqlStatementErrorCallback); };
+
+ CalendarDatabase.db.transaction(sqlTransactionCallback);
+ },
+
+ saveAsNewEventToDB: function(calendarEvent)
+ {
+ // SQL statement to insert new event into the database table
+ var insertEventStatement = "INSERT INTO WebKitCalendarEvents(id, eventTitle, eventLocation, startTime, endTime, eventCalendar, eventDetails) VALUES (?, ?, ?, ?, ?, ?, ?)";
+
+ // Arguments to the SQL statement above
+ var sqlArguments = [calendarEvent.id, calendarEvent.title, calendarEvent.location, calendarEvent.from.getTime(), calendarEvent.to.getTime(), calendarEvent.calendar, calendarEvent.details];
+
+ // SQLTransactionCallback
+ function sqlTransactionCallback(tx) { tx.executeSql(insertEventStatement, sqlArguments); };
+
+ CalendarDatabase.db.transaction(sqlTransactionCallback);
+ },
+
+ saveEventToDB: function(calendarEvent) {
+ // SQL statement to update an event in the database table
+ var updateEventStatement = "UPDATE WebKitCalendarEvents SET eventTitle = ?, eventLocation = ?, startTime = ?, endTime = ?, eventCalendar = ?, eventDetails = ? WHERE id = ?";
+
+ // Arguments to the SQL statement above
+ var sqlArguments = [calendarEvent.title, calendarEvent.location, calendarEvent.from.getTime(), calendarEvent.to.getTime(), calendarEvent.calendar, calendarEvent.details, calendarEvent.id];
+
+ // SQLTransactionCallback
+ function sqlTransactionCallback(tx) { tx.executeSql(updateEventStatement, sqlArguments); };
+
+ CalendarDatabase.db.transaction(sqlTransactionCallback);
+ },
+
+ deleteEventFromDB: function(calendarEvent)
+ {
+ // SQL statement to delete an event from the database table
+ var deleteEventStatement = "DELETE FROM WebKitCalendarEvents WHERE id = ?";
+
+ // Arguments to the SQL statement above
+ var sqlArguments = [calendarEvent.id];
+
+ // SQLTransactionCallback
+ function sqlTransactionCallback(tx) { tx.executeSql(deleteEventStatement, sqlArguments); };
+ CalendarDatabase.db.transaction(sqlTransactionCallback);
+ },
+
+ queryEventsInDB: function(query)
+ {
+ var self = this;
+
+ // SQL query to search for events with keyword
+ var searchEventQuery = "SELECT id, eventTitle, eventLocation, startTime, endTime, eventCalendar, eventDetails FROM WebKitCalendarEvents WHERE eventTitle LIKE ? OR eventDetails LIKE ? OR eventLocation LIKE ?";
+
+ // Arguments to the SQL query
+ var sqlArguments = [query, query, query];
+
+ // SQLStatementCallback that processes the query result
+ function sqlStatementCallback(tx, result) { self.processQueryResults(result.rows); };
+
+ // SQLStatementErrorCallback that reports any error from the query
+ function sqlStatementErrorCallback(tx, error) { alert("Failed to retrieve events from database - " + error.message); };
+
+ // SQLTransactionCallback
+ function sqlTransactionCallback(tx) { tx.executeSql(searchEventQuery, sqlArguments, sqlStatementCallback, sqlTransactionCallback); };
+
+ CalendarDatabase.db.transaction(sqlTransactionCallback);
+ },
+
+ loadEventsFromDBForCalendarType: function(calendarType)
+ {
+ var self = this;
+
+ // SQL query to retrieve all the events for this month
+ var eventsQuery = "SELECT id, eventTitle, eventLocation, startTime, endTime, eventCalendar, eventDetails FROM WebKitCalendarEvents WHERE (startTime BETWEEN ? and ?) AND eventCalendar = ?";
+
+ // Arguments to the SQL query above
+ var sqlArguments = [calendarStartTime, calendarEndTime, calendarType];
+
+ // SQLStatementCallback to process the query result
+ function sqlStatementCallback(tx, result) { self.processLoadedEvents(result.rows); };
+
+ // SQLStatementErrorCallback that handles any error from the query
+ function sqlStatementErrorCallback(tx, error) { alert("Failed to retrieve events from database - " + error.message); };
+
+ // SQLTransactionCallback
+ function sqlTransactionCallback(tx) { tx.executeSql(eventsQuery, sqlArguments, sqlStatementCallback, sqlStatementErrorCallback); };
+
+ CalendarDatabase.db.transaction(sqlTransactionCallback);
+ },
+
+ processLoadedEvents: function(rows)
+ {
+ for (var i = 0; i < rows.length; i++) {
+ var row = rows.item(i);
+ var dayDate = Date.dayDateFromTime(row["startTime"]);
+ var dayObj = dateToDayObjectMap[dayDate.getTime()];
+ if (!dayObj)
+ continue;
+
+ dayObj.insertEvent(CalendarEvent.calendarEventFromResultRow(row, false));
+
+ // Keep track of the highest id seen.
+ if (row["id"] > highestID)
+ highestID = row["id"];
+ }
+ },
+
+ processQueryResults: function(rows)
+ {
+ var searchResultsList = document.getElementById("searchResults");
+ searchResultsList.removeChildren();
+ unhighlightAllEvents();
+ for (var i = 0; i < rows.length; i++) {
+ var row = rows.item(i);
+ var calendarEvent = CalendarEvent.calendarEventFromResultRow(row, true);
+ highlightEventInCalendar(calendarEvent);
+ searchResultsList.appendChild(calendarEvent.searchResultAsListItem());
+ }
+ }
+}
+
+// Open Database!
+CalendarDatabase.open();
+
+function highlightEventInCalendar(calendarEvent)
+{
+ var itemsToHighlight = document.querySelectorAll("ul.contents li." + calendarEvent.calendar);
+ for (var i = 0; i < itemsToHighlight.length; ++i) {
+ var listItem = itemsToHighlight[i];
+ if (listItem.innerText !== calendarEvent.title)
+ continue;
+ if (listItem.hasStyleClass("highlighted"))
+ continue;
+ listItem.addStyleClass("highlighted");
+ break;
+ }
+}
+
+function unhighlightAllEvents()
+{
+ var highlightedItems = document.querySelectorAll("ul.contents li.highlighted");
+ for (var i = 0; i < highlightedItems.length; ++i)
+ highlightedItems[i].removeStyleClass("highlighted");
+}
+
+// Day object -------------------------------------------------------
+
+function dayObjectFromElement(element)
+{
+ var parent = element;
+ while (parent && !parent.dayObject)
+ parent = parent.parentNode;
+ return parent ? parent.dayObject : null;
+}
+
+function Day(date)
+{
+ this.date = new Date(date);
+ this.divNode = null;
+ this.contentsListNode = null;
+ this.eventsArray = null;
+}
+
+Day.prototype.attach = function(parent)
+{
+ /* The HTML of each day looks like this:
+ <div class="day box">
+ <div class="date">1</div>
+ <ul class="contents">
+ <li>Event 1</li>
+ <li>Event 2</li>
+ </ul>
+ </div>
+ */
+
+ if (this.divNode)
+ throw("We have already created html elements for this day!");
+ this.divNode = document.createElement("div");
+ this.divNode.dayObject = this;
+ this.divNode.addStyleClass("day");
+ this.divNode.addStyleClass("box");
+ if (this.date.getMonth() != monthOnDisplay.getMonth())
+ this.divNode.addStyleClass("notThisMonth");
+ if (this.date.isToday())
+ this.divNode.addStyleClass("today");
+
+ var dateDiv = document.createElement("div");
+ dateDiv.addStyleClass("date");
+ dateDiv.innerText = this.date.getDate();
+ this.divNode.appendChild(dateDiv);
+
+ this.contentsListNode = document.createElement("ul");
+ this.contentsListNode.addStyleClass("contents");
+ this.contentsListNode.addEventListener("dblclick", Day.newEvent, false);
+ this.divNode.appendChild(this.contentsListNode);
+
+ parent.appendChild(this.divNode);
+}
+
+Day.newEvent = function(event)
+{
+ var element = event.target;
+ var dayObj = dayObjectFromElement(element);
+ if (!dayObj || dayObj.contentsListNode != element)
+ return;
+
+ var calendarEvent = new CalendarEvent(dayObj.date, dayObj, selectedCalendarType, false);
+ calendarEvent.title = "New Event";
+ calendarEvent.from = dayObj.defaultEventStartTime();
+ var endTime = new Date(calendarEvent.from);
+ endTime.setHours(endTime.getHours() + 1);
+ calendarEvent.to = endTime;
+ dayObj.insertEvent(calendarEvent);
+ CalendarDatabase.saveAsNewEventToDB(calendarEvent);
+ selectedCalendarEvent = calendarEvent;
+ calendarEvent.show();
+
+ stopEvent(event);
+}
+
+Day.prototype.insertEvent = function(calendarEvent)
+{
+ if (!this.eventsArray)
+ this.eventsArray = new Array();
+ // Remove the event from array if it's already there.
+ var index = this.eventsArray.indexOf(calendarEvent);
+ if (index >= 0)
+ this.eventsArray.splice(index, 1);
+ calendarEvent.detach();
+ // Don't display this in the calendar if the calendar is unchecked
+ if (!isCalendarTypeVisible(calendarEvent.calendar))
+ return;
+ // We want to insert the calendarEvent in order of start time
+ for (index = 0; index < this.eventsArray.length; index++) {
+ if (this.eventsArray[index].from > calendarEvent.from)
+ break;
+ }
+ this.eventsArray.splice(index, 0, calendarEvent);
+ calendarEvent.attach();
+}
+
+Day.prototype.deleteEvent = function(calendarEvent)
+{
+ CalendarDatabase.deleteEventFromDB(calendarEvent);
+ this.hideEvent(calendarEvent);
+}
+
+Day.prototype.hideEvent = function(calendarEvent)
+{
+ calendarEvent.detach();
+ selectedCalendarEvent = null;
+ if (!this.eventsArray)
+ return;
+ var index = this.eventsArray.indexOf(calendarEvent);
+ if (index >= 0)
+ this.eventsArray.splice(index, 1);
+}
+
+Day.prototype.hideEventsOfCalendarType = function(calendarType)
+{
+ if (!this.eventsArray)
+ return;
+ var i = 0;
+ while (i < this.eventsArray.length) {
+ if (this.eventsArray[i].calendar == calendarType)
+ this.hideEvent(this.eventsArray[i]);
+ else
+ i++;
+ }
+}
+
+Day.prototype.defaultEventStartTime = function()
+{
+ var startTime;
+ if (!this.eventsArray || !this.eventsArray.length) {
+ startTime = new Date(this.date);
+ startTime.setHours(9, 0, 0, 0); // Default: events start at 9am!
+ return startTime;
+ }
+ var lastEvent = this.eventsArray[this.eventsArray.length-1];
+ startTime = new Date(lastEvent.to);
+ startTime.roundToHour();
+ return startTime;
+}
+
+// CalendarEvent object -------------------------------------------------------
+
+function calendarEventFromElement(element)
+{
+ var parent = element;
+ while (parent) {
+ if (parent.calendarEvent)
+ return parent.calendarEvent;
+ parent = parent.parentNode;
+ }
+ return null;
+}
+
+function CalendarEvent(date, day, calendar, fromSearch)
+{
+ this.date = date;
+ this.day = day;
+ this.fromSearch = fromSearch;
+ this.id = ++highestID;
+ this.title = "";
+ this.location = "";
+ this.from = null;
+ this.to = null;
+ this.calendar = calendar;
+ this.details = "";
+ this.listItemNode = null;
+}
+
+CalendarEvent.prototype.attach = function()
+{
+ var parentNode = this.day.contentsListNode;
+ if (!parentNode)
+ throw("Must have created day box's html hierarchy before adding calendar events.");
+ if (this.listItemNode)
+ parentNode.removeChild(this.listItemNode);
+ this.listItemNode = document.createElement("li");
+ this.listItemNode.calendarEvent = this;
+ this.listItemNode.addStyleClass(this.calendar);
+ this.listItemNode.innerText = this.title;
+ this.listItemNode.addEventListener("click", CalendarEvent.eventSelected, false);
+ var index = this.day.eventsArray.indexOf(this);
+ if (index < 0)
+ throw("Cannot attach if CalendarEvent does not belong to a Day object.");
+ var adjacentNode = null;
+ if (index < this.day.contentsListNode.childNodes.length)
+ adjacentNode = this.day.contentsListNode.childNodes[index];
+ this.day.contentsListNode.insertBefore(this.listItemNode, adjacentNode);
+}
+
+CalendarEvent.prototype.detach = function()
+{
+ var parentNode = this.day.contentsListNode;
+ if (parentNode && this.listItemNode)
+ parentNode.removeChild(this.listItemNode);
+ this.listItemNode = null;
+}
+
+CalendarEvent.eventSelected = function(event)
+{
+ var calendarEvent = calendarEventFromElement(event.target);
+ if (!calendarEvent)
+ return;
+ calendarEvent.selected();
+ stopEvent(event);
+}
+
+CalendarEvent.prototype.selected = function()
+{
+ if (selectedCalendarEvent == this) {
+ selectedCalendarEvent.show();
+ return;
+ }
+ if (selectedCalendarEvent)
+ selectedCalendarEvent.listItemNode.removeStyleClass("selected");
+ selectedCalendarEvent = this;
+ if (selectedCalendarEvent)
+ selectedCalendarEvent.listItemNode.addStyleClass("selected");
+}
+
+function minutesString(minutes)
+{
+ if (minutes < 10)
+ return "0" + minutes;
+ return minutes.toString();
+}
+
+CalendarEvent.prototype.show = function()
+{
+ // Update the event details
+ document.getElementById("eventTitle").innerText = this.title;
+ document.getElementById("eventLocation").innerText = this.location;
+ document.getElementById("eventFromDate").innerText = this.date.toLocaleDateString();
+ document.getElementById("eventFromHours").innerText = this.from.getHours();
+ document.getElementById("eventFromMinutes").innerText = minutesString(this.from.getMinutes());
+ document.getElementById("eventToDate").innerText = this.date.toLocaleDateString();
+ document.getElementById("eventToHours").innerText = this.to.getHours();
+ document.getElementById("eventToMinutes").innerText = minutesString(this.to.getMinutes());
+ document.getElementById("eventCalendarType").value = this.calendar;
+ document.getElementById("eventDetails").innerText = this.details;
+
+ // Reset the map
+ requestMapImage();
+
+ // Fade out the gridView
+ document.getElementById("gridView").addStyleClass("inactive");
+
+ // Show event details
+ document.getElementById("eventOverlay").addStyleClass("show");
+}
+
+CalendarEvent.prototype.detailsUpdated = function()
+{
+ // FIXME: error checking!!
+ this.title = document.getElementById("eventTitle").innerText;
+ this.location = document.getElementById("eventLocation").innerText;
+ this.from.setHours(document.getElementById("eventFromHours").innerText);
+ this.from.setMinutes(document.getElementById("eventFromMinutes").innerText);
+ this.to.setHours(document.getElementById("eventToHours").innerText);
+ this.to.setMinutes(document.getElementById("eventToMinutes").innerText);
+ this.calendar = document.getElementById("eventCalendarType").value;
+ this.details = document.getElementById("eventDetails").innerText;
+
+ CalendarDatabase.saveEventToDB(this);
+ if (!this.fromSearch && this.day)
+ this.day.insertEvent(this);
+}
+
+CalendarEvent.prototype.toString = function()
+{
+ return "CalendarEvent: " + this.title + " starting on " + this.from.toString();
+}
+
+CalendarEvent.prototype.searchResultAsListItem = function()
+{
+ var listItem = document.createElement("li");
+ var titleText = document.createTextNode(this.title);
+ var locationText = this.location.length > 0 ? document.createTextNode(this.location) : null;
+ var startTimeText = document.createTextNode(this.from.toLocaleString());
+ listItem.appendChild(titleText);
+ listItem.appendChild(document.createElement("br"));
+ if (locationText) {
+ listItem.appendChild(locationText);
+ listItem.appendChild(document.createElement("br"));
+ }
+ listItem.appendChild(startTimeText);
+ listItem.calendarEvent = this;
+ listItem.addStyleClass(this.calendar);
+ listItem.addEventListener("click", CalendarEvent.eventSelected, false);
+ this.listItemNode = listItem;
+ return listItem;
+}
+
+CalendarEvent.calendarEventFromResultRow = function(row, fromSearch)
+{
+ var dayDate = Date.dayDateFromTime(row["startTime"]);
+ var dayObj = dateToDayObjectMap[dayDate.getTime()];
+ var calendarEvent = new CalendarEvent(dayDate, dayObj, row["eventCalendar"], fromSearch);
+ calendarEvent.id = row["id"];
+ calendarEvent.title = row["eventTitle"];
+ calendarEvent.location = row["eventLocation"];
+ calendarEvent.from = new Date();
+ calendarEvent.from.setTime(row["startTime"]);
+ calendarEvent.to = new Date();
+ calendarEvent.to.setTime(row["endTime"]);
+ calendarEvent.details = row["eventDetails"];
+ return calendarEvent;
+}
+
+// Miscellaneous methods ------------------------------------------------
+
+function stopEvent(event)
+{
+ event.preventDefault();
+ event.stopPropagation();
+}
--- /dev/null
+CACHE MANIFEST
+# All resources required to make Calendar work offline
+
+Boom.aiff
+Calendar.html
+Calendar.css
+Calendar.js
+CalendarApp.icns
+LocationImage.js
+Utilities.js
+favicon.ico
+Images/AirPort4.png
+Images/AirPortError.png
+Images/disclosureTriangleSmallDown.png
+Images/disclosureTriangleSmallRight.png
+Images/statusbarBackground.png
+Images/statusbarResizerVertical.png
+
--- /dev/null
+function LocationImage()
+{
+
+}
+
+LocationImage.prototype.requestLocationImage = function(address, callback)
+{
+
+}
+
+
+LocationImage.prototype.geocodeResultReceived = function(point, callback)
+{
+
+}
--- /dev/null
+// Element extension --------------------------------------------------------------
+
+Element.prototype.removeStyleClass = function(className)
+{
+ // Test for the simple case before using a RegExp.
+ if (this.className === className) {
+ this.className = "";
+ return;
+ }
+
+ var regex = new RegExp("(^|\\s+)" + className.escapeForRegExp() + "($|\\s+)");
+ if (regex.test(this.className))
+ this.className = this.className.replace(regex, " ");
+}
+
+Element.prototype.addStyleClass = function(className)
+{
+ if (className && !this.hasStyleClass(className))
+ this.className += (this.className.length ? " " + className : className);
+}
+
+Element.prototype.hasStyleClass = function(className)
+{
+ if (!className)
+ return false;
+ // Test for the simple case before using a RegExp.
+ if (this.className === className)
+ return true;
+ var regex = new RegExp("(^|\\s)" + className.escapeForRegExp() + "($|\\s)");
+ return regex.test(this.className);
+}
+
+Element.prototype.removeChildren = function()
+{
+ while (this.firstChild)
+ this.removeChild(this.firstChild);
+}
+
+Element.prototype.findParentOfTagName = function(tagName)
+{
+ var parent = this;
+ while (parent) {
+ if (parent.tagName == tagName)
+ return parent;
+ parent = parent.parentNode;
+ }
+ return null;
+}
+
+Element.prototype.__defineGetter__("totalOffsetLeft", function()
+{
+ var total = 0;
+ for (var element = this; element; element = element.offsetParent)
+ total += element.offsetLeft;
+ return total;
+});
+
+Element.prototype.__defineGetter__("totalOffsetTop", function()
+{
+ var total = 0;
+ for (var element = this; element; element = element.offsetParent)
+ total += element.offsetTop;
+ return total;
+});
+
+// String extension ------------------------------------------------------
+
+String.prototype.hasSubstring = function(string, caseInsensitive)
+{
+ if (!caseInsensitive)
+ return this.indexOf(string) !== -1;
+ return this.match(new RegExp(string.escapeForRegExp(), "i"));
+}
+
+String.prototype.escapeCharacters = function(chars)
+{
+ var foundChar = false;
+ for (var i = 0; i < chars.length; ++i) {
+ if (this.indexOf(chars.charAt(i)) !== -1) {
+ foundChar = true;
+ break;
+ }
+ }
+
+ if (!foundChar)
+ return this;
+
+ var result = "";
+ for (var i = 0; i < this.length; ++i) {
+ if (chars.indexOf(this.charAt(i)) !== -1)
+ result += "\\";
+ result += this.charAt(i);
+ }
+
+ return result;
+}
+
+String.prototype.escapeForRegExp = function()
+{
+ return this.escapeCharacters("^[]{}()\\.$*+?|");
+}
+
+// Number extension ------------------------------------------------------
+
+Number.constrain = function(num, min, max)
+{
+ if (num < min)
+ num = min;
+ else if (num > max)
+ num = max;
+ return num;
+}
+
+// Date extension --------------------------------------------------------
+
+Date.prototype.isToday = function()
+{
+ var today = new Date();
+ return (today.getFullYear() == this.getFullYear() && today.getMonth() == this.getMonth() && today.getDate() == this.getDate());
+}
+
+Date.prototype.roundToHour = function() {
+ if (this.getMinutes() == 0)
+ return; // Already rounded to the hour
+ if (this.getHours() < 23)
+ this.setHours(this.getHours() + 1);
+ this.setMinutes(0);
+}
+
+Date.dayDateFromTime = function(time) {
+ var date = new Date(time);
+ date.setHours(0, 0, 0, 0);
+ return date;
+}
--- /dev/null
+# This is a comment. I love comments.
+
+# This file controls what Internet media types are sent to the client for
+# given file extension(s). Sending the correct media type to the client
+# is important so they know how to handle the content of the file.
+# Extra types can either be added here or by using an AddType directive
+# in your config files. For more information about Internet media types,
+# please read RFC 2045, 2046, 2047, 2048, and 2077. The Internet media type
+# registry is at <http://www.iana.org/assignments/media-types/>.
+
+# MIME type Extensions
+application/activemessage
+application/andrew-inset ez
+application/applefile
+application/atom+xml atom
+application/atomcat+xml atomcat
+application/atomicmail
+application/atomsvc+xml atomsvc
+application/auth-policy+xml
+application/batch-smtp
+application/beep+xml
+application/cals-1840
+application/ccxml+xml ccxml
+application/cellml+xml
+application/cnrp+xml
+application/commonground
+application/conference-info+xml
+application/cpl+xml
+application/csta+xml
+application/cstadata+xml
+application/cybercash
+application/davmount+xml davmount
+application/dca-rft
+application/dec-dx
+application/dialog-info+xml
+application/dicom
+application/dns
+application/dvcs
+application/ecmascript ecma
+application/edi-consent
+application/edi-x12
+application/edifact
+application/epp+xml
+application/eshop
+application/fastinfoset
+application/fastsoap
+application/fits
+application/font-tdpfr pfr
+application/h224
+application/http
+application/hyperstudio stk
+application/iges
+application/im-iscomposing+xml
+application/index
+application/index.cmd
+application/index.obj
+application/index.response
+application/index.vnd
+application/iotp
+application/ipp
+application/isup
+application/javascript js
+application/json json
+application/kpml-request+xml
+application/kpml-response+xml
+application/mac-binhex40 hqx
+application/mac-compactpro cpt
+application/macwriteii
+application/marc mrc
+application/mathematica ma nb mb
+application/mathml+xml mathml
+application/mbms-associated-procedure-description+xml
+application/mbms-deregister+xml
+application/mbms-envelope+xml
+application/mbms-msk+xml
+application/mbms-msk-response+xml
+application/mbms-protection-description+xml
+application/mbms-reception-report+xml
+application/mbms-register+xml
+application/mbms-register-response+xml
+application/mbms-user-service-description+xml
+application/mbox mbox
+application/mediaservercontrol+xml mscml
+application/mikey
+application/mp4 mp4s
+application/mpeg4-generic
+application/mpeg4-iod
+application/mpeg4-iod-xmt
+application/msword doc dot
+application/mxf mxf
+application/nasdata
+application/news-message-id
+application/news-transmission
+application/nss
+application/ocsp-request
+application/ocsp-response
+application/octet-stream bin dms lha lzh class so iso dmg dist distz pkg bpk dump elc scpt
+application/oda oda
+application/oebps-package+xml
+application/ogg ogg
+application/parityfec
+application/pdf pdf
+application/pgp-encrypted pgp
+application/pgp-keys
+application/pgp-signature asc sig
+application/pics-rules prf
+application/pidf+xml
+application/pkcs10 p10
+application/pkcs7-mime p7m p7c
+application/pkcs7-signature p7s
+application/pkix-cert cer
+application/pkix-crl crl
+application/pkix-pkipath pkipath
+application/pkixcmp pki
+application/pls+xml pls
+application/poc-settings+xml
+application/postscript ai eps ps
+application/prs.alvestrand.titrax-sheet
+application/prs.cww cww
+application/prs.nprend
+application/prs.plucker
+application/qsig
+application/rdf+xml rdf
+application/reginfo+xml rif
+application/relax-ng-compact-syntax rnc
+application/remote-printing
+application/resource-lists+xml rl
+application/riscos
+application/rlmi+xml
+application/rls-services+xml rs
+application/rsd+xml rsd
+application/rss+xml rss
+application/rtf rtf
+application/rtx
+application/samlassertion+xml
+application/samlmetadata+xml
+application/sbml+xml sbml
+application/sdp sdp
+application/set-payment
+application/set-payment-initiation setpay
+application/set-registration
+application/set-registration-initiation setreg
+application/sgml
+application/sgml-open-catalog
+application/shf+xml shf
+application/sieve
+application/simple-filter+xml
+application/simple-message-summary
+application/simplesymbolcontainer
+application/slate
+application/smil
+application/smil+xml smi smil
+application/soap+fastinfoset
+application/soap+xml
+application/spirits-event+xml
+application/srgs gram
+application/srgs+xml grxml
+application/ssml+xml ssml
+application/timestamp-query
+application/timestamp-reply
+application/tve-trigger
+application/vemmi
+application/vividence.scriptfile
+application/vnd.3gpp.bsf+xml
+application/vnd.3gpp.pic-bw-large plb
+application/vnd.3gpp.pic-bw-small psb
+application/vnd.3gpp.pic-bw-var pvb
+application/vnd.3gpp.sms
+application/vnd.3gpp2.bcmcsinfo+xml
+application/vnd.3gpp2.sms
+application/vnd.3m.post-it-notes pwn
+application/vnd.accpac.simply.aso aso
+application/vnd.accpac.simply.imp imp
+application/vnd.acucobol acu
+application/vnd.acucorp atc acutc
+application/vnd.adobe.xdp+xml xdp
+application/vnd.adobe.xfdf xfdf
+application/vnd.aether.imp
+application/vnd.amiga.ami ami
+application/vnd.anser-web-certificate-issue-initiation cii
+application/vnd.anser-web-funds-transfer-initiation fti
+application/vnd.antix.game-component atx
+application/vnd.apple.installer+xml mpkg
+application/vnd.audiograph aep
+application/vnd.autopackage
+application/vnd.avistar+xml
+application/vnd.blueice.multipass mpm
+application/vnd.bmi bmi
+application/vnd.businessobjects rep
+application/vnd.cab-jscript
+application/vnd.canon-cpdl
+application/vnd.canon-lips
+application/vnd.cendio.thinlinc.clientconf
+application/vnd.chemdraw+xml cdxml
+application/vnd.chipnuts.karaoke-mmd mmd
+application/vnd.cinderella cdy
+application/vnd.cirpack.isdn-ext
+application/vnd.claymore cla
+application/vnd.clonk.c4group c4g c4d c4f c4p c4u
+application/vnd.commerce-battelle
+application/vnd.commonspace csp cst
+application/vnd.contact.cmsg cdbcmsg
+application/vnd.cosmocaller cmc
+application/vnd.crick.clicker clkx
+application/vnd.crick.clicker.keyboard clkk
+application/vnd.crick.clicker.palette clkp
+application/vnd.crick.clicker.template clkt
+application/vnd.crick.clicker.wordbank clkw
+application/vnd.criticaltools.wbs+xml wbs
+application/vnd.ctc-posml pml
+application/vnd.cups-pdf
+application/vnd.cups-postscript
+application/vnd.cups-ppd ppd
+application/vnd.cups-raster
+application/vnd.cups-raw
+application/vnd.curl curl
+application/vnd.cybank
+application/vnd.data-vision.rdz rdz
+application/vnd.denovo.fcselayout-link fe_launch
+application/vnd.dna dna
+application/vnd.dolby.mlp mlp
+application/vnd.dpgraph dpg
+application/vnd.dreamfactory dfac
+application/vnd.dvb.esgcontainer
+application/vnd.dvb.ipdcesgaccess
+application/vnd.dxr
+application/vnd.ecdis-update
+application/vnd.ecowin.chart mag
+application/vnd.ecowin.filerequest
+application/vnd.ecowin.fileupdate
+application/vnd.ecowin.series
+application/vnd.ecowin.seriesrequest
+application/vnd.ecowin.seriesupdate
+application/vnd.enliven nml
+application/vnd.epson.esf esf
+application/vnd.epson.msf msf
+application/vnd.epson.quickanime qam
+application/vnd.epson.salt slt
+application/vnd.epson.ssf ssf
+application/vnd.ericsson.quickcall
+application/vnd.eszigno3+xml es3 et3
+application/vnd.eudora.data
+application/vnd.ezpix-album ez2
+application/vnd.ezpix-package ez3
+application/vnd.fdf fdf
+application/vnd.ffsns
+application/vnd.fints
+application/vnd.flographit gph
+application/vnd.fluxtime.clip ftc
+application/vnd.framemaker fm frame maker
+application/vnd.frogans.fnc fnc
+application/vnd.frogans.ltf ltf
+application/vnd.fsc.weblaunch fsc
+application/vnd.fujitsu.oasys oas
+application/vnd.fujitsu.oasys2 oa2
+application/vnd.fujitsu.oasys3 oa3
+application/vnd.fujitsu.oasysgp fg5
+application/vnd.fujitsu.oasysprs bh2
+application/vnd.fujixerox.art-ex
+application/vnd.fujixerox.art4
+application/vnd.fujixerox.hbpl
+application/vnd.fujixerox.ddd ddd
+application/vnd.fujixerox.docuworks xdw
+application/vnd.fujixerox.docuworks.binder xbd
+application/vnd.fut-misnet
+application/vnd.fuzzysheet fzs
+application/vnd.genomatix.tuxedo txd
+application/vnd.google-earth.kml+xml kml
+application/vnd.google-earth.kmz kmz
+application/vnd.grafeq gqf gqs
+application/vnd.gridmp
+application/vnd.groove-account gac
+application/vnd.groove-help ghf
+application/vnd.groove-identity-message gim
+application/vnd.groove-injector grv
+application/vnd.groove-tool-message gtm
+application/vnd.groove-tool-template tpl
+application/vnd.groove-vcard vcg
+application/vnd.handheld-entertainment+xml zmm
+application/vnd.hbci hbci
+application/vnd.hcl-bireports
+application/vnd.hhe.lesson-player les
+application/vnd.hp-hpgl hpgl
+application/vnd.hp-hpid hpid
+application/vnd.hp-hps hps
+application/vnd.hp-jlyt jlt
+application/vnd.hp-pcl pcl
+application/vnd.hp-pclxl pclxl
+application/vnd.httphone
+application/vnd.hzn-3d-crossword x3d
+application/vnd.ibm.afplinedata
+application/vnd.ibm.electronic-media
+application/vnd.ibm.minipay mpy
+application/vnd.ibm.modcap afp listafp list3820
+application/vnd.ibm.rights-management irm
+application/vnd.ibm.secure-container sc
+application/vnd.igloader igl
+application/vnd.immervision-ivp ivp
+application/vnd.immervision-ivu ivu
+application/vnd.informedcontrol.rms+xml
+application/vnd.intercon.formnet xpw xpx
+application/vnd.intertrust.digibox
+application/vnd.intertrust.nncp
+application/vnd.intu.qbo qbo
+application/vnd.intu.qfx qfx
+application/vnd.ipunplugged.rcprofile rcprofile
+application/vnd.irepository.package+xml irp
+application/vnd.is-xpr xpr
+application/vnd.jam jam
+application/vnd.japannet-directory-service
+application/vnd.japannet-jpnstore-wakeup
+application/vnd.japannet-payment-wakeup
+application/vnd.japannet-registration
+application/vnd.japannet-registration-wakeup
+application/vnd.japannet-setstore-wakeup
+application/vnd.japannet-verification
+application/vnd.japannet-verification-wakeup
+application/vnd.jcp.javame.midlet-rms rms
+application/vnd.jisp jisp
+application/vnd.kahootz ktz ktr
+application/vnd.kde.karbon karbon
+application/vnd.kde.kchart chrt
+application/vnd.kde.kformula kfo
+application/vnd.kde.kivio flw
+application/vnd.kde.kontour kon
+application/vnd.kde.kpresenter kpr kpt
+application/vnd.kde.kspread ksp
+application/vnd.kde.kword kwd kwt
+application/vnd.kenameaapp htke
+application/vnd.kidspiration kia
+application/vnd.kinar kne knp
+application/vnd.koan skp skd skt skm
+application/vnd.liberty-request+xml
+application/vnd.llamagraphics.life-balance.desktop lbd
+application/vnd.llamagraphics.life-balance.exchange+xml lbe
+application/vnd.lotus-1-2-3 123
+application/vnd.lotus-approach apr
+application/vnd.lotus-freelance pre
+application/vnd.lotus-notes nsf
+application/vnd.lotus-organizer org
+application/vnd.lotus-screencam scm
+application/vnd.lotus-wordpro lwp
+application/vnd.macports.portpkg portpkg
+application/vnd.marlin.drm.actiontoken+xml
+application/vnd.marlin.drm.conftoken+xml
+application/vnd.marlin.drm.mdcf
+application/vnd.mcd mcd
+application/vnd.medcalcdata mc1
+application/vnd.mediastation.cdkey cdkey
+application/vnd.meridian-slingshot
+application/vnd.mfer mwf
+application/vnd.mfmp mfm
+application/vnd.micrografx.flo flo
+application/vnd.micrografx.igx igx
+application/vnd.mif mif
+application/vnd.minisoft-hp3000-save
+application/vnd.mitsubishi.misty-guard.trustweb
+application/vnd.mobius.daf daf
+application/vnd.mobius.dis dis
+application/vnd.mobius.mbk mbk
+application/vnd.mobius.mqy mqy
+application/vnd.mobius.msl msl
+application/vnd.mobius.plc plc
+application/vnd.mobius.txf txf
+application/vnd.mophun.application mpn
+application/vnd.mophun.certificate mpc
+application/vnd.motorola.flexsuite
+application/vnd.motorola.flexsuite.adsi
+application/vnd.motorola.flexsuite.fis
+application/vnd.motorola.flexsuite.gotap
+application/vnd.motorola.flexsuite.kmr
+application/vnd.motorola.flexsuite.ttc
+application/vnd.motorola.flexsuite.wem
+application/vnd.mozilla.xul+xml xul
+application/vnd.ms-artgalry cil
+application/vnd.ms-asf asf
+application/vnd.ms-cab-compressed cab
+application/vnd.ms-excel xls xlm xla xlc xlt xlw
+application/vnd.ms-fontobject eot
+application/vnd.ms-htmlhelp chm
+application/vnd.ms-ims ims
+application/vnd.ms-lrm lrm
+application/vnd.ms-playready.initiator+xml
+application/vnd.ms-powerpoint ppt pps pot
+application/vnd.ms-project mpp mpt
+application/vnd.ms-tnef
+application/vnd.ms-wmdrm.lic-chlg-req
+application/vnd.ms-wmdrm.lic-resp
+application/vnd.ms-wmdrm.meter-chlg-req
+application/vnd.ms-wmdrm.meter-resp
+application/vnd.ms-works wps wks wcm wdb
+application/vnd.ms-wpl wpl
+application/vnd.ms-xpsdocument xps
+application/vnd.mseq mseq
+application/vnd.msign
+application/vnd.music-niff
+application/vnd.musician mus
+application/vnd.ncd.control
+application/vnd.nervana
+application/vnd.netfpx
+application/vnd.neurolanguage.nlu nlu
+application/vnd.noblenet-directory nnd
+application/vnd.noblenet-sealer nns
+application/vnd.noblenet-web nnw
+application/vnd.nokia.catalogs
+application/vnd.nokia.conml+wbxml
+application/vnd.nokia.conml+xml
+application/vnd.nokia.isds-radio-presets
+application/vnd.nokia.iptv.config+xml
+application/vnd.nokia.landmark+wbxml
+application/vnd.nokia.landmark+xml
+application/vnd.nokia.landmarkcollection+xml
+application/vnd.nokia.n-gage.ac+xml
+application/vnd.nokia.n-gage.data ngdat
+application/vnd.nokia.n-gage.symbian.install n-gage
+application/vnd.nokia.ncd
+application/vnd.nokia.pcd+wbxml
+application/vnd.nokia.pcd+xml
+application/vnd.nokia.radio-preset rpst
+application/vnd.nokia.radio-presets rpss
+application/vnd.novadigm.edm edm
+application/vnd.novadigm.edx edx
+application/vnd.novadigm.ext ext
+application/vnd.oasis.opendocument.chart odc
+application/vnd.oasis.opendocument.chart-template otc
+application/vnd.oasis.opendocument.formula odf
+application/vnd.oasis.opendocument.formula-template otf
+application/vnd.oasis.opendocument.graphics odg
+application/vnd.oasis.opendocument.graphics-template otg
+application/vnd.oasis.opendocument.image odi
+application/vnd.oasis.opendocument.image-template oti
+application/vnd.oasis.opendocument.presentation odp
+application/vnd.oasis.opendocument.presentation-template otp
+application/vnd.oasis.opendocument.spreadsheet ods
+application/vnd.oasis.opendocument.spreadsheet-template ots
+application/vnd.oasis.opendocument.text odt
+application/vnd.oasis.opendocument.text-master otm
+application/vnd.oasis.opendocument.text-template ott
+application/vnd.oasis.opendocument.text-web oth
+application/vnd.obn
+application/vnd.olpc-sugar xo
+application/vnd.oma-scws-config
+application/vnd.oma-scws-http-request
+application/vnd.oma-scws-http-response
+application/vnd.oma.bcast.associated-procedure-parameter+xml
+application/vnd.oma.bcast.drm-trigger+xml
+application/vnd.oma.bcast.imd+xml
+application/vnd.oma.bcast.notification+xml
+application/vnd.oma.bcast.sgboot
+application/vnd.oma.bcast.sgdd+xml
+application/vnd.oma.bcast.sgdu
+application/vnd.oma.bcast.simple-symbol-container
+application/vnd.oma.bcast.smartcard-trigger+xml
+application/vnd.oma.bcast.sprov+xml
+application/vnd.oma.dd2+xml dd2
+application/vnd.oma.drm.risd+xml
+application/vnd.oma.group-usage-list+xml
+application/vnd.oma.poc.groups+xml
+application/vnd.oma.xcap-directory+xml
+application/vnd.omads-email+xml
+application/vnd.omads-file+xml
+application/vnd.omads-folder+xml
+application/vnd.omaloc-supl-init
+application/vnd.openofficeorg.extension oxt
+application/vnd.osa.netdeploy
+application/vnd.osgi.dp dp
+application/vnd.otps.ct-kip+xml
+application/vnd.palm prc pdb pqa oprc
+application/vnd.paos.xml
+application/vnd.pg.format str
+application/vnd.pg.osasli ei6
+application/vnd.piaccess.application-licence
+application/vnd.picsel efif
+application/vnd.poc.group-advertisement+xml
+application/vnd.pocketlearn plf
+application/vnd.powerbuilder6 pbd
+application/vnd.powerbuilder6-s
+application/vnd.powerbuilder7
+application/vnd.powerbuilder7-s
+application/vnd.powerbuilder75
+application/vnd.powerbuilder75-s
+application/vnd.preminet
+application/vnd.previewsystems.box box
+application/vnd.proteus.magazine mgz
+application/vnd.publishare-delta-tree qps
+application/vnd.pvi.ptid1 ptid
+application/vnd.pwg-multiplexed
+application/vnd.pwg-xhtml-print+xml
+application/vnd.qualcomm.brew-app-res
+application/vnd.quark.quarkxpress qxd qxt qwd qwt qxl qxb
+application/vnd.rapid
+application/vnd.recordare.musicxml mxl
+application/vnd.recordare.musicxml+xml
+application/vnd.renlearn.rlprint
+application/vnd.rn-realmedia rm
+application/vnd.ruckus.download
+application/vnd.s3sms
+application/vnd.scribus
+application/vnd.sealed.3df
+application/vnd.sealed.csf
+application/vnd.sealed.doc
+application/vnd.sealed.eml
+application/vnd.sealed.mht
+application/vnd.sealed.net
+application/vnd.sealed.ppt
+application/vnd.sealed.tiff
+application/vnd.sealed.xls
+application/vnd.sealedmedia.softseal.html
+application/vnd.sealedmedia.softseal.pdf
+application/vnd.seemail see
+application/vnd.sema sema
+application/vnd.semd semd
+application/vnd.semf semf
+application/vnd.shana.informed.formdata ifm
+application/vnd.shana.informed.formtemplate itp
+application/vnd.shana.informed.interchange iif
+application/vnd.shana.informed.package ipk
+application/vnd.simtech-mindmapper twd twds
+application/vnd.smaf mmf
+application/vnd.solent.sdkm+xml sdkm sdkd
+application/vnd.spotfire.dxp dxp
+application/vnd.spotfire.sfs sfs
+application/vnd.sss-cod
+application/vnd.sss-dtf
+application/vnd.sss-ntf
+application/vnd.street-stream
+application/vnd.sun.wadl+xml
+application/vnd.sus-calendar sus susp
+application/vnd.svd svd
+application/vnd.swiftview-ics
+application/vnd.syncml+xml xsm
+application/vnd.syncml.dm+wbxml bdm
+application/vnd.syncml.dm+xml xdm
+application/vnd.syncml.ds.notification
+application/vnd.tao.intent-module-archive tao
+application/vnd.tmobile-livetv tmo
+application/vnd.trid.tpt tpt
+application/vnd.triscape.mxs mxs
+application/vnd.trueapp tra
+application/vnd.truedoc
+application/vnd.ufdl ufd ufdl
+application/vnd.uiq.theme utz
+application/vnd.umajin umj
+application/vnd.unity unityweb
+application/vnd.uoml+xml uoml
+application/vnd.uplanet.alert
+application/vnd.uplanet.alert-wbxml
+application/vnd.uplanet.bearer-choice
+application/vnd.uplanet.bearer-choice-wbxml
+application/vnd.uplanet.cacheop
+application/vnd.uplanet.cacheop-wbxml
+application/vnd.uplanet.channel
+application/vnd.uplanet.channel-wbxml
+application/vnd.uplanet.list
+application/vnd.uplanet.list-wbxml
+application/vnd.uplanet.listcmd
+application/vnd.uplanet.listcmd-wbxml
+application/vnd.uplanet.signal
+application/vnd.vcx vcx
+application/vnd.vd-study
+application/vnd.vectorworks
+application/vnd.vidsoft.vidconference
+application/vnd.visio vsd vst vss vsw
+application/vnd.visionary vis
+application/vnd.vividence.scriptfile
+application/vnd.vsf vsf
+application/vnd.wap.sic
+application/vnd.wap.slc
+application/vnd.wap.wbxml wbxml
+application/vnd.wap.wmlc wmlc
+application/vnd.wap.wmlscriptc wmlsc
+application/vnd.webturbo wtb
+application/vnd.wfa.wsc
+application/vnd.wordperfect wpd
+application/vnd.wqd wqd
+application/vnd.wrq-hp3000-labelled
+application/vnd.wt.stf stf
+application/vnd.wv.csp+wbxml
+application/vnd.wv.csp+xml
+application/vnd.wv.ssp+xml
+application/vnd.xara xar
+application/vnd.xfdl xfdl
+application/vnd.xmpie.cpkg
+application/vnd.xmpie.dpkg
+application/vnd.xmpie.plan
+application/vnd.xmpie.ppkg
+application/vnd.xmpie.xlim
+application/vnd.yamaha.hv-dic hvd
+application/vnd.yamaha.hv-script hvs
+application/vnd.yamaha.hv-voice hvp
+application/vnd.yamaha.smaf-audio saf
+application/vnd.yamaha.smaf-phrase spf
+application/vnd.yellowriver-custom-menu cmp
+application/vnd.zzazz.deck+xml zaz
+application/voicexml+xml vxml
+application/watcherinfo+xml
+application/whoispp-query
+application/whoispp-response
+application/winhlp hlp
+application/wita
+application/wordperfect5.1
+application/wsdl+xml wsdl
+application/wspolicy+xml wspolicy
+application/x-ace-compressed ace
+application/x-bcpio bcpio
+application/x-bittorrent torrent
+application/x-bzip bz
+application/x-bzip2 bz2 boz
+application/x-cdlink vcd
+application/x-chat chat
+application/x-chess-pgn pgn
+application/x-compress
+application/x-cpio cpio
+application/x-csh csh
+application/x-director dcr dir dxr fgd
+application/x-dvi dvi
+application/x-futuresplash spl
+application/x-gtar gtar
+application/x-gzip
+application/x-hdf hdf
+application/x-java-jnlp-file jnlp
+application/x-latex latex
+application/x-ms-wmd wmd
+application/x-ms-wmz wmz
+application/x-msaccess mdb
+application/x-msbinder obd
+application/x-mscardfile crd
+application/x-msclip clp
+application/x-msdownload exe dll com bat msi
+application/x-msmediaview mvb m13 m14
+application/x-msmetafile wmf
+application/x-msmoney mny
+application/x-mspublisher pub
+application/x-msschedule scd
+application/x-msterminal trm
+application/x-mswrite wri
+application/x-netcdf nc cdf
+application/x-pkcs12 p12 pfx
+application/x-pkcs7-certificates p7b spc
+application/x-pkcs7-certreqresp p7r
+application/x-rar-compressed rar
+application/x-sh sh
+application/x-shar shar
+application/x-shockwave-flash swf
+application/x-stuffit sit
+application/x-stuffitx sitx
+application/x-sv4cpio sv4cpio
+application/x-sv4crc sv4crc
+application/x-tar tar
+application/x-tcl tcl
+application/x-tex tex
+application/x-texinfo texinfo texi
+application/x-ustar ustar
+application/x-wais-source src
+application/x-x509-ca-cert der crt
+application/x400-bp
+application/xcap-att+xml
+application/xcap-caps+xml
+application/xcap-el+xml
+application/xcap-error+xml
+application/xcap-ns+xml
+application/xenc+xml xenc
+application/xhtml+xml xhtml xht
+application/xml xml xsl
+application/xml-dtd dtd
+application/xml-external-parsed-entity
+application/xmpp+xml
+application/xop+xml xop
+application/xslt+xml xslt
+application/xspf+xml xspf
+application/xv+xml mxml xhvml xvml xvm
+application/zip zip
+audio/32kadpcm
+audio/3gpp
+audio/3gpp2
+audio/ac3
+audio/amr
+audio/amr-wb
+audio/amr-wb+
+audio/asc
+audio/basic au snd
+audio/bv16
+audio/bv32
+audio/clearmode
+audio/cn
+audio/dat12
+audio/dls
+audio/dsr-es201108
+audio/dsr-es202050
+audio/dsr-es202211
+audio/dsr-es202212
+audio/dvi4
+audio/eac3
+audio/evrc
+audio/evrc-qcp
+audio/evrc0
+audio/evrc1
+audio/evrcb
+audio/evrcb0
+audio/evrcb1
+audio/g722
+audio/g7221
+audio/g723
+audio/g726-16
+audio/g726-24
+audio/g726-32
+audio/g726-40
+audio/g728
+audio/g729
+audio/g7291
+audio/g729d
+audio/g729e
+audio/gsm
+audio/gsm-efr
+audio/ilbc
+audio/l16
+audio/l20
+audio/l24
+audio/l8
+audio/lpc
+audio/midi mid midi kar rmi
+audio/mobile-xmf
+audio/mp4 mp4a
+audio/mp4a-latm m4a m4p
+audio/mpa
+audio/mpa-robust
+audio/mpeg mpga mp2 mp2a mp3 m2a m3a
+audio/mpeg4-generic
+audio/parityfec
+audio/pcma
+audio/pcmu
+audio/prs.sid
+audio/qcelp
+audio/red
+audio/rtp-enc-aescm128
+audio/rtp-midi
+audio/rtx
+audio/smv
+audio/smv0
+audio/smv-qcp
+audio/sp-midi
+audio/t140c
+audio/t38
+audio/telephone-event
+audio/tone
+audio/vdvi
+audio/vmr-wb
+audio/vnd.3gpp.iufp
+audio/vnd.4sb
+audio/vnd.audiokoz
+audio/vnd.celp
+audio/vnd.cisco.nse
+audio/vnd.cmles.radio-events
+audio/vnd.cns.anp1
+audio/vnd.cns.inf1
+audio/vnd.digital-winds eol
+audio/vnd.dlna.adts
+audio/vnd.dolby.mlp
+audio/vnd.everad.plj
+audio/vnd.hns.audio
+audio/vnd.lucent.voice lvp
+audio/vnd.nokia.mobile-xmf
+audio/vnd.nortel.vbk
+audio/vnd.nuera.ecelp4800 ecelp4800
+audio/vnd.nuera.ecelp7470 ecelp7470
+audio/vnd.nuera.ecelp9600 ecelp9600
+audio/vnd.octel.sbc
+audio/vnd.qcelp
+audio/vnd.rhetorex.32kadpcm
+audio/vnd.sealedmedia.softseal.mpeg
+audio/vnd.vmx.cvsd
+audio/wav wav
+audio/x-aiff aif aiff aifc
+audio/x-mpegurl m3u
+audio/x-ms-wax wax
+audio/x-ms-wma wma
+audio/x-pn-realaudio ram ra
+audio/x-pn-realaudio-plugin rmp
+audio/x-wav wav
+chemical/x-cdx cdx
+chemical/x-cif cif
+chemical/x-cmdf cmdf
+chemical/x-cml cml
+chemical/x-csml csml
+chemical/x-pdb pdb
+chemical/x-xyz xyz
+image/bmp bmp
+image/cgm cgm
+image/fits
+image/g3fax g3
+image/gif gif
+image/ief ief
+image/jp2 jp2
+image/jpeg jpeg jpg jpe
+image/jpm
+image/jpx
+image/naplps
+image/pict pict pic pct
+image/png png
+image/prs.btif btif
+image/prs.pti
+image/svg+xml svg svgz
+image/t38
+image/tiff tiff tif
+image/tiff-fx
+image/vnd.adobe.photoshop psd
+image/vnd.cns.inf2
+image/vnd.djvu djvu djv
+image/vnd.dwg dwg
+image/vnd.dxf dxf
+image/vnd.fastbidsheet fbs
+image/vnd.fpx fpx
+image/vnd.fst fst
+image/vnd.fujixerox.edmics-mmr mmr
+image/vnd.fujixerox.edmics-rlc rlc
+image/vnd.globalgraphics.pgb
+image/vnd.microsoft.icon ico
+image/vnd.mix
+image/vnd.ms-modi mdi
+image/vnd.net-fpx npx
+image/vnd.sealed.png
+image/vnd.sealedmedia.softseal.gif
+image/vnd.sealedmedia.softseal.jpg
+image/vnd.svf
+image/vnd.wap.wbmp wbmp
+image/vnd.xiff xif
+image/x-cmu-raster ras
+image/x-cmx cmx
+image/x-icon
+image/x-macpaint pntg pnt mac
+image/x-pcx pcx
+image/x-pict pic pct
+image/x-portable-anymap pnm
+image/x-portable-bitmap pbm
+image/x-portable-graymap pgm
+image/x-portable-pixmap ppm
+image/x-quicktime qtif qti
+image/x-rgb rgb
+image/x-xbitmap xbm
+image/x-xpixmap xpm
+image/x-xwindowdump xwd
+message/cpim
+message/delivery-status
+message/disposition-notification
+message/external-body
+message/http
+message/news
+message/partial
+message/rfc822 eml mime
+message/s-http
+message/sip
+message/sipfrag
+message/tracking-status
+model/iges igs iges
+model/mesh msh mesh silo
+model/vnd.dwf dwf
+model/vnd.flatland.3dml
+model/vnd.gdl gdl
+model/vnd.gs.gdl
+model/vnd.gtw gtw
+model/vnd.moml+xml
+model/vnd.mts mts
+model/vnd.parasolid.transmit.binary
+model/vnd.parasolid.transmit.text
+model/vnd.vtu vtu
+model/vrml wrl vrml
+multipart/alternative
+multipart/appledouble
+multipart/byteranges
+multipart/digest
+multipart/encrypted
+multipart/form-data
+multipart/header-set
+multipart/mixed
+multipart/parallel
+multipart/related
+multipart/report
+multipart/signed
+multipart/voice-message
+text/calendar ics ifb
+text/css css
+text/csv csv
+text/directory
+text/dns
+text/enriched
+text/html html htm
+text/cache-manifest manifest
+text/parityfec
+text/plain txt text conf def list log in
+text/prs.fallenstein.rst
+text/prs.lines.tag dsc
+text/red
+text/rfc822-headers
+text/richtext rtx
+text/rtf
+text/rtp-enc-aescm128
+text/rtx
+text/sgml sgml sgm
+text/t140
+text/tab-separated-values tsv
+text/troff t tr roff man me ms
+text/uri-list uri uris urls
+text/vnd.abc
+text/vnd.curl
+text/vnd.dmclientscript
+text/vnd.esmertec.theme-descriptor
+text/vnd.fly fly
+text/vnd.fmi.flexstor flx
+text/vnd.in3d.3dml 3dml
+text/vnd.in3d.spot spot
+text/vnd.iptc.newsml
+text/vnd.iptc.nitf
+text/vnd.latex-z
+text/vnd.motorola.reflex
+text/vnd.ms-mediapackage
+text/vnd.net2phone.commcenter.command
+text/vnd.sun.j2me.app-descriptor jad
+text/vnd.trolltech.linguist
+text/vnd.wap.si
+text/vnd.wap.sl
+text/vnd.wap.wml wml
+text/vnd.wap.wmlscript wmls
+text/x-asm s asm
+text/x-c c cc cxx cpp h hh dic
+text/x-fortran f for f77 f90
+text/x-pascal p pas
+text/x-java-source java
+text/x-setext etx
+text/x-uuencode uu
+text/x-vcalendar vcs
+text/x-vcard vcf
+text/xml
+text/xml-external-parsed-entity
+video/3gpp 3gp
+video/3gpp-tt
+video/3gpp2 3g2
+video/bmpeg
+video/bt656
+video/celb
+video/dv
+video/h261 h261
+video/h263 h263
+video/h263-1998
+video/h263-2000
+video/h264 h264
+video/jpeg jpgv
+video/jpm jpm jpgm
+video/mj2 mj2 mjp2
+video/mp1s
+video/mp2p
+video/mp2t
+video/mp4 mp4 mp4v mpg4 m4v
+video/mp4v-es
+video/mpeg mpeg mpg mpe m1v m2v
+video/mpeg4-generic
+video/mpv
+video/nv
+video/parityfec
+video/pointer
+video/quicktime qt mov
+video/raw
+video/rtp-enc-aescm128
+video/rtx
+video/smpte292m
+video/vc1
+video/vnd.dlna.mpeg-tts
+video/vnd.fvt fvt
+video/vnd.hns.video
+video/vnd.motorola.video
+video/vnd.motorola.videop
+video/vnd.mpegurl mxu m4u
+video/vnd.nokia.interleaved-multimedia
+video/vnd.nokia.videovoip
+video/vnd.objectvideo
+video/vnd.sealed.mpeg1
+video/vnd.sealed.mpeg4
+video/vnd.sealed.swf
+video/vnd.sealedmedia.softseal.mov
+video/vnd.vivo viv
+video/x-dv dv dif
+video/x-fli fli
+video/x-ms-asf asf asx
+video/x-ms-wm wm
+video/x-ms-wmv wmv
+video/x-ms-wmx wmx
+video/x-ms-wvx wvx
+video/x-msvideo avi
+video/x-sgi-movie movie
+x-conference/x-cooltalk ice