Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 40 additions & 5 deletions mail/base/content/glodaFacetView.js
Original file line number Diff line number Diff line change
Expand Up @@ -463,12 +463,47 @@ ActiveNonSingularConstraint.prototype = {

var FacetContext = {
facetDriver: new FacetDriver(Gloda.lookupNounDef("message"), window),
_sortContactsBy: "frequency",

updateSortMode(mode) {
FacetContext._sortContactsBy = mode;
console.log("Sort mode selected:", mode);

const involvesFacet = FacetContext.faceters.find(
f => f.attrDef.attributeName === "involves"
);
if (!involvesFacet || !involvesFacet.xblNode) {
console.error("People facet or its binding (xblNode) not found.");
return;
}

const groups = involvesFacet.xblNode.orderedGroups;

const getName = g =>
g.value?._contact?._name || g.value?._name || g.label || "";
const getCount = g =>
typeof g.groupCount === "number" ? g.groupCount : 0;

console.log("Before sort:", groups.map(g => ({
name: getName(g),
count: getCount(g),
})));

if (mode === "alphabetical") {
groups.sort((a, b) => getName(a).localeCompare(getName(b)));
} else {
groups.sort((a, b) => getCount(b) - getCount(a));
}

console.log("After sort:", groups.map(g => ({
name: getName(g),
count: getCount(g),
})));

involvesFacet.xblNode.orderedGroups = groups;
involvesFacet.xblNode.build(false);
},

/**
* The root collection which our active set is a subset of. We hold onto this
* for garbage collection reasons, although the tab that owns us should also
* be holding on.
*/
_collection: null,
set collection(aCollection) {
this._collection = aCollection;
Expand Down
9 changes: 9 additions & 0 deletions mail/base/content/glodaFacetView.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@
<div class="facets facets-sidebar" id="facets">
<h1 id="filter-header-label">&glodaFacetView.filters.label;</h1>
<div>
<!-- Sort Mode Toggle -->
<div style="margin-bottom: 10px;">
<label for="facet-sort-mode" style="font-weight: bold;">Sort contacts:</label>
<select id="facet-sort-mode" onchange="FacetContext.updateSortMode(this.value)">
<option value="frequency">Sort by Frequency</option>
<option value="alphabetical">Sort Alphabetically</option>
</select>
</div>
<facet-boolean
id="facet-fromMe"
type="boolean"
Expand All @@ -55,6 +63,7 @@
attr="toMe"
uninitialized="true"
/>

<facet-boolean
id="facet-star"
type="boolean"
Expand Down
61 changes: 36 additions & 25 deletions mail/components/addrbook/content/vcard-edit/edit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,7 @@ class VCardTypeSelectionComponent extends HTMLElement {
* the `home`, `work` and `(None)` types. Also used to set the telemetry
* identifier.
*/

createTypeSelection(vCardPropertyEntry, options) {
let template;
let types;
Expand All @@ -1014,41 +1015,51 @@ class VCardTypeSelectionComponent extends HTMLElement {
this.selectEl = this.querySelector("select");
const selectId = vCardIdGen.next().value;
this.selectEl.id = selectId;
this.selectEl.dataset.telemetryId = `vcard-type-selection-${options.propertyType}`;

// Just abandon any values we don't have UI for. We don't have any way to
// know whether to keep them or not, and they're very rarely used.
const paramsType = vCardPropertyEntry.params.type;
// toLowerCase is called because other vCard sources are saving the type
// in upper case. E.g. from Google.
if (Array.isArray(paramsType)) {
const lowerCaseTypes = paramsType.map(type => type.toLowerCase());
this.selectEl.value = lowerCaseTypes.find(t => types.includes(t)) || "";
} else if (paramsType && types.includes(paramsType.toLowerCase())) {
this.selectEl.value = paramsType.toLowerCase();

// Support for previously-saved custom labels
const currentType = vCardPropertyEntry.params.type?.toLowerCase();
if (currentType && !types.includes(currentType)) {
const customOption = new Option(currentType, currentType);
customOption.setAttribute("data-custom", "true");
this.selectEl.appendChild(customOption);
this.selectEl.value = currentType;
} else {
this.selectEl.value = currentType || "";
}

// Change the value on the vCardPropertyEntry.
// handle dropdown changes
this.selectEl.addEventListener("change", () => {
if (this.selectEl.value) {
vCardPropertyEntry.params.type = this.selectEl.value;
const selected = this.selectEl.value;

if (selected === "custom") {
const label = prompt("Enter your custom email type:");
if (label) {
const customOption = new Option(label, label);
customOption.setAttribute("data-custom", "true");

// Remove previous custom if any
const existing = [...this.selectEl.options].find(
o => o.getAttribute("data-custom") === "true"
);
if (existing) this.selectEl.removeChild(existing);

this.selectEl.appendChild(customOption);
this.selectEl.value = label;
vCardPropertyEntry.params.type = label;
} else {
this.selectEl.value = "";
delete vCardPropertyEntry.params.type;
}
} else {
delete vCardPropertyEntry.params.type;
vCardPropertyEntry.params.type = selected;
}
});

// Set an aria-labelledyby on the select.
// Accessibility support
if (options.labelledBy) {
if (!document.getElementById(options.labelledBy)) {
throw new Error(`No such label element with id ${options.labelledBy}`);
}
this.querySelector("select").setAttribute(
"aria-labelledby",
options.labelledBy
);
this.selectEl.setAttribute("aria-labelledby", options.labelledBy);
}

// Create a label element for the select.
if (options.createLabel) {
const labelEl = document.createElement("label");
labelEl.htmlFor = selectId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,10 @@
<select class="vcard-type-selection">
<option value="work" data-l10n-id="vcard-entry-type-work"/>
<option value="home" data-l10n-id="vcard-entry-type-home"/>
<option value="school" data-l10n-id="vcard-entry-type-school"/>
<option value="secondary" data-l10n-id="vcard-entry-type-secondary" />
<option value="" data-l10n-id="vcard-entry-type-none" selected="selected"/>
<option value="custom" data-l10n-id="vcard-entry-type-custom" />
</select>
</template>

Expand Down
16 changes: 16 additions & 0 deletions mail/components/addrbook/test/browser/browser_custom_add_type.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
add_task(async function testCustomLabel() {
let abWindow = await openAddressBookWindow();
let newContactBtn = abWindow.document.getElementById("booksPaneCreateContact");
newContactBtn.click();


let editEl = abWindow.document.querySelector("vcard-edit");
let typeDropdown = editEl.querySelector("select.vcard-type-selection");


// Simulate selection of 'custom'
typeDropdown.value = "custom";
typeDropdown.dispatchEvent(new Event("change", { bubbles: true }));


});
41 changes: 41 additions & 0 deletions mail/components/addrbook/test/browser/browser_sorting_freq.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
add_task(async function test_contact_facet_sorting() {

let tabmail = document.getElementById("tabmail");
let searchInput = document.getElementById("searchInput");

searchInput.value = "test";
searchInput.doCommand();

await TestUtils.waitForCondition(() =>
tabmail.tabInfo.some(tab => tab.mode.name === "glodaFacet")
);

let facetTab = tabmail.tabInfo.find(tab => tab.mode.name === "glodaFacet");
let browser = facetTab.browser;

await BrowserTestUtils.browserLoaded(browser);

await SpecialPowers.spawn(browser, [], () => {
let sortSelect = content.document.getElementById("facet-sort-mode");
sortSelect.value = "frequency";
sortSelect.dispatchEvent(new content.Event("change", { bubbles: true }));
});

let contactGroups = await SpecialPowers.spawn(browser, [], () => {
let involvesFacet = content.wrappedJSObject.FacetContext.faceters.find(
f => f.attrDef.attributeName === "involves"
);
return involvesFacet.orderedGroups.map(g => ({
name: g.value?._contact?._name || g.value?._name || "",
count: g.groupCount,
}));
});

// Assert that the list is in descending frequency order
for (let i = 1; i < contactGroups.length; i++) {
ok(
contactGroups[i - 1].count >= contactGroups[i].count,
`Group "${contactGroups[i - 1].name}" (${contactGroups[i - 1].count}) should have >= messages than "${contactGroups[i].name}" (${contactGroups[i].count})`
);
}
});
4 changes: 4 additions & 0 deletions mail/locales/en-US/messenger/addressbook/vcard.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ vcard-entry-type-home = Home

vcard-entry-type-work = Work

vcard-entry-type-school = School

vcard-entry-type-secondary = Secondary

vcard-entry-type-none = None

vcard-entry-type-custom = Custom
Expand Down