Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* globals calFilter, calFilter, getViewBox, openEventDialogForViewing,
modifyEventWithDialog, createEventWithDialog, currentView,
calendarController, editSelectedEvents, deleteSelectedEvents,
getEventStatusString, goToggleToolbar, CalendarFilteredTreeView */
/* exported toggleUnifinder, prepareCalendarUnifinder, getUnifinderView,
* finishCalendarUnifinder, unifinderDoubleClick */
/**
* This file provides API for initializing and manipulating the unifinder view
* in the calendar, which provides users with a list of event occurrences
* filterable by date and event title.
*/
var { cal } = ChromeUtils.importESModule("resource:///modules/calendar/calUtils.sys.mjs");
/**
* Toggles the hidden state of the unifinder.
*/
function toggleUnifinder() {
const wasHidden = document.getElementById("bottom-events-box").hidden;
if (!wasHidden) {
// It will be hidden, deactivate the view.
getUnifinderView().deactivate();
}
// Toggle the elements
goToggleToolbar("bottom-events-box", "calendar_show_unifinder_command");
goToggleToolbar("calendar-view-splitter");
window.dispatchEvent(new CustomEvent("viewresize"));
if (wasHidden) {
// It's now visible, activate the view.
getUnifinderView().activate();
refreshUnifinderFilterInterval();
}
}
/**
* Gets the tree element for the unifinder.
*
* @returns {XULTreeElement} The tree element containing the current filtered
* list of events.
*/
function getUnifinderTree() {
return document.getElementById("unifinder-search-results-tree");
}
/**
* Called when calendar component is loaded to prepare the unifinder. This
* function is used to add observers, event listeners, etc.
*/
async function prepareCalendarUnifinder() {
const filteredView = new CalendarFilteredTreeView();
filteredView.itemType = Ci.calICalendar.ITEM_FILTER_TYPE_EVENT;
const unifinderTree = getUnifinderTree();
unifinderTree.view = filteredView;
const viewBox = getViewBox();
// Listen for the selected interval to change so we can adjust our filter.
viewBox.addEventListener("dayselect", refreshUnifinderFilterInterval);
// Listen for the selected item(s) to change in the calendar so we can adjust
// our selection.
viewBox.addEventListener("itemselect", unifinderItemSelect);
// Listen for the selected item(s) to change in the unifinder so we can adjust
// the calendar view.
unifinderTree.addEventListener("select", unifinderSelect, true);
const searchBox = document.getElementById("unifinder-search-field");
searchBox.addEventListener("command", updateUnifinderFilterText);
}
/**
* Called when the window is unloaded to clean up any observers and listeners
* added.
*/
function finishCalendarUnifinder() {
const unifinderTree = getUnifinderTree();
unifinderTree.removeEventListener("select", unifinderSelect, true);
const viewBox = getViewBox();
viewBox.removeEventListener("dayselect", refreshUnifinderFilterInterval);
viewBox.removeEventListener("itemselect", unifinderItemSelect);
const searchBox = document.getElementById("unifinder-search-field");
searchBox.removeEventListener("command", updateUnifinderFilterText);
getUnifinderView()?.deactivate();
}
/**
* Gets the tree view backing the unifinder box.
*
* @returns {CalendarFilteredTreeView} The tree view for the unifinder.
*/
function getUnifinderView() {
const unifinderTree = getUnifinderTree();
// This function can be called while the Find Events pane is hidden, in which
// case the view will be null.
return unifinderTree.view?.wrappedJSObject;
}
/**
* Handler function for double clicking the unifinder.
*
* @param event The DOM doubleclick event.
*/
function unifinderDoubleClick(event) {
const calendarEvent = getUnifinderView().getItemAtCoordinates(event.clientX, event.clientY);
if (calendarEvent) {
if (Services.prefs.getBoolPref("calendar.events.defaultActionEdit", true)) {
modifyEventWithDialog(calendarEvent, true);
return;
}
openEventDialogForViewing(calendarEvent);
} else {
createEventWithDialog();
}
}
/**
* Handle selection events in the unifinder, ensuring that they are synced to
* the calendar view.
*
* @param event The DOM selection event.
*/
async function unifinderSelect() {
const treeView = getUnifinderView();
const currentSelection = treeView.selection;
if (!currentSelection || currentSelection.getRangeCount() == 0) {
return;
}
const selectedItems = [];
// Get the selected events from the tree
const start = {};
const end = {};
const numRanges = currentSelection.getRangeCount();
for (let range = 0; range < numRanges; range++) {
currentSelection.getRangeAt(range, start, end);
for (let i = start.value; i <= end.value; i++) {
try {
selectedItems.push(treeView.getItemAt(i));
} catch (e) {
cal.WARN("Error getting Event from row: " + e + "\n");
}
}
}
const view = currentView();
if (selectedItems.length == 1) {
// Go to the day of the selected item in the current view.
const startDate = selectedItems[0].startDate;
if (view.startDate.compare(startDate) > 0 || view.endDate.compare(startDate) <= 0) {
view.goToDay(startDate);
await view.ready;
}
}
// Set up the selected items in the view. Pass in true to suppress firing the
// "itemselect" event, so we don't end up in a circular loop.
view.setSelectedItems(selectedItems, true);
calendarController.onSelectionChanged({ detail: selectedItems });
getUnifinderTree().focus();
}
/**
* Handle items being selected outside the unifinder, keeping the unifinder
* selection in sync with the calendar view.
*
* @param {CustomEvent} event - The "itemselect" event representing the change
* in selection.
*/
function unifinderItemSelect(event) {
const treeView = getUnifinderView();
if (!treeView) {
// Unifinder is hidden.
return;
}
// `nsITreeSelection` automatically fires a select event when re-enabling
// select events after suppression. The result is this "brutal hack" to avoid
// bogus selection events when we sync from the calendar. Remove the select
// handler entirely until the event has been fired.
const unifinderTree = getUnifinderTree();
unifinderTree.removeEventListener("select", unifinderSelect, true);
treeView.setSelectionFromItems(event.detail);
// Re-enable the handler once the event has been fired.
unifinderTree.addEventListener("select", unifinderSelect, true);
}
/**
* Handler function for keypress in the unifinder.
*
* @param aEvent The DOM Key event.
*/
function unifinderKeyPress(aEvent) {
switch (aEvent.key) {
case "Enter":
// Enter, edit the event
editSelectedEvents();
aEvent.stopPropagation();
aEvent.preventDefault();
break;
case "Backspace":
case "Delete":
deleteSelectedEvents();
aEvent.stopPropagation();
aEvent.preventDefault();
break;
}
}
/**
* Update the text used for filtering events in the unifinder based on the value
* in the search box.
*/
function updateUnifinderFilterText() {
const filteredView = getUnifinderView();
const searchBox = document.getElementById("unifinder-search-field");
if (searchBox.value) {
filteredView.setFilterFunction(item => item.title.toLowerCase().includes(searchBox.value));
} else {
filteredView.clearFilter();
}
}
/**
* Updates the start and end date of the filer based on the current interval
* selection.
*/
function refreshUnifinderFilterInterval() {
const view = currentView();
const today = cal.dtz.now();
today.isDate = true;
let startDate, endDate;
const intervalSelection = document.getElementById("event-filter-menulist").selectedItem.value;
switch (intervalSelection) {
case "today":
startDate = today;
endDate = today.clone();
endDate.day++;
break;
case "P7D":
startDate = today;
endDate = today.clone();
endDate.day += 7;
break;
case "P14D":
startDate = today;
endDate = today.clone();
endDate.day += 14;
break;
case "P31D":
startDate = today;
endDate = today.clone();
endDate.day += 31;
break;
case "next6Months":
startDate = today;
endDate = today.clone();
endDate.month += 6;
break;
case "next12Months":
startDate = today;
endDate = today.clone();
endDate.month += 12;
break;
case "thisCalendarMonth":
startDate = today.startOfMonth;
endDate = today.endOfMonth;
endDate.day++;
break;
case "current":
startDate = view.selectedDay;
endDate = startDate.clone();
endDate.day++;
break;
case "currentview":
startDate = view.startDate;
endDate = view.endDate;
break;
}
const filteredView = getUnifinderView();
if (filteredView) {
filteredView.startDate = startDate;
filteredView.endDate = endDate;
filteredView.refreshItems();
}
}