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
63 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
6dc3fe3
WIP - project typeahead is loading items correctly
Aug 7, 2014
cc5df93
typeahead for project saves correctly
Aug 8, 2014
86c0a67
minor cleanup and a loading indicator for the typeahead
Aug 12, 2014
1ee7689
Fixed bug where bloodhound was not correctly reloading data. We need …
Aug 20, 2014
f6cca47
Adding client header to API requests
Oct 18, 2014
e2d9938
Fix banner icon alignment
Oct 18, 2014
fac8add
Merge branch 'launch' into search-typeahead
Oct 24, 2014
e29dc1b
Updated Bloodhound to use client headers
Oct 24, 2014
c14a98a
.gitignore
Oct 24, 2014
7d7f917
Project and User typeaheads both use typeahead.js, code cleanup to fo…
Oct 28, 2014
fc472d5
Clean up old user typeahead and related CSS
Nov 7, 2014
d776bc6
Refactor typeahead and bloodhound code.
Jan 6, 2015
68d9943
Updated readme to include typeahead.js
Jan 6, 2015
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
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Ignore IntelliJ
*.iml
*.idea

# Ignore OS X
*.DS_Store

# Ignore vim
*~
*.swp
*.swo
14 changes: 9 additions & 5 deletions README → README.md
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 All @@ -35,4 +34,9 @@ To install:
2. Navigate chrome to `chrome://extensions`
3. Check the `Developer mode` toggle
4. Click on `Load Unpacked Extension...`
5. Select the folder containing the extension
5. Select the folder containing the extension

This package uses the following libraries:
* jQuery 1.7.1
* jQuery UI 1.8.10
* Typeahead.JS (https://twitter.github.io/typeahead.js/)
123 changes: 112 additions & 11 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 @@ -59,10 +133,22 @@ Asana.ApiBridge = {
url: url,
timeout: 30000, // 30 second timeout
headers: {
"X-Requested-With": "XMLHttpRequest"
//********************************************************************
// WARNING: If you are using this extension as an example of
// connecting to Asana's API, please note that we recommend using the
// supported forms of authentication, which can be found at
// http://developer.asana.com/documentation/#Authentication
// We will be deprecating cookie authentication in the Chrome
// Extension in favor of Oauth2
//********************************************************************
"X-Requested-With": "XMLHttpRequest",
"X-Allow-Asana-Client": "1"
},
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 +166,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']);
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