Skip to content
This repository was archived by the owner on Jan 11, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
17ace17
cp
slobak May 29, 2013
f52ac5a
removed options
slobak May 30, 2013
4397aa3
moved success/error
slobak May 30, 2013
f42933a
fix typekit
slobak May 30, 2013
dc8478a
styling
slobak May 31, 2013
15ee2ff
changing layout, wip
slobak May 31, 2013
401fdb8
fixed API communication
slobak May 31, 2013
d222331
basic typeahead, no keyboard selection yet
slobak May 31, 2013
a9dfbb6
keyboard selection, kinda working
slobak May 31, 2013
97d1b76
better keyb selection
slobak May 31, 2013
e452950
working scrolling on keyboard
slobak May 31, 2013
891936f
Grab favicon url from page and fix some bugs
slobak May 31, 2013
d5e8466
Fix resize bug, and show contacts in order
slobak May 31, 2013
f2c7be9
Fix task creation functionality, add logging
slobak Jun 1, 2013
b0f447a
Load correct typekit for locale
slobak Jun 1, 2013
a6162f3
Checkpoint.
Jun 1, 2013
6e39ad1
More changes.
Jun 1, 2013
150085b
Finalized CSS.
Jun 1, 2013
fd957b6
added logging, default assigned to logged-in user
slobak Jun 1, 2013
55d1b87
fix client name
slobak Jun 1, 2013
79abe6c
fixed initial rendering of assignee
slobak Jun 1, 2013
c6b218c
assignee looks like no one but is self by default
slobak Jun 1, 2013
5fbabd3
cp
slobak Jun 3, 2013
2afbf1a
Alt and Tab hotkeys are now more robust
slobak Jun 3, 2013
82c8dbf
fill in field with selected user's name
slobak Jun 3, 2013
9276a4d
fix dimensions for popup
slobak Jun 3, 2013
ccc52ab
select on hover
slobak Jun 3, 2013
af5324f
Final styling.
Jun 3, 2013
96ac135
Updated comments
slobak Jun 4, 2013
221f12e
Update signup link
slobak Jun 4, 2013
3831898
Log workspace change
slobak Jun 4, 2013
0fb9a95
Increase photo resolution, better for retina display
slobak Jun 4, 2013
334e64d
Fix breakage on workspace change when assignee already selected
slobak Jun 4, 2013
7e3f79e
anonymous man
Jun 4, 2013
4a22c90
Fix scrollwheel scrolling of assignee list
slobak Jun 4, 2013
b4df96f
Merge branch 'launch' of github.com:Asana/Chrome-Extension-Example in…
slobak Jun 4, 2013
b3dbb03
Added silhouette
slobak Jun 4, 2013
9c509c1
Update 128px icon
slobak Jun 5, 2013
965e75e
Update manifest with more appropriate configuration
slobak Jun 5, 2013
5992756
Always put current user first
slobak Jun 5, 2013
fbc435c
Moved to use command API instead of a content script
slobak Jun 6, 2013
8a8c343
reduce content script execution to just getting selection
slobak Jun 6, 2013
558a441
Update README
slobak Jun 6, 2013
03a957f
final fixes.
Jun 7, 2013
cdfcca3
Default assignee to current user, update filtered list when opened, r…
slobak Jun 7, 2013
530704f
Don't focus popup body
slobak Jun 7, 2013
fe27575
Use browser action for command and switch keyboard shortcut to Alt+Sh…
slobak Jun 8, 2013
5e5bbdb
Fix wrapping on completion
slobak Jun 10, 2013
28b4920
Fix broken font
slobak Jun 11, 2013
ab2a0aa
Update version for type fix
slobak Jun 11, 2013
bc547d6
Experiments with hooking navigation
alexdavies74 Nov 22, 2013
0e630e5
trying to hook just "open in new tab"
alexdavies74 Nov 23, 2013
0f89f97
Navigating works, if you monkey patch your asana tab
alexdavies74 Nov 23, 2013
41e8e72
Integration work with actual asana
alexdavies74 Nov 23, 2013
5fa13c2
factor out function for navigating existing asana
alexdavies74 Nov 23, 2013
046ff7d
wip
Nov 23, 2013
8e0f6d5
Merge branch 'fast-navigation' of https://github.com/Asana/Chrome-Ext…
Nov 23, 2013
b460a3d
Fix abstractions around separated navigate event function
alexdavies74 Nov 23, 2013
c434f67
successfully squelches asana clicks
Nov 23, 2013
5c64b35
Merge branch 'fast-navigation' of https://github.com/Asana/Chrome-Ext…
Nov 23, 2013
49af0e3
maybe done
Nov 23, 2013
79ad376
mini fixes to chris's work
alexdavies74 Nov 23, 2013
1880d6b
Lots of tidying up, removing logs we don't need anymore
alexdavies74 Nov 24, 2013
2a90a8e
Intercept all clicks, and intercept when the A element isn’t the deep…
alexdavies74 Nov 25, 2013
8188f76
Bump version
alexdavies74 Nov 25, 2013
afafe9d
Don’t use the asana tab you just clicked to navigate when you ctrl-cl…
alexdavies74 Dec 10, 2013
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ integrates Asana into your web experience in the following ways:

* Creates a button in your button-bar which, when clicked, pops up a
QuickAdd window to create a new task associated with the current web page.
It will populate the task name with the page title by default, and
put the URL in the notes, along with any text you may have selected
when you pressed the button.
You can click a button to populate the task name with the page title and
the URL and current selected text in the notes.

* Installs the special Asana TAB+Q keyboard shortcut. When this key combo
* Installs the special Asana ALT+A keyboard shortcut. When this key combo
is pressed from any web page, it brings up the same popup.
This functionality will operate on any window opened after the extension
is loaded.
Expand Down
112 changes: 102 additions & 10 deletions api_bridge.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
/**
* Functionality to communicate with the Asana API. This should get loaded
* in the "server" portion of the chrome extension because it will make
* HTTP requests and needs cross-domain priveleges.
* HTTP requests and needs cross-domain privileges.
*
* The bridge does not need to use an auth token to connect to
* the API, because since it is a browser extension it can access
* the user's cookies, and can use them to authenticate to the API.
* This capability is specific to browser extensions, and other
* types of applications would have to obtain an auth token to communicate
* with the API.
* the API. Since it is a browser extension it can access the user's cookies
* and can use them to authenticate to the API. This capability is specific
* to browser extensions, and other types of applications would have to obtain
* an auth token to communicate with the API.
*/
Asana.ApiBridge = {

Expand All @@ -17,6 +16,26 @@ Asana.ApiBridge = {
*/
API_VERSION: "1.0",

/**
* @type {Integer} How long an entry stays in the cache.
*/
CACHE_TTL_MS: 15 * 60 * 1000,

/**
* @type {Boolean} Set to true on the server (background page), which will
* actually make the API requests. Clients will just talk to the API
* through the ExtensionServer.
*
*/
is_server: false,

/**
* @type {dict} Map from API path to cache entry for recent GET requests.
* date {Date} When cache entry was last refreshed
* response {*} Cached request.
*/
_cache: {},

/**
* @param opt_options {dict} Options to use; if unspecified will be loaded.
* @return {String} The base URL to use for API requests.
Expand All @@ -37,9 +56,64 @@ Asana.ApiBridge = {
* data {dict} Object representing response of API call, depends on
* method. Only available if response was a 200.
* error {String?} Error message, if there was a problem.
* @param options {dict?}
* miss_cache {Boolean} Do not check cache before requesting
*/
request: function(http_method, path, params, callback) {
var url = this.baseApiUrl() + path;
request: function(http_method, path, params, callback, options) {
var me = this;
http_method = http_method.toUpperCase();

// If we're not the server page, send a message to it to make the
// API request.
if (!me.is_server) {
console.info("Client API Request", http_method, path, params);
chrome.runtime.sendMessage({
type: "api",
method: http_method,
path: path,
params: params,
options: options || {}
}, callback);
return;
}

console.info("Server API Request", http_method, path, params);

// Serve from cache first.
if (!options.miss_cache && http_method === "GET") {
var data = me._readCache(path, new Date());
if (data) {
console.log("Serving request from cache", path);
callback(data);
return;
}
}

// Be polite to Asana API and tell them who we are.
var manifest = chrome.runtime.getManifest();
var client_name = [
"chrome-extension",
chrome.i18n.getMessage("@@extension_id"),
manifest.version,
manifest.name
].join(":");

var url = me.baseApiUrl() + path;
var body_data;
if (http_method === "PUT" || http_method === "POST") {
// POST/PUT request, put params in body
body_data = {
data: params,
options: { client_name: client_name }
};
} else {
// GET/DELETE request, add params as URL parameters.
var url_params = Asana.update({ opt_client_name: client_name }, params);
url += "?" + $.param(url_params);
}

console.log("Making request to API", http_method, url);

chrome.cookies.get({
url: url,
name: 'ticket'
Expand All @@ -63,6 +137,9 @@ Asana.ApiBridge = {
},
accept: "application/json",
success: function(data, status, xhr) {
if (http_method === "GET") {
me._writeCache(path, data, new Date());
}
callback(data);
},
error: function(xhr, status, error) {
Expand All @@ -80,20 +157,35 @@ Asana.ApiBridge = {
}
callback(response);
} else {
callback({ error: error || status });
callback({ errors: [{message: error || status }]});
}
},
xhrFields: {
withCredentials: true
}
};
if (http_method === "POST" || http_method === "PUT") {
attrs.data = JSON.stringify({data: params});
attrs.data = JSON.stringify(body_data);
attrs.dataType = "json";
attrs.processData = false;
attrs.contentType = "application/json";
}
$.ajax(attrs);
});
},

_readCache: function(path, date) {
var entry = this._cache[path];
if (entry && entry.date >= date - this.CACHE_TTL_MS) {
return entry.response;
}
return null;
},

_writeCache: function(path, response, date) {
this._cache[path] = {
response: response,
date: date
};
}
};
66 changes: 65 additions & 1 deletion asana.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,68 @@
/**
* Define the top-level Asana namespace.
*/
Asana = {};
Asana = {

// When popping up a window, the size given is for the content.
// When resizing the same window, the size must include the chrome. Sigh.
CHROME_TITLEBAR_HEIGHT: 24,
// Natural dimensions of popup window. The Chrome popup window adds 10px
// bottom padding, so we must add that as well when considering how tall
// our popup window should be.
POPUP_UI_HEIGHT: 310 + 10,
POPUP_UI_WIDTH: 410,
// Size of popup when expanded to include assignee list.
POPUP_EXPANDED_UI_HEIGHT: 310 + 10 + 129,

// If the modifier key is TAB, amount of time user has from pressing it
// until they can press Q and still get the popup to show up.
QUICK_ADD_WINDOW_MS: 5000


};

/**
* Things borrowed from asana library.
*/


Asana.update = function(to, from) {
for (var k in from) {
to[k] = from[k];
}
return to;
};

Asana.Node = {

/**
* Ensures that the bottom of the element is visible. If it is not then it
* will be scrolled up enough to be visible.
*
* Note: this does not take account of the size of the window. That's ok for
* now because the scrolling element is not the top-level element.
*/
ensureBottomVisible: function(node) {
var el = $(node);
var pos = el.position();
var element_from_point = document.elementFromPoint(
pos.left, pos.top + el.height());
if (element_from_point === null ||
$(element_from_point).closest(node).size() === 0) {
node.scrollIntoView(/*alignWithTop=*/ false);
}
}

};

if (!RegExp.escape) {
// Taken from http://simonwillison.net/2006/Jan/20/escape/
RegExp.escape = function(text, opt_do_not_escape_spaces) {
if (opt_do_not_escape_spaces !== true) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); // nolint
} else {
// only difference is lack of escaping \s
return text.replace(/[-[\]{}()*+?.,\\^$|#]/g, "\\$&"); // nolint
}
};
}
13 changes: 0 additions & 13 deletions background.html

This file was deleted.

26 changes: 26 additions & 0 deletions background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Asana.ExtensionServer.listen();
Asana.ServerModel.startPrimingCache();

// Modify referer header sent to typekit, to allow it to serve to us.
// See http://stackoverflow.com/questions/12631853/google-chrome-extensions-with-typekit-fonts
chrome.webRequest.onBeforeSendHeaders.addListener(function(details) {
var requestHeaders = details.requestHeaders;
for (var i = 0; i < requestHeaders.length; ++i) {
if (requestHeaders[i].name.toLowerCase() === 'referer') {
// The request was certainly not initiated by a Chrome extension...
return;
}
}
// Set Referer
requestHeaders.push({
name: 'referer',
// Host must match the domain in our Typekit kit settings
value: 'https://abkfopjdddhbjkiamjhkmogkcfedcnml'
});
return {
requestHeaders: requestHeaders
};
}, {
urls: ['*://use.typekit.net/*'],
types: ['stylesheet', 'script']
}, ['requestHeaders','blocking']);
16 changes: 16 additions & 0 deletions click_commandeer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
document.onclick = clickHandler;

function clickHandler(e){
if (!e) e = window.event;

var closestAnchor = $(e.target).closest("A");
if (closestAnchor.length === 1){
var link_url = closestAnchor.prop("href");
if (link_url.indexOf("https://app.asana.com") === 0) {
console.log("Asana chrome extension intercepting link ctrl-click")
var fragment = link_url.substr("https://app.asana.com".length)
chrome.runtime.sendMessage({fragment: fragment});
e.preventDefault();
}
}
}
25 changes: 11 additions & 14 deletions extension_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,19 @@ Asana.ExtensionServer = {
* requests from page clients, which can't make cross-domain requests.
*/
listen: function() {
var self = this;
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
var me = this;

// Mark our Api Bridge as the server side (the one that actually makes
// API requests to Asana vs. just forwarding them to the server window).
Asana.ApiBridge.is_server = true;

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.type === "api") {
// Request to the API. Pass it on to the bridge.
Asana.ApiBridge.api(
request.method, request.path, request.data || {}, sendResponse);

} else if (request.type === "quick_add") {
// QuickAdd request, made from a content window.
// Open up a new popup, and set the request information on its window
// (see popup.html for how it's used)
var popup = window.open(
chrome.extension.getURL('popup.html') + '?external=true',
"asana_quick_add",
"dependent=1,resizable=0,location=0,menubar=0,status=0,toolbar=0,width=410,height=310");
popup.quick_add_request = request;
Asana.ApiBridge.request(
request.method, request.path, request.params, sendResponse,
request.options || {});
return true; // will call sendResponse asynchronously
}
});
}
Expand Down
Binary file modified icon128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icon16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icon48.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading