Bug 546932 - Add UI (preffed-off) for creating a CardDAV address book. r=mkmelin
This commit is contained in:
Родитель
0da2fd1890
Коммит
7a76f848a0
|
@ -498,6 +498,9 @@ pref("toolbar.customization.usesheet", true);
|
|||
pref("toolbar.customization.usesheet", false);
|
||||
#endif
|
||||
|
||||
// Whether to enable experimental CardDAV support.
|
||||
pref("mail.addr_book.carddav.enabled", false);
|
||||
|
||||
// Number of recipient rows shown by default
|
||||
pref("mail.compose.addresswidget.numRowsShownDefault", 3);
|
||||
|
||||
|
|
|
@ -244,6 +244,15 @@ function AbNewAddressBook() {
|
|||
);
|
||||
}
|
||||
|
||||
function AbNewCardDAVBook() {
|
||||
window.openDialog(
|
||||
"chrome://messenger/content/addressbook/abCardDAVDialog.xhtml",
|
||||
"",
|
||||
"chrome,modal,resizable=no,centerscreen",
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
function AbEditSelectedDirectory() {
|
||||
let selectedDir = getSelectedDirectory();
|
||||
if (!selectedDir) {
|
||||
|
|
|
@ -303,10 +303,17 @@ function SetNameColumn(cmd) {
|
|||
Services.prefs.setIntPref(kPrefMailAddrBookLastNameFirst, prefValue);
|
||||
}
|
||||
|
||||
function onOSXFileMenuInit() {
|
||||
document
|
||||
.getElementById("menu_osxAddressBook")
|
||||
.setAttribute("checked", AbOSXAddressBookExists());
|
||||
function onFileMenuInit() {
|
||||
let osxMenuItem = document.getElementById("menu_osxAddressBook");
|
||||
if (osxMenuItem) {
|
||||
osxMenuItem.setAttribute("checked", AbOSXAddressBookExists());
|
||||
}
|
||||
document.getElementById(
|
||||
"menu_newCardDAVBook"
|
||||
).hidden = !Services.prefs.getBoolPref(
|
||||
"mail.addr_book.carddav.enabled",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
function CommandUpdate_AddressBook() {
|
||||
|
|
|
@ -302,11 +302,7 @@
|
|||
<toolbaritem id="menubar-items" align="center">
|
||||
<menubar id="mail-menubar">
|
||||
<menu id="menu_File" label="&fileMenu.label;" accesskey="&fileMenu.accesskey;">
|
||||
#ifdef XP_MACOSX
|
||||
<menupopup id="menu_FilePopup" onpopupshowing="onOSXFileMenuInit();">
|
||||
#else
|
||||
<menupopup id="menu_FilePopup">
|
||||
#endif
|
||||
<menupopup id="menu_FilePopup" onpopupshowing="onFileMenuInit();">
|
||||
<menu id="menu_New" class="menu-iconic"
|
||||
label="&newMenu.label;" accesskey="&newMenu.accesskey;">
|
||||
<menupopup id="menu_NewPopup">
|
||||
|
@ -325,6 +321,11 @@
|
|||
accesskey="&newAddressBookCmd.accesskey;"
|
||||
oncommand="AbNewAddressBook()"
|
||||
class="menuitem-iconic"/>
|
||||
<menuitem id="menu_newCardDAVBook"
|
||||
label="&newCardDAVBookCmd.label;"
|
||||
accesskey="&newCardDAVBookCmd.accesskey;"
|
||||
oncommand="AbNewCardDAVBook()"
|
||||
class="menuitem-iconic"/>
|
||||
<menuitem id="addLDAP" label="&newLDAPDirectoryCmd.label;"
|
||||
accesskey="&newLDAPDirectoryCmd.accesskey;"
|
||||
oncommand="AbNewLDAPDirectory()"
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
<!ENTITY newListCmd.accesskey "L">
|
||||
<!ENTITY newAddressBookCmd.label "Address Book…">
|
||||
<!ENTITY newAddressBookCmd.accesskey "B">
|
||||
<!ENTITY newCardDAVBookCmd.label "CardDAV Address Book…">
|
||||
<!ENTITY newCardDAVBookCmd.accesskey "V">
|
||||
<!ENTITY newLDAPDirectoryCmd.label "LDAP Directory…">
|
||||
<!ENTITY newLDAPDirectoryCmd.accesskey "D">
|
||||
<!ENTITY newMessageCmd.label "Message">
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
# 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/.
|
||||
|
||||
carddav-window =
|
||||
.title = New CardDAV Address Book
|
||||
|
||||
carddav-dialog =
|
||||
.buttonlabelaccept = Continue
|
||||
.buttonaccesskeyaccept = C
|
||||
|
||||
carddav-experimental-warning = CardDAV address book support is experimental and could permanently damage your data. Use at your own risk.
|
||||
|
||||
carddav-provider-label =
|
||||
.value = CardDAV Provider:
|
||||
.accesskey = P
|
||||
|
||||
carddav-provider-option-other = Other provider…
|
||||
|
||||
carddav-url-label =
|
||||
.value = CardDAV URL:
|
||||
.accesskey = V
|
||||
|
||||
carddav-username-label =
|
||||
.value = Username:
|
||||
.accesskey = U
|
||||
carddav-username-input =
|
||||
.placeholder = you@example.com
|
||||
|
||||
carddav-password-label =
|
||||
.value = Password:
|
||||
.accesskey = w
|
||||
carddav-password-input =
|
||||
.placeholder = Password
|
||||
|
||||
carddav-remember-password =
|
||||
.label = Remember password
|
||||
.accesskey = m
|
||||
|
||||
carddav-loading = Looking up configuration…
|
||||
carddav-connection-error = Failed to connect.
|
||||
carddav-none-found = Found no address books to add for the specified account.
|
||||
carddav-already-added = All address books for the specified account have already been added.
|
||||
|
||||
carddav-available-books = Available address books:
|
|
@ -0,0 +1,68 @@
|
|||
/* 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/. */
|
||||
|
||||
#bigScaryWarning {
|
||||
background-color: #FFE900;
|
||||
border-color: #F2D00F;
|
||||
color: -moz-DialogText;
|
||||
padding: 4px;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
#bigScaryWarning image {
|
||||
-moz-context-properties: fill;
|
||||
fill: currentColor;
|
||||
margin: 4px 8px;
|
||||
}
|
||||
|
||||
#bigScaryWarning description {
|
||||
width: 30em;
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: normal;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
#statusArea {
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2px;
|
||||
padding: 3px 4px;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
#statusArea #statusImageBefore {
|
||||
-moz-context-properties: fill;
|
||||
fill: currentColor;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-size: 16px 16px;
|
||||
}
|
||||
|
||||
#statusArea[status=error] {
|
||||
background-color: #FFE900;
|
||||
border-color: #F2D00F;
|
||||
color: -moz-DialogText;
|
||||
}
|
||||
|
||||
#statusArea[status=error] #statusImageBefore {
|
||||
list-style-image: url("chrome://global/skin/icons/warning.svg");
|
||||
}
|
||||
|
||||
#statusArea[status=loading] {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
#statusArea[status=loading] #statusImageBefore {
|
||||
background: url("chrome://global/skin/icons/loading.png") no-repeat;
|
||||
}
|
||||
|
||||
@media (min-resolution: 1.1dppx) {
|
||||
#statusArea[status=loading] #statusImageBefore {
|
||||
background-image: url("chrome://global/skin/icons/loading@2x.png");
|
||||
background-size: 16px 16px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
/* 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/. */
|
||||
|
||||
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
var { MailServices } = ChromeUtils.import(
|
||||
"resource:///modules/MailServices.jsm"
|
||||
);
|
||||
var { CardDAVDirectory } = ChromeUtils.import(
|
||||
"resource:///modules/CardDAVDirectory.jsm"
|
||||
);
|
||||
|
||||
var gUrl;
|
||||
var gUsername;
|
||||
var gPassword;
|
||||
|
||||
var uiElements = {};
|
||||
|
||||
window.addEventListener("DOMContentLoaded", async () => {
|
||||
for (let id of [
|
||||
"dialog",
|
||||
"url",
|
||||
"username",
|
||||
"password",
|
||||
"rememberPassword",
|
||||
"statusArea",
|
||||
"statusMessage",
|
||||
"resultsArea",
|
||||
"availableBooks",
|
||||
]) {
|
||||
uiElements[id] = document.getElementById("carddav-" + id);
|
||||
}
|
||||
|
||||
await document.l10n.ready;
|
||||
|
||||
let presets = {
|
||||
Fastmail: "https://carddav.fastmail.com",
|
||||
Google: "https://www.googleapis.com",
|
||||
};
|
||||
// TODO: Only load presets of accounts we have.
|
||||
let provider = document.getElementById("carddav-provider");
|
||||
for (let [label, hostname] of Object.entries(presets)) {
|
||||
let option = document.createElementNS(
|
||||
"http://www.w3.org/1999/xhtml",
|
||||
"option"
|
||||
);
|
||||
option.label = label;
|
||||
option.value = hostname;
|
||||
provider.appendChild(option);
|
||||
}
|
||||
let other = document.createElementNS(
|
||||
"http://www.w3.org/1999/xhtml",
|
||||
"option"
|
||||
);
|
||||
other.value = "";
|
||||
other.setAttribute("data-l10n-id", "carddav-provider-option-other");
|
||||
provider.appendChild(other);
|
||||
|
||||
uiElements.url.value = provider.value;
|
||||
});
|
||||
|
||||
function handleChangeProvider(event) {
|
||||
uiElements.url.value = event.target.value;
|
||||
changeCardDAVURL(uiElements.url.value);
|
||||
}
|
||||
|
||||
function handleCardDAVURLInput(event) {
|
||||
changeCardDAVURL(event.target.value);
|
||||
}
|
||||
|
||||
function changeCardDAVURL(value) {
|
||||
setStatus();
|
||||
uiElements.resultsArea.hidden = true;
|
||||
gUrl = uiElements.url.value.trim();
|
||||
if (gUrl && !gUrl.match(/^https?:\/\//)) {
|
||||
gUrl = "https://" + gUrl;
|
||||
}
|
||||
gUrl = gUrl ? new URL(gUrl) : "";
|
||||
}
|
||||
|
||||
async function check() {
|
||||
gUrl = uiElements.url.value;
|
||||
gUsername = uiElements.username.value;
|
||||
gPassword = uiElements.password.value;
|
||||
|
||||
setStatus("loading", "carddav-loading");
|
||||
|
||||
try {
|
||||
if (!gUrl.match(/^https?:\/\//)) {
|
||||
gUrl = "https://" + gUrl;
|
||||
}
|
||||
gUrl = new URL(gUrl);
|
||||
|
||||
let response = await CardDAVDirectory.makeRequest(
|
||||
`${gUrl.origin}/.well-known/carddav`,
|
||||
{
|
||||
method: "PROPFIND",
|
||||
headers: {
|
||||
"Content-Type": "text/xml",
|
||||
Depth: 0,
|
||||
},
|
||||
body: `<propfind xmlns="DAV:">
|
||||
<prop>
|
||||
<current-user-principal/>
|
||||
</prop>
|
||||
</propfind>`,
|
||||
}
|
||||
);
|
||||
let href =
|
||||
gUrl.origin +
|
||||
response.dom.querySelector("current-user-principal href").textContent;
|
||||
|
||||
response = await CardDAVDirectory.makeRequest(href, {
|
||||
method: "PROPFIND",
|
||||
headers: {
|
||||
"Content-Type": "text/xml",
|
||||
Depth: 0,
|
||||
},
|
||||
body: `<propfind xmlns="DAV:" xmlns:card="urn:ietf:params:xml:ns:carddav">
|
||||
<prop>
|
||||
<card:addressbook-home-set/>
|
||||
</prop>
|
||||
</propfind>`,
|
||||
});
|
||||
href =
|
||||
gUrl.origin +
|
||||
response.dom.querySelector("addressbook-home-set href").textContent;
|
||||
|
||||
response = await CardDAVDirectory.makeRequest(href, {
|
||||
method: "PROPFIND",
|
||||
headers: {
|
||||
"Content-Type": "text/xml",
|
||||
Depth: 1,
|
||||
},
|
||||
body: `<propfind xmlns="DAV:" xmlns:cs="http://calendarserver.org/ns/">
|
||||
<prop>
|
||||
<resourcetype/>
|
||||
<displayname/>
|
||||
<cs:getctag/>
|
||||
</prop>
|
||||
</propfind>`,
|
||||
});
|
||||
|
||||
while (uiElements.availableBooks.lastChild) {
|
||||
uiElements.availableBooks.lastChild.remove();
|
||||
}
|
||||
|
||||
let existing = [...MailServices.ab.directories].map(d =>
|
||||
d.getStringValue("carddav.url", "")
|
||||
);
|
||||
let alreadyAdded = 0;
|
||||
for (let r of response.dom.querySelectorAll("response")) {
|
||||
if (r.querySelector("resourcetype addressbook")) {
|
||||
let bookURL = new URL(r.querySelector("href").textContent, gUrl).href;
|
||||
if (existing.includes(bookURL)) {
|
||||
alreadyAdded++;
|
||||
continue;
|
||||
}
|
||||
let checkbox = uiElements.availableBooks.appendChild(
|
||||
document.createXULElement("checkbox")
|
||||
);
|
||||
checkbox.setAttribute(
|
||||
"label",
|
||||
r.querySelector("displayname").textContent
|
||||
);
|
||||
checkbox.checked = true;
|
||||
checkbox.value = bookURL;
|
||||
}
|
||||
}
|
||||
if (uiElements.availableBooks.childElementCount == 0) {
|
||||
if (alreadyAdded > 0) {
|
||||
setStatus("error", "carddav-already-added");
|
||||
} else {
|
||||
setStatus("error", "carddav-none-found");
|
||||
}
|
||||
} else {
|
||||
uiElements.resultsArea.hidden = false;
|
||||
setStatus();
|
||||
}
|
||||
} catch (ex) {
|
||||
setStatus("error", "carddav-connection-error");
|
||||
}
|
||||
}
|
||||
|
||||
function setStatus(status, message) {
|
||||
uiElements.dialog.getButton("accept").disabled = status == "error";
|
||||
if (status) {
|
||||
uiElements.statusArea.setAttribute("status", status);
|
||||
document.l10n.setAttributes(uiElements.statusMessage, message);
|
||||
window.sizeToContent();
|
||||
} else {
|
||||
uiElements.statusArea.removeAttribute("status");
|
||||
document.l10n.setAttributes(uiElements.statusMessage, null);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("dialogaccept", event => {
|
||||
if (uiElements.resultsArea.hidden) {
|
||||
event.preventDefault();
|
||||
check();
|
||||
return;
|
||||
}
|
||||
|
||||
if (uiElements.availableBooks.childElementCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newLoginInfo = Cc[
|
||||
"@mozilla.org/login-manager/loginInfo;1"
|
||||
].createInstance(Ci.nsILoginInfo);
|
||||
newLoginInfo.init(gUrl.origin, null, "CardDAV", gUsername, gPassword, "", "");
|
||||
// TODO: Login might exist.
|
||||
Services.logins.addLogin(newLoginInfo);
|
||||
|
||||
let book;
|
||||
for (let checkbox of uiElements.availableBooks.children) {
|
||||
if (checkbox.checked) {
|
||||
let dirPrefId = MailServices.ab.newAddressBook(
|
||||
checkbox.getAttribute("label"),
|
||||
null,
|
||||
102,
|
||||
null
|
||||
);
|
||||
book = MailServices.ab.getDirectoryFromId(dirPrefId);
|
||||
book.setStringValue("carddav.url", checkbox.value);
|
||||
|
||||
let dir = CardDAVDirectory.forFile(book.fileName);
|
||||
dir.fetchAllFromServer();
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,112 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://messenger/skin/menulist.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://messenger/content/addressbook/abCardDAVDialog.css" type="text/css"?>
|
||||
|
||||
<window data-l10n-id="carddav-window"
|
||||
data-l10n-attrs="title"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml">
|
||||
<dialog id="carddav-dialog"
|
||||
data-l10n-id="carddav-dialog"
|
||||
data-l10n-attrs="buttonlabelaccept,buttonaccesskeyaccept">
|
||||
|
||||
<linkset>
|
||||
<html:link rel="localization" href="messenger/addressbook/abCardDAVDialog.ftl"/>
|
||||
</linkset>
|
||||
|
||||
<script src="chrome://global/content/globalOverlay.js"/>
|
||||
<script src="chrome://global/content/editMenuOverlay.js"/>
|
||||
<script src="chrome://messenger/content/addressbook/abCardDAVDialog.js"/>
|
||||
|
||||
<hbox id="bigScaryWarning"
|
||||
align="center">
|
||||
<image src="chrome://global/skin/icons/warning.svg"
|
||||
width="32"
|
||||
height="32"/>
|
||||
<description data-l10n-id="carddav-experimental-warning"/>
|
||||
</hbox>
|
||||
|
||||
<html:table>
|
||||
<html:tr>
|
||||
<html:th>
|
||||
<label control="carddav-provider"
|
||||
data-l10n-id="carddav-provider-label"
|
||||
data-l10n-attrs="value,accesskey"/>
|
||||
</html:th>
|
||||
<html:td>
|
||||
<html:select id="carddav-provider" onchange="handleChangeProvider(event);">
|
||||
</html:select>
|
||||
</html:td>
|
||||
</html:tr>
|
||||
<html:tr>
|
||||
<html:th>
|
||||
<label control="carddav-url"
|
||||
data-l10n-id="carddav-url-label"
|
||||
data-l10n-attrs="value,accesskey"/>
|
||||
</html:th>
|
||||
<html:td>
|
||||
<html:input id="carddav-url" type="url"
|
||||
placeholder="https://carddav.example.com/dav/addressbooks/user/"
|
||||
oninput="handleCardDAVURLInput(event);"/>
|
||||
</html:td>
|
||||
</html:tr>
|
||||
<html:tr>
|
||||
<html:th>
|
||||
<label control="carddav-username"
|
||||
data-l10n-id="carddav-username-label"
|
||||
data-l10n-attrs="value,accesskey"
|
||||
flex="1"/>
|
||||
</html:th>
|
||||
<html:td>
|
||||
<html:input id="carddav-username"
|
||||
type="text"
|
||||
data-l10n-id="carddav-username-input"/>
|
||||
</html:td>
|
||||
</html:tr>
|
||||
<html:tr>
|
||||
<html:th>
|
||||
<label control="carddav-password"
|
||||
data-l10n-id="carddav-password-label"
|
||||
flex="1"/>
|
||||
</html:th>
|
||||
<html:td>
|
||||
<html:input id="carddav-password" type="password"/>
|
||||
</html:td>
|
||||
</html:tr>
|
||||
<html:tr>
|
||||
<html:th></html:th>
|
||||
<html:td>
|
||||
<checkbox id="carddav-rememberPassword"
|
||||
data-l10n-id="carddav-remember-password"
|
||||
data-l10n-attrs="label,accesskey"
|
||||
checked="true"/>
|
||||
</html:td>
|
||||
</html:tr>
|
||||
</html:table>
|
||||
|
||||
<vbox id="carddav-statusArea"
|
||||
align="center"
|
||||
pack="center">
|
||||
<hbox align="center"
|
||||
pack="center">
|
||||
<image id="statusImageBefore"/>
|
||||
<description id="carddav-statusMessage"
|
||||
flex="1"> </description>
|
||||
<!-- Include 160 = nbsp, to make the element occupy the
|
||||
full height, for at least one line. With a normal space,
|
||||
it does not have sufficient height. -->
|
||||
</hbox>
|
||||
</vbox>
|
||||
|
||||
<vbox id="carddav-resultsArea"
|
||||
hidden="true">
|
||||
<label data-l10n-id="carddav-available-books"/>
|
||||
<vbox id="carddav-availableBooks"></vbox>
|
||||
</vbox>
|
||||
</dialog>
|
||||
</window>
|
|
@ -48,12 +48,16 @@ ChromeUtils.defineModuleGetter(
|
|||
// Keep track of all database connections, and close them at shutdown, since
|
||||
// nothing else ever tells us to close them.
|
||||
|
||||
// Also track all directories by filename, for AddrBookDirectory.forFile.
|
||||
|
||||
var connections = new Map();
|
||||
var directories = new Map();
|
||||
Services.obs.addObserver(() => {
|
||||
for (let connection of connections.values()) {
|
||||
connection.asyncClose();
|
||||
}
|
||||
connections.clear();
|
||||
directories.clear();
|
||||
}, "quit-application");
|
||||
|
||||
// Close a connection on demand. This serves as an escape hatch from C++ code.
|
||||
|
@ -107,6 +111,7 @@ function openConnectionTo(file) {
|
|||
* Closes the SQLite connection to `file` and removes it from the cache.
|
||||
*/
|
||||
function closeConnectionTo(file) {
|
||||
directories.delete(file.leafName);
|
||||
let connection = connections.get(file.path);
|
||||
if (connection) {
|
||||
return new Promise(resolve => {
|
||||
|
@ -161,6 +166,7 @@ class AddrBookDirectory {
|
|||
}
|
||||
|
||||
this._fileName = fileName;
|
||||
directories.set(fileName, this);
|
||||
}
|
||||
|
||||
get _prefBranch() {
|
||||
|
@ -1079,6 +1085,10 @@ class AddrBookDirectory {
|
|||
valueLocal
|
||||
);
|
||||
}
|
||||
|
||||
static forFile(fileName) {
|
||||
return directories.get(fileName);
|
||||
}
|
||||
}
|
||||
AddrBookDirectory.prototype.QueryInterface = ChromeUtils.generateQI([
|
||||
Ci.nsIAbDirectory,
|
||||
|
|
|
@ -406,6 +406,14 @@ class CardDAVDirectory extends AddrBookDirectory {
|
|||
}
|
||||
}
|
||||
|
||||
static forFile(fileName) {
|
||||
let directory = super.forFile(fileName);
|
||||
if (directory instanceof CardDAVDirectory) {
|
||||
return directory;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an HTTP request. If the request needs a username and password, the
|
||||
* given authPrompt is called.
|
||||
|
|
|
@ -9,6 +9,9 @@ messenger.jar:
|
|||
content/messenger/addressbook/pref-editdirectories.xhtml (addrbook/prefs/content/pref-editdirectories.xhtml)
|
||||
content/messenger/addressbook/abAddressBookNameDialog.js (addrbook/content/abAddressBookNameDialog.js)
|
||||
content/messenger/addressbook/abAddressBookNameDialog.xhtml (addrbook/content/abAddressBookNameDialog.xhtml)
|
||||
content/messenger/addressbook/abCardDAVDialog.css (addrbook/content/abCardDAVDialog.css)
|
||||
content/messenger/addressbook/abCardDAVDialog.js (addrbook/content/abCardDAVDialog.js)
|
||||
content/messenger/addressbook/abCardDAVDialog.xhtml (addrbook/content/abCardDAVDialog.xhtml)
|
||||
content/messenger/addressbook/abResultsPane.js (addrbook/content/abResultsPane.js)
|
||||
content/messenger/addressbook/abDragDrop.js (addrbook/content/abDragDrop.js)
|
||||
content/messenger/addressbook/abMailListDialog.js (addrbook/content/abMailListDialog.js)
|
||||
|
|
Загрузка…
Ссылка в новой задаче