Merge from bug/642656
This commit is contained in:
Коммит
8878255ff7
|
@ -36,7 +36,10 @@ function ($, object, fn, dispatch, rdapi, accounts) {
|
|||
this.svcAccount = svcAccount;
|
||||
|
||||
this.callbacks = [];
|
||||
this.lastUpdated = this.fromStore().lastUpdated;
|
||||
this.fromStore(fn.bind(this, function (data) {
|
||||
this.lastUpdated = data;
|
||||
}));
|
||||
|
||||
// Time check is one day.
|
||||
this.timeCheck = 24 * 60 * 60 * 1000;
|
||||
|
||||
|
@ -70,9 +73,11 @@ function ($, object, fn, dispatch, rdapi, accounts) {
|
|||
/**
|
||||
* Retrieves stored contacts. Should only be used internally or by subclasses.
|
||||
*/
|
||||
fromStore: function () {
|
||||
fromStore: function (callback) {
|
||||
var acct = this.svcAccount;
|
||||
return accounts.getData(acct.domain, acct.userid, acct.username, 'contacts') || {};
|
||||
accounts.getData(acct.domain, acct.userid, acct.username, 'contacts', function (data) {
|
||||
callback(data || {});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -112,53 +117,62 @@ function ($, object, fn, dispatch, rdapi, accounts) {
|
|||
*/
|
||||
notify: function (callback) {
|
||||
this.callbacks.push(callback);
|
||||
this.contacts = this.fromStore().list;
|
||||
this.fromStore(fn.bind(this, function (data) {
|
||||
|
||||
if (!this.contacts || this.needFetch()) {
|
||||
this.fetch();
|
||||
} else {
|
||||
this.notifyCallbacks();
|
||||
}
|
||||
this.contacts = data.list;
|
||||
|
||||
if (!this.contacts || this.needFetch()) {
|
||||
this.fetch();
|
||||
} else {
|
||||
this.notifyCallbacks();
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
fetch: function () {
|
||||
var acct = this.svcAccount,
|
||||
svcData = accounts.getService(acct.domain, acct.userid, acct.username);
|
||||
var acct = this.svcAccount;
|
||||
|
||||
rdapi('contacts/' + acct.domain, {
|
||||
type: 'POST',
|
||||
domain: acct.domain,
|
||||
data: {
|
||||
username: acct.username,
|
||||
userid: acct.userid,
|
||||
startindex: 0,
|
||||
maxresults: 500,
|
||||
account: JSON.stringify(svcData)
|
||||
},
|
||||
//Only wait for 10 seconds, then give up.
|
||||
timeout: 10000,
|
||||
success: fn.bind(this, function (json) {
|
||||
//Transform data to a form usable by the front end.
|
||||
if (json && !json.error) {
|
||||
var entries = json.result.entry;
|
||||
accounts.getService(acct.domain, acct.userid,
|
||||
acct.username, fn.bind(this, function (svcData) {
|
||||
|
||||
this.contacts = this.getFormattedContacts(entries);
|
||||
this.lastUpdated = (new Date()).getTime();
|
||||
rdapi('contacts/' + acct.domain, {
|
||||
type: 'POST',
|
||||
data: {
|
||||
username: acct.username,
|
||||
userid: acct.userid,
|
||||
startindex: 0,
|
||||
maxresults: 500,
|
||||
account: JSON.stringify(svcData)
|
||||
},
|
||||
//Only wait for 10 seconds, then give up.
|
||||
timeout: 10000,
|
||||
success: fn.bind(this, function (json) {
|
||||
//Transform data to a form usable by the front end.
|
||||
if (json && !json.error) {
|
||||
var entries = json.result.entry;
|
||||
|
||||
this.toStore({
|
||||
list: this.contacts
|
||||
});
|
||||
}
|
||||
}),
|
||||
error: fn.bind(this, function (xhr, textStatus, errorThrown) {
|
||||
// does not matter what the error is, just eat it and hide
|
||||
// the UI showing a wait.
|
||||
// If xhr.status === 503, could do a retry, and dispatch a
|
||||
// 'serverErrorPossibleRetry', but wait for UX to be worked out
|
||||
// in https://bugzilla.mozilla.org/show_bug.cgi?id=642653
|
||||
this.notifyCallbacks();
|
||||
})
|
||||
});
|
||||
this.getFormattedContacts(entries,
|
||||
fn.bind(this, function (contacts) {
|
||||
this.contacts = contacts;
|
||||
this.lastUpdated = (new Date()).getTime();
|
||||
|
||||
this.toStore({
|
||||
list: this.contacts
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}),
|
||||
error: fn.bind(this, function (xhr, textStatus, errorThrown) {
|
||||
// does not matter what the error is, just eat it and hide
|
||||
// the UI showing a wait.
|
||||
// If xhr.status === 503, could do a retry, and dispatch a
|
||||
// 'serverErrorPossibleRetry', but wait for UX to be worked out
|
||||
// in https://bugzilla.mozilla.org/show_bug.cgi?id=642653
|
||||
this.notifyCallbacks();
|
||||
})
|
||||
});
|
||||
}));
|
||||
},
|
||||
|
||||
notifyCallbacks: function () {
|
||||
|
@ -184,9 +198,11 @@ function ($, object, fn, dispatch, rdapi, accounts) {
|
|||
/**
|
||||
* Translates contact data from server into a format used on the client.
|
||||
* @param {Array} entries
|
||||
* @returns {Array}
|
||||
* @param {Function} callback called once contacts are formatted. Could
|
||||
* involve asyn operations. The callback will receive an array of contacts.
|
||||
*
|
||||
*/
|
||||
getFormattedContacts: function (entries) {
|
||||
getFormattedContacts: function (entries, callback) {
|
||||
var data = [];
|
||||
entries.forEach(function (entry) {
|
||||
if (entry.accounts && entry.accounts.length) {
|
||||
|
@ -200,7 +216,7 @@ function ($, object, fn, dispatch, rdapi, accounts) {
|
|||
});
|
||||
}
|
||||
});
|
||||
return data;
|
||||
callback(data);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -63,7 +63,6 @@ function (object, Contacts, $, accounts, fn) {
|
|||
incorporate: function (contactsText) {
|
||||
var acct = this.svcAccount,
|
||||
newContacts = [],
|
||||
storedContacts,
|
||||
contacts = contactsText.split(',');
|
||||
|
||||
contacts.forEach(fn.bind(this, function (contact) {
|
||||
|
@ -83,50 +82,53 @@ function (object, Contacts, $, accounts, fn) {
|
|||
|
||||
if (newContacts.length) {
|
||||
// update storage of manually entered contacts.
|
||||
storedContacts = accounts.getData(acct.domain,
|
||||
acct.userid,
|
||||
acct.username,
|
||||
'enteredContacts') || [];
|
||||
accounts.getData(acct.domain, acct.userid, acct.username,
|
||||
'enteredContacts', fn.bind(this, function (storedContacts) {
|
||||
|
||||
storedContacts = storedContacts.concat(newContacts);
|
||||
accounts.setData(acct.domain, acct.userid, acct.username,
|
||||
'enteredContacts', storedContacts);
|
||||
storedContacts = storedContacts || [];
|
||||
|
||||
// update the master merged list of contacts.
|
||||
this.contacts = this.contacts.concat(newContacts);
|
||||
this.toStore({
|
||||
list: this.contacts
|
||||
});
|
||||
storedContacts = storedContacts.concat(newContacts);
|
||||
accounts.setData(acct.domain, acct.userid, acct.username,
|
||||
'enteredContacts', storedContacts);
|
||||
|
||||
// update the master merged list of contacts.
|
||||
this.contacts = this.contacts.concat(newContacts);
|
||||
this.toStore({
|
||||
list: this.contacts
|
||||
});
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
getFormattedContacts: function (entries) {
|
||||
getFormattedContacts: function (entries, callback) {
|
||||
var data = [],
|
||||
acct = this.svcAccount,
|
||||
storedContacts = accounts.getData(acct.domain,
|
||||
acct.userid,
|
||||
acct.username,
|
||||
'enteredContacts');
|
||||
acct = this.svcAccount;
|
||||
|
||||
// convert server data to the right format.
|
||||
entries.forEach(function (entry) {
|
||||
if (entry.emails && entry.emails.length) {
|
||||
entry.emails.forEach(function (email) {
|
||||
var displayName = entry.displayName ? entry.displayName : email.value;
|
||||
data.push({
|
||||
displayName: displayName,
|
||||
email: email.value
|
||||
});
|
||||
accounts.getData(acct.domain, acct.userid, acct.username,
|
||||
'enteredContacts', fn.bind(this, function (storedContacts) {
|
||||
|
||||
// convert server data to the right format.
|
||||
entries.forEach(function (entry) {
|
||||
if (entry.emails && entry.emails.length) {
|
||||
entry.emails.forEach(function (email) {
|
||||
var displayName = entry.displayName ?
|
||||
entry.displayName : email.value;
|
||||
data.push({
|
||||
displayName: displayName,
|
||||
email: email.value
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// add in any manually saved email addresses.
|
||||
if (storedContacts) {
|
||||
data = data.concat(storedContacts);
|
||||
}
|
||||
// add in any manually saved email addresses.
|
||||
if (storedContacts) {
|
||||
data = data.concat(storedContacts);
|
||||
}
|
||||
|
||||
return data;
|
||||
callback(data);
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -35,187 +35,29 @@ function (storage, dispatch, rdapi, services) {
|
|||
(username && cache.username === username));
|
||||
}
|
||||
|
||||
function fromJson(value) {
|
||||
if (value) {
|
||||
value = JSON.parse(value);
|
||||
function pubAccountsChanged() {
|
||||
if (opener && !opener.closed) {
|
||||
dispatch.pub('accountsChanged', null, opener);
|
||||
}
|
||||
return value;
|
||||
dispatch.pub('accountsChanged');
|
||||
}
|
||||
|
||||
var store = storage(), impl,
|
||||
changeTypes = {
|
||||
//localStorage is the most robust, since the change in localStorage
|
||||
//can be listened to across windows.
|
||||
// Listen for chrome storage changes, and if it is for
|
||||
// serviceCache, translate that into a higher level message
|
||||
// understood by the web modules.
|
||||
dispatch.sub('storeNotifyChange', function (data) {
|
||||
if (data.key === 'serviceCache') {
|
||||
pubAccountsChanged();
|
||||
}
|
||||
});
|
||||
|
||||
'localStorage': {
|
||||
dispatch.sub('storeNotifyRemoveAll', function (data) {
|
||||
// If the storage has all been removed, then need to update
|
||||
// since the serviceCache was also removed.
|
||||
pubAccountsChanged();
|
||||
});
|
||||
|
||||
accounts: function (ok, error) {
|
||||
// accounts now simply provides existing accounts retreived during
|
||||
// the oauth dances
|
||||
var serviceCache = fromJson(store.serviceCache) || [];
|
||||
|
||||
//Call ok callback with current knowledge. If there is a change in the
|
||||
//account info, then the changed event will be triggered later.
|
||||
if (ok) {
|
||||
ok(serviceCache);
|
||||
}
|
||||
},
|
||||
|
||||
update: function (account_data) {
|
||||
// XXX TODO
|
||||
// get the account and push it into localstore, don't overwrite, we
|
||||
// get one account at a time here
|
||||
// We write into serviceCache which will be used by api calls
|
||||
// to send the auth keys
|
||||
var serviceCache = fromJson(store.serviceCache) || [],
|
||||
existing = false,
|
||||
profile, p, a, acct;
|
||||
|
||||
// we store the entire object in serviceCache
|
||||
if (serviceCache) {
|
||||
for (a = 0; a < serviceCache.length; a++) {
|
||||
if (isCacheMatch(serviceCache[a], account_data.domain,
|
||||
account_data.userid, account_data.username)) {
|
||||
serviceCache[a] = account_data;
|
||||
existing = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!existing) {
|
||||
serviceCache.push(account_data);
|
||||
}
|
||||
store.serviceCache = JSON.stringify(serviceCache);
|
||||
impl.changed();
|
||||
},
|
||||
|
||||
remove: function (domain, userid, username) {
|
||||
var serviceCache = fromJson(store.serviceCache),
|
||||
i, cache, a, p, s, svc;
|
||||
|
||||
if (serviceCache) {
|
||||
for (i = 0; (cache = serviceCache[i]); i++) {
|
||||
if (isCacheMatch(cache, domain, userid, username)) {
|
||||
serviceCache.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
store.serviceCache = JSON.stringify(serviceCache);
|
||||
}
|
||||
|
||||
// clear the contacts cache
|
||||
svc = services.domains[domain];
|
||||
|
||||
// remove this clearCache call when 3.6 is removed.
|
||||
svc.clearCache(store);
|
||||
|
||||
// Delete auxillary data.
|
||||
impl.clearData(domain, userid, username);
|
||||
|
||||
impl.changed();
|
||||
},
|
||||
|
||||
/**
|
||||
* Set auxillary data related to an account. Deleted when the account
|
||||
* is deleted.
|
||||
*/
|
||||
setData: function (domain, userid, username, name, value) {
|
||||
var key = [domain, userid, username].join('|') + 'Data',
|
||||
data = fromJson(store[key]) || {};
|
||||
|
||||
if (value === undefined || value === null) {
|
||||
delete data[name];
|
||||
} else {
|
||||
data[name] = value;
|
||||
}
|
||||
|
||||
store[key] = JSON.stringify(data);
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get auxillary data related to an account.
|
||||
*/
|
||||
getData: function (domain, userid, username, name) {
|
||||
var key = [domain, userid, username].join('|') + 'Data',
|
||||
data = fromJson(store[key]) || {};
|
||||
|
||||
return data ? data[name] : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears auxillary data related to an account. Deleted when the account
|
||||
* is deleted.
|
||||
*/
|
||||
clearData: function (domain, userid, username) {
|
||||
var key = [domain, userid, username].join('|') + 'Data';
|
||||
delete store[key];
|
||||
},
|
||||
|
||||
getService: function (domain, userid, username) {
|
||||
var serviceCache = fromJson(store.serviceCache),
|
||||
i, cache;
|
||||
|
||||
if (serviceCache) {
|
||||
for (i = 0; (cache = serviceCache[i]); i++) {
|
||||
if (isCacheMatch(cache, domain, userid, username)) {
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
changed: function () {
|
||||
store.accountChanged = (new Date()).getTime();
|
||||
//Force the onchange events to occur. Sometimes the storage
|
||||
//events do not fire?
|
||||
if (opener && !opener.closed) {
|
||||
dispatch.pub('accountsChanged', null, opener);
|
||||
}
|
||||
dispatch.pub('accountsChanged');
|
||||
},
|
||||
|
||||
onChange: function (action) {
|
||||
//Listen to storage changes, and if a the accountChanged key
|
||||
//changes, refresh.
|
||||
var lastValue = store.accountChanged;
|
||||
window.addEventListener('storage', function (evt) {
|
||||
//Only refresh if the accounts were changed.
|
||||
if (store.accountChanged !== lastValue) {
|
||||
action();
|
||||
}
|
||||
}, false);
|
||||
//Also use direct notification in case storage events fail.
|
||||
dispatch.sub('accountsChanged', action);
|
||||
}
|
||||
},
|
||||
//Some extensions mess with localStorage, so in that case, fall back to
|
||||
//using dispatching.
|
||||
'memory': {
|
||||
|
||||
accounts: function (ok, error) {
|
||||
},
|
||||
|
||||
changed: function () {
|
||||
//Use dispatching. Dispatch to current window, but also to an opener
|
||||
//if available.
|
||||
store.accountChanged = (new Date()).getTime();
|
||||
|
||||
if (opener) {
|
||||
dispatch.pub('accountsChanged', null, opener);
|
||||
}
|
||||
dispatch.pub('accountsChanged');
|
||||
},
|
||||
|
||||
onChange: function (action) {
|
||||
dispatch.sub('accountsChanged', action);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
impl = changeTypes[storage.type];
|
||||
var store = storage();
|
||||
|
||||
/**
|
||||
* Gets the accounts. Can use a cached value.
|
||||
|
@ -223,15 +65,46 @@ function (storage, dispatch, rdapi, services) {
|
|||
* @param {Function} error function to call if an error.
|
||||
*/
|
||||
function accounts(ok, error) {
|
||||
return impl.accounts(ok, error);
|
||||
store.get('serviceCache', function (data) {
|
||||
if (ok) {
|
||||
ok(data || []);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the accounts from a json account object.
|
||||
* @param {Object} cookie object to update from
|
||||
*/
|
||||
accounts.update = function (account_data) {
|
||||
impl.update(account_data);
|
||||
accounts.update = function (accountData) {
|
||||
// get the account and push it into storage, don't overwrite, we
|
||||
// get one account at a time here
|
||||
// We write into serviceCache which will be used by api calls
|
||||
// to send the auth keys
|
||||
|
||||
store.get('serviceCache', function (serviceCache) {
|
||||
|
||||
var existing = false,
|
||||
a;
|
||||
|
||||
serviceCache = serviceCache || [];
|
||||
|
||||
// we store the entire object in serviceCache
|
||||
if (serviceCache) {
|
||||
for (a = 0; a < serviceCache.length; a++) {
|
||||
if (isCacheMatch(serviceCache[a], accountData.domain,
|
||||
accountData.userid, accountData.username)) {
|
||||
serviceCache[a] = accountData;
|
||||
existing = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!existing) {
|
||||
serviceCache.push(accountData);
|
||||
}
|
||||
store.set('serviceCache', serviceCache);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -240,8 +113,24 @@ function (storage, dispatch, rdapi, services) {
|
|||
* @param {string} userid
|
||||
* @param {string} username
|
||||
*/
|
||||
accounts.remove = function (account, userid, username) {
|
||||
impl.remove(account, userid, username);
|
||||
accounts.remove = function (domain, userid, username) {
|
||||
store.get('serviceCache', function (serviceCache) {
|
||||
|
||||
var i, cache;
|
||||
|
||||
if (serviceCache) {
|
||||
for (i = 0; (cache = serviceCache[i]); i++) {
|
||||
if (isCacheMatch(cache, domain, userid, username)) {
|
||||
serviceCache.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
store.set('serviceCache', serviceCache);
|
||||
}
|
||||
|
||||
// Delete auxillary data.
|
||||
accounts.clearData(domain, userid, username);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -249,9 +138,23 @@ function (storage, dispatch, rdapi, services) {
|
|||
* @param {string} domain
|
||||
* @param {string} userid
|
||||
* @param {string} username
|
||||
* @param {Function} callback the callback to call once date is retrieved.
|
||||
*/
|
||||
accounts.getService = function (account, userid, username) {
|
||||
return impl.getService(account, userid, username);
|
||||
accounts.getService = function (domain, userid, username, callback) {
|
||||
|
||||
store.get('serviceCache', function (serviceCache) {
|
||||
var i, cache;
|
||||
|
||||
if (serviceCache) {
|
||||
for (i = 0; (cache = serviceCache[i]); i++) {
|
||||
if (isCacheMatch(cache, domain, userid, username)) {
|
||||
callback(cache);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -259,7 +162,7 @@ function (storage, dispatch, rdapi, services) {
|
|||
* info is no longer valid/expired.
|
||||
*/
|
||||
accounts.clear = function () {
|
||||
delete store.serviceCache;
|
||||
store.remove('serviceCache');
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -267,9 +170,27 @@ function (storage, dispatch, rdapi, services) {
|
|||
* @param {string} domain
|
||||
* @param {string} userid
|
||||
* @param {string} username
|
||||
* @param {string} name the data key name
|
||||
* @param {Object} value the data to store at that key name.
|
||||
*/
|
||||
accounts.setData = function (account, userid, username, name, value) {
|
||||
return impl.setData(account, userid, username, name, value);
|
||||
accounts.setData = function (domain, userid, username, name, value) {
|
||||
|
||||
var key = [domain, userid, username].join('|') + 'Data';
|
||||
|
||||
store.get('key', function (data) {
|
||||
data = data || {};
|
||||
|
||||
|
||||
if (value === undefined || value === null) {
|
||||
delete data[name];
|
||||
} else {
|
||||
data[name] = value;
|
||||
}
|
||||
|
||||
store.set(key, data);
|
||||
});
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -277,9 +198,17 @@ function (storage, dispatch, rdapi, services) {
|
|||
* @param {string} domain
|
||||
* @param {string} userid
|
||||
* @param {string} username
|
||||
* @param {string} name the key name for the auxillary data.
|
||||
* @param {Function} callbac the callback to call when the data is retreived.
|
||||
*/
|
||||
accounts.getData = function (account, userid, username, name) {
|
||||
return impl.getData(account, userid, username, name);
|
||||
accounts.getData = function (domain, userid, username, name, callback) {
|
||||
var key = [domain, userid, username].join('|') + 'Data';
|
||||
|
||||
store.get(key, function (data) {
|
||||
data = data || {};
|
||||
|
||||
callback(data[name] || null);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -288,15 +217,9 @@ function (storage, dispatch, rdapi, services) {
|
|||
* @param {string} userid
|
||||
* @param {string} username
|
||||
*/
|
||||
accounts.clearData = function (account, userid, username) {
|
||||
return impl.clearData(account, userid, username);
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the cache of accounts has changed.
|
||||
*/
|
||||
accounts.changed = function () {
|
||||
return impl.changed();
|
||||
accounts.clearData = function (domain, userid, username) {
|
||||
var key = [domain, userid, username].join('|') + 'Data';
|
||||
store.remove(key);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -311,7 +234,7 @@ function (storage, dispatch, rdapi, services) {
|
|||
* Call it with no args to get the default behavior, page reload.
|
||||
*/
|
||||
accounts.onChange = function (action) {
|
||||
return impl.onChange(action || defaultAction);
|
||||
dispatch.sub('accountsChanged', action || defaultAction);
|
||||
};
|
||||
|
||||
return accounts;
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
* dojo._toDom()
|
||||
*/
|
||||
|
||||
define([ 'require', './object', './jig', 'module'],
|
||||
function (require, object, jig, module) {
|
||||
define([ 'require', './object', './fn', './jig', 'module'],
|
||||
function (require, object, fn, jig, module) {
|
||||
|
||||
var tempNode,
|
||||
baseAttrName = 'data-' + module.id.replace(/\//g, '-') + '-' +
|
||||
|
@ -35,31 +35,70 @@ function (require, object, jig, module) {
|
|||
init: function (data, relNode, position) {
|
||||
object.mixin(this, data, true);
|
||||
|
||||
var asyncCreate, onFinishCreate;
|
||||
|
||||
//Start widget lifecycle
|
||||
if (this.onCreate) {
|
||||
this.onCreate();
|
||||
asyncCreate = this.onCreate();
|
||||
}
|
||||
|
||||
if (this.template) {
|
||||
this.node = this.render();
|
||||
if (this.onRender) {
|
||||
this.onRender(relNode);
|
||||
onFinishCreate = fn.bind(this, function () {
|
||||
if (this.template) {
|
||||
this.node = this.render();
|
||||
if (this.onRender) {
|
||||
this.onRender(relNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (relNode && this.node) {
|
||||
if (position === 'before') {
|
||||
relNode.parentNode.insertBefore(this.node, relNode);
|
||||
} else if (position === 'after') {
|
||||
relNode.parentNode.insertBefore(this.node, relNode.nextSibling);
|
||||
} else if (position === 'prepend' && relNode.firstChild) {
|
||||
relNode.insertBefore(this.node, relNode.firstChild);
|
||||
} else {
|
||||
relNode.appendChild(this.node);
|
||||
if (relNode && this.node) {
|
||||
if (position === 'before') {
|
||||
relNode.parentNode.insertBefore(this.node, relNode);
|
||||
} else if (position === 'after') {
|
||||
relNode.parentNode.insertBefore(this.node, relNode.nextSibling);
|
||||
} else if (position === 'prepend' && relNode.firstChild) {
|
||||
relNode.insertBefore(this.node, relNode.firstChild);
|
||||
} else {
|
||||
relNode.appendChild(this.node);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (asyncCreate) {
|
||||
asyncCreate.then(onFinishCreate);
|
||||
this.asyncCreate = asyncCreate;
|
||||
} else {
|
||||
onFinishCreate();
|
||||
}
|
||||
},
|
||||
|
||||
// poor man deferred thingy for now. May want to swap out
|
||||
// with a real promise implementation at some point.
|
||||
makeCreateCallback: function () {
|
||||
var p = {
|
||||
_callbacks: [],
|
||||
then: function (callback) {
|
||||
if ('value' in p) {
|
||||
callback(p.value);
|
||||
} else {
|
||||
p._callbacks.push(callback);
|
||||
}
|
||||
},
|
||||
resolve: function (value) {
|
||||
if ('value' in p) {
|
||||
throw new Error ('Async widget already resolved');
|
||||
} else {
|
||||
// Setting the value
|
||||
p.value = value;
|
||||
p._callbacks.forEach(function (callback) {
|
||||
callback(value);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return p;
|
||||
},
|
||||
|
||||
render: function (relativeNode) {
|
||||
var doc, child, renderedNode, id;
|
||||
if (this.template) {
|
||||
|
|
|
@ -33,7 +33,8 @@
|
|||
*/
|
||||
define(['jquery'], function ($) {
|
||||
|
||||
var origin = location.protocol + "//" + location.host;
|
||||
var origin = location.protocol + "//" + location.host,
|
||||
wins = [];
|
||||
|
||||
return {
|
||||
sub: function (topic, callback, win, targetOrigin) {
|
||||
|
@ -72,10 +73,35 @@ define(['jquery'], function ($) {
|
|||
|
||||
pub: function (topic, data, win) {
|
||||
win = win || window;
|
||||
win.postMessage(JSON.stringify({
|
||||
var text = JSON.stringify({
|
||||
topic: topic,
|
||||
data: data
|
||||
}), origin);
|
||||
}),
|
||||
i, otherWin;
|
||||
|
||||
// Notify primary target.
|
||||
win.postMessage(text, origin);
|
||||
|
||||
// notify other windows too, can go away if settings work is done
|
||||
// in share panel.
|
||||
if (wins.length) {
|
||||
for (i = 0; (otherWin = wins[i]); i++) {
|
||||
if (otherWin.closed) {
|
||||
wins.splice(i, 1);
|
||||
i -= 1;
|
||||
} else {
|
||||
otherWin.postMessage(text, origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Used by settings page so that it can get all the same disptaches as
|
||||
// the share panel, important since data storage is primarily accessed and
|
||||
// data update events triggred in the share panel window. This code can
|
||||
// go away if the settings work is done in the share panel.
|
||||
trackWindow: function (win) {
|
||||
wins.push(win);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -22,17 +22,15 @@
|
|||
* */
|
||||
|
||||
/*jslint indent: 2 */
|
||||
/*global define: false, window: false, location: true, localStorage: false,
|
||||
/*global define: false, window: false, location: true,
|
||||
opener: false, setTimeout: false, navigator: false */
|
||||
|
||||
'use strict';
|
||||
|
||||
define([ 'blade/object', 'storage'],
|
||||
function (object, storage) {
|
||||
define([ 'blade/object'],
|
||||
function (object) {
|
||||
|
||||
var newHotness = parseFloat(navigator.userAgent.split('Firefox/')[1]) >= 4,
|
||||
store = storage(),
|
||||
svcs, prop;
|
||||
var svcs, prop;
|
||||
|
||||
function SvcBase(name, options) {
|
||||
if (!name) {
|
||||
|
@ -54,29 +52,6 @@ function (object, storage) {
|
|||
}
|
||||
SvcBase.constructor = SvcBase;
|
||||
SvcBase.prototype = {
|
||||
clearCache: function (store) {
|
||||
//This first delete is only needed for 3.6 support.
|
||||
//Remove the call in accounts.js when it is removed.
|
||||
delete store[this.type + 'Contacts'];
|
||||
},
|
||||
//This method can be removed once 3.6 support is dropped.
|
||||
getContacts: function (store) {
|
||||
if (store[this.type + 'Contacts']) {
|
||||
var contacts = JSON.parse(store[this.type + 'Contacts']);
|
||||
return contacts;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
//This method can be removed once 3.6 support is dropped.
|
||||
setContacts: function (store, contacts) {
|
||||
store[this.type + 'Contacts'] = JSON.stringify(contacts);
|
||||
},
|
||||
|
||||
// stub function that should not return data for non-mail services
|
||||
// for the FF 3.6 extension.
|
||||
get36FormattedContacts: function () {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/* common functionality for email based services */
|
||||
|
@ -276,14 +251,6 @@ function (object, storage) {
|
|||
for (prop in svcs.domains) {
|
||||
if (svcs.domains.hasOwnProperty(prop)) {
|
||||
svcs.domainList.push(prop);
|
||||
|
||||
// Clear out the old contacts model.
|
||||
// TODO: Remove this once the 3.6 add-on/UI is finally
|
||||
// shut off.
|
||||
if (newHotness) {
|
||||
delete store[svcs.domains[prop].type + 'Contacts'];
|
||||
delete store.contactsModelVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
/*global define: false, location: false */
|
||||
"use strict";
|
||||
|
||||
define(['storage', 'blade/url'], function (storage, url) {
|
||||
define(['blade/url'], function (url) {
|
||||
var cache = {};
|
||||
|
||||
function shareOptions(str) {
|
||||
|
@ -40,10 +40,9 @@ define(['storage', 'blade/url'], function (storage, url) {
|
|||
}
|
||||
|
||||
var options = {},
|
||||
store = storage(),
|
||||
vimeoCdnRegExp = /vimeocdn\.com\//,
|
||||
vimeoSourceRegExp = /clip_id=(\d+)/,
|
||||
urlArgs, prop, source, videoId;
|
||||
urlArgs, source, videoId;
|
||||
|
||||
if (str) {
|
||||
urlArgs = url.queryToObject(str);
|
||||
|
@ -84,22 +83,6 @@ define(['storage', 'blade/url'], function (storage, url) {
|
|||
}
|
||||
//END domain-specific hacks.
|
||||
|
||||
//Save the extension version in the localStorage, for use in
|
||||
//other pages like settings.
|
||||
if (options.version) {
|
||||
store.extensionVersion = options.version;
|
||||
}
|
||||
|
||||
//Save the preferences in localStorage, for use in
|
||||
//other ppages like setting.
|
||||
if (options.prefs) {
|
||||
for (prop in options.prefs) {
|
||||
if (options.prefs.hasOwnProperty(prop)) {
|
||||
store['prefs.' + prop] = options.prefs[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache[str] = options;
|
||||
|
||||
return options;
|
||||
|
|
|
@ -25,28 +25,90 @@
|
|||
/*global define: false, localStorage: false */
|
||||
"use strict";
|
||||
|
||||
define([], function () {
|
||||
var store = localStorage, type = 'localStorage';
|
||||
define(['dispatch'], function (dispatch) {
|
||||
var internalStore = {},
|
||||
callbacks = {},
|
||||
store;
|
||||
|
||||
//Capability detect for localStorage. At least one add-on does weird things
|
||||
//with it.
|
||||
try {
|
||||
store.tempVar = 'temp';
|
||||
//Test reading.
|
||||
if (store.tempVar === 'temp') {}
|
||||
//Test deletion.
|
||||
delete store.tempVar;
|
||||
} catch (e) {
|
||||
//Just use a simple in-memory object. Not as nice, but code will still work.
|
||||
store = {};
|
||||
type = 'memory';
|
||||
// Temporary workaround to allow separate tab of settings to still have
|
||||
// access to the chrome storage. Not a good idea to do long term.
|
||||
if (opener && !opener.closed && opener.require &&
|
||||
(store = opener.require('storage'))) {
|
||||
return store;
|
||||
}
|
||||
|
||||
store = {
|
||||
get: function (key, callback) {
|
||||
var keyCallbacks;
|
||||
|
||||
if (key in internalStore) {
|
||||
// favor internal storage when available.
|
||||
callback(internalStore[key]);
|
||||
} else {
|
||||
// hold on to the callback until there is an answer.
|
||||
keyCallbacks = callbacks[key];
|
||||
|
||||
if (!keyCallbacks) {
|
||||
keyCallbacks = callbacks[key] = [];
|
||||
|
||||
// ask the real storage for it.
|
||||
dispatch.pub('storeGet', key);
|
||||
}
|
||||
|
||||
keyCallbacks.push(callback);
|
||||
}
|
||||
},
|
||||
|
||||
set: function (key, value) {
|
||||
internalStore[key] = value;
|
||||
|
||||
dispatch.pub('storeSet', {
|
||||
key: key,
|
||||
value: value
|
||||
});
|
||||
},
|
||||
|
||||
remove: function (key) {
|
||||
delete internalStore[key];
|
||||
dispatch.pub('storeRemove', key);
|
||||
}
|
||||
};
|
||||
|
||||
dispatch.sub('storeGetReturn', function (data) {
|
||||
var key = data.key,
|
||||
value = data.value,
|
||||
keyCallbacks = callbacks[key];
|
||||
|
||||
internalStore[key] = value;
|
||||
|
||||
if (keyCallbacks) {
|
||||
keyCallbacks.forEach(function (callback) {
|
||||
callback(value);
|
||||
});
|
||||
|
||||
delete callbacks[key];
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
dispatch.sub('storeNotifyChange', function (data) {
|
||||
if (data.value === null) {
|
||||
delete internalStore[data.key];
|
||||
} else {
|
||||
internalStore[data.key] = data.value;
|
||||
}
|
||||
});
|
||||
|
||||
dispatch.sub('storeNotifyRemoveAll', function (data) {
|
||||
// Reset the internal store
|
||||
internalStore = {};
|
||||
});
|
||||
|
||||
function storage() {
|
||||
return store;
|
||||
}
|
||||
|
||||
storage.type = type;
|
||||
storage.type = 'chrome';
|
||||
|
||||
return storage;
|
||||
});
|
|
@ -80,6 +80,8 @@
|
|||
|
||||
<div id="settings" class="panel hidden">
|
||||
<h1>Configure your sharing settings</h1>
|
||||
Commented out for now.
|
||||
<!--
|
||||
<ul>
|
||||
<li class="hbox">
|
||||
<span class="key">F1</span>
|
||||
|
@ -91,6 +93,7 @@
|
|||
<input type="checkbox" id="bookmarking">
|
||||
</li>
|
||||
</ul>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<div id="advanced" class="panel hidden">
|
||||
|
|
|
@ -33,9 +33,6 @@ function (require, $, fn, rdapi, oauth, jig,
|
|||
dispatch, storage, accounts, dotCompare, url,
|
||||
services, placeholder) {
|
||||
var store = storage(),
|
||||
shortenPrefs = store.shortenPrefs,
|
||||
isGreaterThan072 = dotCompare(store.extensionVersion, "0.7.3") > -1,
|
||||
isGreaterThan073 = dotCompare(store.extensionVersion, "0.7.4") > -1,
|
||||
options = url.queryToObject(location.href.split('#')[1] || '') || {},
|
||||
existingAccounts = {},
|
||||
showNew = options.show === 'new';
|
||||
|
@ -75,7 +72,9 @@ function (require, $, fn, rdapi, oauth, jig,
|
|||
|
||||
json.forEach(function (account) {
|
||||
// protect against old style account data
|
||||
if (typeof(account.profile) === 'undefined') return;
|
||||
if (typeof(account.profile) === 'undefined') {
|
||||
return;
|
||||
}
|
||||
html += jig('#accountTemplate', account.profile);
|
||||
|
||||
// remember which accounts already have an entry
|
||||
|
@ -93,11 +92,9 @@ function (require, $, fn, rdapi, oauth, jig,
|
|||
html = '';
|
||||
services.domainList.forEach(function (domain) {
|
||||
var data = services.domains[domain];
|
||||
if (isGreaterThan073 || !existingAccounts[domain]) {
|
||||
data.domain = domain;
|
||||
data.enableSignOut = !data.forceLogin && existingAccounts[domain];
|
||||
html += jig('#addTemplate', services.domains[domain]);
|
||||
}
|
||||
data.domain = domain;
|
||||
data.enableSignOut = !data.forceLogin && existingAccounts[domain];
|
||||
html += jig('#addTemplate', services.domains[domain]);
|
||||
});
|
||||
|
||||
if (html) {
|
||||
|
@ -129,7 +126,7 @@ function (require, $, fn, rdapi, oauth, jig,
|
|||
|
||||
var shortenDom = $('#shortenForm'),
|
||||
bitlyCheckboxDom = $('#bitlyCheckbox'),
|
||||
pref, node;
|
||||
node;
|
||||
|
||||
|
||||
//Function placed inside this function to get access to DOM variables.
|
||||
|
@ -199,7 +196,7 @@ function (require, $, fn, rdapi, oauth, jig,
|
|||
|
||||
function resetShortenData() {
|
||||
clearShortenData();
|
||||
delete store.shortenPrefs;
|
||||
store.remove('shortenPrefs');
|
||||
hideShortenForm();
|
||||
}
|
||||
|
||||
|
@ -209,13 +206,15 @@ function (require, $, fn, rdapi, oauth, jig,
|
|||
$("#wrapper").css({ "min-height" : (h) });
|
||||
});
|
||||
|
||||
if (shortenPrefs) {
|
||||
shortenPrefs = JSON.parse(shortenPrefs);
|
||||
setShortenData(shortenPrefs);
|
||||
showShortenForm();
|
||||
} else {
|
||||
hideShortenForm();
|
||||
}
|
||||
store.get('shortenPrefs', function (shortenPrefs) {
|
||||
if (shortenPrefs) {
|
||||
shortenPrefs = JSON.parse(shortenPrefs);
|
||||
setShortenData(shortenPrefs);
|
||||
showShortenForm();
|
||||
} else {
|
||||
hideShortenForm();
|
||||
}
|
||||
});
|
||||
|
||||
$('body')
|
||||
.delegate('#bitlyCheckbox', 'click', function (evt) {
|
||||
|
@ -242,15 +241,15 @@ function (require, $, fn, rdapi, oauth, jig,
|
|||
dataType: 'json',
|
||||
success: function (json) {
|
||||
if (json.status_code === 200 && json.data.valid) {
|
||||
store.shortenPrefs = JSON.stringify(data);
|
||||
store.set('shortenPrefs', JSON.stringify(data));
|
||||
} else {
|
||||
$('#bitlyNotValid').removeClass('hidden');
|
||||
delete store.shortenPrefs;
|
||||
store.remove('shortenPrefs');
|
||||
}
|
||||
},
|
||||
error: function (xhr, textStatus, errorThrown) {
|
||||
$('#bitlyNotValid').removeClass('hidden');
|
||||
delete store.shortenPrefs;
|
||||
store.remove('shortenPrefs');
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -274,7 +273,7 @@ function (require, $, fn, rdapi, oauth, jig,
|
|||
if (success) {
|
||||
//Make sure to bring the user back to this service if
|
||||
//the auth is successful.
|
||||
store.lastSelection = selectionName;
|
||||
store.set('lastSelection', selectionName);
|
||||
} else {
|
||||
showStatus('statusOAuthFailed');
|
||||
}
|
||||
|
@ -295,31 +294,8 @@ function (require, $, fn, rdapi, oauth, jig,
|
|||
accounts.clear();
|
||||
}
|
||||
evt.preventDefault();
|
||||
})
|
||||
.delegate('#settings [type="checkbox"]', 'click', function (evt) {
|
||||
//Listen for changes in prefs and update localStorage, inform opener
|
||||
//of changes.
|
||||
var node = evt.target,
|
||||
prefId = node.id,
|
||||
value = node.checked;
|
||||
|
||||
store['prefs.' + prefId] = value;
|
||||
if (opener && !opener.closed) {
|
||||
dispatch.pub('prefChanged', {
|
||||
name: prefId,
|
||||
value: value
|
||||
}, opener);
|
||||
}
|
||||
});
|
||||
|
||||
//Set up state of the prefs.
|
||||
pref = store['prefs.use_accel_key'];
|
||||
pref = pref ? JSON.parse(pref) : false;
|
||||
$('#use_accel_key')[0].checked = pref || false;
|
||||
pref = store['prefs.bookmarking'];
|
||||
pref = pref ? JSON.parse(pref) : false;
|
||||
$('#bookmarking')[0].checked = pref || false;
|
||||
|
||||
// create ellipsis for gecko
|
||||
$(function () {
|
||||
$(".overflow").textOverflow(null, true);
|
||||
|
@ -328,12 +304,8 @@ function (require, $, fn, rdapi, oauth, jig,
|
|||
// tabs
|
||||
// Only show settings if extension can actually handle setting of them.
|
||||
// Same for advanced.
|
||||
if (isGreaterThan072) {
|
||||
$('li[data-tab="settings"]').removeClass('hidden');
|
||||
}
|
||||
if (isGreaterThan073) {
|
||||
$('li[data-tab="advanced"]').removeClass('hidden');
|
||||
}
|
||||
$('li[data-tab="settings"]').removeClass('hidden');
|
||||
$('li[data-tab="advanced"]').removeClass('hidden');
|
||||
|
||||
$('body')
|
||||
// Set up tab switching behavior.
|
||||
|
@ -382,5 +354,15 @@ function (require, $, fn, rdapi, oauth, jig,
|
|||
'&output=json&' +
|
||||
'q=http%3A%2F%2Fmozillalabs.com%2Fmessaging%2Ffeed%2F';
|
||||
$('head')[0].appendChild(node);
|
||||
|
||||
// Make sure this window gets all events, particularly related to storage.
|
||||
// This can go away if the settings work is done inside the share panel.
|
||||
// Use a setTimeout because the opener could be reloading, for instance,
|
||||
// after an account is added.
|
||||
if (opener && !opener.closed && opener.require) {
|
||||
setTimeout(function () {
|
||||
opener.require('dispatch').trackWindow(window);
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,9 +23,15 @@
|
|||
|
||||
/*jslint plusplus: false, indent: 2, nomen: false */
|
||||
/*global require: false, define: false, location: true, window: false, alert: false,
|
||||
document: false, setTimeout: false, localStorage: false */
|
||||
document: false, setTimeout: false, localStorage: false, parent: false */
|
||||
"use strict";
|
||||
|
||||
// Allow tests to plug into the page by notify them if this is a test.
|
||||
if (location.hash === '#test') {
|
||||
parent.postMessage(JSON.stringify({topic: 'registerForTests'}),
|
||||
location.protocol + "//" + location.host);
|
||||
}
|
||||
|
||||
require({
|
||||
paths: {
|
||||
widgets: '../share/panel/scripts/widgets'
|
||||
|
@ -43,7 +49,7 @@ function (require, $, object, fn, rdapi, oauth,
|
|||
DebugPanel, AccountPanel, dotCompare) {
|
||||
|
||||
var actions = services.domains,
|
||||
options, bodyDom, timer, pageInfo, sendData, showNew,
|
||||
options, bodyDom, pageInfo, sendData, showNew,
|
||||
onFirstShareState = null,
|
||||
accountPanels = [],
|
||||
store = storage(),
|
||||
|
@ -55,7 +61,6 @@ function (require, $, object, fn, rdapi, oauth,
|
|||
statusSharing: true,
|
||||
statusShared: true
|
||||
},
|
||||
isGreaterThan076 = dotCompare(store.extensionVersion, "0.7.7") > -1,
|
||||
// If it has been more than a day,
|
||||
// refresh the UI, record a timestamp for it.
|
||||
refreshStamp = (new Date()).getTime(),
|
||||
|
@ -242,7 +247,7 @@ function (require, $, object, fn, rdapi, oauth,
|
|||
} else if (json.error) {
|
||||
showStatus('statusError', json.error.message);
|
||||
} else {
|
||||
store.lastSelection = actions[sendData.domain].type;
|
||||
store.set('lastSelection', actions[sendData.domain].type);
|
||||
showStatusShared();
|
||||
//Be sure to delete sessionRestore data
|
||||
accountPanels.forEach(function (panel) {
|
||||
|
@ -282,53 +287,58 @@ function (require, $, object, fn, rdapi, oauth,
|
|||
|
||||
sendData = data;
|
||||
|
||||
var svcData = accounts.getService(data.domain, data.userid, data.username),
|
||||
svcConfig = services.domains[data.domain],
|
||||
shortenPrefs = store.shortenPrefs,
|
||||
shortenData;
|
||||
// get any shortener prefs before trying to send.
|
||||
store.get('shortenPrefs', function (shortenPrefs) {
|
||||
|
||||
sendData.account = JSON.stringify(svcData);
|
||||
accounts.getService(data.domain, data.userid, data.username,
|
||||
function (svcData) {
|
||||
|
||||
// hide the panel now, but only if the extension can show status
|
||||
// itself (0.7.7 or greater)
|
||||
updateChromeStatus(SHARE_START);
|
||||
if (isGreaterThan076) {
|
||||
hide();
|
||||
}
|
||||
var svcConfig = services.domains[data.domain],
|
||||
shortenData;
|
||||
|
||||
//First see if a bitly URL is needed.
|
||||
if (svcConfig.shorten && shortenPrefs) {
|
||||
shortenData = {
|
||||
format: 'json',
|
||||
longUrl: sendData.link
|
||||
};
|
||||
sendData.account = JSON.stringify(svcData);
|
||||
|
||||
// Unpack the user prefs
|
||||
shortenPrefs = JSON.parse(shortenPrefs);
|
||||
// hide the panel now, but only if the extension can show status
|
||||
// itself (0.7.7 or greater)
|
||||
updateChromeStatus(SHARE_START);
|
||||
hide();
|
||||
|
||||
if (shortenPrefs) {
|
||||
object.mixin(shortenData, shortenPrefs, true);
|
||||
}
|
||||
//First see if a bitly URL is needed.
|
||||
if (svcConfig.shorten && shortenPrefs) {
|
||||
shortenData = {
|
||||
format: 'json',
|
||||
longUrl: sendData.link
|
||||
};
|
||||
|
||||
// Make sure the server does not try to shorten.
|
||||
delete sendData.shorten;
|
||||
// Unpack the user prefs
|
||||
shortenPrefs = JSON.parse(shortenPrefs);
|
||||
|
||||
$.ajax({
|
||||
url: 'http://api.bitly.com/v3/shorten',
|
||||
type: 'GET',
|
||||
data: shortenData,
|
||||
dataType: 'json',
|
||||
success: function (json) {
|
||||
sendData.shorturl = json.data.url;
|
||||
callSendApi();
|
||||
},
|
||||
error: function (xhr, textStatus, errorThrown) {
|
||||
showStatus('statusShortenerError', errorThrown);
|
||||
if (shortenPrefs) {
|
||||
object.mixin(shortenData, shortenPrefs, true);
|
||||
}
|
||||
|
||||
// Make sure the server does not try to shorten.
|
||||
delete sendData.shorten;
|
||||
|
||||
$.ajax({
|
||||
url: 'http://api.bitly.com/v3/shorten',
|
||||
type: 'GET',
|
||||
data: shortenData,
|
||||
dataType: 'json',
|
||||
success: function (json) {
|
||||
sendData.shorturl = json.data.url;
|
||||
callSendApi();
|
||||
},
|
||||
error: function (xhr, textStatus, errorThrown) {
|
||||
showStatus('statusShortenerError', errorThrown);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callSendApi();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callSendApi();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -343,67 +353,106 @@ function (require, $, object, fn, rdapi, oauth,
|
|||
|
||||
$('#shareui').removeClass('hidden');
|
||||
|
||||
//Figure out what accounts we do have
|
||||
accounts.forEach(function (account) {
|
||||
// protect against old style account data
|
||||
if (typeof(account.profile) === 'undefined') {
|
||||
return;
|
||||
}
|
||||
store.get('lastSelection', function (lastSelection) {
|
||||
store.get('accountAdded', function (accountAdded) {
|
||||
|
||||
var domain = account.profile.accounts[0].domain,
|
||||
data, PanelCtor;
|
||||
var asyncCount = 0,
|
||||
asyncConstructionDone = false,
|
||||
accountPanel;
|
||||
|
||||
if (domain && actions[domain]) {
|
||||
//Make sure to see if there is a match for last selection
|
||||
if (actions[domain].type === store.lastSelection) {
|
||||
lastSelectionMatch = i;
|
||||
// Finishes account creation. Actually runs *after* the work done
|
||||
// below this function. Need a function callback since AccountPanel
|
||||
// construction is async.
|
||||
function finishCreate() {
|
||||
asyncCount -= 1;
|
||||
|
||||
// Could still be waiting for other async creations. If so, wait.
|
||||
if (asyncCount > 0 || !asyncConstructionDone) {
|
||||
return;
|
||||
}
|
||||
|
||||
// add the account panels now
|
||||
accountsDom.append(fragment);
|
||||
|
||||
//Add debug panel if it is allowed.
|
||||
if (options.prefs.system === 'dev') {
|
||||
debugPanel = new DebugPanel({}, accountsDom[0]);
|
||||
}
|
||||
|
||||
checkBase64Preview();
|
||||
|
||||
//If no matching accounts match the last selection clear it.
|
||||
if (lastSelectionMatch < 0 && !accountAdded && lastSelection) {
|
||||
store.remove('lastSelection');
|
||||
lastSelectionMatch = 0;
|
||||
}
|
||||
|
||||
// which domain was last active?
|
||||
$("#accounts").accordion({ active: lastSelectionMatch });
|
||||
|
||||
//Reset the just added state now that accounts have been configured one time.
|
||||
if (accountAdded) {
|
||||
store.remove('accountAdded');
|
||||
}
|
||||
|
||||
//Inform extension the content size has changed, but use a delay,
|
||||
//to allow any reflow/adjustments.
|
||||
setTimeout(function () {
|
||||
dispatch.pub('sizeToContent');
|
||||
}, 100);
|
||||
}
|
||||
|
||||
data = actions[domain];
|
||||
data.domain = domain;
|
||||
//Figure out what accounts we do have
|
||||
accounts.forEach(function (account) {
|
||||
// protect against old style account data
|
||||
if (typeof(account.profile) === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the contructor function for the panel.
|
||||
PanelCtor = require(panelOverlayMap[domain] || 'widgets/AccountPanel');
|
||||
var domain = account.profile.accounts[0].domain,
|
||||
data, PanelCtor;
|
||||
|
||||
accountPanels.push(new PanelCtor({
|
||||
options: options,
|
||||
account: account,
|
||||
svc: data
|
||||
}, fragment));
|
||||
}
|
||||
if (domain && actions[domain]) {
|
||||
//Make sure to see if there is a match for last selection
|
||||
if (actions[domain].type === lastSelection) {
|
||||
lastSelectionMatch = i;
|
||||
}
|
||||
|
||||
i++;
|
||||
data = actions[domain];
|
||||
data.domain = domain;
|
||||
|
||||
// Get the contructor function for the panel.
|
||||
PanelCtor = require(panelOverlayMap[domain] || 'widgets/AccountPanel');
|
||||
|
||||
accountPanel = new PanelCtor({
|
||||
options: options,
|
||||
account: account,
|
||||
svc: data
|
||||
}, fragment);
|
||||
|
||||
// if an async creation, then wait until all are created before
|
||||
// proceeding with UI construction.
|
||||
if (accountPanel.asyncCreate) {
|
||||
asyncCount += 1;
|
||||
accountPanel.asyncCreate.then(finishCreate);
|
||||
}
|
||||
|
||||
accountPanels.push(accountPanel);
|
||||
}
|
||||
|
||||
i++;
|
||||
});
|
||||
|
||||
asyncConstructionDone = true;
|
||||
|
||||
// The async creation could have finished if all the data values
|
||||
// for the account panels were already cached. If so, then finish
|
||||
// out the UI construction.
|
||||
if (!asyncCount) {
|
||||
finishCreate();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// add the account panels now
|
||||
accountsDom.append(fragment);
|
||||
|
||||
//Add debug panel if it is allowed.
|
||||
if (options.prefs.system === 'dev') {
|
||||
debugPanel = new DebugPanel({}, accountsDom[0]);
|
||||
}
|
||||
|
||||
checkBase64Preview();
|
||||
|
||||
//If no matching accounts match the last selection clear it.
|
||||
if (lastSelectionMatch < 0 && !store.accountAdded && store.lastSelection) {
|
||||
delete store.lastSelection;
|
||||
lastSelectionMatch = 0;
|
||||
}
|
||||
|
||||
// which domain was last active?
|
||||
$("#accounts").accordion({ active: lastSelectionMatch });
|
||||
|
||||
//Reset the just added state now that accounts have been configured one time.
|
||||
if (store.accountAdded) {
|
||||
delete store.accountAdded;
|
||||
}
|
||||
|
||||
//Inform extension the content size has changed, but use a delay,
|
||||
//to allow any reflow/adjustments.
|
||||
setTimeout(function () {
|
||||
dispatch.pub('sizeToContent');
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function updateAccounts(accounts) {
|
||||
|
@ -441,34 +490,11 @@ function (require, $, object, fn, rdapi, oauth,
|
|||
}
|
||||
} else {
|
||||
showStatus('statusSettings');
|
||||
|
||||
//Clean up storage
|
||||
services.domainList.forEach(function (domain) {
|
||||
delete store[services.domains[domain].type + 'Contacts'];
|
||||
});
|
||||
|
||||
dispatch.pub('sizeToContent');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//For the "new items" link, only show it for x number of days after showing it.
|
||||
//NOTE: when updating for newer releases, delete the old value from the
|
||||
//storage.
|
||||
delete store.newTimerV1;
|
||||
delete store.newTimerV2;
|
||||
timer = store.newTimerV3;
|
||||
if (!timer) {
|
||||
store.newTimerV3 = (new Date()).getTime();
|
||||
showNew = true;
|
||||
} else {
|
||||
timer = JSON.parse(timer);
|
||||
//If time since first seen is greater than three days, hide the new link.
|
||||
if ((new Date()).getTime() - timer < (3 * 24 * 60 * 60 * 1000)) {
|
||||
showNew = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Set up initialization work for the first share state passing.
|
||||
onFirstShareState = function () {
|
||||
// Wait until DOM ready to start the DOM work.
|
||||
|
|
|
@ -77,10 +77,11 @@ function (object, Widget, $, template,
|
|||
shareTypeLabel: 'send to'
|
||||
},
|
||||
|
||||
onCreate: function () {
|
||||
onCreate: function (onAsynCreateDone) {
|
||||
var profile = this.account.profile,
|
||||
name = profile.displayName,
|
||||
userName, savedOptions;
|
||||
userName,
|
||||
onFinishCreate = this.makeCreateCallback();
|
||||
|
||||
//Set up the svcAccount property
|
||||
this.svcAccount = profile.accounts[0];
|
||||
|
@ -94,58 +95,65 @@ function (object, Widget, $, template,
|
|||
|
||||
//Check for saved data. Only use if the URL
|
||||
//and the account match
|
||||
savedOptions = store[this.storeId];
|
||||
if (savedOptions) {
|
||||
savedOptions = JSON.parse(savedOptions);
|
||||
store.get(this.storeId, fn.bind(this, function (savedOptions) {
|
||||
if (savedOptions) {
|
||||
savedOptions = JSON.parse(savedOptions);
|
||||
|
||||
if (this.theGameHasChanged(savedOptions)) {
|
||||
this.clearSavedData();
|
||||
savedOptions = null;
|
||||
} else {
|
||||
//Mix in the savedOptions with options.
|
||||
this.options = object.create(this.options, [savedOptions]);
|
||||
if (this.theGameHasChanged(savedOptions)) {
|
||||
this.clearSavedData();
|
||||
savedOptions = null;
|
||||
} else {
|
||||
//Mix in the savedOptions with options.
|
||||
this.options = object.create(this.options, [savedOptions]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Set up the photo property
|
||||
this.photo = profile.photos && profile.photos[0] && profile.photos[0].value;
|
||||
//Set up the photo property
|
||||
this.photo = profile.photos && profile.photos[0] && profile.photos[0].value;
|
||||
|
||||
//Set up nicer display name
|
||||
// XXX for email services, we should show the email account, but we
|
||||
// cannot rely on userid being a 'pretty' name we can display
|
||||
userName = this.svcAccount.username;
|
||||
if (userName && userName !== name) {
|
||||
name = name + " (" + userName + ")";
|
||||
}
|
||||
|
||||
this.displayName = name;
|
||||
|
||||
// Figure out what module will handle contacts.
|
||||
this.contactsName = (this.svc.overlays &&
|
||||
this.svc.overlays[this.contactsName]) ||
|
||||
this.contactsName;
|
||||
|
||||
//Listen for options changes and update the account.
|
||||
this.optionsChangedSub = dispatch.sub('optionsChanged', fn.bind(this, function (options) {
|
||||
this.options = options;
|
||||
this.optionsChanged();
|
||||
}));
|
||||
|
||||
//Listen for updates to base64Preview
|
||||
this.base64PreviewSub = dispatch.sub('base64Preview', fn.bind(this, function (dataUrl) {
|
||||
$('[name="picture_base64"]', this.bodyNode).val(jigFuncs.rawBase64(dataUrl));
|
||||
}));
|
||||
|
||||
// listen for successful send, and if so, update contacts list, if
|
||||
// the send matches this account.
|
||||
this.sendCompleteSub = dispatch.sub('sendComplete', fn.bind(this, function (data) {
|
||||
var acct = this.svcAccount;
|
||||
if (data.to && acct.domain === data.domain &&
|
||||
acct.userid === data.userid &&
|
||||
acct.username === data.username) {
|
||||
this.contacts.incorporate(data.to);
|
||||
//Set up nicer display name
|
||||
// XXX for email services, we should show the email account, but we
|
||||
// cannot rely on userid being a 'pretty' name we can display
|
||||
userName = this.svcAccount.username;
|
||||
if (userName && userName !== name) {
|
||||
name = name + " (" + userName + ")";
|
||||
}
|
||||
|
||||
this.displayName = name;
|
||||
|
||||
// Figure out what module will handle contacts.
|
||||
this.contactsName = (this.svc.overlays &&
|
||||
this.svc.overlays[this.contactsName]) ||
|
||||
this.contactsName;
|
||||
|
||||
//Listen for options changes and update the account.
|
||||
this.optionsChangedSub = dispatch.sub('optionsChanged', fn.bind(this, function (options) {
|
||||
this.options = options;
|
||||
this.optionsChanged();
|
||||
}));
|
||||
|
||||
//Listen for updates to base64Preview
|
||||
this.base64PreviewSub = dispatch.sub('base64Preview', fn.bind(this, function (dataUrl) {
|
||||
$('[name="picture_base64"]', this.bodyNode).val(jigFuncs.rawBase64(dataUrl));
|
||||
}));
|
||||
|
||||
// listen for successful send, and if so, update contacts list, if
|
||||
// the send matches this account.
|
||||
this.sendCompleteSub = dispatch.sub('sendComplete', fn.bind(this, function (data) {
|
||||
var acct = this.svcAccount;
|
||||
if (data.to && acct.domain === data.domain &&
|
||||
acct.userid === data.userid &&
|
||||
acct.username === data.username) {
|
||||
this.contacts.incorporate(data.to);
|
||||
}
|
||||
}));
|
||||
|
||||
// indicate async creation is done.
|
||||
onFinishCreate.resolve();
|
||||
}));
|
||||
|
||||
// return onFinishCreate to indicate this is an async creation
|
||||
return onFinishCreate;
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
|
@ -242,7 +250,7 @@ function (object, Widget, $, template,
|
|||
|
||||
clearSavedData: function () {
|
||||
this.memStore = {};
|
||||
delete store[this.storeId];
|
||||
store.remove(this.storeId);
|
||||
|
||||
//Also clear up the form data.
|
||||
var root = $(this.bodyNode);
|
||||
|
@ -258,7 +266,7 @@ function (object, Widget, $, template,
|
|||
|
||||
saveData: function () {
|
||||
var data = this.getFormData();
|
||||
store[this.storeId] = JSON.stringify(data);
|
||||
store.set(this.storeId, data);
|
||||
},
|
||||
|
||||
validate: function (sendData) {
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Fake Chrome Container</title>
|
||||
<style>
|
||||
iframe {
|
||||
width: 350px;
|
||||
height: 500px;
|
||||
display: block;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#chromeActions {
|
||||
float: left;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#chromeActions button {
|
||||
display: block;
|
||||
margin: 10px;
|
||||
}
|
||||
</style>
|
||||
<script data-main="chrome.js" src="../../1/scripts/requireplugins-jquery.js" charset="utf-8"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Fake Chrome Container</h1>
|
||||
|
||||
<iframe id="testFrame" src="../../blank.html"></iframe>
|
||||
|
||||
<form id="chromeActions">
|
||||
<button id="reloadPanel">Reload Panel</button>
|
||||
<button id="logStore">Log Data Store to Console</button>
|
||||
<button id="clearStore">Clear Data Store</button>
|
||||
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,192 @@
|
|||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Raindrop.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Mozilla Messaging, Inc..
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* */
|
||||
|
||||
/*jslint indent: 2 */
|
||||
/*global define: false, window: false, console: false */
|
||||
"use strict";
|
||||
|
||||
define(['jquery', 'dispatch'], function ($, dispatch) {
|
||||
|
||||
// debug
|
||||
window.addEventListener('message', function (evt) {
|
||||
console.log("GOT POSTMESSAGE: ", evt.data, evt);
|
||||
}, false);
|
||||
|
||||
var sub = dispatch.sub,
|
||||
testWindow, chrome,
|
||||
dataStore = (localStorage.chromeTestStore &&
|
||||
JSON.parse(localStorage.chromeTestStore)) || {},
|
||||
origin = location.protocol + "//" + location.host;
|
||||
|
||||
// expose the data store for inspection in firebug
|
||||
window.dataStore = dataStore;
|
||||
|
||||
|
||||
chrome = {
|
||||
saveStore: function () {
|
||||
localStorage.chromeTestStore = JSON.stringify(dataStore);
|
||||
},
|
||||
|
||||
logStore: function () {
|
||||
console.log('Chrome dataStore: ', dataStore);
|
||||
},
|
||||
|
||||
clearStore: function () {
|
||||
delete localStorage.chromeTestStore;
|
||||
dataStore = {};
|
||||
},
|
||||
|
||||
loadPanel: function () {
|
||||
testWindow.location = '../../1/share/panel/#test';
|
||||
},
|
||||
|
||||
reloadPanel: function () {
|
||||
testWindow.location.reload(true);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
// Helpers that deal with the target window subscriptions.
|
||||
function targetPub(topic, data) {
|
||||
dispatch.pub(topic, data, testWindow);
|
||||
}
|
||||
|
||||
// Listen for the event that the panel will give in test mode.
|
||||
sub('registerForTests', function () {
|
||||
|
||||
var subs = {
|
||||
panelReady: function () {
|
||||
targetPub('shareState', {
|
||||
status: 0,
|
||||
open: true,
|
||||
options: {
|
||||
version: '0.7.2',
|
||||
title: 'Firefox web browser',
|
||||
description: 'All about firefox',
|
||||
medium: null,
|
||||
source: null,
|
||||
url: 'http://www.mozilla.com/en-US/firefox/fx/',
|
||||
canonicalUrl: null,
|
||||
shortUrl: null,
|
||||
previews: [{
|
||||
http_url: 'http://mozcom-cdn.mozilla.net/img/firefox-100.jpg'
|
||||
}],
|
||||
siteName: '',
|
||||
prefs: {
|
||||
system: 'dev',
|
||||
bookmarking: true,
|
||||
use_accel_key: true
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
storeGet: function (key) {
|
||||
var value = dataStore[key];
|
||||
//JSON wants null.
|
||||
if (value === undefined) {
|
||||
value = null;
|
||||
}
|
||||
targetPub('storeGetReturn', {
|
||||
key: key,
|
||||
value: value
|
||||
});
|
||||
},
|
||||
|
||||
storeSet: function (data) {
|
||||
dataStore[data.key] = data.value;
|
||||
chrome.saveStore();
|
||||
targetPub('storeNotifyChange', {
|
||||
key: data.key,
|
||||
value: data.value
|
||||
});
|
||||
},
|
||||
|
||||
storeRemove: function (key) {
|
||||
delete dataStore[key];
|
||||
chrome.saveStore();
|
||||
targetPub('storeNotifyChange', {
|
||||
key: key,
|
||||
value: null
|
||||
});
|
||||
}
|
||||
},
|
||||
prop;
|
||||
|
||||
// register all events.
|
||||
testWindow.addEventListener('message', function (evt) {
|
||||
if (evt.origin === origin) {
|
||||
var message;
|
||||
try {
|
||||
var message = JSON.parse(evt.data);
|
||||
} catch (e) {
|
||||
console.error('Could not JSON parse: ' + evt.data);
|
||||
}
|
||||
|
||||
if (message && message.topic) {
|
||||
if (subs[message.topic]) {
|
||||
subs[message.topic](message.data);
|
||||
} else {
|
||||
// actually quite a few of these, uncomment if you want a play
|
||||
// by play of topics going through the testWindow.
|
||||
console.log("Unhandled topic: " + message.topic, message.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
|
||||
});
|
||||
|
||||
window.addEventListener('load', function (evt) {
|
||||
testWindow = $('#testFrame')[0].contentWindow;
|
||||
|
||||
// load the share panel
|
||||
chrome.loadPanel();
|
||||
|
||||
// bind some event listeners, these just bind button IDs to trigger
|
||||
// the same-named method on this chrome module.
|
||||
$('body').delegate('#chromeActions button', 'click', function (evt) {
|
||||
var id = evt.target.id;
|
||||
if (id && chrome[id]) {
|
||||
chrome[id]();
|
||||
}
|
||||
|
||||
evt.preventDefault();
|
||||
});
|
||||
}, false);
|
||||
|
||||
// There is a chance it is already ready (particularly in webkit),
|
||||
// so try to grab it now.
|
||||
testWindow = $('#testFrame');
|
||||
if (testWindow && (testWindow = testWindow[0])) {
|
||||
testWindow = testWindow.contentWindow;
|
||||
chrome.loadPanel();
|
||||
} else {
|
||||
testWindow = null;
|
||||
}
|
||||
|
||||
// Return a module, then accessible via commandline as
|
||||
// require('chrome')
|
||||
return chrome;
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
# QUnit test for Firefox Share
|
||||
|
||||
qunit.js and qunit.css were fetched from http://code.jquery.com/qunit/git/
|
||||
on 2011-24-03 at 3:40PM Pacific time.
|
||||
|
||||
Just open up index.html to run the tests. But serve the file from a web server,
|
||||
do not run it locally, so that things like XMLHttpRequests can work properly.
|
|
@ -0,0 +1,38 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="qunit.css" type="text/css" media="screen" />
|
||||
<script src="../../scripts/requireplugins-jquery.js"></script>
|
||||
<script src="qunit.js"></script>
|
||||
|
||||
<script>
|
||||
// Tell QUnit to wait until everything is loaded.
|
||||
QUnit.stop();
|
||||
|
||||
require({
|
||||
baseUrl: '../../scripts'
|
||||
});
|
||||
|
||||
require([
|
||||
|
||||
// List test files here.
|
||||
'test-dotCompare.js'
|
||||
|
||||
], function () {
|
||||
require.ready(function () {
|
||||
// All modules loaded, and DOM ready, go!
|
||||
QUnit.start();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="qunit-header">QUnit test for Firefox Share</h1>
|
||||
<h2 id="qunit-banner"></h2>
|
||||
<div id="qunit-testrunner-toolbar"></div>
|
||||
<h2 id="qunit-userAgent"></h2>
|
||||
<ol id="qunit-tests"></ol>
|
||||
<div id="qunit-fixture">test markup, will be hidden</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,215 @@
|
|||
/** Font Family and Sizes */
|
||||
|
||||
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
|
||||
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
|
||||
}
|
||||
|
||||
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
|
||||
#qunit-tests { font-size: smaller; }
|
||||
|
||||
|
||||
/** Resets */
|
||||
|
||||
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
/** Header */
|
||||
|
||||
#qunit-header {
|
||||
padding: 0.5em 0 0.5em 1em;
|
||||
|
||||
color: #8699a4;
|
||||
background-color: #0d3349;
|
||||
|
||||
font-size: 1.5em;
|
||||
line-height: 1em;
|
||||
font-weight: normal;
|
||||
|
||||
border-radius: 15px 15px 0 0;
|
||||
-moz-border-radius: 15px 15px 0 0;
|
||||
-webkit-border-top-right-radius: 15px;
|
||||
-webkit-border-top-left-radius: 15px;
|
||||
}
|
||||
|
||||
#qunit-header a {
|
||||
text-decoration: none;
|
||||
color: #c2ccd1;
|
||||
}
|
||||
|
||||
#qunit-header a:hover,
|
||||
#qunit-header a:focus {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#qunit-banner {
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
#qunit-testrunner-toolbar {
|
||||
padding: 0.5em 0 0.5em 2em;
|
||||
color: #5E740B;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
#qunit-userAgent {
|
||||
padding: 0.5em 0 0.5em 2.5em;
|
||||
background-color: #2b81af;
|
||||
color: #fff;
|
||||
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
|
||||
}
|
||||
|
||||
|
||||
/** Tests: Pass/Fail */
|
||||
|
||||
#qunit-tests {
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
#qunit-tests li {
|
||||
padding: 0.4em 0.5em 0.4em 2.5em;
|
||||
border-bottom: 1px solid #fff;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#qunit-tests li strong {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#qunit-tests li a {
|
||||
padding: 0.5em;
|
||||
color: #c2ccd1;
|
||||
text-decoration: none;
|
||||
}
|
||||
#qunit-tests li a:hover,
|
||||
#qunit-tests li a:focus {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
#qunit-tests ol {
|
||||
margin-top: 0.5em;
|
||||
padding: 0.5em;
|
||||
|
||||
background-color: #fff;
|
||||
|
||||
border-radius: 15px;
|
||||
-moz-border-radius: 15px;
|
||||
-webkit-border-radius: 15px;
|
||||
|
||||
box-shadow: inset 0px 2px 13px #999;
|
||||
-moz-box-shadow: inset 0px 2px 13px #999;
|
||||
-webkit-box-shadow: inset 0px 2px 13px #999;
|
||||
}
|
||||
|
||||
#qunit-tests table {
|
||||
border-collapse: collapse;
|
||||
margin-top: .2em;
|
||||
}
|
||||
|
||||
#qunit-tests th {
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
padding: 0 .5em 0 0;
|
||||
}
|
||||
|
||||
#qunit-tests td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#qunit-tests pre {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#qunit-tests del {
|
||||
background-color: #e0f2be;
|
||||
color: #374e0c;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#qunit-tests ins {
|
||||
background-color: #ffcaca;
|
||||
color: #500;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/*** Test Counts */
|
||||
|
||||
#qunit-tests b.counts { color: black; }
|
||||
#qunit-tests b.passed { color: #5E740B; }
|
||||
#qunit-tests b.failed { color: #710909; }
|
||||
|
||||
#qunit-tests li li {
|
||||
margin: 0.5em;
|
||||
padding: 0.4em 0.5em 0.4em 0.5em;
|
||||
background-color: #fff;
|
||||
border-bottom: none;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
/*** Passing Styles */
|
||||
|
||||
#qunit-tests li li.pass {
|
||||
color: #5E740B;
|
||||
background-color: #fff;
|
||||
border-left: 26px solid #C6E746;
|
||||
}
|
||||
|
||||
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
|
||||
#qunit-tests .pass .test-name { color: #366097; }
|
||||
|
||||
#qunit-tests .pass .test-actual,
|
||||
#qunit-tests .pass .test-expected { color: #999999; }
|
||||
|
||||
#qunit-banner.qunit-pass { background-color: #C6E746; }
|
||||
|
||||
/*** Failing Styles */
|
||||
|
||||
#qunit-tests li li.fail {
|
||||
color: #710909;
|
||||
background-color: #fff;
|
||||
border-left: 26px solid #EE5757;
|
||||
}
|
||||
|
||||
#qunit-tests > li:last-child {
|
||||
border-radius: 0 0 15px 15px;
|
||||
-moz-border-radius: 0 0 15px 15px;
|
||||
-webkit-border-bottom-right-radius: 15px;
|
||||
-webkit-border-bottom-left-radius: 15px;
|
||||
}
|
||||
|
||||
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
|
||||
#qunit-tests .fail .test-name,
|
||||
#qunit-tests .fail .module-name { color: #000000; }
|
||||
|
||||
#qunit-tests .fail .test-actual { color: #EE5757; }
|
||||
#qunit-tests .fail .test-expected { color: green; }
|
||||
|
||||
#qunit-banner.qunit-fail { background-color: #EE5757; }
|
||||
|
||||
|
||||
/** Result */
|
||||
|
||||
#qunit-testresult {
|
||||
padding: 0.5em 0.5em 0.5em 2.5em;
|
||||
|
||||
color: #2b81af;
|
||||
background-color: #D2E0E6;
|
||||
|
||||
border-bottom: 1px solid white;
|
||||
}
|
||||
|
||||
/** Fixture */
|
||||
|
||||
#qunit-fixture {
|
||||
position: absolute;
|
||||
top: -10000px;
|
||||
left: -10000px;
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,23 @@
|
|||
/*jslint strict: false, indent: 2 */
|
||||
/*global define: false, module: false, test: false, equals: false */
|
||||
|
||||
define(['dotCompare'], function (dotCompare) {
|
||||
|
||||
module('dotCompare');
|
||||
|
||||
test('A is bigger than B', function () {
|
||||
equals(1, dotCompare('0.7.4', '0.7.1'));
|
||||
equals(1, dotCompare('1.7.4', '0.8.5'));
|
||||
});
|
||||
|
||||
test('B is bigger than A', function () {
|
||||
equals(-1, dotCompare('0.7.4', '0.8.1'));
|
||||
equals(-1, dotCompare('0.8.5', '1.7.4'));
|
||||
});
|
||||
|
||||
test('A is equal to B', function () {
|
||||
equals(0, dotCompare('0.7.4', '0.7.4'));
|
||||
equals(0, dotCompare('0.0.1', '0.0.1'));
|
||||
});
|
||||
|
||||
});
|
Загрузка…
Ссылка в новой задаче