Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,46 @@
/* global LazyLoader, VCardLoadUI, VCardLoadController, VCardHandler */
'use strict';
/*
* This class is the one in charge of loading the minimum set of
* resources needed for the view to load. Any other JS/CSS/Element
* not needed in the critical path *must* be lazy loaded when needed.
*
* Once localization and all the basic JS/CSS/Elements are loaded,
* we will initialize UI and Controller. Both JS classes *must* be
* independent and will communicate through events.
*/
window.addEventListener('DOMContentLoaded', function() {
LazyLoader.load(['/shared/js/l10n.js']).then(function() {
LazyLoader.load([
document.getElementById('multiple-select-view')
]);
});
});
window.addEventListener('load', function() {
var dependencies = [
'/shared/js/contacts/import/utilities/vcard_reader.js',
'/contacts/views/vcard_load/js/vcard_handler.js',
'/contacts/views/vcard_load/js/vcard_load_ui.js',
'/contacts/views/vcard_load/js/vcard_load_controller.js'
];
LazyLoader.load(dependencies).then(function() {
VCardLoadUI.init();
VCardLoadController.init();
navigator.mozSetMessageHandler('activity', function(activity) {
VCardLoadController.setActivity(activity);
VCardHandler.handle(activity)
.then(contacts => {
var data = activity.source.data;
var filename = VCardHandler.getFileName(data.filename || data.src);
// Do Render
VCardLoadUI.render(contacts, filename);
});
});
});
});

View file

@ -0,0 +1,58 @@
/* global VCardReader */
(function(exports) {
'use strict';
function handle(activity) {
return getvCardReader(activity.source.data.blob).then(readvCard);
}
function getvCardReader(blob) {
return new Promise((resolve, reject) => {
var fileReader = new FileReader();
fileReader.readAsText(blob);
fileReader.onloadend = function() {
resolve(new VCardReader(fileReader.result));
};
fileReader.onerror = function() {
console.error('The blob could not be read as a file.');
reject(fileReader.error);
};
});
}
function readvCard(vCardReader) {
var contacts = [];
return new Promise(function(resolve, reject) {
var cursor = vCardReader.getAll();
cursor.onsuccess = function(event) {
if (!event.target.result) {
resolve(contacts);
return;
}
contacts.push(event.target.result);
cursor.continue();
};
cursor.onerror = function(event) {
console.error('Error while retrieving cursor from vcard');
reject();
};
});
}
function getFileName(path) {
var barIndex = path.lastIndexOf('/');
if (barIndex > -1) {
path = path.substring(barIndex + 1);
}
return path;
}
exports.VCardHandler = {
'handle': handle,
'getFileName': getFileName
};
})(window);

View file

@ -0,0 +1,100 @@
/* global LazyLoader, ContactsService, Matcher, contacts */
(function(exports) {
'use strict';
var _activity;
const STATUS_TIMEOUT = 3000; // ms before closing view which shows status msg
function close() {
if (_activity) {
_activity.postResult({});
} else {
window.history.back();
}
}
function cleanup() {
window.removeEventListener('closeAction', close);
window.removeEventListener('saveAction', importAll);
}
function importAll(evt) {
if (!evt.detail.contactsToImport) {
console.error('Missing parameter');
return;
}
cleanup();
var contactsToImport = evt.detail.contactsToImport;
var numDupsMerged = 0,
importedContacts = 0,
parsedContacts = 0;
const DEPENDENCIES = [
'/shared/js/contacts/import/utilities/status.js',
'/shared/js/simple_phone_matcher.js',
'/shared/js/contacts/contacts_matcher.js',
'/shared/js/contacts/contacts_merger.js',
'/shared/js/contacts/merger_adapter.js',
'/contacts/services/contacts.js',
'/shared/js/text_normalizer.js'
];
LazyLoader.load(DEPENDENCIES, function() {
contactsToImport.forEach((contact, index) => {
Matcher.match(contact, 'passive', {
onmatch: (matches) => {
var callbacks = {
success: () => {
numDupsMerged++;
doContinue(true);
},
error: doContinue
};
contacts.adaptAndMerge(contact, matches, callbacks);
},
onmismatch: () => {
ContactsService.save(
contact,
function(e) {
if (e) {
console.error(e);
doContinue();
return;
}
doContinue(true);
}
);
}
});
});
});
function doContinue(isContactImported) {
parsedContacts++;
if (isContactImported) {
importedContacts++;
}
if (contactsToImport.length <= parsedContacts) {
window.dispatchEvent(new CustomEvent('showStatusAction', {
'detail': {
'numDups': numDupsMerged,
'importedContacts': importedContacts
}
}));
window.setTimeout(close, STATUS_TIMEOUT);
}
}
}
exports.VCardLoadController = {
init: function() {
window.addEventListener('closeAction', close);
window.addEventListener('saveAction', importAll);
},
setActivity: function(activity) {
_activity = activity;
}
};
})(window);

View file

@ -0,0 +1,97 @@
/*global utils */
(function(exports) {
'use strict';
var contactsToImport = [];
var header, saveButton, container, template, title;
function cacheElements() {
header = document.getElementById('multiple-select-view-header');
saveButton = document.getElementById('save-button');
container = document.getElementById('multiple-select-container');
template = document.getElementById('contactToImportTemplate');
title = document.getElementById('multiple-select-view-title');
}
function addListeners() {
saveButton.addEventListener('click',
dispatchEvent.bind(null, 'saveAction',
{'contactsToImport': contactsToImport}));
header.addEventListener('action', dispatchEvent.bind(null, 'closeAction'));
window.addEventListener('showStatusAction', onShowStatus);
}
/**
* Initializes references to DOM elements. This is needed before calling
* render.
*/
function init() {
cacheElements();
addListeners();
}
/**
* Allows to preview which contacts will be imported. Requires to call init
* first when DOM is ready.
*
* @param cursor - Reader object to continue reading from vCard.
* @param {string} filename - Name of the vcf file we are reading from.
*/
function render(contacts, filename) {
container.innerHTML = ''; // Avoids rendering the same content twice.
title.textContent = filename;
contacts.forEach(doRenderContact);
function doRenderContact(contact) {
var clone = document.importNode(template.content, true);
var contactData = clone.querySelectorAll('p');
contactData[0].textContent =
Array.isArray(contact.name) && contact.name[0];
var org = (Array.isArray(contact.org) && contact.org[0]) ?
contact.org[0] : '';
contactData[1].textContent = org;
if (Array.isArray(contact.photo) && contact.photo[0] instanceof Blob) {
var picture = clone.querySelector('aside span');
picture.style.backgroundImage = 'url(' +
window.URL.createObjectURL(contact.photo[0]) + ')';
}
contactsToImport.push(contact);
container.appendChild(clone);
}
}
function dispatchEvent(name, data) {
window.dispatchEvent(new CustomEvent(name, {'detail': data}));
}
function onShowStatus(evt) {
if (!evt.detail) {
return;
}
var imported = evt.detail.importedContacts,
merged = evt.detail.numDups;
utils.status.show({
id: 'vCardContacts-imported',
args: {
n: imported
}
}, {
id: 'contactsMerged',
args: {
numDups: merged
}
});
}
exports.VCardLoadUI = {
'init': init,
'render': render
};
})(window);

View file

@ -0,0 +1,4 @@
aside span {
background-position: center;
background-size: 5rem;
}

View file

@ -0,0 +1,64 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
<meta charset="utf-8">
<meta name="theme-group" content="theme-communications">
<meta name="theme-color" content="var(--header-background)">
<title> Contacts </title>
<!-- Building Blocks -->
<link href="/shared/style/input_areas.css" rel="stylesheet">
<link href="/shared/style/lists.css" rel="stylesheet">
<link href="/shared/style/scrolling.css" rel="stylesheet">
<link href="/shared/style/buttons.css" rel="stylesheet">
<link href="/shared/style/progress_activity.css" rel="stylesheet">
<link href="/shared/style/status.css" rel="stylesheet">
<link href="/shared/style/switches.css" rel="stylesheet">
<link href="/shared/style/confirm.css" rel="stylesheet">
<link href="/shared/style/action_menu.css" rel="stylesheet">
<link href="/shared/style/edit_mode.css" rel="stylesheet">
<link href="/shared/style/animations.css" rel="stylesheet">
<link href="/contacts/style/app.css" rel="stylesheet">
<link href="/contacts/style/contacts.css" rel="stylesheet">
<link href="/contacts/style/overlay.css" rel="stylesheet">
<link href="/shared/pages/import/style/curtain_frame.css" rel="stylesheet">
<link href="/contacts/style/status.css" rel="stylesheet">
<link href="/shared/style/contacts.css" rel="stylesheet">
<link href="/shared/style/contacts/contacts_buttons.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="app://theme.gaiamobile.org/shared/elements/gaia-theme/gaia-theme.css" />
<!-- Web Components -->
<script defer src="/shared/elements/config.js"></script>
<script defer src="/shared/js/component_utils.js"></script>
<script defer src="/shared/elements/gaia_subheader/script.js"></script>
<script defer src="/shared/elements/gaia-header/dist/gaia-header.js"></script>
<!-- Localization -->
<link rel="localization" href="/shared/elements/gaia_menu/locales/gaia_menu.{locale}.properties">
<script defer src="/shared/js/html_imports.js"></script>
<script defer src="/shared/js/lazy_loader.js"></script>
<script defer src='/contacts/views/vcard_load/js/boot.js'></script>
<meta name="defaultLanguage" content="en-US">
<meta name="availableLanguages" content="en-US">
<link rel="localization" href="/contacts/locales/contacts.{locale}.properties">
<link rel="localization" href="/shared/locales/import_contacts/import_contacts.{locale}.properties">
<link rel="localization" href="/shared/locales/date/date.{locale}.properties">
<link rel="localization" href="/shared/locales/phone_types/phone_types.{locale}.properties">
<!-- Load just the templates needed -->
<link rel="import" href="/contacts/elements/multiple_select.html">
<link rel="import" href="/contacts/elements/status.html">
</head>
<body role="application" class="app-contacts skin-comms theme-communications">
<section is="multiple-select-view" id="multiple-select-view" role="region" class="view current"></section>
<section is="status-message" id="statusMsg" role="status" class="hidden"></section>
</body>
</html>